/*
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.exoplatform.services.jcr.api.importing;

import org.exoplatform.services.jcr.JcrAPIBaseTest;
import org.exoplatform.services.jcr.core.ExtendedSession;
import org.exoplatform.services.jcr.core.ExtendedWorkspace;
import org.exoplatform.services.jcr.core.nodetype.NodeTypeDataManager;
import org.exoplatform.services.jcr.impl.core.nodetype.NodeTypeManagerImpl;
import org.exoplatform.services.jcr.util.IdGenerator;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.jcr.ImportUUIDBehavior;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;

/**
 * Created by The eXo Platform SAS.
 * 
 * @author <a href="mailto:Sergey.Kabashnyuk@gmail.com">Sergey Kabashnyuk</a>
 * @version $Id: AbstractImportTest.java 14244 2008-05-14 11:44:54Z ksm $
 */
public abstract class AbstractImportTest extends JcrAPIBaseTest
{
   /**
    * Initialization flag.
    */
   private static boolean isInitialized = false;

   /**
    * Logger.
    */
   private Log log = ExoLogger.getLogger("exo.jcr.component.core.BaseImportTest");

   private final Random random = new Random();

   @Override
   public void initRepository() throws RepositoryException
   {
      super.initRepository();
      if (!isInitialized)
      {
         NodeTypeManagerImpl ntManager = (NodeTypeManagerImpl)session.getWorkspace().getNodeTypeManager();
         InputStream is = TestDocumentViewImport.class.getResourceAsStream("/nodetypes/ext-registry-nodetypes.xml");
         ntManager.registerNodeTypes(is, 0, NodeTypeDataManager.TEXT_XML);
         ntManager.registerNodeTypes(TestDocumentViewImport.class
            .getResourceAsStream("/org/exoplatform/services/jcr/api/nodetypes/ecm/nodetypes-config.xml"), 0,
            NodeTypeDataManager.TEXT_XML);
         ntManager.registerNodeTypes(TestDocumentViewImport.class
            .getResourceAsStream("/org/exoplatform/services/jcr/api/nodetypes/ecm/nodetypes-config-extended.xml"), 0,
            NodeTypeDataManager.TEXT_XML);

         isInitialized = true;
      }
   }

   /**
    * Deserialize xml content to specific node from InputStream.
    * 
    * @param importRoot
    * @param saveType
    * @param isImportedByStream
    * @param uuidBehavior
    * @param is
    * @throws RepositoryException
    * @throws SAXException
    * @throws IOException
    */
   protected void deserialize(Node importRoot, XmlSaveType saveType, boolean isImportedByStream, int uuidBehavior,
      InputStream is) throws RepositoryException, SAXException, IOException
   {

      ExtendedSession extendedSession = (ExtendedSession)importRoot.getSession();
      ExtendedWorkspace extendedWorkspace = (ExtendedWorkspace)extendedSession.getWorkspace();
      if (isImportedByStream)
      {
         if (saveType == XmlSaveType.SESSION)
         {
            extendedSession.importXML(importRoot.getPath(), is, uuidBehavior);
         }
         else if (saveType == XmlSaveType.WORKSPACE)
         {
            extendedWorkspace.importXML(importRoot.getPath(), is, uuidBehavior);
         }

      }
      else
      {
         XMLReader reader = XMLReaderFactory.createXMLReader();
         if (saveType == XmlSaveType.SESSION)
         {
            reader.setContentHandler(extendedSession.getImportContentHandler(importRoot.getPath(), uuidBehavior));

         }
         else if (saveType == XmlSaveType.WORKSPACE)
         {
            reader.setContentHandler(extendedWorkspace.getImportContentHandler(importRoot.getPath(), uuidBehavior));
         }
         InputSource inputSource = new InputSource(is);

         reader.parse(inputSource);

      }
   }

   protected void executeSingeleThreadImportTests(int attempts, Class<? extends BeforeExportAction> firstAction,
      Class<? extends BeforeImportAction> secondAction, Class<? extends AfterImportAction> thirdAction)
      throws TransformerConfigurationException, IOException, RepositoryException, SAXException, InterruptedException
   {
      ExecutorService executor = Executors.newSingleThreadExecutor();
      executeImportTests(executor, attempts, firstAction, secondAction, thirdAction);
      executor.shutdown();
   }

   protected void executeMultiThreadImportTests(int threadCount, int attempts,
      Class<? extends BeforeExportAction> firstAction, Class<? extends BeforeImportAction> secondAction,
      Class<? extends AfterImportAction> thirdAction) throws TransformerConfigurationException, IOException,
      RepositoryException, SAXException, InterruptedException
   {
      ExecutorService executor = Executors.newCachedThreadPool();
      executeImportTests(executor, attempts, firstAction, secondAction, thirdAction);
      executor.shutdown();
   }

   /**
    * boolean isExportedByStream, boolean isImportedByStream, boolean
    * isSystemViewExport
    * 
    * @param firstAction
    * @param secondAction
    * @param thirdAction
    * @throws TransformerConfigurationException
    * @throws IOException
    * @throws RepositoryException
    * @throws SAXException
    * @throws InterruptedException
    */
   private void executeImportTests(ExecutorService executor, int attempts,
      Class<? extends BeforeExportAction> firstAction, Class<? extends BeforeImportAction> secondAction,
      Class<? extends AfterImportAction> thirdAction) throws TransformerConfigurationException, IOException,
      RepositoryException,

      SAXException, InterruptedException
   {
      XmlSaveType[] posibleSaveTypes = new XmlSaveType[]{XmlSaveType.SESSION, XmlSaveType.WORKSPACE};

      int[] posibleImportUUIDBehaviors =
         new int[]{ImportUUIDBehavior.IMPORT_UUID_COLLISION_REMOVE_EXISTING,
            ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW,
            ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW};

      boolean[] posibleExportedByStream = new boolean[]{true, false};
      boolean[] posibleImportedByStream = new boolean[]{true, false};
      boolean[] posibleSystemViewExport = new boolean[]{true, false};

      Collection<Callable<XmlTestResult>> tasks = new ArrayList<Callable<XmlTestResult>>();
      List<Session> sessions = new ArrayList<Session>();
      for (int z = 0; z < attempts; z++)
      {

         for (int i = 0; i < posibleSaveTypes.length; i++)
         {
            for (int j = 0; j < posibleImportUUIDBehaviors.length; j++)
            {
               for (int j2 = 0; j2 < posibleExportedByStream.length; j2++)
               {
                  for (int k = 0; k < posibleImportedByStream.length; k++)
                  {
                     for (int k2 = 0; k2 < posibleSystemViewExport.length; k2++)
                     {
                        BeforeExportAction be;
                        BeforeImportAction bi;
                        AfterImportAction ai;
                        try
                        {

                           Session testSession = repository.login(credentials);
                           String testRootName = IdGenerator.generate();
                           Node testRoot = testSession.getRootNode().addNode(testRootName);
                           testSession.save();

                           be = ((BeforeExportAction)initImportExportAction(firstAction, testSession, testRoot));
                           bi = ((BeforeImportAction)initImportExportAction(secondAction, testSession, testRoot));
                           ai = ((AfterImportAction)initImportExportAction(thirdAction, testSession, testRoot));

                           if (testSession.getRootNode().hasNode(testRootName))
                           {
                              testSession.getRootNode().getNode(testRootName).remove();
                              testSession.save();

                           }

                           sessions.add(testSession);
                        }
                        catch (IllegalArgumentException e)
                        {
                           throw new RepositoryException(e);
                        }
                        catch (InstantiationException e)
                        {
                           throw new RepositoryException(e);
                        }
                        catch (IllegalAccessException e)
                        {
                           throw new RepositoryException(e);
                        }
                        catch (InvocationTargetException e)
                        {
                           throw new RepositoryException(e);
                        }

                        tasks.add(new XmlTestTask<XmlTestResult>(be, bi, ai, posibleSaveTypes[i],
                           posibleImportUUIDBehaviors[j], posibleExportedByStream[j2], posibleImportedByStream[k],
                           posibleSystemViewExport[k2]));

                     }
                  }
               }

            }
         }
      }
      executor.invokeAll(tasks);
      for (Session testSession : sessions)
      {
         testSession.logout();
      }
   }

   /**
    * Serialize content of MIX_REFERENCEABLE_NODE_NAME to byte array.
    * 
    * @param rootNode
    * @param isSystemView
    * @param isStream
    * @return
    * @throws IOException
    * @throws RepositoryException
    * @throws SAXException
    * @throws TransformerConfigurationException
    */
   protected byte[] serialize(Node exportRootNode, boolean isSystemView, boolean isStream) throws IOException,
      RepositoryException, SAXException, TransformerConfigurationException
   {

      ExtendedSession extendedSession = (ExtendedSession)exportRootNode.getSession();

      ByteArrayOutputStream outStream = new ByteArrayOutputStream();

      if (isSystemView)
      {

         if (isStream)
         {
            extendedSession.exportSystemView(exportRootNode.getPath(), outStream, false, false);
         }
         else
         {
            SAXTransformerFactory saxFact = (SAXTransformerFactory)TransformerFactory.newInstance();
            TransformerHandler handler = saxFact.newTransformerHandler();
            handler.setResult(new StreamResult(outStream));
            extendedSession.exportSystemView(exportRootNode.getPath(), handler, false, false);
         }
      }
      else
      {
         if (isStream)
         {
            extendedSession.exportDocumentView(exportRootNode.getPath(), outStream, false, false);
         }
         else
         {
            SAXTransformerFactory saxFact = (SAXTransformerFactory)TransformerFactory.newInstance();
            TransformerHandler handler = saxFact.newTransformerHandler();
            handler.setResult(new StreamResult(outStream));
            extendedSession.exportDocumentView(exportRootNode.getPath(), handler, false, false);
         }
      }
      outStream.close();
      return outStream.toByteArray();
   }
   
   /**
    * Serialize content of MIX_REFERENCEABLE_NODE_NAME to file.
    * 
    * @param rootNode
    * @param isSystemView
    * @param isStream
    * @return
    * @throws IOException
    * @throws RepositoryException
    * @throws SAXException
    * @throws TransformerConfigurationException
    */
   protected File serializeToFile(Node exportRootNode, boolean isSystemView, boolean isStream) throws IOException,
                                                                                          RepositoryException,
                                                                                          SAXException,
                                                                                          TransformerConfigurationException {

     ExtendedSession extendedSession = (ExtendedSession) exportRootNode.getSession();

     File file = File.createTempFile("export", ".xml");
     
     FileOutputStream outStream = new FileOutputStream(file);

     if (isSystemView) {

       if (isStream) {
         extendedSession.exportSystemView(exportRootNode.getPath(), outStream, false, false);
       } else {
         SAXTransformerFactory saxFact = (SAXTransformerFactory) TransformerFactory.newInstance();
         TransformerHandler handler = saxFact.newTransformerHandler();
         handler.setResult(new StreamResult(outStream));
         extendedSession.exportSystemView(exportRootNode.getPath(), handler, false, false);
       }
     } else {
       if (isStream) {
         extendedSession.exportDocumentView(exportRootNode.getPath(), outStream, false, false);
       } else {
         SAXTransformerFactory saxFact = (SAXTransformerFactory) TransformerFactory.newInstance();
         TransformerHandler handler = saxFact.newTransformerHandler();
         handler.setResult(new StreamResult(outStream));
         extendedSession.exportDocumentView(exportRootNode.getPath(), handler, false, false);
       }
     }
     outStream.close();
     return file;
   }

   protected void serialize(Node rootNode, boolean isSystemView, boolean isStream, File content) throws IOException,
      RepositoryException, SAXException, TransformerConfigurationException
   {
      ExtendedSession extendedSession = (ExtendedSession)rootNode.getSession();

      OutputStream outStream = new FileOutputStream(content);
      if (isSystemView)
      {

         if (isStream)
         {
            extendedSession.exportSystemView(rootNode.getPath(), outStream, false, false);
         }
         else
         {
            SAXTransformerFactory saxFact = (SAXTransformerFactory)TransformerFactory.newInstance();
            TransformerHandler handler = saxFact.newTransformerHandler();
            handler.setResult(new StreamResult(outStream));
            extendedSession.exportSystemView(rootNode.getPath(), handler, false, false);
         }
      }
      else
      {
         if (isStream)
         {
            extendedSession.exportDocumentView(rootNode.getPath(), outStream, false, false);
         }
         else
         {
            SAXTransformerFactory saxFact = (SAXTransformerFactory)TransformerFactory.newInstance();
            TransformerHandler handler = saxFact.newTransformerHandler();
            handler.setResult(new StreamResult(outStream));
            extendedSession.exportDocumentView(rootNode.getPath(), handler, false, false);
         }
      }
      outStream.close();

   }

   private ImportExportAction initImportExportAction(final Class<? extends ImportExportAction> importExportAction,
      final Session initSession, final Node testRoot) throws IllegalArgumentException, InstantiationException,
      IllegalAccessException, InvocationTargetException
   {

      Constructor<?>[] constructors = importExportAction.getDeclaredConstructors();

      Constructor<? extends ImportExportAction> constructor = null;
      for (int i = 0; i < constructors.length; i++)
      {
         if (constructors[i].getParameterTypes().length > 1)
            constructor = (Constructor<? extends ImportExportAction>)constructors[i];
      }

      ImportExportAction action = constructor.newInstance(new Object[]{this, initSession, testRoot});
      return action;
   }

   protected abstract class AfterImportAction extends ImportExportAction
   {

      public AfterImportAction(Session session, Node testRootNode) throws RepositoryException
      {
         super(session, testRootNode);
      }

      @Override
      public void cleanUp() throws RepositoryException
      {
         session.refresh(false);
         Node rootNode = session.getRootNode();
         if (rootNode.hasNodes())
         {
            // clean test root
            for (NodeIterator children = rootNode.getNodes(); children.hasNext();)
            {
               Node node = children.nextNode();
               if (!node.getPath().startsWith("/jcr:system"))
               {
                  // log.info("DELETing ------------- "+node.getPath());
                  node.remove();
               }
            }
            session.save();

         }
      }
   }

   protected abstract class BeforeExportAction extends ImportExportAction
   {

      public BeforeExportAction(Session session, Node testRootNode) throws RepositoryException
      {
         super(session, testRootNode);
      }

      public abstract Node getExportRoot() throws RepositoryException;
   }

   protected abstract class BeforeImportAction extends ImportExportAction
   {

      public BeforeImportAction(Session session, Node testRootNode) throws RepositoryException
      {
         super(session, testRootNode);
      }

      public abstract Node getImportRoot() throws RepositoryException;
   }

   protected abstract class ImportExportAction
   {

      protected final Node testRootNode;

      protected final Session testSession;

      public ImportExportAction(Session testSession, Node testRootNode) throws RepositoryException
      {
         super();
         this.testSession = testSession;
         this.testRootNode = testRootNode;
      }

      public void cleanUp() throws RepositoryException
      {

      };

      public void execute() throws RepositoryException
      {
      }

   }

   private class XmlTestTask<XmlTestResult> implements Callable<XmlTestResult>
   {

      private final BeforeExportAction firstAction;

      private final XmlSaveType importSaveType;

      private final int importUUIDBehavior;

      private final boolean isExportedByStream;

      private final boolean isImportedByStream;

      private final boolean isSystemViewExport;

      private final BeforeImportAction secondAction;

      private final AfterImportAction thirdAction;

      public XmlTestTask(BeforeExportAction firstAction, BeforeImportAction secondAction,
         AfterImportAction thirdAction, XmlSaveType importSaveType, int importUUIDBehavior, boolean isExportedByStream,
         boolean isImportedByStream, boolean isSystemViewExport)
      {
         super();
         this.firstAction = firstAction;
         this.secondAction = secondAction;
         this.thirdAction = thirdAction;
         this.importSaveType = importSaveType;
         this.importUUIDBehavior = importUUIDBehavior;
         this.isExportedByStream = isExportedByStream;
         this.isImportedByStream = isImportedByStream;
         this.isSystemViewExport = isSystemViewExport;

      }

      public XmlTestResult call() throws Exception
      {
         if (log.isDebugEnabled())
            log.debug("isSys=" + isSystemViewExport + "\t" + "isES=" + isExportedByStream + "\t" + "importST="
               + importSaveType.toString() + "\t" + "isIS=" + isImportedByStream + "\t" + "importBehavior="
               + importUUIDBehavior + "\t");
         firstAction.execute();
         firstAction.cleanUp();

         byte[] buf = serialize(firstAction.getExportRoot(), isSystemViewExport, isExportedByStream);

         secondAction.execute();
         secondAction.cleanUp();

         Node importRoot = secondAction.getImportRoot();
         Exception resultException = null;
         try
         {
            deserialize(importRoot, importSaveType, isImportedByStream, importUUIDBehavior, new ByteArrayInputStream(
               buf));
            if (importSaveType.equals(XmlSaveType.SESSION))
               importRoot.getSession().save();
         }
         catch (RepositoryException e)
         {
            resultException = e;
         }
         catch (SAXException e)
         {
            resultException = e;
         }
         if (resultException != null && importUUIDBehavior != ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW)
         {

            if (resultException instanceof RepositoryException)
               throw (RepositoryException)resultException;
            else if (resultException instanceof SAXException)
               throw (SAXException)resultException;

         }

         thirdAction.execute();
         thirdAction.cleanUp();

         return null;
      }

   }

   public class XmlTestResult
   {

   }
}
