/*
 * 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.impl.storage.fs;

import org.exoplatform.services.jcr.JcrImplBaseTest;
import org.exoplatform.services.jcr.dataflow.DataManager;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.TransactionChangesLog;
import org.exoplatform.services.jcr.datamodel.IllegalPathException;
import org.exoplatform.services.jcr.datamodel.InternalQName;
import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.ItemType;
import org.exoplatform.services.jcr.datamodel.NodeData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.QPath;
import org.exoplatform.services.jcr.datamodel.QPathEntry;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.Constants;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.jcr.impl.dataflow.TransientNodeData;
import org.exoplatform.services.jcr.impl.dataflow.TransientPropertyData;
import org.exoplatform.services.jcr.impl.dataflow.TransientValueData;
import org.exoplatform.services.jcr.impl.dataflow.session.SessionChangesLog;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.ValueFormatException;

/**
 * Created by The eXo Platform SAS 10.07.2007
 * 
 * @author <a href="mailto:peter.nedonosko@exoplatform.com.ua">Peter Nedonosko</a>
 * @version $Id: TestJCRVSReadWrite.java 11907 2008-03-13 15:36:21Z ksm $
 */
public class TestJCRVSReadWrite extends JcrImplBaseTest
{

   private static Log log = ExoLogger.getLogger("exo.jcr.component.core.TestJCRVSReadWrite");

   public static final int FILES_COUNT = 1000;

   // public static final int FILE1_SIZE_KB = 1;
   // public static final int FILE1_SIZE = FILE1_SIZE_KB * 1024;
   public static int FILE1_SIZE = 0;

   // public static final int FILE2_SIZE_KB = 2;
   // public static final int FILE2_SIZE = FILE2_SIZE_KB * 1024;
   public static int FILE2_SIZE = 0;

   protected Node testRoot = null;

   protected List<String> properties = null;

   protected BufferedInputStream fBLOB1 = null;

   protected BufferedInputStream fBLOB2 = null;

   @Override
   public void setUp() throws Exception
   {
      super.setUp();

      if (fBLOB1 == null)
      {
         // fBLOB1 = createBLOBTempFile("treeVSTest_", FILE1_SIZE_KB);
         // fBLOB1.deleteOnExit();
         fBLOB1 = new BufferedInputStream(new ByteArrayInputStream("qazws".getBytes()));
         FILE1_SIZE = fBLOB1.available();
      }

      if (fBLOB2 == null)
      {
         // fBLOB2 = createBLOBTempFile("treeVSTest_", FILE2_SIZE_KB);
         // fBLOB2.deleteOnExit();
         fBLOB2 = new BufferedInputStream(new ByteArrayInputStream("qazwsxedcr".getBytes()));
         FILE2_SIZE = fBLOB2.available();
      }

      testRoot = root.addNode("tree_vs_test");
      root.save();
   }

   @Override
   protected void tearDown() throws Exception
   {
      long time = System.currentTimeMillis();

      if (root.hasNode(testRoot.getName()))
      {
         testRoot.remove();
         root.save();
      }

      log.info("Tear down of " + getName() + ",\t" + (System.currentTimeMillis() - time));

      super.tearDown();
   }

   protected List<String> createJCRAPICase() throws Exception
   {
      List<String> props = new ArrayList<String>();
      String rootPath = testRoot.getPath();
      fBLOB1.mark(FILE1_SIZE);
      fBLOB2.mark(FILE2_SIZE);
      for (int i = 0; i < FILES_COUNT; i++)
      {
         try
         {
            Node resource = testRoot.addNode("blob" + i, "nt:file").addNode("jcr:content", "nt:unstructured"); // ,
            // "nt:resource"
            String path = "";
            if (i % 10 == 0)
            {
               Value[] vals =
                  new Value[]{session.getValueFactory().createValue(fBLOB1),
                     session.getValueFactory().createValue(fBLOB2),};
               path = resource.setProperty("jcr:data", vals).getPath();
            }
            else
            {
               path = resource.setProperty("jcr:data", fBLOB1).getPath();
            }
            resource.setProperty("jcr:mimeType", "application/x-octet-stream");
            resource.setProperty("jcr:lastModified", Calendar.getInstance());
            testRoot.save();

            props.add(path.substring(rootPath.length() + 1));
         }
         catch (RepositoryException e)
         {
            log.warn("Can't create test case, " + e);
            throw new Exception("Can't create test case, " + e, e);
         }
         finally
         {
            fBLOB1.reset();
            fBLOB2.reset();
         }
      }
      return props;
   }

   protected void deleteJCRAPICase() throws RepositoryException
   {
      for (NodeIterator iter = testRoot.getNodes(); iter.hasNext();)
      {
         iter.nextNode().remove();
      }

      testRoot.save();
   }

   protected List<QPathEntry[]> createInternalAPICase() throws Exception
   {
      DataManager dm =
         ((NodeImpl)testRoot).getSession().getTransientNodesManager().getTransactManager().getStorageDataManager();

      List<QPathEntry[]> props = new ArrayList<QPathEntry[]>();
      NodeData rootData = (NodeData)((NodeImpl)testRoot).getData();
      fBLOB1.mark(FILE1_SIZE);
      fBLOB2.mark(FILE2_SIZE);
      for (int i = 0; i < FILES_COUNT; i++)
      {
         try
         {
            SessionChangesLog changes = new SessionChangesLog(((NodeImpl)testRoot).getSession());

            TransientNodeData ntfile =
               TransientNodeData.createNodeData(rootData, InternalQName.parse("[]blob" + i), Constants.NT_FILE);
            changes.add(ItemState.createAddedState(ntfile));

            TransientPropertyData ntfilePrimaryType =
               TransientPropertyData.createPropertyData(ntfile, Constants.JCR_PRIMARYTYPE, PropertyType.NAME, false,
                  new TransientValueData(Constants.NT_FILE));
            changes.add(ItemState.createAddedState(ntfilePrimaryType));

            TransientNodeData res =
               TransientNodeData.createNodeData(ntfile, Constants.JCR_CONTENT, Constants.NT_UNSTRUCTURED);
            changes.add(ItemState.createAddedState(res));

            TransientPropertyData resPrimaryType =
               TransientPropertyData.createPropertyData(res, Constants.JCR_PRIMARYTYPE, PropertyType.NAME, false,
                  new TransientValueData(Constants.NT_UNSTRUCTURED));
            changes.add(ItemState.createAddedState(resPrimaryType));

            List<ValueData> data = new ArrayList<ValueData>();
            if (i % 10 == 0)
            {
               data.add(new TransientValueData(fBLOB1));
               data.add(new TransientValueData(fBLOB2));
            }
            else
            {
               data.add(new TransientValueData(fBLOB1));
            }

            TransientPropertyData resData =
               TransientPropertyData.createPropertyData(res, Constants.JCR_DATA, PropertyType.BINARY, data.size() > 1,
                  data);
            changes.add(ItemState.createAddedState(resData));

            TransientPropertyData resMimeType =
               TransientPropertyData.createPropertyData(res, Constants.JCR_MIMETYPE, PropertyType.STRING, false,
                  new TransientValueData("application/x-octet-stream"));
            changes.add(ItemState.createAddedState(resMimeType));

            TransientPropertyData resLastModified =
               TransientPropertyData.createPropertyData(res, Constants.JCR_LASTMODIFIED, PropertyType.DATE, false,
                  new TransientValueData(Calendar.getInstance()));
            changes.add(ItemState.createAddedState(resLastModified));

            QPath path = resData.getQPath();

            dm.save(new TransactionChangesLog(changes));

            props.add(path.getRelPath(path.getEntries().length - rootData.getQPath().getEntries().length));
         }
         catch (RepositoryException e)
         {
            log.warn("Can't create test case, " + e);
            throw new Exception("Can't create test case, " + e, e);
         }
         finally
         {
            fBLOB1.reset();
            fBLOB2.reset();
         }
      }
      return props;
   }

   protected void deleteInternalAPICase() throws RepositoryException
   {
      final DataManager dm =
         ((NodeImpl)testRoot).getSession().getTransientNodesManager().getTransactManager().getStorageDataManager();
      final SessionChangesLog changes = new SessionChangesLog(((NodeImpl)testRoot).getSession());

      class Remover
      {
         void delete(NodeData node) throws RepositoryException
         {
            for (NodeData nd : dm.getChildNodesData(node))
            {
               new Remover().delete(nd);
            }
            for (PropertyData pd : dm.getChildPropertiesData(node))
            {
               changes.add(ItemState.createDeletedState(pd));
            }
            changes.add(ItemState.createDeletedState(node));
         }
      }

      NodeData rootData = (NodeData)((NodeImpl)testRoot).getData();

      new Remover().delete(rootData);

      dm.save(new TransactionChangesLog(changes));
   }

   // copied from SessionDataManager
   protected ItemData getItemData(DataManager manager, NodeData parent, QPathEntry[] relPathEntries, ItemType itemType)
      throws RepositoryException
   {
      ItemData item = parent;
      for (int i = 0; i < relPathEntries.length; i++)
      {
         if (i == relPathEntries.length - 1)
         {
            item = manager.getItemData(parent, relPathEntries[i], itemType);
         }
         else
         {
            item = manager.getItemData(parent, relPathEntries[i], ItemType.UNKNOWN);
         }

         if (item == null)
         {
            break;
         }

         if (item.isNode())
         {
            parent = (NodeData)item;
         }
         else if (i < relPathEntries.length - 1)
         {
            throw new IllegalPathException("Path can not contains a property as the intermediate element");
         }
      }
      return item;
   }

   public void testname() throws Exception
   {

   }

   public void _testReadWriteJCRAPI() throws Exception
   {
      long time = System.currentTimeMillis();
      List<String> props = createJCRAPICase();
      log.info(getName() + " ADD -- " + (System.currentTimeMillis() - time));

      time = System.currentTimeMillis();
      // read randomize
      Set<String> caseProps = new HashSet<String>(props);
      for (String prop : caseProps)
      {
         try
         {
            InputStream stream = testRoot.getProperty(prop).getStream();
            assertEquals("Value has wrong length", FILE1_SIZE, stream.available());
         }
         catch (ValueFormatException e)
         {
            Value[] vs = testRoot.getProperty(prop).getValues();
            for (int i = 0; i < vs.length; i++)
            {
               assertEquals("Value has wrong length", i == 0 ? FILE1_SIZE : FILE2_SIZE, vs[i].getStream().available());
            }
         }
      }
      log.info(getName() + " READ -- " + (System.currentTimeMillis() - time));

      time = System.currentTimeMillis();
      deleteJCRAPICase();
      log.info(getName() + " DELETE -- " + (System.currentTimeMillis() - time));
   }

   public void _testReadWriteInternalAPI() throws Exception
   {

      DataManager manager =
         ((NodeImpl)testRoot).getSession().getTransientNodesManager().getTransactManager().getStorageDataManager();
      NodeData parent = (NodeData)((NodeImpl)testRoot).getData();

      long time = System.currentTimeMillis();
      List<QPathEntry[]> props = createInternalAPICase();
      log.info(getName() + " ADD -- " + (System.currentTimeMillis() - time));

      time = System.currentTimeMillis();
      // read randomize
      Set<QPathEntry[]> caseProps = new HashSet<QPathEntry[]>(props);
      for (QPathEntry[] prop : caseProps)
      {
         PropertyData p = (PropertyData)getItemData(manager, parent, prop, ItemType.PROPERTY);
         List<ValueData> vals = p.getValues();
         for (int i = 0; i < vals.size(); i++)
         {
            assertEquals("Value has wrong length", i == 0 ? FILE1_SIZE : FILE2_SIZE, vals.get(i).getAsStream()
               .available());
         }
      }
      log.info(getName() + " READ -- " + (System.currentTimeMillis() - time));

      // time = System.currentTimeMillis();
      // deleteInternalAPICase();
      // log.info(getName() + " DELETE -- " + (System.currentTimeMillis() - time));
   }

}
