/*
 * Copyright (C) 2003-2007 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.ecm.jcr.model;

import java.util.*;

import javax.jcr.*;
import javax.jcr.version.Version;
import javax.jcr.version.VersionIterator;

import org.exoplatform.ecm.webui.utils.Utils;
import org.exoplatform.services.cms.documents.VersionHistoryUtils;
import org.exoplatform.services.cms.impl.DMSConfiguration;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;

public class VersionNode {
  private static final String EXO_LAST_MODIFIED_DATE = "exo:lastModifiedDate";

  private boolean           isExpanded     = true;

  private List<VersionNode> children_      = new ArrayList<VersionNode>();

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

  private Calendar          createdTime_;

  private String            name_          = "";

  private String            displayName         = "";

  private String            path_          = "";

  private String            ws_            = "";

  private String            uuid_;

  private String[]          versionLabels_ = new String[] {};

  private String            author_        = "";

  public VersionNode(Node node, Session session) {
    Version version = node instanceof Version ? (Version) node : null;
    try {
      Property property = getProperty(node, EXO_LAST_MODIFIED_DATE);
      if (property != null) {
        createdTime_ = property.getDate();
      }
      name_ = version == null ? "" : version.getName();
      path_ = node.getPath();
      ws_ = node.getSession().getWorkspace().getName();
      uuid_ = node.getUUID();
      Property prop = getProperty(node, Utils.EXO_LASTMODIFIER);
      author_ = prop == null ? null : prop.getString();
      if (version == null) {
        if (node.isNodeType(Utils.MIX_VERSIONABLE)) {
          addVersions(node, session);
        }
      } else {
        addVersions(node, session);
      }
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unexpected error", e);
      }
    }
  }

  /**
   * Add versions of the given node as children with the right name and display name, and set
   * the name dan display name of the current version.
   * If the node has a property exo:maxVersion, it means some versions have been removed and
   * we must use this value as the current version.
   * If the node has a property exo:versionList, it means some versions have been removed then
   * some others have been created. Since the display name of the versions does not follow the sequential numbering
   * anymore (for example 1,3,4 if version 2 has been removed), the property exo:versionList maps the
   * version name with the correct display name.
   * To be noted : display name is always decremented by 1 from the version index of offset since
   * we want the display name to start from 0
   * @param node JCR node
   * @param session JCR session
   * @throws RepositoryException
   */
  private void addVersions(Node node, Session session) throws RepositoryException {
    if(node instanceof Version) {
      Version version = (Version) node;
      versionLabels_ = version.getContainingHistory().getVersionLabels(version);
    } else {
      int maxVersion = 0;
      Map<String, String> mapVersionName = new HashMap<>();
      if(node.isNodeType(VersionHistoryUtils.MIX_DISPLAY_VERSION_NAME)){
        //maxVersion of root version
        if(node.hasProperty(VersionHistoryUtils.MAX_VERSION_PROPERTY)){
          maxVersion = (int) node.getProperty(VersionHistoryUtils.MAX_VERSION_PROPERTY).getLong();
        }
        //list of version name entries (jcrID, display version)
        if(node.hasProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY)){
          Value[] values = node.getProperty(VersionHistoryUtils.LIST_VERSION_PROPERTY).getValues();
          for (Value value : values){
            mapVersionName.put(value.getString().split(VersionHistoryUtils.VERSION_SEPARATOR)[0],
                    value.getString().split(VersionHistoryUtils.VERSION_SEPARATOR)[1]);
          }
        }
      }
      Version rootVersion = node.getVersionHistory().getRootVersion();
      VersionIterator allVersions = node.getVersionHistory().getAllVersions();
      int maxIndex = 0;
      while (allVersions.hasNext()) {
        Version version = allVersions.nextVersion();

        if(version.getUUID().equals(rootVersion.getUUID())) {
          continue;
        }

        int versionIndex = Integer.parseInt(version.getName());
        maxIndex = Math.max(maxIndex , versionIndex);

        String versionOffset = mapVersionName.get(version.getName());

        VersionNode versionNode = new VersionNode(version, session);
        if(versionOffset != null) {
          versionNode.setDisplayName(String.valueOf(Integer.parseInt(versionOffset) - 1));
        } else {
          versionNode.setDisplayName(String.valueOf(versionIndex - 1));
        }
        children_.add(versionNode);
      }
      name_ = String.valueOf(maxIndex + 1);
      displayName = maxVersion > 0 ?  String.valueOf(maxVersion - 1) : String.valueOf(maxIndex);
      versionLabels_ = node.getVersionHistory().getVersionLabels(rootVersion);
    }
  }

  private Property getProperty(Node node, String propName) throws RepositoryException {
    Property property = null;
    if (node.hasProperty(propName)) {
      property = node.getProperty(propName);
    } else if (node.hasNode(Utils.JCR_FROZEN) && node.getNode(Utils.JCR_FROZEN).hasProperty(propName)) {
      property = node.getNode(Utils.JCR_FROZEN).getProperty(propName);
    }
    return property;
  }

  public boolean isExpanded() {
    return isExpanded;
  }

  public void setExpanded(boolean isExpanded) {
    this.isExpanded = isExpanded;
  }

  public String getName() {
    return name_;
  }

  public String getDisplayName() {
    return displayName;
  }

  public void setDisplayName(String displayName_) {
    this.displayName = displayName_;
  }

  public String getWs() {
    return ws_;
  }

  public String getPath() {
    return path_;
  }

  public int getChildrenSize() {
    return children_.size();
  }

  public List<VersionNode> getChildren() {
    return children_;
  }

  public Calendar getCreatedTime() {
    return createdTime_;
  }

  public String getAuthor() {
    return author_;
  }

  public String[] getVersionLabels() {
    return versionLabels_;
  }

  public Node getNode(String nodeName) throws RepositoryException {
    DMSConfiguration dmsConf = WCMCoreUtils.getService(DMSConfiguration.class);
    String systemWS = dmsConf.getConfig().getSystemWorkspace();
    ManageableRepository repo = WCMCoreUtils.getRepository();
    SessionProvider provider = systemWS.equals(ws_) ? WCMCoreUtils.getSystemSessionProvider()
                                                   : WCMCoreUtils.getUserSessionProvider();
    return ((Node) provider.getSession(ws_, repo).getItem(path_)).getNode(nodeName);
  }

  public boolean hasNode(String nodeName) throws Exception {
    DMSConfiguration dmsConf = WCMCoreUtils.getService(DMSConfiguration.class);
    String systemWS = dmsConf.getConfig().getSystemWorkspace();
    ManageableRepository repo = WCMCoreUtils.getRepository();
    SessionProvider provider = systemWS.equals(ws_) ? WCMCoreUtils.getSystemSessionProvider()
                                                   : WCMCoreUtils.getUserSessionProvider();
    return ((Node) provider.getSession(ws_, repo).getItem(path_)).hasNode(nodeName);
  }

  public String getUUID() {
    return uuid_;
  }

  public VersionNode findVersionNode(String path) throws RepositoryException {
    if (path_.equals(path))
      return this;
    VersionNode node = null;
    Iterator<VersionNode> iter = children_.iterator();
    while (iter.hasNext()) {
      VersionNode child = (VersionNode) iter.next();
      node = child.findVersionNode(path);
      if (node != null)
        return node;
    }
    return null;
  }

  public void removeVersionInChild(VersionNode versionNode1, VersionNode versionNodeRemove) throws RepositoryException {
    if (versionNode1.getChildren().contains(versionNodeRemove))
      versionNode1.getChildren().remove(versionNodeRemove);
    else {
      for (VersionNode vsN : versionNode1.getChildren()) {
        removeVersionInChild(vsN, versionNodeRemove);
      }
    }
  }

}
