/*
 * 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.dataflow.serialization;

import org.exoplatform.services.jcr.JcrImplBaseTest;
import org.exoplatform.services.jcr.dataflow.ChangesLogIterator;
import org.exoplatform.services.jcr.dataflow.ItemState;
import org.exoplatform.services.jcr.dataflow.PlainChangesLog;
import org.exoplatform.services.jcr.dataflow.TransactionChangesLog;
import org.exoplatform.services.jcr.dataflow.serialization.UnknownClassIdException;
import org.exoplatform.services.jcr.datamodel.ItemData;
import org.exoplatform.services.jcr.datamodel.PropertyData;
import org.exoplatform.services.jcr.datamodel.ValueData;
import org.exoplatform.services.jcr.impl.dataflow.SpoolConfig;
import org.exoplatform.services.jcr.impl.dataflow.persistent.FilePersistedValueData;
import org.exoplatform.services.jcr.impl.dataflow.persistent.StreamPersistedValueData;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Created by The eXo Platform SAS. <br/>Date: 16.02.2009
 * 
 * @author <a href="mailto:alex.reshetnyak@exoplatform.com.ua">Alex Reshetnyak</a>
 * @version $Id: JcrImplSerializationBaseTest.java 111 2008-11-11 11:11:11Z
 *          rainf0x $
 */
public abstract class JcrImplSerializationBaseTest extends JcrImplBaseTest
{

   protected void checkIterator(Iterator<ItemState> expected, Iterator<ItemState> changes) throws Exception
   {

      while (expected.hasNext())
      {

         assertTrue(changes.hasNext());
         ItemState expect = expected.next();
         ItemState elem = changes.next();

         assertEquals(expect.getState(), elem.getState());
         // assertEquals(expect.getAncestorToSave(), elem.getAncestorToSave());
         ItemData expData = expect.getData();
         ItemData elemData = elem.getData();
         assertEquals(expData.getQPath(), elemData.getQPath());
         assertEquals(expData.isNode(), elemData.isNode());
         assertEquals(expData.getIdentifier(), elemData.getIdentifier());
         assertEquals(expData.getParentIdentifier(), elemData.getParentIdentifier());

         if (!expData.isNode())
         {
            PropertyData expProp = (PropertyData)expData;
            PropertyData elemProp = (PropertyData)elemData;
            assertEquals(expProp.getType(), elemProp.getType());
            assertEquals(expProp.isMultiValued(), elemProp.isMultiValued());

            List<ValueData> expValDat = expProp.getValues();
            List<ValueData> elemValDat = elemProp.getValues();
            // Both value data are null 
            if (expValDat != null && elemValDat != null)
            {
               assertEquals(expValDat.size(), elemValDat.size());
               for (int j = 0; j < expValDat.size(); j++)
               {

                  // are of the same class
                  assertEquals(expValDat.get(j).getClass(), elemValDat.get(j).getClass());
                  //
                  if (expValDat.get(j) instanceof FilePersistedValueData)
                  {
                     // if files in both instances are null
                     Boolean nullFiles =
                        ((FilePersistedValueData)expValDat.get(j)).getFile() == null
                           && ((FilePersistedValueData)elemValDat.get(j)).getFile() == null;
                     // if there are instances of StreamPersistedValueData
                     if (expValDat.get(j) instanceof StreamPersistedValueData)
                     {
                        // and they both have null spool files
                        nullFiles &=
                           ((StreamPersistedValueData)expValDat.get(j)).getTempFile() == null
                              && ((StreamPersistedValueData)elemValDat.get(j)).getTempFile() == null;
                     }
                     if (nullFiles)
                     {
                        // Both value data are equals
                        continue;
                     }
                  }

                  assertTrue(java.util.Arrays.equals(expValDat.get(j).getAsByteArray(), elemValDat.get(j)
                     .getAsByteArray()));

                  // check is received property values ReplicableValueData
                  // assertTrue(elemValDat.get(j) instanceof ReplicableValueData);
               }
            }
         }
      }
      assertFalse(changes.hasNext());

   }

   protected void checkResults(List<TransactionChangesLog> srcLog) throws Exception
   {
      File jcrfile = serializeLogs(srcLog);

      List<TransactionChangesLog> destLog = deSerializeLogs(jcrfile);

      assertEquals(srcLog.size(), destLog.size());

      for (int i = 0; i < srcLog.size(); i++)
      {
         checkIterator(srcLog.get(i).getAllStates().iterator(), destLog.get(i).getAllStates().iterator());
      }
   }

   protected File serializeLogs(List<TransactionChangesLog> logs) throws IOException, UnknownClassIdException
   {
      File jcrfile = File.createTempFile("jcr", "test");
      ObjectWriterImpl jcrout = new ObjectWriterImpl(new FileOutputStream(jcrfile));

      TransactionChangesLogWriter wr = new TransactionChangesLogWriter();
      for (TransactionChangesLog tcl : logs)
      {
         wr.write(jcrout, tcl);
      }

      jcrout.flush();
      jcrout.close();

      return jcrfile;
   }

   protected List<TransactionChangesLog> deSerializeLogs(File jcrfile) throws IOException, UnknownClassIdException
   {
      ObjectReaderImpl jcrin = new ObjectReaderImpl(new FileInputStream(jcrfile));

      List<TransactionChangesLog> readed = new ArrayList<TransactionChangesLog>();

      try
      {
         while (true)
         {
            TransactionChangesLog obj =
               (new TransactionChangesLogReader(SpoolConfig.getDefaultSpoolConfig(), holder)).read(jcrin);
            // TransactionChangesLog obj = new TransactionChangesLog();
            // obj.readObject(jcrin);
            readed.add(obj);
         }
      }
      catch (EOFException e)
      {
         // ok
      }

      //Imitation of save.
      imitationSave(readed);

      return readed;
   }

   /**
    * Imitation of JCR save
    * 
    * @param readed
    * @return
    * @throws IOException 
    */
   private void imitationSave(List<TransactionChangesLog> readed) throws IOException
   {
      for (TransactionChangesLog tLog : readed)
      {
         ChangesLogIterator it = tLog.getLogIterator();

         while (it.hasNextLog())
         {
            PlainChangesLog pLog = it.nextLog();

            for (ItemState state : pLog.getAllStates())
            {
               ItemData itemData = state.getData();

               if (!itemData.isNode())
               {
                  PropertyData propData = (PropertyData)itemData;
                  if (propData.getValues() != null)
                  {
                     for (ValueData valueData : propData.getValues())
                     {
                        if (valueData instanceof StreamPersistedValueData)
                        {
                           // imitation of JCR save
                           if (((StreamPersistedValueData)valueData).getTempFile() != null)
                           {
                              ((StreamPersistedValueData)valueData)
                                 .setPersistedFile(((StreamPersistedValueData)valueData).getTempFile());
                           }
                           else
                           {
                              File file = File.createTempFile("tempFile", "tmp");
                              file.deleteOnExit();
                              if (((StreamPersistedValueData)valueData).getStream() != null)
                              {
                                 copy(((StreamPersistedValueData)valueData).getStream(), new FileOutputStream(file));
                                 ((StreamPersistedValueData)valueData).setPersistedFile(file);
                              }
                           }
                        }
                     }
                  }
               }
            }
         }
      }
   }

   protected long copy(InputStream in, OutputStream out) throws IOException
   {
      // compare classes as in Java6 Channels.newChannel(), Java5 has a bug in newChannel().
      boolean inFile = in instanceof FileInputStream && FileInputStream.class.equals(in.getClass());
      boolean outFile = out instanceof FileOutputStream && FileOutputStream.class.equals(out.getClass());
      if (inFile && outFile)
      {
         // it's user file
         FileChannel infch = ((FileInputStream)in).getChannel();
         FileChannel outfch = ((FileOutputStream)out).getChannel();

         long size = 0;
         long r = 0;
         do
         {
            r = outfch.transferFrom(infch, r, infch.size());
            size += r;
         }
         while (r < infch.size());
         return size;
      }
      else
      {
         // it's user stream (not a file)
         ReadableByteChannel inch = inFile ? ((FileInputStream)in).getChannel() : Channels.newChannel(in);
         WritableByteChannel outch = outFile ? ((FileOutputStream)out).getChannel() : Channels.newChannel(out);

         long size = 0;
         int r = 0;
         ByteBuffer buff = ByteBuffer.allocate(32 * 1024);
         buff.clear();
         while ((r = inch.read(buff)) >= 0)
         {
            buff.flip();

            // copy all
            do
            {
               outch.write(buff);
            }
            while (buff.hasRemaining());

            buff.clear();
            size += r;
         }

         if (outFile)
         {
            ((FileChannel)outch).force(true); // force all data to FS
         }

         return size;
      }
   }
}
