package org.exoplatform.social.space.customization;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.jcr.ImportUUIDBehavior;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;

import org.exoplatform.commons.utils.CommonsUtils;
import org.exoplatform.commons.utils.ExoProperties;
import org.exoplatform.container.PortalContainer;
import org.exoplatform.container.component.RequestLifeCycle;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.config.UserACL;
import org.exoplatform.portal.config.UserPortalConfigService;
import org.exoplatform.portal.config.model.Application;
import org.exoplatform.portal.config.model.ApplicationType;
import org.exoplatform.portal.config.model.Container;
import org.exoplatform.portal.config.model.ModelObject;
import org.exoplatform.portal.config.model.Page;
import org.exoplatform.portal.config.model.PortalConfig;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.navigation.NavigationContext;
import org.exoplatform.portal.mop.page.PageContext;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.page.PageService;
import org.exoplatform.portal.mop.page.PageState;
import org.exoplatform.portal.pom.spi.portlet.Portlet;
import org.exoplatform.portal.pom.spi.portlet.Preference;
import org.exoplatform.services.cms.BasePath;
import org.exoplatform.services.cms.impl.DMSConfiguration;
import org.exoplatform.services.cms.impl.DMSRepositoryConfiguration;
import org.exoplatform.services.cms.impl.Utils;
import org.exoplatform.services.deployment.DeploymentDescriptor;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.app.SessionProviderService;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.jcr.ext.hierarchy.NodeHierarchyCreator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.security.IdentityConstants;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.social.core.space.SpaceApplication;
import org.exoplatform.social.core.space.SpaceTemplate;
import org.exoplatform.social.core.space.SpaceUtils;
import org.exoplatform.social.core.space.model.Space;
import org.exoplatform.social.core.space.spi.SpaceService;
import org.exoplatform.social.core.space.spi.SpaceTemplateService;

public class SpaceCustomizationService {
  final static private Log        LOG                          = ExoLogger.getExoLogger(SpaceCustomizationService.class);

  final static private String     GROUPS_PATH                  = "groupsPath";

  private static final String     SPACE_GROUP_ID_PREFERENCE    = "{spaceGroupId}";

  private static final String     SPACE_NEW_HOME_PAGE_TEMPLATE = "custom space";

  private static final String     SCV_PORTLEt_NAME             = "SingleContentViewer";

  private static final String     ACTIVITY_FOLDER_UPLOAD_NAME  = "Activity Stream Documents";

  private SessionProviderService  sessionProviderService;

  private NodeHierarchyCreator    nodeHierarchyCreator         = null;

  private DMSConfiguration        dmsConfiguration             = null;

  private RepositoryService       repositoryService            = null;

  private ConfigurationManager    configurationManager         = null;

  private DataStorage             dataStorageService           = null;

  PageService                     pageService                  = null;

  private UserPortalConfigService userPortalConfigService      = null;

  private SpaceService            spaceService                 = null;

  private SpaceTemplateService    spaceTemplateService         = null;

  private UserACL                 userACL                      = null;

  private String                  groupsPath;

  public SpaceCustomizationService(DataStorage dataStorageService_,
                                   PageService pageService_,
                                   UserPortalConfigService userPortalConfigService_,
                                   SessionProviderService sessionProviderService_,
                                   NodeHierarchyCreator nodeHierarchyCreator_,
                                   DMSConfiguration dmsConfiguration_,
                                   RepositoryService repositoryService_,
                                   ConfigurationManager configurationManager_,
                                   UserACL userACL_) {
    this.nodeHierarchyCreator = nodeHierarchyCreator_;
    this.dmsConfiguration = dmsConfiguration_;
    this.repositoryService = repositoryService_;
    this.userACL = userACL_;
    this.configurationManager = configurationManager_;
    this.sessionProviderService = sessionProviderService_;
    this.dataStorageService = dataStorageService_;
    this.pageService = pageService_;
    this.userPortalConfigService = userPortalConfigService_;
    groupsPath = nodeHierarchyCreator.getJcrPath(GROUPS_PATH);
    if (groupsPath.lastIndexOf("/") == groupsPath.length() - 1) {
      groupsPath = groupsPath.substring(0, groupsPath.lastIndexOf("/"));
    }
  }

  public void editSpaceDriveViewPermissions(String viewNodeName, String permission) throws RepositoryException {
    if (LOG.isDebugEnabled()) {
      LOG.debug("Trying to add permission " + permission + " for ECMS view " + viewNodeName);
    }
    String viewsPath = nodeHierarchyCreator.getJcrPath(BasePath.CMS_VIEWS_PATH);
    ManageableRepository manageableRepository = repositoryService.getCurrentRepository();
    DMSRepositoryConfiguration dmsRepoConfig = dmsConfiguration.getConfig();
    Session session = manageableRepository.getSystemSession(dmsRepoConfig.getSystemWorkspace());
    Node viewHomeNode = (Node) session.getItem(viewsPath);
    if (viewHomeNode.hasNode(viewNodeName)) {
      Node contentNode = viewHomeNode.getNode(viewNodeName);
      String contentNodePermissions = contentNode.getProperty("exo:accessPermissions").getString();
      contentNode.setProperty("exo:accessPermissions", contentNodePermissions.concat(",").concat(permission));
      viewHomeNode.save();
      if (LOG.isDebugEnabled()) {
        LOG.debug("Permission " + permission + " added with success to ECMS view " + viewNodeName);
      }
    } else {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Can not find view node: " + viewNodeName);
      }
    }
  }

  /*
   * (non-Javadoc)
   * @see org.exoplatform.services.deployment.DeploymentPlugin#deploy(org.
   * exoplatform.services.jcr.ext.common.SessionProvider)
   */
  public void deployContentToSpaceDrive(SessionProvider sessionProvider,
                                        String spaceId,
                                        DeploymentDescriptor deploymentDescriptor)
                                                                                   throws Exception {

    String sourcePath = deploymentDescriptor.getSourcePath();
    LOG.info("Deploying '" + sourcePath + "'content to '" + spaceId + "' Space JCR location");

    // sourcePath should start with: war:/, jar:/, classpath:/, file:/
    Boolean cleanupPublication = deploymentDescriptor.getCleanupPublication();

    InputStream inputStream = configurationManager.getInputStream(sourcePath);
    ManageableRepository repository = repositoryService.getCurrentRepository();
    Session session = sessionProvider.getSession(deploymentDescriptor.getTarget().getWorkspace(), repository);
    String targetNodePath = deploymentDescriptor.getTarget().getNodePath();
    if (targetNodePath.indexOf("/") == 0) {
      targetNodePath = targetNodePath.replaceFirst("/", "");
    }
    if (targetNodePath.lastIndexOf("/") == targetNodePath.length() - 1) {
      targetNodePath = targetNodePath.substring(0, targetNodePath.lastIndexOf("/"));
    }
    // if target path contains folders, then create them
    if (!targetNodePath.equals("")) {
      Node spaceRootNode = (Node) session.getItem(groupsPath + spaceId);
      Utils.makePath(spaceRootNode, targetNodePath, NodetypeConstant.NT_UNSTRUCTURED);
    }
    String fullTargetNodePath = groupsPath + spaceId + "/" + targetNodePath;
    Node parentTargetNode = (Node) session.getItem(fullTargetNodePath);
    NodeIterator nodeIterator = parentTargetNode.getNodes();
    List<String> initialChildNodesUUID = new ArrayList<String>();
    List<String> initialChildNodesNames = new ArrayList<String>();
    while (nodeIterator.hasNext()) {
      Node node = nodeIterator.nextNode();
      String uuid = null;
      try {
        uuid = node.getUUID();
      } catch (Exception exception) {
        // node is not referenceable
        continue;
      }
      initialChildNodesUUID.add(uuid);
      initialChildNodesNames.add(node.getName());
    }

    session.importXML(fullTargetNodePath, inputStream, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW);

    parentTargetNode = (Node) session.getItem(fullTargetNodePath);
    nodeIterator = parentTargetNode.getNodes();
    List<ExtendedNode> newChildNodesUUID = new ArrayList<ExtendedNode>();
    while (nodeIterator.hasNext()) {
      ExtendedNode childNode = (ExtendedNode) nodeIterator.nextNode();
      String uuid = null;
      try {
        uuid = childNode.getUUID();
      } catch (Exception exception) {
        // node is not referenceable
        continue;
      }
      // determines wether this is a new node or not
      if (!initialChildNodesUUID.contains(uuid)) {
        if (initialChildNodesNames.contains(childNode.getName())) {
          LOG.info(childNode.getName() + " already exists under " + fullTargetNodePath + ". This node will not be imported!");
          childNode.remove();
        } else {
          newChildNodesUUID.add(childNode);
        }
      }
    }
    String spaceMembershipManager = userACL.getAdminMSType() + ":" + spaceId;
    for (ExtendedNode extendedNode : newChildNodesUUID) {
      if (extendedNode.isNodeType(NodetypeConstant.EXO_PRIVILEGEABLE)) {
        extendedNode.clearACL();
      } else if (extendedNode.canAddMixin(NodetypeConstant.EXO_PRIVILEGEABLE)) {
        extendedNode.addMixin("exo:privilegeable");
        extendedNode.clearACL();
      } else {
        throw new IllegalStateException("Can't change permissions on node imported to the added Space.");
      }
      extendedNode.setPermission(IdentityConstants.ANY, new String[] { PermissionType.READ });
      extendedNode.setPermission(spaceMembershipManager, PermissionType.ALL);
      if (cleanupPublication) {

        /**
         * This code allows to cleanup the publication lifecycle in the target
         * folder after importing the data. By using this, the publication live
         * revision property will be re-initialized and the content will be set
         * as published directly. Thus, the content will be visible in front
         * side.
         */
        if (extendedNode.hasProperty("publication:liveRevision") && extendedNode.hasProperty("publication:currentState")) {
          LOG.info("\"" + extendedNode.getName() + "\" publication lifecycle has been cleaned up");
          extendedNode.setProperty("publication:liveRevision", "");
          extendedNode.setProperty("publication:currentState", "published");
        }
      }
    }

    session.save();
    session.logout();
    LOG.info(deploymentDescriptor.getSourcePath() + " is deployed succesfully into " + fullTargetNodePath);
  }

  public void createSpaceHomePage(String spacePrettyName, String spaceGroupId, ExoProperties welcomeSCVCustomPreferences) {

    RequestLifeCycle.begin(PortalContainer.getInstance());
    try {
      LOG.info("Updating '" + spaceGroupId + "' Space Home Page");

      // creates the new home page
      Space space = getSpaceService().getSpaceByGroupId(spaceGroupId);
      if (space == null) {
        throw new IllegalStateException("Can't find space with group id " + spaceGroupId);
      }
      String spaceType = space.getType();
      SpaceTemplateService spaceTemplateService = getSpaceTemplateService();
      SpaceTemplate spaceTemplate = spaceTemplateService.getSpaceTemplateByName(spaceType);
      if (spaceTemplate == null) {
        LOG.warn("Could not find space template:{}. Space home page will not be created for space:{}.",
                 spaceType,
                 spacePrettyName);
        return;
      }
      SpaceApplication homeApplication = spaceTemplate.getSpaceHomeApplication();
      if (homeApplication == null) {
        LOG.warn("Could not find home application for template:{}. Space home page will not be created for space:{}.",
                 spaceType,
                 spacePrettyName);
        return;
      }
      String portletName = homeApplication.getPortletName();
      String pageName = PortalConfig.GROUP_TYPE + "::" + spaceGroupId + "::" + portletName;
      Page oldSpaceHomePage = dataStorageService.getPage(pageName);
      PageContext pageContext = pageService.loadPage(PageKey.parse(oldSpaceHomePage.getPageId()));
      pageContext.update(oldSpaceHomePage);

      // creates the customized home page for the space and set few fields
      // with values from the old home page
      Page customSpaceHomePage = userPortalConfigService.createPageTemplate(SPACE_NEW_HOME_PAGE_TEMPLATE,
                                                                            PortalConfig.GROUP_TYPE,
                                                                            spaceGroupId);
      customSpaceHomePage.setTitle(oldSpaceHomePage.getTitle());
      customSpaceHomePage.setName(oldSpaceHomePage.getName());
      customSpaceHomePage.setAccessPermissions(oldSpaceHomePage.getAccessPermissions());
      customSpaceHomePage.setEditPermission(oldSpaceHomePage.getEditPermission());
      customSpaceHomePage.setOwnerType(PortalConfig.GROUP_TYPE);
      customSpaceHomePage.setOwnerId(spaceGroupId);
      // needs to populate the accessPermissions list to all children:
      // containers and applications
      editChildrenAccesPermisions(customSpaceHomePage.getChildren(), customSpaceHomePage.getAccessPermissions());
      // dataStorageService.save(customSpaceHomePage);
      // mandatory preference "Space_URL" should be added to the home page
      // applications
      editSpaceURLPreference(customSpaceHomePage.getChildren(), spacePrettyName);
      // gets the welcome SingleContentViewer Portlet
      Application<Portlet> welcomeSCVPortlet = getPortletApplication(customSpaceHomePage.getChildren(), SCV_PORTLEt_NAME);
      // configures the welcome SingleContentViewer Portlet
      editSCVPreference(welcomeSCVPortlet, spaceGroupId, welcomeSCVCustomPreferences);

      //
      NavigationContext navContext = SpaceUtils.createGroupNavigation(spaceGroupId);

      SiteKey siteKey = navContext.getKey();
      PageKey pageKey = new PageKey(siteKey, customSpaceHomePage.getName());
      PageState pageState = new PageState(
                                          customSpaceHomePage.getTitle(),
                                          customSpaceHomePage.getDescription(),
                                          customSpaceHomePage.isShowMaxWindow(),
                                          customSpaceHomePage.getFactoryId(),
                                          customSpaceHomePage.getAccessPermissions() != null ? Arrays.asList(customSpaceHomePage.getAccessPermissions())
                                                                                             : null,
                                          customSpaceHomePage.getEditPermission(),
                                          customSpaceHomePage.getMoveAppsPermissions() != null ? Arrays.asList(customSpaceHomePage.getMoveAppsPermissions())
                                                                                               : null,
                                          customSpaceHomePage.getMoveContainersPermissions() != null ? Arrays.asList(customSpaceHomePage.getMoveContainersPermissions())
                                                                                                     : null);

      pageService.savePage(new PageContext(pageKey, pageState));
      dataStorageService.save(customSpaceHomePage);
    } catch (Exception e) {
      LOG.error("Error while customizing the Space home page for space: " + spaceGroupId, e);
    } finally {
      try {
        RequestLifeCycle.end();
      } catch (Exception e) {
        LOG.warn("An exception has occurred while proceed RequestLifeCycle.end() : " + e.getMessage());
      }
    }
  }

  public SpaceService getSpaceService() {
    if (this.spaceService == null) {
      this.spaceService = (SpaceService) PortalContainer.getInstance().getComponentInstanceOfType(SpaceService.class);
    }
    return this.spaceService;
  }

  public SpaceTemplateService getSpaceTemplateService() {
    if (this.spaceTemplateService == null) {
      this.spaceTemplateService = CommonsUtils.getService(SpaceTemplateService.class);
    }
    return this.spaceTemplateService;
  }

  public void createSpaceDefaultFolders(String groupId) throws Exception {
    Node parentNode;
    SessionProvider sessionProvider = sessionProviderService.getSystemSessionProvider(null);
    ManageableRepository currentRepository = repositoryService.getCurrentRepository();
    String workspaceName = currentRepository.getConfiguration().getDefaultWorkspaceName();
    Session session = sessionProvider.getSession(workspaceName, currentRepository);
    String groupPath = nodeHierarchyCreator.getJcrPath("groupsPath");
    String spaceParentPath = groupPath + groupId + "/Documents";
    if (!session.itemExists(spaceParentPath)) {
      throw new IllegalStateException("Root node of space '" + spaceParentPath + "' doesn't exist");
    }
    parentNode = (Node) session.getItem(spaceParentPath);

    if (!parentNode.hasNode(ACTIVITY_FOLDER_UPLOAD_NAME)) {
      parentNode.addNode(ACTIVITY_FOLDER_UPLOAD_NAME);
      session.save();
    }
  }

  private void editSCVPreference(Application<Portlet> selectedPortlet,
                                 String prefValue,
                                 ExoProperties welcomeSCVCustomPreferences)
                                                                            throws Exception {
    // loads the scv preferences
    Portlet prefs = dataStorageService.load(selectedPortlet.getState(), ApplicationType.PORTLET);
    if (prefs == null) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("The portlet prefs == null : portlet application " + selectedPortlet.getId());
      }
      prefs = new Portlet();
    }
    // edits the nodeIdentifier preference
    for (String preferenceName : welcomeSCVCustomPreferences.keySet()) {
      String preferenceValue = welcomeSCVCustomPreferences.get(preferenceName);
      if (preferenceValue.contains(SPACE_GROUP_ID_PREFERENCE)) {
        preferenceValue = preferenceValue.replace(SPACE_GROUP_ID_PREFERENCE, prefValue);
      }
      prefs.putPreference(new Preference(preferenceName, preferenceValue, false));
    }
  }

  @SuppressWarnings("unchecked")
  private void editSpaceURLPreference(List<ModelObject> children, String prefValue) throws Exception {
    if (children == null || children.size() == 0) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Can not get a portlet application from children.\nChildren == null or have no items");
      }
    }
    // parses the children list
    for (ModelObject modelObject : children) {
      // if a container, check for its children
      if (modelObject instanceof Container) {
        editSpaceURLPreference(((Container) modelObject).getChildren(), prefValue);
      } else {
        // if a portlet application, set the preference value
        if (modelObject instanceof Application && ((Application<?>) modelObject).getType().equals(ApplicationType.PORTLET)) {
          Application<Portlet> application = (Application<Portlet>) modelObject;
          Portlet portletPreference = dataStorageService.load(application.getState(), ApplicationType.PORTLET);
          if (portletPreference == null) {
            portletPreference = new Portlet();
          }
          portletPreference.putPreference(new Preference(SpaceUtils.SPACE_URL, prefValue, false));

        }
      }
    }
  }

  @SuppressWarnings("unchecked")
  private Application<Portlet> getPortletApplication(List<ModelObject> children, String portletName) throws Exception {
    if (children == null || children.size() == 0) {
      if (LOG.isDebugEnabled()) {
        LOG.debug("Can not get a portlet application from children.\nChildren == null or have no items");
      }
    }
    for (ModelObject modelObject : children) {
      Application<Portlet> selectedApplication = null;
      if (modelObject instanceof Container) {
        selectedApplication = getPortletApplication(((Container) modelObject).getChildren(), portletName);
      } else {
        if (modelObject instanceof Application && ((Application<?>) modelObject).getType().equals(ApplicationType.PORTLET)) {
          Application<Portlet> application = (Application<Portlet>) modelObject;
          String portletId = this.dataStorageService.getId(application.getState());
          if (portletId.endsWith("/" + portletName)) {
            selectedApplication = application;
          }
        }
      }
      if (selectedApplication != null) {
        return selectedApplication;
      }
    }
    return null;
  }

  @SuppressWarnings("unchecked")
  private void editChildrenAccesPermisions(List<ModelObject> children, String[] accessPermissions) {
    if (children != null && children.size() > 0) {
      for (ModelObject modelObject : children) {
        if (modelObject instanceof Container) {
          ((Container) modelObject).setAccessPermissions(accessPermissions);
          editChildrenAccesPermisions(((Container) modelObject).getChildren(), accessPermissions);
        } else {
          if (modelObject instanceof Application && ((Application<?>) modelObject).getType().equals(ApplicationType.PORTLET)) {
            Application<Portlet> application = (Application<Portlet>) modelObject;
            application.setAccessPermissions(accessPermissions);
          }
        }
      }
    }
  }

}
