/*
 * Copyright (C) 2003-2009 eXo Platform SAS.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 *
 * 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, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.wcm.category;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.jcr.Node;
import javax.jcr.Session;
import javax.jcr.Workspace;
import javax.jcr.nodetype.NodeType;
import javax.jcr.nodetype.PropertyDefinition;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.container.configuration.ConfigurationManager;
import org.exoplatform.container.xml.InitParams;
import org.exoplatform.container.xml.ObjectParameter;
import org.exoplatform.container.xml.ValueParam;
import org.exoplatform.services.cms.BasePath;
import org.exoplatform.services.cms.JcrInputProperty;
import org.exoplatform.services.cms.actions.ActionServiceContainer;
import org.exoplatform.services.cms.actions.impl.ActionConfig;
import org.exoplatform.services.cms.actions.impl.ActionConfig.TaxonomyAction;
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.cms.link.LinkManager;
import org.exoplatform.services.cms.taxonomy.TaxonomyService;
import org.exoplatform.services.cms.taxonomy.impl.TaxonomyAlreadyExistsException;
import org.exoplatform.services.cms.taxonomy.impl.TaxonomyConfig;
import org.exoplatform.services.cms.taxonomy.impl.TaxonomyConfig.Permission;
import org.exoplatform.services.cms.taxonomy.impl.TaxonomyConfig.Taxonomy;
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.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.wcm.portal.artifacts.CreatePortalPlugin;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 * chuong_phan@exoplatform.com, phan.le.thanh.chuong@gmail.com
 * Aug 11, 2009
 */
public class CreateTaxonomyPlugin extends CreatePortalPlugin {

  protected static final Log log = ExoLogger.getLogger("wcm:CreateTaxonomyPlugin");

  public static final String MIX_AFFECTED_NODETYPE  = "mix:affectedNodeTypes";

  public static final String AFFECTED_NODETYPE      = "exo:affectedNodeTypeNames";

  public static final String ALL_DOCUMENT_TYPES     = "ALL_DOCUMENT_TYPES";

  /** The workspace. */
  private String                  workspace                  = "";

  /** The path. */
  private String                  path                       = "";

  /** The tree name. */
  private String                  treeName                   = "";

  /** The permissions. */
  private List<Permission>        permissions                = new ArrayList<Permission>(4);

  /** The auto create in new repository_. */
  private boolean                 autoCreateInNewRepository_ = true;

  /** The repository service_. */
  private RepositoryService       repositoryService;

  /** The taxonomy service_. */
  private TaxonomyService         taxonomyService;

  /** The link manager service_. */
  private LinkManager             linkManager;

  /** The base taxonomies storage_. */
  private String                  baseTaxonomiesStorage;

  /** The base taxonomies definition_. */
  private String                  baseTaxonomiesDefinition;

  /** The action service container_. */
  private ActionServiceContainer  actionServiceContainer;

  /** The params_. */
  private InitParams              params;

  /** The dms configuration_. */
  private DMSConfiguration        dmsConfiguration;

  /** The name. */
  private String                  name;

  private String                   portalName;

  /**
   * Instantiates a new initial taxonomy plugin.
   *
   * @param params the params
   * @param configurationManager the configuration manager
   * @param repositoryService the repository service
   * @param nodeHierarchyCreator the node hierarchy creator
   * @param taxonomyService the taxonomy service
   * @param actionServiceContainer the action service container
   * @param dmsConfiguration the dms configuration
   *
   * @throws Exception the exception
   */
  public CreateTaxonomyPlugin(InitParams params,
                               ConfigurationManager configurationManager,
                               RepositoryService repositoryService,
                               NodeHierarchyCreator nodeHierarchyCreator,
                               TaxonomyService taxonomyService,
                               ActionServiceContainer actionServiceContainer,
                               DMSConfiguration dmsConfiguration,
                               LinkManager linkManager) throws Exception {
    super(params, configurationManager, repositoryService);

    this.repositoryService = repositoryService;
    this.baseTaxonomiesStorage = nodeHierarchyCreator.getJcrPath(BasePath.TAXONOMIES_TREE_STORAGE_PATH);
    this.baseTaxonomiesDefinition = nodeHierarchyCreator.getJcrPath(BasePath.TAXONOMIES_TREE_DEFINITION_PATH);
    this.taxonomyService = taxonomyService;
    this.actionServiceContainer = actionServiceContainer;
    this.params = params;
    this.dmsConfiguration = dmsConfiguration;
    this.linkManager = linkManager;
  }

  /*
   * (non-Javadoc)
   * @see
   * org.exoplatform.services.wcm.portal.artifacts.BasePortalArtifactsPlugin
   * #deployToPortal(java.lang.String,
   * org.exoplatform.services.jcr.ext.common.SessionProvider)
   */
  public void deployToPortal(SessionProvider sessionProvider, String portalName) throws Exception {
    this.portalName = portalName;
    ValueParam autoCreated = params.getValueParam("autoCreateInNewRepository");
    ValueParam workspaceParam = params.getValueParam("workspace");
    ValueParam pathParam = params.getValueParam("path");
    ValueParam nameParam = params.getValueParam("treeName");
    if (autoCreated != null)
      autoCreateInNewRepository_ = Boolean.parseBoolean(autoCreated.getValue());
    if (pathParam == null || workspaceParam == null || workspaceParam.getValue().trim().length() == 0) {
      path = baseTaxonomiesStorage;
    } else {
      path = pathParam.getValue();
      workspace = workspaceParam.getValue();
    }
    if (nameParam != null) {
      treeName = nameParam.getValue();
    }

    if (treeName.contains("{treeName}")) {
      treeName = StringUtils.replace(treeName, "{treeName}", portalName);
    } else {
      treeName = treeName + "-" + portalName;
    }
    path = StringUtils.replace(path, "{portalName}", portalName);

    Session session = null;
    try {
      // Get source information
      Node srcTaxonomy = taxonomyService.getTaxonomyTree(treeName);
      String srcWorkspace = srcTaxonomy.getSession().getWorkspace().getName();

      // Get destination information
      ManageableRepository repository = repositoryService.getCurrentRepository();
      session = sessionProvider.getSession(this.workspace, repository);
      Workspace destWorkspace = session.getWorkspace();
      String destPath = path + "/" + srcTaxonomy.getName();

      // If same workspace
      if (srcWorkspace.equals(destWorkspace.getName())) {
        destWorkspace.move(srcTaxonomy.getPath(), destPath);
      } else {
        // Clone taxonomy tree across workspace
        destWorkspace.clone(srcWorkspace, srcTaxonomy.getPath(), destPath, true);

        // Remove old link taxonomy tree in definition
        String dmsSystemWorkspaceName = dmsConfiguration.getConfig().getSystemWorkspace();
        Node taxonomyDefinition = (Node) sessionProvider.getSession(dmsSystemWorkspaceName,
                                                                    repository)
                                                        .getItem(baseTaxonomiesDefinition);
        Node srcLinkTaxonomy = taxonomyDefinition.getNode(srcTaxonomy.getName());
        srcLinkTaxonomy.remove();

        // Remove old taxonomy tree
        srcTaxonomy.remove();

        // Register new taxonomy tree in definition
        Node destTaxonomy = (Node) session.getItem(destPath);
        linkManager.createLink(taxonomyDefinition, destTaxonomy);
      }
      session.save();
      return;
    } catch (Exception e) {
      init();
    }
  }

  /**
   * Inits the.
   *
   * @throws Exception the exception
   */
  public void init() throws Exception {
    importPredefineTaxonomies();
  }

  /**
   * Inits the.
   *
   * @param repository the repository
   *
   * @throws Exception the exception
   */
  @Deprecated
  public void init(String repository) throws Exception {
    if (!autoCreateInNewRepository_)
      return;
    importPredefineTaxonomies();
  }

  /* (non-Javadoc)
   * @see org.exoplatform.services.deployment.DeploymentPlugin#getName()
   */
  public String getName() {
    return name;
  }

  /* (non-Javadoc)
   * @see org.exoplatform.services.deployment.DeploymentPlugin#setName(java.lang.String)
   */
  public void setName(String name) {
    this.name = name;
  }

  /**
   * Gets the path.
   *
   * @return the path
   */
  public String getPath() {
    return path;
  }

  /**
   * Sets the path.
   *
   * @param path the new path
   */
  public void setPath(String path) {
    this.path = path;
  }

  /**
   * Gets the permissions.
   *
   * @return the permissions
   */
  public List<Permission> getPermissions() {
    return permissions;
  }

  /**
   * Sets the permissions.
   *
   * @param permissions the new permissions
   */
  public void setPermissions(List<Permission> permissions) {
    this.permissions = permissions;
  }

  /**
   * Gets the workspace.
   *
   * @return the workspace
   */
  public String getWorkspace() {
    return workspace;
  }

  /**
   * Sets the workspace.
   *
   * @param workspace the new workspace
   */
  public void setWorkspace(String workspace) {
    this.workspace = workspace;
  }

  /**
   * Import predefine taxonomies.
   *
   * @param repository the repository
   *
   * @throws Exception the exception
   */
  @SuppressWarnings("unchecked")
  private void importPredefineTaxonomies() throws Exception {
    ManageableRepository manageableRepository = this.repositoryService.getCurrentRepository();
    DMSRepositoryConfiguration dmsRepoConfig = this.dmsConfiguration.getConfig();
    if (getWorkspace() == null) {
      setWorkspace(dmsRepoConfig.getSystemWorkspace());
    }
    Session session = manageableRepository.getSystemSession(getWorkspace());
    Node taxonomyStorageNode = (Node) session.getItem(getPath());
    if (taxonomyStorageNode.hasProperty("exo:isImportedChildren")) {
      session.logout();
      return;
    }
    taxonomyStorageNode.setProperty("exo:isImportedChildren", true);
    Iterator<ObjectParameter> it = params.getObjectParamIterator();
    Node taxonomyStorageNodeSystem = Utils.makePath(taxonomyStorageNode, treeName, "exo:taxonomy",
            null);
    session.save();
    while (it.hasNext()) {
      ObjectParameter objectParam = it.next();
      if (objectParam.getName().equals("permission.configuration")) {
        TaxonomyConfig config = (TaxonomyConfig) objectParam.getObject();
        for (Taxonomy taxonomy : config.getTaxonomies()) {
          Map mapPermissions = getPermissions(taxonomy.getPermissions());
          if (mapPermissions != null) {
            ((ExtendedNode) taxonomyStorageNodeSystem).setPermissions(mapPermissions);
          }
          if (taxonomyStorageNodeSystem.canAddMixin("mix:referenceable")) {
            taxonomyStorageNodeSystem.addMixin("mix:referenceable");
          }
        }
      } else if (objectParam.getName().equals("taxonomy.configuration")) {
        TaxonomyConfig config = (TaxonomyConfig) objectParam.getObject();
        for (Taxonomy taxonomy : config.getTaxonomies()) {
          Node taxonomyNode = Utils.makePath(taxonomyStorageNodeSystem, taxonomy.getPath(),
              "exo:taxonomy", getPermissions(taxonomy.getPermissions()));
          if (taxonomyNode.canAddMixin("mix:referenceable")) {
            taxonomyNode.addMixin("mix:referenceable");
          }
          taxonomyNode.getSession().save();
        }
      } else if (objectParam.getName().equals("predefined.actions")) {
        ActionConfig config = (ActionConfig) objectParam.getObject();
        List actions = config.getActions();
        for (Iterator iter = actions.iterator(); iter.hasNext();) {
          TaxonomyAction action = (TaxonomyAction) iter.next();
          addAction(action, taxonomyStorageNodeSystem);
        }
      }

    }
    taxonomyStorageNode.save();
    try {
      taxonomyService.addTaxonomyTree(taxonomyStorageNodeSystem);
    } catch (TaxonomyAlreadyExistsException e) {
      if (log.isInfoEnabled()) log.error("Cannot add taxonomy tree", e);
    }
    session.save();
    session.logout();
  }

  /**
   * Adds the action.
   *
   * @param action the action
   * @param srcNode the src node
   * @param repository the repository
   *
   * @throws Exception the exception
   */
  @SuppressWarnings("unchecked")
  private void addAction(ActionConfig.TaxonomyAction action, Node srcNode)
      throws Exception {
    Map<String, JcrInputProperty> sortedInputs = new HashMap<String, JcrInputProperty>();
    JcrInputProperty jcrInputName = new JcrInputProperty();
    jcrInputName.setJcrPath("/node/exo:name");
    jcrInputName.setValue(action.getName());
    sortedInputs.put("/node/exo:name", jcrInputName);
    JcrInputProperty jcrInputDes = new JcrInputProperty();
    jcrInputDes.setJcrPath("/node/exo:description");
    jcrInputDes.setValue(action.getDescription());
    sortedInputs.put("/node/exo:description", jcrInputDes);

    JcrInputProperty jcrInputLife = new JcrInputProperty();
    jcrInputLife.setJcrPath("/node/exo:lifecyclePhase");
    jcrInputLife.setValue(action.getLifecyclePhase().toArray(new String[0]));
    sortedInputs.put("/node/exo:lifecyclePhase", jcrInputLife);

    JcrInputProperty jcrInputHomePath = new JcrInputProperty();
    jcrInputHomePath.setJcrPath("/node/exo:storeHomePath");
    String homepath = action.getHomePath();
    homepath = StringUtils.replace(homepath, "{portalName}", portalName);
    homepath = StringUtils.replace(homepath, "{treeName}", treeName);
    jcrInputHomePath.setValue(homepath);
    sortedInputs.put("/node/exo:storeHomePath", jcrInputHomePath);

    JcrInputProperty jcrInputTargetWspace = new JcrInputProperty();
    jcrInputTargetWspace.setJcrPath("/node/exo:targetWorkspace");
    jcrInputTargetWspace.setValue(action.getTargetWspace());
    sortedInputs.put("/node/exo:targetWorkspace", jcrInputTargetWspace);

    JcrInputProperty jcrInputTargetPath = new JcrInputProperty();
    jcrInputTargetPath.setJcrPath("/node/exo:targetPath");
    String targetPath = action.getTargetPath();
    targetPath = StringUtils.replace(targetPath, "{portalName}", portalName);
    jcrInputTargetPath.setValue(targetPath);
    sortedInputs.put("/node/exo:targetPath", jcrInputTargetPath);

    JcrInputProperty rootProp = sortedInputs.get("/node");
    if (rootProp == null) {
      rootProp = new JcrInputProperty();
      rootProp.setJcrPath("/node");
      rootProp.setValue((sortedInputs.get("/node/exo:name")).getValue());
      sortedInputs.put("/node", rootProp);
    } else {
      rootProp.setValue((sortedInputs.get("/node/exo:name")).getValue());
    }
    actionServiceContainer.addAction(srcNode, action.getType(), sortedInputs);
    Node actionNode = actionServiceContainer.getAction(srcNode, action.getName());
    if (action.getRoles() != null) {
      String[] roles = StringUtils.split(action.getRoles(), ";");
      actionNode.setProperty("exo:roles", roles);
    }

    Iterator mixins = action.getMixins().iterator();
    NodeType nodeType;
    String value;
    ManageableRepository manageableRepository = WCMCoreUtils.getRepository();
    while (mixins.hasNext()) {
      ActionConfig.Mixin mixin = (ActionConfig.Mixin) mixins.next();
      actionNode.addMixin(mixin.getName());
      Map<String, String> props = mixin.getParsedProperties();
      Set keys = props.keySet();
      nodeType = manageableRepository.getNodeTypeManager().getNodeType(mixin.getName());
      for (Iterator iterator = keys.iterator(); iterator.hasNext();) {
        String key = (String) iterator.next();
        for(PropertyDefinition pro : nodeType.getPropertyDefinitions()) {
          if (pro.getName().equals(key)) {
            if (pro.isMultiple()) {
              value = props.get(key);
              if (value != null) {
                actionNode.setProperty(key, value.split(","));
              }
            } else {
              actionNode.setProperty(key, props.get(key));
            }
            break;
          }
        }
      }
    }
    actionNode.getSession().save();
  }

  /**
   * Gets the permissions.
   *
   * @param listPermissions the list permissions
   *
   * @return the permissions
   */
  private Map<String, String[]> getPermissions(List<Permission> listPermissions) {
    Map<String, String[]> permissionsMap = new HashMap<String, String[]>();
    for (Permission permission : listPermissions) {
      StringBuilder strPer = new StringBuilder();
      if ("true".equals(permission.getRead()))
        strPer.append(PermissionType.READ);
      if ("true".equals(permission.getAddNode()))
        strPer.append(",").append(PermissionType.ADD_NODE);
      if ("true".equals(permission.getSetProperty()))
        strPer.append(",").append(PermissionType.SET_PROPERTY);
      if ("true".equals(permission.getRemove()))
        strPer.append(",").append(PermissionType.REMOVE);
      permissionsMap.put(permission.getIdentity(), strPer.toString().split(","));
    }
    return permissionsMap;
  }

}
