/*
 * 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.webui.component.explorer.versions;

import java.text.DateFormat;
import java.util.Calendar;
import java.util.Locale;

import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.version.Version;
import javax.jcr.version.VersionHistory;

import org.apache.commons.lang3.StringUtils;

import org.exoplatform.commons.diff.DiffResult;
import org.exoplatform.commons.diff.DiffService;
import org.exoplatform.ecm.webui.component.explorer.UIDocumentWorkspace;
import org.exoplatform.ecm.webui.utils.Utils;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.document.DocumentReader;
import org.exoplatform.services.document.DocumentReaderService;
import org.exoplatform.services.document.HandlerNotFoundException;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIComponent;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;

/**
 * Created by The eXo Platform SARL Author : Pham Tuan tuan.pham@exoplatform.com
 * May 3, 2007
 */

@ComponentConfig(template = "app:/groovy/webui/component/explorer/versions/UIDiff.gtmpl", events = {
    @EventConfig(listeners = UIDiff.CompareActionListener.class),
    @EventConfig(listeners = UIDiff.CloseCompareActionListener.class) })
public class UIDiff extends UIComponent {
  private static final String EXO_LAST_MODIFIED_DATE = "exo:lastModifiedDate";

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

  public static final String FROM_PARAM          = "from";

  public static final String TO_PARAM            = "to";

  private String             baseVersionName_;

  private String             baseVersionDate_;

  private String             baseVersionAuthor_;

  private String             baseVersionLabel_;

  private String             versionName_;

  private String             versionDate_;

  private String             versionAuthor_;

  private String             versionLabel_;

  private String             previousText_       = null;

  private String             currentText_        = null;

  private boolean            versionCompareable_ = true;

  private String             diffResultHTML_     = null;

  private boolean            isImage_            = false;

  private String             baseImage_          = null;

  private String             currentImage_       = null;

  public void setVersions(Node baseVersion, Node version) throws Exception {
    UIDocumentWorkspace uiDocumentWorkspace = getAncestorOfType(UIDocumentWorkspace.class);
    UIVersionInfo uiVersionInfo = uiDocumentWorkspace.getChild(UIVersionInfo.class);

    versionCompareable_ = baseVersion != null && version != null && !baseVersion.getUUID().equals(version.getUUID());
    if (!versionCompareable_) {
      throw new IllegalStateException("Can't compare both versions");
    } else {
      Node versionRootVersion = null;
      if (version instanceof Version) {
        versionRootVersion = ((Version) version).getContainingHistory().getRootVersion();
      } else {
        versionRootVersion = version.getVersionHistory().getRootVersion();
      }

      Node baseVersionRootVersion = null;
      if (baseVersion instanceof Version) {
        baseVersionRootVersion = ((Version) baseVersion).getContainingHistory().getRootVersion();
      } else {
        baseVersionRootVersion = baseVersion.getVersionHistory().getRootVersion();
      }

      versionCompareable_ = baseVersionRootVersion.getUUID().endsWith(versionRootVersion.getUUID());
      if (!versionCompareable_) {
        throw new IllegalStateException("Versions to compare are the same");
      }
    }

    Node currentNode = uiVersionInfo.getCurrentNode();
    VersionHistory versionHistory = currentNode.getVersionHistory();
    Version rootVersion = versionHistory.getRootVersion();

    // Make baseVersion parameter always the oldest version
    if (currentNode.getUUID().equals(baseVersion.getUUID())) {
      // switch version and baseVersion to make sure that baseVersion is the
      // oldest
      Node tmpNode = baseVersion;
      baseVersion = version;
      version = tmpNode;
    } else if (currentNode.getUUID().equals(version.getUUID())) {
      // It's ok, the version' indice > baseVersion indice
    } else if (baseVersion instanceof Version && version instanceof Version) {
      // compare versions by indice
      int baseVersionIndice = Integer.parseInt(baseVersion.getName());
      int versionNumIndice = Integer.parseInt(version.getName());

      if (baseVersionIndice == versionNumIndice) {
        throw new IllegalStateException("Can't compare the same version");
      } else if (baseVersionIndice > versionNumIndice) {
        Node tmpNode = baseVersion;
        baseVersion = version;
        version = tmpNode;
      }
    }

    // Base version is of type Version all the time
    baseVersionName_ = ((Version) baseVersion).getName();
    // Current version can be of type Version or Node (draft node)
    versionName_ = version instanceof Version ? version.getName() : uiVersionInfo.getRootVersionNum();

    // Base version is of type Version all the time
    Calendar modifiedDate = getModifiedDate(baseVersion);
    baseVersionDate_ = formatDate(modifiedDate);

    // Current version can be of type Version or Node (draft node)
    modifiedDate = getModifiedDate(version);
    versionDate_ = formatDate(modifiedDate);

    baseVersionAuthor_ = getLastModifier(baseVersion);
    versionAuthor_ = getLastModifier(version);

    // Base version is of type Version all the time
    String[] baseVersionLabels = versionHistory.getVersionLabels((Version) baseVersion);
    baseVersionLabel_ = baseVersionLabels == null || baseVersionLabels.length == 0 ? null : baseVersionLabels[0];
    // Current version can be of type Version or Node (draft node)
    if (version instanceof Version) {
      String[] versionLabels = versionHistory.getVersionLabels((Version) version);
      versionLabel_ = versionLabels == null || versionLabels.length == 0 ? null : versionLabels[0];
    } else {
      String[] versionLabels = versionHistory.getVersionLabels(rootVersion);
      versionLabel_ = versionLabels == null || versionLabels.length == 0 ? null : versionLabels[0];
    }

    isImage_ = isOriginalNodeImage(version) && isOriginalNodeImage(baseVersion);

    previousText_ = null;
    currentText_ = null;
    diffResultHTML_ = null;
    currentImage_ = null;
    baseImage_ = null;
    isImage_ = isOriginalNodeImage(version) && isOriginalNodeImage(baseVersion);

    if (isImage_) {
      String wsName = currentNode.getSession().getWorkspace().getName();
      String originalNodePath = currentNode.getPath();
      String basePath = "/" + WCMCoreUtils.getPortalName() + "/" + WCMCoreUtils.getRestContextName() + "/jcr/"
          + WCMCoreUtils.getRepository().getConfiguration().getName() + "/" + wsName + originalNodePath;

      long timeInMS = Calendar.getInstance().getTimeInMillis();

      currentImage_ = basePath + (currentNode.getUUID().equals(version.getUUID()) ? "?" + timeInMS : ("?version=" + version.getName() + "&" + timeInMS));
      baseImage_ = basePath + (currentNode.getUUID().equals(baseVersion.getUUID()) ? "?" + timeInMS : "?version=" + baseVersion.getName() + "&" + timeInMS);
    } else {
      try {
        previousText_ = getText(baseVersion);
        currentText_ = getText(version);
      } catch (HandlerNotFoundException e) {
        versionCompareable_ = false;
      }

      if (versionCompareable_) {
        if (StringUtils.isBlank(previousText_) && StringUtils.isBlank(currentText_)) {
          versionCompareable_ = false;
        } else if ((previousText_ != null) && (currentText_ != null)) {
          DiffService diffService = WCMCoreUtils.getService(DiffService.class);
          DiffResult diffResult = diffService.getDifferencesAsHTML(previousText_, currentText_, true);
          diffResultHTML_ = diffResult.getDiffHTML();
        }
      }
    }
  }

  public String getBaseVersionNum() throws Exception {
    return baseVersionName_;
  }

  public String getCurrentVersionNum() throws Exception {
    return versionName_;
  }

  public String getBaseVersionDate() throws Exception {
    return baseVersionDate_;
  }

  public String getCurrentVersionDate() throws Exception {
    return versionDate_;
  }

  public String getBaseVersionAuthor() {
    return baseVersionAuthor_;
  }

  public String getCurrentVersionAuthor() {
    return versionAuthor_;
  }

  public String getBaseVersionLabel() {
    return baseVersionLabel_;
  }

  public String getCurrentVersionLabel() {
    return versionLabel_;
  }

  public String getBaseImage() throws Exception {
    return baseImage_;
  }

  public String getCurrentImage() throws Exception {
    return currentImage_;
  }

  public boolean isCompareable() {
    return versionCompareable_;
  }

  public String getDifferences() throws Exception {
    return diffResultHTML_;
  }

  public boolean isImage() {
    return isImage_;
  }

  private Calendar getModifiedDate(Node node) throws RepositoryException {
    Property property = getProperty(node, EXO_LAST_MODIFIED_DATE);
    return property == null ? null : property.getDate();
  }

  private String getLastModifier(Node node) throws RepositoryException {
    Property property = getProperty(node, Utils.EXO_LASTMODIFIER);
    return property == null ? null : property.getString();
  }

  private boolean isOriginalNodeImage(Node node) throws Exception {
    Property mimeProperty = getProperty(node, Utils.JCR_MIMETYPE);
    String mimeType = mimeProperty == null ? null : mimeProperty.getString();
    return StringUtils.isNotBlank(mimeType) && mimeType.startsWith("image");
  }

  private String getText(Node node) throws Exception {
    Property mimeProperty = getProperty(node, Utils.JCR_MIMETYPE);
    Property dataProperty = getProperty(node, Utils.JCR_DATA);
    if (mimeProperty != null && dataProperty != null) {
      String mimeType = mimeProperty.getString();
      if (mimeType.startsWith("text")) {
        return dataProperty.getString();
      }
      DocumentReaderService readerService = getApplicationComponent(DocumentReaderService.class);
      DocumentReader documentReader = readerService.getDocumentReader(mimeType);
      if (documentReader != null) {
        try {
          return documentReader.getContentAsText(dataProperty.getStream());
        } catch (Exception e) {
          LOG.warn("An error occured while getting text from file " + node.getPath() + " with mimeType " + mimeType
              + " with document reader = " + documentReader + ". File comparaison will be disabled", e);
        }
      }
    }
    throw new HandlerNotFoundException();
  }

  private Property getProperty(Node node, String propertyName) throws RepositoryException, PathNotFoundException {
    Property property = null;
    if (node instanceof Version) {
      if (node.hasNode(Utils.JCR_FROZEN)) {
        node = node.getNode(Utils.JCR_FROZEN);
      } else {
        return null;
      }
    }
    if (node.hasProperty(propertyName)) {
      property = node.getProperty(propertyName);
    } else if (node.hasNode(Utils.JCR_CONTENT)) {
      Node content = node.getNode(Utils.JCR_CONTENT);
      if (content.hasProperty(propertyName)) {
        property = content.getProperty(propertyName);
      }
    }
    return property;
  }

  private String formatDate(Calendar calendar) {
    Locale currentLocale = Util.getPortalRequestContext().getLocale();
    DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT, currentLocale);
    return dateFormat.format(calendar.getTime());
  }

  static public class CloseCompareActionListener extends EventListener<UIDiff> {
    public void execute(Event<UIDiff> event) throws Exception {
      UIDiff uiDiff = event.getSource();
      if (uiDiff.isRendered()) {
        uiDiff.setRendered(false);
      }
      UIDocumentWorkspace uiDocumentWorkspace = uiDiff.getAncestorOfType(UIDocumentWorkspace.class);
      UIVersionInfo uiVersionInfo = uiDocumentWorkspace.getChild(UIVersionInfo.class);
      uiVersionInfo.setRendered(true);
      event.getRequestContext().addUIComponentToUpdateByAjax(uiDocumentWorkspace);
    }
  }

  static public class CompareActionListener extends EventListener<UIDiff> {
    public void execute(Event<UIDiff> event) throws Exception {
      UIDiff uiDiff = (UIDiff) event.getSource();
      String fromVersionName = event.getRequestContext().getRequestParameter(FROM_PARAM);
      String toVersionName = event.getRequestContext().getRequestParameter(TO_PARAM);
      UIDocumentWorkspace uiDocumentWorkspace = uiDiff.getAncestorOfType(UIDocumentWorkspace.class);
      UIVersionInfo uiVersionInfo = uiDocumentWorkspace.getChild(UIVersionInfo.class);
      uiDiff.setVersions(uiVersionInfo.getVersion(fromVersionName), uiVersionInfo.getVersion(toVersionName));
    }
  }
}
