/*
 * Copyright (C) 2010 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.portal.mop.user;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;

import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.PageNavigation;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.mop.navigation.GenericScope;
import org.exoplatform.portal.mop.navigation.NavigationContext;
import org.exoplatform.portal.mop.navigation.NavigationServiceException;
import org.exoplatform.portal.mop.navigation.NodeChangeListener;
import org.exoplatform.portal.mop.navigation.NodeContext;
import org.exoplatform.portal.mop.navigation.NodeState;
import org.exoplatform.portal.mop.navigation.Scope;
import org.exoplatform.portal.mop.navigation.VisitMode;
import org.exoplatform.services.organization.Group;
import org.exoplatform.services.security.ConversationState;
import org.exoplatform.services.security.IdentityConstants;

import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;

/**
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
public class UserPortalImpl implements UserPortal {

    public static final Comparator<UserNavigation> USER_NAVIGATION_COMPARATOR = new Comparator<UserNavigation>() {
        public int compare(UserNavigation nav1, UserNavigation nav2) {
            /* Because we cannot tell how expensive getPriority() is
             * we better store the priorities into local variables */
            int priority1 = nav1.getPriority();
            int priority2 = nav2.getPriority();
            if (priority1 == priority2) {
                /* A fix for GTNPORTAL-3322. The original comparator has not imposed
                 * a total ordering as required by the Comparator interface: "The implementor
                 * must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y."
                 * This was not the case when comparing two nodes both having UNDEFINED_PRIORITY. */
                return 0;
            } else if (priority1 == PageNavigation.UNDEFINED_PRIORITY) {
                return 1;
            } else if (priority2 == PageNavigation.UNDEFINED_PRIORITY) {
                return -1;
            } else {
                return priority1 - priority2;
            }
        }
    };

    private final Logger log = LoggerFactory.getLogger(UserPortalImpl.class);

    /** . */
    final UserPortalConfigService service;

    /** . */
    private final PortalConfig portal;

    /** . */
    final UserPortalContext context;

    /** . */
    final String userName;

    /** . */
    private List<UserNavigation> navigations;

    /** . */
    private final String portalName;

    /** . */
    private final Locale portalLocale;

    public UserPortalImpl(UserPortalConfigService service, String portalName, PortalConfig portal, String userName,
            UserPortalContext context) {
        if (context == null) {
            throw new NullPointerException("No null context argument allowed");
        }

        //
        String locale = portal.getLocale();

        //
        this.portalLocale = locale != null ? new Locale(locale) : null;
        this.service = service;
        this.portalName = portalName;
        this.portal = portal;
        this.userName = userName;
        this.context = context;
        this.navigations = null;
    }

    public Locale getLocale() {
        return portalLocale;
    }

    /**
     * Returns an immutable sorted list of the valid navigations related to the user.
     *
     * @return the navigations
     * @throws UserPortalException any user portal exception
     */
    public List<UserNavigation> getNavigations() throws UserPortalException, NavigationServiceException {
        if (navigations == null) {
            List<UserNavigation> navigations = new ArrayList<UserNavigation>(userName == null ? 1 : 10);
            NavigationContext portalNav = service.getNavigationService().loadNavigation(
                    new SiteKey(SiteType.PORTAL, portalName));
            if (portalNav != null && portalNav.getState() != null) {
                navigations.add(new UserNavigation(this, portalNav, service.getUserACL().hasEditPermission(portal)));
            }
            //
            if (userName != null) {
                // Add user nav if any
                NavigationContext userNavigation = service.getNavigationService().loadNavigation(SiteKey.user(userName));
                if (userNavigation != null && userNavigation.getState() != null) {
                    navigations.add(new UserNavigation(this, userNavigation, true));
                }

                // Add group navigations
                if (service.getUserACL().getSuperUser().equals(userName)) {
                    List<NavigationContext> navCtxs = service.getNavigationService().loadNavigations(SiteType.GROUP);
                    for (NavigationContext navCtx : navCtxs) {
                        if (!navCtx.getKey().getName().equals(service.getUserACL().getGuestsGroup())) {
                            navigations.add(new UserNavigation(this, navCtx, true));
                        }
                    }
                } else {
                    Collection<?> groups = null;
                    try {
                        ConversationState conv = ConversationState.getCurrent();
                        if (conv != null && conv.getIdentity() != null && !IdentityConstants.ANONIM.equals(conv.getIdentity().getUserId())
                            && !IdentityConstants.SYSTEM.equals(conv.getIdentity().getUserId())) {
                          groups = conv.getIdentity().getGroups();
                        } else {
                          groups = service.getOrganizationService().getGroupHandler().findGroupsOfUser(userName);
                        }
                    } catch (Exception e) {
                        if(log.isDebugEnabled()) {
                            log.debug("Could not retrieve groups", e);
                        }
                        //throw new UserPortalException("Could not retrieve groups", e);
                    }

                    if(groups != null) {
                        for (Object group : groups) {
                            String groupId = null;
                            if(group instanceof Group) {
                              groupId = ((Group) group).getId().trim();
                            } else {
                              groupId = group.toString().trim();
                            }
                            if (!groupId.equals(service.getUserACL().getGuestsGroup())) {
                                NavigationContext groupNavigation = service.getNavigationService().loadNavigation(
                                        SiteKey.group(groupId));
                                if (groupNavigation != null && groupNavigation.getState() != null) {
                                    navigations.add(new UserNavigation(this, groupNavigation, service.getUserACL()
                                            .hasEditPermissionOnNavigation(groupNavigation.getKey())));
                                }
                            }
                        }
                    }
                }

                // Sort the list finally
                Collections.sort(navigations, USER_NAVIGATION_COMPARATOR);
            }

            //
            this.navigations = Collections.unmodifiableList(navigations);
        }
        return navigations;
    }

    public UserNavigation getNavigation(SiteKey key) throws NullPointerException, UserPortalException,
            NavigationServiceException {
        if (key == null) {
            throw new NullPointerException("No null key accepted");
        }
        for (UserNavigation navigation : getNavigations()) {
            if (navigation.getKey().equals(key)) {
                return navigation;
            }
        }
        return null;
    }

    public void refresh() {
        navigations = null;
    }

    public UserNode getNode(UserNavigation userNavigation, Scope scope, UserNodeFilterConfig filterConfig,
            NodeChangeListener<UserNode> listener) throws NullPointerException, UserPortalException, NavigationServiceException {
        UserNodeContext context = new UserNodeContext(userNavigation, filterConfig);
        NodeContext<UserNode> nodeContext = service.getNavigationService().loadNode(context, userNavigation.navigation, scope,
                new UserNodeListener(listener));
        if (nodeContext != null) {
            return nodeContext.getNode().filter();
        } else {
            return null;
        }
    }

    public void updateNode(UserNode node, Scope scope, NodeChangeListener<UserNode> listener) throws NullPointerException,
            IllegalArgumentException, UserPortalException, NavigationServiceException {
        if (node == null) {
            throw new NullPointerException("No null node accepted");
        }
        service.getNavigationService().updateNode(node.context, scope, new UserNodeListener(listener));
        node.filter();
    }

    public void rebaseNode(UserNode node, Scope scope, NodeChangeListener<UserNode> listener) throws NullPointerException,
            IllegalArgumentException, UserPortalException, NavigationServiceException {
        if (node == null) {
            throw new NullPointerException("No null node accepted");
        }
        service.getNavigationService().rebaseNode(node.context, scope, new UserNodeListener(listener));
        node.filter();
    }

    public void saveNode(UserNode node, NodeChangeListener<UserNode> listener) throws NullPointerException,
            UserPortalException, NavigationServiceException {
        if (node == null) {
            throw new NullPointerException("No null node accepted");
        }
        service.getNavigationService().saveNode(node.context, new UserNodeListener(listener));
        navigations = null;
        node.filter();
    }

    /**
     * Note : the scope implementation is not stateless but we don't care in this case.
     */
    private class MatchingScope extends GenericScope.Branch.Visitor implements Scope {
        final UserNavigation userNavigation;
        final UserNodeFilterConfig filterConfig;
        final String[] match;
        int score;
        String id;
        UserNode userNode;

        MatchingScope(UserNavigation userNavigation, UserNodeFilterConfig filterConfig, String[] match) {
            this.userNavigation = userNavigation;
            this.filterConfig = filterConfig;
            this.match = match;
        }

        public Visitor get() {
            return this;
        }

        @Override
        protected int getSize() {
            return match.length;
        }

        @Override
        protected String getName(int index) {
            return match[index];
        }

        @Override
        protected Visitor getFederated() {
            return Scope.CHILDREN.get();
        }

        void resolve() throws NavigationServiceException {
            UserNodeContext context = new UserNodeContext(userNavigation, filterConfig);
            NodeContext<UserNode> nodeContext = service.getNavigationService().loadNode(context, userNavigation.navigation,
                    this, null);
            if (score > 0) {
                userNode = nodeContext.getNode().filter().find(id);
            }
        }

        public VisitMode enter(int depth, String id, String name, NodeState state) {
            VisitMode vm = super.enter(depth, id, name, state);
            if (depth == 0) {
                score = 0;
                MatchingScope.this.id = null;
            } else {
                if (vm == VisitMode.ALL_CHILDREN) {
                    MatchingScope.this.id = id;
                    score++;
                }
            }
            return vm;
        }
    }

    public UserNode getDefaultPath(UserNodeFilterConfig filterConfig) throws UserPortalException, NavigationServiceException {
        for (UserNavigation userNavigation : getNavigations()) {
            UserNode node = getDefaultPath(userNavigation, filterConfig);
            if (node != null) {
                return node;
            }
        }

        //
        return null;
    }

    public UserNode getDefaultPath(UserNavigation userNavigation, UserNodeFilterConfig filterConfig)
            throws UserPortalException, NavigationServiceException {
        NavigationContext navigation = userNavigation.navigation;
        if (navigation.getState() != null) {
            UserNodeContext context = new UserNodeContext(userNavigation, null);
            NodeContext<UserNode> nodeContext = service.getNavigationService().loadNode(context, navigation, Scope.CHILDREN,
                    null);
            if (nodeContext != null) {
                UserNode root = nodeContext.getNode();

                //
                if (filterConfig == null) {
                    filterConfig = UserNodeFilterConfig.builder().build();
                }
                UserNodeFilter filter = new UserNodeFilter(userNavigation.portal, filterConfig);

                // Filter node by node
                for (UserNode node : root.getChildren()) {
                    if (node.context.accept(filter)) {
                        return node;
                    }
                }
            }
        }
        return null;
    }

    public UserNode resolvePath(UserNodeFilterConfig filterConfig, String path) throws NullPointerException,
            UserPortalException, NavigationServiceException {
        if (path == null) {
            throw new NullPointerException("No null path accepted");
        }

        // Parse path
        String[] segments = Utils.parsePath(path);

        // Find the first navigation available or return null
        if (segments == null) {
            return getDefaultPath(filterConfig);
        }

        // Create a filter as we need one for the path
        if (filterConfig == null) {
            filterConfig = UserNodeFilterConfig.builder().build();
        } else {
            filterConfig = UserNodeFilterConfig.builder(filterConfig).build();
        }

        // Restrict the filter with path
        filterConfig.path = segments;

        // Get navigations
        List<UserNavigation> navigations = getNavigations();

        //
        MatchingScope best = null;
        for (UserNavigation navigation : navigations) {
            MatchingScope scope = new MatchingScope(navigation, filterConfig, segments);
            scope.resolve();
            if (scope.score == segments.length) {
                best = scope;
                break;
            } else {
                if (best == null) {
                    best = scope;
                } else {
                    if (scope.score > best.score) {
                        best = scope;
                    }
                }
            }
        }

        //
        if (best != null && best.score > 0) {
            UserNode ret = best.userNode;
            if (ret != null) {
                ret.owner.filterConfig.path = null;
            }
            return ret;
        } else {
            return getDefaultPath(null);
        }
    }

    public UserNode resolvePath(UserNavigation navigation, UserNodeFilterConfig filterConfig, String path)
            throws NullPointerException, UserPortalException, NavigationServiceException {
        if (navigation == null) {
            throw new NullPointerException("No null navigation accepted");
        }
        if (path == null) {
            throw new NullPointerException("No null path accepted");
        }

        //
        String[] segments = Utils.parsePath(path);

        //
        if (segments == null) {
            return null;
        }

        // Create a filter as we need one for the path
        if (filterConfig == null) {
            filterConfig = UserNodeFilterConfig.builder().build();
        } else {
            filterConfig = UserNodeFilterConfig.builder(filterConfig).build();
        }

        // Restrict the filter with the path
        filterConfig.path = segments;

        //
        MatchingScope scope = new MatchingScope(navigation, filterConfig, segments);
        scope.resolve();

        //
        if (scope.score > 0) {
            UserNode ret = scope.userNode;
            if (ret != null) {
                ret.owner.filterConfig.path = null;
            }
            return ret;
        } else {
            return null;
        }
    }
}
