package org.exoplatform.platform.common.ext;

import org.exoplatform.services.cms.CmsService;
import org.exoplatform.services.cms.link.LinkManager;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.access.SystemIdentity;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.listener.ListenerService;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.organization.User;
import org.exoplatform.services.organization.UserEventListener;
import org.exoplatform.services.security.IdentityConstants;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

import javax.jcr.*;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by IntelliJ IDEA.
 * User: kmenzli
 * Date: 28/12/11
 * Time: 16:14
 * To change this template use File | Settings | File Templates.
 */
public class UserDriveExtListener  extends UserEventListener {



    private static final Log LOG = ExoLogger.getLogger(UserDriveExtListener.class);

    private static final String SYMLINK_NAME        = "Public";

    private static final String SYMLIN_TYPE         = "exo:symlink";

    private static final String PUPLIC_DRIVE_EXT    = "/Public";

    private static final String PRIVATE_DRIVE_EXT   = "/Private";

    private final static String WORKSPACE           = "exo:workspace";

    private final static String UUID                = "exo:uuid";

    private final static String PRIMARY_TYPE        = "exo:primaryType";

    /**   RepositoryService used to get current Repository */
    private final RepositoryService repositoryService_;

    /**   LinkManager Service to create jcr symlink **/
    private final LinkManager linkManager_;

    /**   NodeHierarchyCreator Service : used to get complet path to a given user Folder */
    private NodeHierarchyCreator nodeHierarchyCreator_ ;

    public UserDriveExtListener (RepositoryService repositoryService, LinkManager linkManager, NodeHierarchyCreator nodeHierarchyCreator) throws Exception
    {
        this.repositoryService_ = repositoryService;

        this.linkManager_ =  linkManager;

        nodeHierarchyCreator_ = nodeHierarchyCreator ;
    }

    public void postSave(User user, boolean isNew) throws Exception
    {

        if (isNew)
        {
            createLink(user.getUserName());
        }
    }
    private void createLink(String userName) throws Exception
    {

        SessionProvider sessionProvider = SessionProvider.createSystemProvider();


        Session session = getSession(sessionProvider);

        /** path to private user drive*/
        String privatePathUserFolder = "";

        /** path to public user drive*/
        String publicPathUserFolder = "";

        try {

            Node userNode = nodeHierarchyCreator_.getUserNode(sessionProvider,userName);

            privatePathUserFolder = userNode.getPath().concat(PRIVATE_DRIVE_EXT);

            publicPathUserFolder = userNode.getPath().concat(PUPLIC_DRIVE_EXT);

            Node symLinkParent = (Node) session.getItem(privatePathUserFolder);

            Node targetNode = (Node) session.getItem(publicPathUserFolder);

            Node linkNode = null;

            /**
            linkNode = linkManager_.createLink(symLinkParent,SYMLIN_TYPE,targetNode,SYMLINK_NAME);
            */

            linkNode = createLink(symLinkParent,SYMLIN_TYPE,targetNode,SYMLINK_NAME);

            ((ExtendedNode)linkNode).setPermission(SystemIdentity.ANY, new String[]{PermissionType.READ});

            linkNode.save();


        } catch (Exception e) {

            LOG.error("An error occurs while processing the creation of Public Symlink " ,e);

        }  finally {

            if (session != null) {
                session.logout();
            }

            if (sessionProvider != null) {
                sessionProvider.close();
            }

        }
    }

    private Session getSession(SessionProvider sessionProvider) throws RepositoryException {

        ManageableRepository repository = repositoryService_.getCurrentRepository();

        return sessionProvider.getSession(repository.getConfiguration().getDefaultWorkspaceName(), repository);
    }

     private Node createLink(Node parent, String linkType, Node target, String linkName) throws RepositoryException {
        if (!target.isNodeType(SYMLIN_TYPE)) {
            if (target.canAddMixin("mix:referenceable")) {
                target.addMixin("mix:referenceable");
                target.getSession().save();
            }
            if (linkType == null || linkType.trim().length() == 0)
                linkType = SYMLIN_TYPE;
            if (linkName == null || linkName.trim().length() == 0)
                linkName = target.getName();
            Node linkNode = parent.addNode(linkName, linkType);
            try {
                updateAccessPermissionToLink(linkNode, target);
            } catch(Exception e) {
                if (LOG.isErrorEnabled()) {
                    LOG.error("CAN NOT UPDATE ACCESS PERMISSIONS FROM TARGET NODE TO LINK NODE", e);
                }
            }
            linkNode.setProperty(WORKSPACE, target.getSession().getWorkspace().getName());
            linkNode.setProperty(PRIMARY_TYPE, target.getPrimaryNodeType().getName());
            linkNode.setProperty(UUID, target.getUUID());
            linkNode.getSession().save();

            return linkNode;
        }
        return null;
    }

    private boolean canChangePermission(Node node) throws RepositoryException {
        try {
            ((ExtendedNode)node).checkPermission(PermissionType.CHANGE_PERMISSION);
            return true;
        } catch(AccessControlException e) {
            return false;
        }
    }

    private void updateAccessPermissionToLink(Node linkNode, Node targetNode) throws Exception {
        if(canChangePermission(linkNode)) {
            if(linkNode.canAddMixin("exo:privilegeable")) {
                linkNode.addMixin("exo:privilegeable");
                ((ExtendedNode)linkNode).setPermission(getNodeOwner(linkNode),PermissionType.ALL);
            }
            removeCurrentIdentites(linkNode);
            Map<String, String[]> perMap = new HashMap<String, String[]>();
            List<String> permsList = new ArrayList<String>();
            List<String> idList = new ArrayList<String>();
            for(AccessControlEntry accessEntry : ((ExtendedNode)targetNode).getACL().getPermissionEntries()) {
                if(!idList.contains(accessEntry.getIdentity())) {
                    idList.add(accessEntry.getIdentity());
                    permsList = ((ExtendedNode)targetNode).getACL().getPermissions(accessEntry.getIdentity());
                    perMap.put(accessEntry.getIdentity(), permsList.toArray(new String[permsList.size()]));
                }
            }
            ((ExtendedNode)linkNode).setPermissions(perMap);
        }
    }

    private String getNodeOwner(Node node) throws ValueFormatException, PathNotFoundException, RepositoryException {
        if(node.hasProperty("exo:owner")) {
            return node.getProperty("exo:owner").getString();
        }
        return IdentityConstants.SYSTEM;
    }
    private void removeCurrentIdentites(Node linkNode) throws AccessControlException, RepositoryException {
        String currentUser = linkNode.getSession().getUserID();
        if (currentUser != null)
            ((ExtendedNode)linkNode).setPermission(currentUser, PermissionType.ALL);
        for(AccessControlEntry accessEntry : ((ExtendedNode)linkNode).getACL().getPermissionEntries()) {
            if(canRemovePermission(linkNode, accessEntry.getIdentity())
                    && ((ExtendedNode)linkNode).getACL().getPermissions(accessEntry.getIdentity()).size() > 0
                    && !accessEntry.getIdentity().equals(currentUser)) {
                ((ExtendedNode) linkNode).removePermission(accessEntry.getIdentity());
            }
        }
    }
    private boolean canRemovePermission(Node node, String identity) throws ValueFormatException,
            PathNotFoundException, RepositoryException {
        String owner = getNodeOwner(node);
        if(identity.equals(IdentityConstants.SYSTEM)) return false;
        if(owner != null && owner.equals(identity)) return false;
        return true;
    }
}
