/*
 * 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 java.io.EOFException;
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.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;

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.persistent.StreamPersistedValueData;
import org.exoplatform.services.jcr.impl.storage.value.fs.operations.ValueFileIOHelper;
import org.exoplatform.services.jcr.storage.value.ValueIOChannel;

/**
 * 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();
            assertEquals(expValDat.size(), elemValDat.size());
            for (int j = 0; j < expValDat.size(); j++)
            {
               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 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 =
               (TransactionChangesLog)(new TransactionChangesLogReader(fileCleaner, maxBufferSize, 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;
                  
                  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();
                           
                           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;
      }
   }
}
