/*
 *
 *    OPEN-XCHANGE legal information
 *
 *    All intellectual property rights in the Software are protected by
 *    international copyright laws.
 *
 *
 *    In some countries OX, OX Open-Xchange, open xchange and OXtender
 *    as well as the corresponding Logos OX Open-Xchange and OX are registered
 *    trademarks of the Open-Xchange, Inc. group of companies.
 *    The use of the Logos is not covered by the GNU General Public License.
 *    Instead, you are allowed to use these Logos according to the terms and
 *    conditions of the Creative Commons License, Version 2.5, Attribution,
 *    Non-commercial, ShareAlike, and the interpretation of the term
 *    Non-commercial applicable to the aforementioned license is published
 *    on the web site http://www.open-xchange.com/EN/legal/index.html.
 *
 *    Please make sure that third-party modules and libraries are used
 *    according to their respective licenses.
 *
 *    Any modifications to this package must retain all copyright notices
 *    of the original copyright holder(s) for the original code used.
 *
 *    After any such modifications, the original and derivative code shall remain
 *    under the copyright of the copyright holder(s) and/or original author(s)per
 *    the Attribution and Assignment Agreement that can be located at
 *    http://www.open-xchange.com/EN/developer/. The contributing author shall be
 *    given Attribution for the derivative code and a license granting use.
 *
 *     Copyright (C) 2004-2011 Open-Xchange, Inc.
 *     Mail: info@open-xchange.com
 *
 *
 *     This program is free software; you can redistribute it and/or modify it
 *     under the terms of the GNU General Public License, Version 2 as published
 *     by the Free Software Foundation.
 *
 *     This program is distributed in the hope that it will be useful, but
 *     WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 *     or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 *     for more details.
 *
 *     You should have received a copy of the GNU General Public License along
 *     with this program; if not, write to the Free Software Foundation, Inc., 59
 *     Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 */

package com.openexchange.imap.entity2acl;

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.openexchange.groupware.AbstractOXException;
import com.openexchange.groupware.contexts.Context;
import com.openexchange.imap.services.IMAPServiceRegistry;
import com.openexchange.mail.api.MailConfig;
import com.openexchange.mailaccount.MailAccount;
import com.openexchange.mailaccount.MailAccountException;
import com.openexchange.mailaccount.MailAccountStorageService;
import com.openexchange.server.ServiceException;
import com.openexchange.server.impl.OCLPermission;
import com.openexchange.user.UserService;

/**
 * {@link DovecotEntity2ACL} - Handles the ACL entities used by Dovecot IMAP server.
 * <p>
 * The current supported identifiers are:
 * <ul>
 * <li><i>owner</i></li>
 * <li><i>anyone</i></li>
 * </ul>
 * <p>
 * Missing handling for identifiers:
 * <ul>
 * <li><i>anonymous</i> (This is a synonym from <i>anyone</i>)</li>
 * <li><i>user=loginid</i> (Rights or negative rights for IMAP account "loginid")</li>
 * <li><i>group=name</i> (Rights or negative rights for account group "name")</li>
 * <li><i>administrators</i> (This is an alias for <i>group=administrators</i>)</li>
 * </ul>
 * <p>
 * The complete implementation should be able to handle an ACL like this one:
 * 
 * <pre>
 * owner aceilrstwx anyone lr user=john w -user=mary r administrators aceilrstwx
 * </pre>
 * 
 * @author <a href="mailto:thorben.betten@open-xchange.com">Thorben Betten</a>
 */
public class DovecotEntity2ACL extends Entity2ACL {

    private static final org.apache.commons.logging.Log LOG = org.apache.commons.logging.LogFactory.getLog(DovecotEntity2ACL.class);

    private static final String ALIAS_OWNER = "owner";

    private static final String ALIAS_ANYONE = "anyone";

    private static final String SHARED_PREFIX = "#shared";

    private static final String getSharedFolderOwner(final String sharedFolderName, final char delim) {
        if (!sharedFolderName.startsWith(SHARED_PREFIX, 0)) {
            return null;
        }
        final String quotedDelim = Pattern.quote(String.valueOf(delim));
        final String abstractPattern = new StringBuilder().append(SHARED_PREFIX).append(quotedDelim).append("([\\p{ASCII}&&[^").append(
            quotedDelim).append("]]+)").append(quotedDelim).append("\\p{ASCII}+").toString();
        final Matcher m = Pattern.compile(abstractPattern, Pattern.CASE_INSENSITIVE).matcher(sharedFolderName);
        if (m.matches()) {
            return m.group(1).replaceAll("\\s+", String.valueOf(delim));
        }
        return null;
    }

    // session-user, fullname & delimiter

    /**
     * Default constructor
     */
    public DovecotEntity2ACL() {
        super();
    }

    @Override
    public String getACLName(final int userId, final Context ctx, final Entity2ACLArgs entity2AclArgs) throws AbstractOXException {
        if (userId == OCLPermission.ALL_GROUPS_AND_USERS) {
            return ALIAS_ANYONE;
        }
        final Object[] args = entity2AclArgs.getArguments(IMAPServer.DOVECOT);
        if (args == null || args.length == 0) {
            throw new Entity2ACLException(Entity2ACLException.Code.MISSING_ARG);
        }
        final int accountId = ((Integer) args[0]).intValue();
        final InetSocketAddress imapAddr = (InetSocketAddress) args[1];
        final int sessionUser = ((Integer) args[2]).intValue();
        final String sharedOwner = getSharedFolderOwner((String) args[3], ((Character) args[4]).charValue());
        if (null == sharedOwner) {
            /*
             * A non-shared folder
             */
            if (sessionUser == userId) {
                /*
                 * Logged-in user is equal to given user
                 */
                return ALIAS_OWNER;
            }
            return getACLNameInternal(userId, ctx, accountId, imapAddr);
        }
        /*
         * A shared folder
         */
        final int sharedOwnerID = getUserIDInternal(sharedOwner, ctx, accountId, imapAddr, sessionUser);
        if (sharedOwnerID == userId) {
            /*
             * Owner is equal to given user
             */
            return ALIAS_OWNER;
        }
        return getACLNameInternal(userId, ctx, accountId, imapAddr);
    }

    private static final String getACLNameInternal(final int userId, final Context ctx, final int accountId, final InetSocketAddress imapAddr) throws AbstractOXException {
        final MailAccountStorageService storageService = IMAPServiceRegistry.getService(MailAccountStorageService.class, true);
        if (null == storageService) {
            throw new ServiceException(ServiceException.Code.SERVICE_UNAVAILABLE, MailAccountStorageService.class.getName());
        }
        final String userLoginInfo;
        {
            final UserService userService = IMAPServiceRegistry.getService(UserService.class, true);
            if (null == userService) {
                throw new ServiceException(ServiceException.Code.SERVICE_UNAVAILABLE, UserService.class.getName());
            }
            userLoginInfo = userService.getUser(userId, ctx).getLoginInfo();
        }
        try {
            return MailConfig.getMailLogin(storageService.getMailAccount(accountId, userId, ctx.getContextId()), userLoginInfo);
        } catch (final MailAccountException e) {
            throw new Entity2ACLException(
                Entity2ACLException.Code.UNKNOWN_USER,
                Integer.valueOf(userId),
                Integer.valueOf(ctx.getContextId()),
                imapAddr);
        }
    }

    @Override
    public UserGroupID getEntityID(final String pattern, final Context ctx, final Entity2ACLArgs entity2AclArgs) throws AbstractOXException {
        if (ALIAS_ANYONE.equalsIgnoreCase(pattern)) {
            return ALL_GROUPS_AND_USERS;
        }
        final Object[] args = entity2AclArgs.getArguments(IMAPServer.DOVECOT);
        if (args == null || args.length == 0) {
            throw new Entity2ACLException(Entity2ACLException.Code.MISSING_ARG);
        }
        final int accountId = ((Integer) args[0]).intValue();
        final InetSocketAddress imapAddr = (InetSocketAddress) args[1];
        final int sessionUser = ((Integer) args[2]).intValue();
        final String sharedOwner = getSharedFolderOwner((String) args[3], ((Character) args[4]).charValue());
        if (null == sharedOwner) {
            /*
             * A non-shared folder
             */
            if (ALIAS_OWNER.equalsIgnoreCase(pattern)) {
                /*
                 * Map alias "owner" to logged-in user
                 */
                return getUserRetval(sessionUser);
            }
            return getUserRetval(getUserIDInternal(pattern, ctx, accountId, imapAddr, sessionUser));
        }
        /*
         * A shared folder
         */
        if (ALIAS_OWNER.equalsIgnoreCase(pattern)) {
            /*
             * Map alias "owner" to shared folder owner
             */
            return getUserRetval(getUserIDInternal(sharedOwner, ctx, accountId, imapAddr, sessionUser));
        }
        return getUserRetval(getUserIDInternal(pattern, ctx, accountId, imapAddr, sessionUser));
    }

    private static int getUserIDInternal(final String pattern, final Context ctx, final int accountId, final InetSocketAddress imapAddr, final int sessionUser) throws AbstractOXException {
        final int[] ids = MailConfig.getUserIDsByMailLogin(pattern, MailAccount.DEFAULT_ID == accountId, imapAddr, ctx);
        if (0 == ids.length) {
            throw new Entity2ACLException(Entity2ACLException.Code.RESOLVE_USER_FAILED, pattern);
        }
        if (1 == ids.length) {
            return ids[0];
        }
        // Prefer session user
        Arrays.sort(ids);
        final int pos = Arrays.binarySearch(ids, sessionUser);
        if (pos >= 0) {
            if (LOG.isWarnEnabled()) {
                LOG.warn(new StringBuilder().append("Found multiple users with login \"").append(pattern).append(
                    "\" subscribed to IMAP server \"").append(imapAddr).append("\": ").append(Arrays.toString(ids)).append(
                    "\nThe session user's ID is returned."));
            }
            return ids[pos];
        }
        // Just select first user ID
        if (LOG.isWarnEnabled()) {
            LOG.warn(new StringBuilder().append("Found multiple users with login \"").append(pattern).append(
                "\" subscribed to IMAP server \"").append(imapAddr).append("\": ").append(Arrays.toString(ids)).append(
                "\nThe first found user is returned."));
        }
        return ids[0];
    }

}
