/*
 * 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.popup.admin;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.ResourceBundle;
import java.util.UUID;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.Value;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.download.DownloadService;
import org.exoplatform.download.InputStreamDownloadResource;
import org.exoplatform.ecm.utils.text.Text;
import org.exoplatform.ecm.webui.component.explorer.UIJCRExplorer;
import org.exoplatform.ecm.webui.utils.Utils;
import org.exoplatform.services.compress.CompressData;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.web.application.RequestContext;
import org.exoplatform.web.application.RequireJS;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIApplication;
import org.exoplatform.webui.core.UIPopupComponent;
import org.exoplatform.webui.core.lifecycle.UIFormLifecycle;
import org.exoplatform.webui.core.model.SelectItemOption;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;
import org.exoplatform.webui.form.UIForm;
import org.exoplatform.webui.form.UIFormInputInfo;
import org.exoplatform.webui.form.UIFormRadioBoxInput;
import org.exoplatform.webui.form.input.UICheckBoxInput;

/**
 * Created by The eXo Platform SARL
 * Author : Dang Van Minh
 *          minh.dang@exoplatform.com
 * Oct 5, 2006
 */
@ComponentConfig(
    lifecycle = UIFormLifecycle.class,
    template =  "app:/groovy/webui/component/explorer/popup/admin/UIFormWithMultiRadioBox.gtmpl",
    events = {
      @EventConfig(listeners = UIExportNode.ExportActionListener.class),
      @EventConfig(listeners = UIExportNode.ExportHistoryActionListener.class),
      @EventConfig(listeners = UIExportNode.CancelActionListener.class)
    }
)
public class UIExportNode extends UIForm implements UIPopupComponent {

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

  public static final String NODE_PATH         = "nodePath";

  public static final String FORMAT            = "format";

  public static final String ZIP               = "zip";

  public static final String DOC_VIEW          = "docview";

  public static final String SYS_VIEW          = "sysview";

  public static final String VERSION_SQL_QUERY = "select * from mix:versionable where jcr:path like '$0/%' "
                                                   + "order by exo:dateCreated DESC";

  public static final String ROOT_SQL_QUERY    = "select * from mix:versionable order by exo:dateCreated DESC";

  private boolean isVerionNode_ = false;

  public UIExportNode() throws Exception {
    RequestContext context = RequestContext.getCurrentInstance();
    ResourceBundle resourceBundle = context.getApplicationResourceBundle();
    List<SelectItemOption<String>> formatItem = new ArrayList<SelectItemOption<String>>() ;
    formatItem.add(new SelectItemOption<String>(
        resourceBundle.getString("Import.label." + SYS_VIEW), SYS_VIEW));
    formatItem.add(new SelectItemOption<String>(
        resourceBundle.getString("Import.label." + DOC_VIEW), DOC_VIEW));
    addUIFormInput(new UIFormInputInfo(NODE_PATH, NODE_PATH, null)) ;
    addUIFormInput(new UIFormRadioBoxInput(FORMAT, SYS_VIEW, formatItem).
                   setAlign(UIFormRadioBoxInput.VERTICAL_ALIGN)) ;
    addUIFormInput(new UICheckBoxInput(ZIP, ZIP, null)) ;
  }

  public void update(Node node) throws Exception {
    getUIFormInputInfo(NODE_PATH).setValue(Text.unescapeIllegalJcrChars(node.getPath())) ;
  }

  public void activate() {
    try {
      update(getAncestorOfType(UIJCRExplorer.class).getCurrentNode());
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Unexpected error!", e.getMessage());
      }
    }
  }

  public void deActivate() { }

  public QueryResult getQueryResult(Node currentNode) throws RepositoryException {
    QueryManager queryManager = currentNode.getSession().getWorkspace().getQueryManager();
    String queryStatement = "";
    if(currentNode.getPath().equals("/")) {
      queryStatement = ROOT_SQL_QUERY;
    } else {
      queryStatement = StringUtils.replace(VERSION_SQL_QUERY,"$0",currentNode.getPath());
    }
    Query query = queryManager.createQuery(queryStatement, Query.SQL);
    return query.execute();
  }

  public String[] getActions() {
    try {
      Node currentNode = getAncestorOfType(UIJCRExplorer.class).getCurrentNode();
      if(currentNode.isNodeType(Utils.MIX_VERSIONABLE)) isVerionNode_ = true;
      QueryResult queryResult = getQueryResult(currentNode);
      if(queryResult.getNodes().getSize() > 0 || isVerionNode_) {
        return new String[] {"Export", "ExportHistory", "Cancel"};
      }
    } catch(Exception e) {
      return new String[] {"Export", "Cancel"};
    }
    return new String[] {"Export", "Cancel"};
  }

  private String getHistoryValue(Node node) throws Exception {
    String versionHistory = node.getProperty("jcr:versionHistory").getValue().getString();
    String baseVersion = node.getProperty("jcr:baseVersion").getValue().getString();
    Value[] predecessors = node.getProperty("jcr:predecessors").getValues();
    StringBuilder historyValue = new StringBuilder();
    StringBuilder predecessorsBuilder = new StringBuilder();
    for(Value value : predecessors) {
      if(predecessorsBuilder.length() > 0) predecessorsBuilder.append(",") ;
      predecessorsBuilder.append(value.getString());
    }
    historyValue.append(node.getUUID()).append("=").append(versionHistory).
      append(";").append(baseVersion).append(";").append(predecessorsBuilder.toString());
    return historyValue.toString();
  }

  /**
   * Create temp file to allow download a big data
   * @return file
   */
  private static File getExportedFile(String prefix, String suffix) throws IOException {
    return File.createTempFile(prefix.concat(UUID.randomUUID().toString()), suffix);
  }

  static public class ExportActionListener extends EventListener<UIExportNode> {
    public void execute(Event<UIExportNode> event) throws Exception {
      UIExportNode uiExport = event.getSource();
      UIJCRExplorer uiExplorer = uiExport.getAncestorOfType(UIJCRExplorer.class);
      UIApplication uiApp = uiExport.getAncestorOfType(UIApplication.class);
      File exportedFile = UIExportNode.getExportedFile("export", ".xml");
      OutputStream out = new BufferedOutputStream(new FileOutputStream(exportedFile));
      InputStream in = new BufferedInputStream(new TempFileInputStream(exportedFile));
      CompressData zipService = new CompressData();
      DownloadService dservice = uiExport.getApplicationComponent(DownloadService.class);
      InputStreamDownloadResource dresource;
      String format = uiExport.<UIFormRadioBoxInput> getUIInput(FORMAT).getValue();
      boolean isZip = uiExport.getUICheckBoxInput(ZIP).isChecked();
      Node currentNode = uiExplorer.getCurrentNode();
      Session session = currentNode.getSession() ;
      String nodePath = currentNode.getPath();
      File zipFile = null;
      try {
        if(isZip) {
          if (format.equals(DOC_VIEW))
            session.exportDocumentView(nodePath, out, false, false);
          else
            session.exportSystemView(nodePath, out, false, false);
          out.flush();
          out.close();
          zipFile = UIExportNode.getExportedFile("data", ".zip");
          out = new BufferedOutputStream(new FileOutputStream(zipFile));
          zipService.addInputStream(format + ".xml", in);
          zipService.createZip(out);
          in.close();
          exportedFile.delete();
          in = new BufferedInputStream(new TempFileInputStream(zipFile));
          dresource = new InputStreamDownloadResource(in, "application/zip");
          dresource.setDownloadName(format + ".zip");
        } else {
          if (format.equals(DOC_VIEW))
            session.exportDocumentView(nodePath, out, false, false);
          else
            session.exportSystemView(nodePath, out, false, false);
          out.flush();
          dresource = new InputStreamDownloadResource(in, "text/xml");
          dresource.setDownloadName(format + ".xml");
        }
        String downloadLink = dservice.getDownloadLink(dservice.addDownloadResource(dresource));
        RequireJS requireJS = event.getRequestContext().getJavascriptManager().getRequireJS();
        requireJS.require("SHARED/ecm-utils", "ecmutil").addScripts("ecmutil.ECMUtils.ajaxRedirect('" + downloadLink + "');");
        uiExplorer.cancelAction();
      } catch (OutOfMemoryError error) {
        uiApp.addMessage(new ApplicationMessage("UIExportNode.msg.OutOfMemoryError", null, ApplicationMessage.ERROR));

        return;
      } finally {
        out.close();
      }
    }
  }

  static public class ExportHistoryActionListener extends EventListener<UIExportNode> {
    public void execute(Event<UIExportNode> event) throws Exception {
      UIExportNode uiExport = event.getSource() ;
      UIJCRExplorer uiExplorer = uiExport.getAncestorOfType(UIJCRExplorer.class) ;
      UIApplication uiApp = uiExport.getAncestorOfType(UIApplication.class);
      CompressData zipService = new CompressData();
      DownloadService dservice = uiExport.getApplicationComponent(DownloadService.class) ;
      InputStreamDownloadResource dresource ;
      Node currentNode = uiExplorer.getCurrentNode();
      String sysWsName = uiExplorer.getRepository().getConfiguration().getSystemWorkspaceName();
      Session session = uiExplorer.getSessionByWorkspace(sysWsName);
      QueryResult queryResult = uiExport.getQueryResult(currentNode);
      NodeIterator queryIter = queryResult.getNodes();
      String format = uiExport.<UIFormRadioBoxInput>getUIInput(FORMAT).getValue() ;
      OutputStream out = null;
      InputStream in = null;
      List<File> lstExporedFile = new ArrayList<File>();
      File exportedFile = null;
      File zipFile = null;
      File propertiesFile = UIExportNode.getExportedFile("mapping", ".properties");
      OutputStream propertiesBOS = new BufferedOutputStream(new FileOutputStream(propertiesFile));
      InputStream propertiesBIS = new BufferedInputStream(new TempFileInputStream(propertiesFile));
      try {
        while(queryIter.hasNext()) {
          exportedFile = UIExportNode.getExportedFile("data", ".xml");
          lstExporedFile.add(exportedFile);
          out = new BufferedOutputStream(new FileOutputStream(exportedFile));
          in = new BufferedInputStream(new TempFileInputStream(exportedFile));
          Node node = queryIter.nextNode();
          String historyValue = uiExport.getHistoryValue(node);
          propertiesBOS.write(historyValue.getBytes());
          propertiesBOS.write('\n');
        if(format.equals(DOC_VIEW))
              session.exportDocumentView(node.getVersionHistory().getPath(), out, false, false );
        else
          session.exportSystemView(node.getVersionHistory().getPath(), out, false, false );
          out.flush();
          zipService.addInputStream(node.getUUID() + ".xml", in);
        }
        if(currentNode.isNodeType(Utils.MIX_VERSIONABLE)) {
          exportedFile = UIExportNode.getExportedFile("data", ".xml");
          lstExporedFile.add(exportedFile);
          out = new BufferedOutputStream(new FileOutputStream(exportedFile));
          in = new BufferedInputStream(new TempFileInputStream(exportedFile));
          String historyValue = uiExport.getHistoryValue(currentNode);
          propertiesBOS.write(historyValue.getBytes());
          propertiesBOS.write('\n');
          if (format.equals(DOC_VIEW))
            session.exportDocumentView(currentNode.getVersionHistory().getPath(), out, false, false);
          else
            session.exportSystemView(currentNode.getVersionHistory().getPath(), out, false, false);
          out.flush();
          zipService.addInputStream(currentNode.getUUID() + ".xml",in);
        }
        propertiesBOS.flush();
        zipService.addInputStream("mapping.properties", propertiesBIS);
        zipFile = UIExportNode.getExportedFile("data", "zip");
        in = new BufferedInputStream(new TempFileInputStream(zipFile));
        out = new BufferedOutputStream(new FileOutputStream(zipFile));
        out.flush();
        zipService.createZip(out);
        dresource = new InputStreamDownloadResource(in, "application/zip") ;
        dresource.setDownloadName(format + "_versionHistory.zip");
        String downloadLink = dservice.getDownloadLink(dservice.addDownloadResource(dresource)) ;
        RequireJS requireJS = event.getRequestContext().getJavascriptManager().getRequireJS();
        requireJS.require("SHARED/ecm-utils", "ecmutil").addScripts("ecmutil.ECMUtils.ajaxRedirect('" + downloadLink + "');");
      } catch (OutOfMemoryError error) {
        uiApp.addMessage(new ApplicationMessage("UIExportNode.msg.OutOfMemoryError", null, ApplicationMessage.ERROR));

        return;
      } finally {
        propertiesBOS.close();
        propertiesBIS.close();
        if (out != null) {
          out.close();
        }
      }
    }
  }

  static public class CancelActionListener extends EventListener<UIExportNode> {
    public void execute(Event<UIExportNode> event) throws Exception {
      UIJCRExplorer uiExplorer = event.getSource().getAncestorOfType(UIJCRExplorer.class) ;
      uiExplorer.cancelAction() ;
    }
  }

  private static class TempFileInputStream extends FileInputStream {

    private final File file;

    public TempFileInputStream(File file) throws FileNotFoundException {
      super(file);
      this.file = file;
      try {
        file.deleteOnExit();
      } catch (Exception e) {
        // ignore me
      }
    }

    @Override
    protected void finalize() throws IOException {
      try {
        file.delete();
      } catch (Exception e) {
        // ignore me
      }
      super.finalize();
    }
  }
}
