/*
 * Copyright (C) 2003-2012 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.services.deployment;

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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
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.commons.notification.BaseNotificationTestCase;
import org.exoplatform.services.compress.CompressData;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 *          exo@exoplatform.com
 * May 18, 2012  
 */
public class UtilsTest extends BaseNotificationTestCase {
    
  private static final String    ROOT_SQL_QUERY      = "select * from mix:versionable order by exo:dateCreated DESC";

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

  private static final String    DOC_VIEW            = "docview";
  
  private static final String    SYS_VIEW            = "sysview";

  private static final int       CREATE_NEW_BEHAVIOR = 0;

  public void setUp() throws Exception {
    super.setUp();
  }
  
  /**
   * Testcase for Utils.makePath()
   *   
   * @throws Exception
   */
  public void testMakePath() throws Exception {
    String level2RelPath = "level1/level2";
    Node level2 = Utils.makePath(root, level2RelPath, "nt:folder");
    assertEquals(level2.getPath(), root.getPath() + level2RelPath);
    
    String level3RelPath = "level1/level2/level3";
    Node level3 = Utils.makePath(root, level3RelPath, "nt:folder");    
    assertEquals(level3.getPath(), root.getPath() + level3RelPath);
    
    String level5RelPath = "level4/level5";
    Node level5 = Utils.makePath(level3, level5RelPath, "nt:folder");    
    assertEquals(level5.getPath(), level3.getPath() + "/" + level5RelPath);
    
    Map<String, String[]> permissionsMap = new HashMap<String, String[]>();
    permissionsMap.put("*:/platform/web-contributors", new String[] {"read", "addNode", "setProperty"});
    String level7RelPath = "level6/level7";
    Node level7 = Utils.makePath(level5, level7RelPath, "nt:folder", permissionsMap);    
    assertEquals(level7.getPath(), level5.getPath() + "/" + level7RelPath);    
  }
  
  /**
   * Testcase for:
   *  - Utils.getMapImportHistory() 
   *  - Utils.processImportHistory()
   * 
   * Description: In this testcase, we will try to export data from node testFolder, and then import it into sandbox
   * 
   * Input JCR data structure:
   * 
   *      /[root]
   *      |--> testFolder
   *      |        |-----> subFolder1
   *      |        |-----> subFolder2
   *      |
   *      |--> sandbox
   *      
   * Expected result: 
   * 
   *      /[root]
   *      |--> testFolder
   *      |        |-----> subFolder1
   *      |        |-----> subFolder2
   *      |
   *      |--> sandbox
   *               |--> testFolder
   *                        |-----> subFolder1
   *                        |-----> subFolder2
   * 
   * @throws Exception
   */
  public void testImportNode() throws Exception {
    /*******************************************
     * Create JCR data structure for test case *
     *******************************************/
    Node testFolder = root.addNode("testFolder", "nt:folder");
    Node sandbox = root.addNode("sandbox", "nt:folder");
    
    testFolder.addNode("subFolder1", "nt:folder");
    testFolder.addNode("subFolder2", "nt:folder");       
    session.save();    
    
    if (!testFolder.isNodeType("mix:versionable")) {
      testFolder.addMixin("mix:versionable");
    }    
    session.save();      
    
    /*****************
     * Export data   *
     *****************/
    File exportData = exportNode(testFolder, SYS_VIEW);     //export test folder to XML
    File zippedVersionHistory = exportVersionHistory (testFolder, SYS_VIEW);      //export version history of test folder
    
    /******************************
     * Import data into sandbox   *
     ******************************/
    //import XML data 
    session.importXML(sandbox.getPath(), new BufferedInputStream(new TempFileInputStream(exportData)), CREATE_NEW_BEHAVIOR);
    session.save();
    //import version history data
    Map<String, String> mapHistoryValue = Utils.getMapImportHistory(new BufferedInputStream(
                                                                        new FileInputStream(zippedVersionHistory)));
    Utils.processImportHistory(sandbox, new BufferedInputStream(new TempFileInputStream(zippedVersionHistory)), mapHistoryValue);
    
    /*****************
     * Assertion     *
     *****************/
    assertTrue(sandbox.hasNode("testFolder"));
    
    Node importedNode = sandbox.getNode("testFolder");    
    assertTrue(importedNode.isNodeType("mix:versionable"));
    assertTrue(importedNode.hasNode("subFolder1"));
    assertTrue(importedNode.hasNode("subFolder2"));
  }
  
  /**
   * Export a node to XML with document view or system view
   * 
   * @param currentNode
   * @param docview or sysview
   * @return XML file
   * @throws Exception
   */
  private File exportNode (Node currentNode, String format) throws Exception {
    //the file which will keep the exported data of the node. 
    File tempExportedFile = getExportedFile(format, ".xml");
    
    //do export
    OutputStream out = new BufferedOutputStream(new FileOutputStream(tempExportedFile));
    if(format.equals(DOC_VIEW)) {
      session.exportDocumentView(currentNode.getPath(), out, false, false);
    } else {
      session.exportSystemView(currentNode.getPath(), out, false, false);
    }
    out.flush();
    out.close();
    
    return tempExportedFile;
  }
  
  /**
   * Export version history data of a node
   * 
   * @param currentNode
   * @param format
   * @return a zipped file containing the version history data of a node
   * @throws Exception
   */
  private File exportVersionHistory (Node currentNode, String format) throws Exception {
    QueryResult queryResult = getVersionableChidren(currentNode);
    NodeIterator queryIter = queryResult.getNodes();
    
    CompressData zipService = new CompressData();
    OutputStream out = null;
    InputStream in = null;
    List<File> lstExporedFile = new ArrayList<File>();
    File exportedFile = null;
    File zipFile = null;
    File propertiesFile = getExportedFile("mapping", ".properties");
    OutputStream propertiesBOS = new BufferedOutputStream(new FileOutputStream(propertiesFile));
    InputStream propertiesBIS = new BufferedInputStream(new TempFileInputStream(propertiesFile));
    while(queryIter.hasNext()) {
      exportedFile = getExportedFile("data", ".xml");
      lstExporedFile.add(exportedFile);
      out = new BufferedOutputStream(new FileOutputStream(exportedFile));
      in = new BufferedInputStream(new TempFileInputStream(exportedFile));
      Node node = queryIter.nextNode();
      String historyValue = 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("mix:versionable")) {
      exportedFile = getExportedFile("data", ".xml");
      lstExporedFile.add(exportedFile);
      out = new BufferedOutputStream(new FileOutputStream(exportedFile));
      in = new BufferedInputStream(new TempFileInputStream(exportedFile));
      String historyValue = 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 = getExportedFile("data", ".zip");
    in = new BufferedInputStream(new FileInputStream(zipFile));
    out = new BufferedOutputStream(new FileOutputStream(zipFile));
    out.flush();
    zipService.createZip(out);
    
    return zipFile;
  }
  
  /**
   * Get the version history information of a node
   * @param node
   * @return
   * @throws Exception
   */
  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();
  }
  
  /**
   * get the versionable nodes of the current node
   * @param currentNode
   * @return
   * @throws RepositoryException
   */
  private QueryResult getVersionableChidren(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();
  }
  
  private File getExportedFile(String prefix, String suffix) throws IOException {
    return File.createTempFile(prefix.concat(UUID.randomUUID().toString()), suffix);
  }

  protected void tearDown() throws Exception {
    super.tearDown();    
  }
  
  /**
   * This input stream will remove the file which it connects to when the virtual machine terminates
   * @author hailt
   */
  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();
    }
  }

}
