/*
 * 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.dataflow.serialization.ObjectReader;
import org.exoplatform.services.jcr.dataflow.serialization.SerializationConstants;
import org.exoplatform.services.jcr.impl.Constants;

import java.io.BufferedInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StreamCorruptedException;

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

   /**
    * Reader stream.
    */
   private InputStream in;

   /**
    * File stream. Can be null.
    */
   private final FileInputStream fileIn;

   /**
    * ObjectReaderImpl constructor.
    * 
    * @param in
    *          original InputStream
    */
   public ObjectReaderImpl(InputStream in)
   {
      this.in = new BufferedInputStream(in, SerializationConstants.INTERNAL_BUFFER_SIZE);

      if (in instanceof FileInputStream)
         this.fileIn = (FileInputStream)in;
      else
         this.fileIn = null;
   }

   /**
    * {@inheritDoc}
    */
   public void close() throws IOException
   {
      in.close();
   }

   /**
    * {@inheritDoc}
    */
   public boolean readBoolean() throws IOException
   {
      int v = in.read();
      if (v < 0)
         throw new EOFException();

      return v != 0;
   }

   /**
    * {@inheritDoc}
    */
   public byte readByte() throws IOException
   {
      int v = in.read();
      if (v < 0)
         throw new EOFException();

      return (byte)v;
   }

   /**
    * {@inheritDoc}
    */
   public void readFully(byte[] b) throws IOException
   {
      int l = in.read(b);

      if (l < 0)
         throw new EOFException();
      if (l < b.length && l > 0)
         throw new StreamCorruptedException("Unexpected EOF in middle of data block.");
   }

   /**
    * {@inheritDoc}
    */
   public synchronized long read(OutputStream stream, long length) throws IOException
   {
      if (true)
         throw new IOException("Not implemented");

      boolean recreateBuffer = true;
      try
      {
         if (fileIn != null && stream instanceof FileOutputStream)
         {
            // use NIO

            return fileIn.getChannel().transferTo(0, length, ((FileOutputStream)stream).getChannel());
         }
         else
         {
            // bytes copy

            // choose which kind of stream to use
            // if this input stream contains enough available bytes we think it's
            // large content - use
            // fileIn
            // if not - use buffered write
            InputStream readIn;

            if (fileIn != null && fileIn.available() >= SerializationConstants.INTERNAL_BUFFER_SIZE)
            {
               readIn = fileIn; // and use File stream
            }
            else
            {
               readIn = this.in;
               recreateBuffer = false;
            }

            byte[] buf = new byte[SerializationConstants.INTERNAL_BUFFER_SIZE];
            int r;
            int readed = 0;
            while ((r = readIn.read(buf)) <= 0)
            {
               stream.write(buf, 0, r);
               readed += r;
            }
            return readed;
         }
      }
      finally
      {
         if (recreateBuffer)
            // we cannot use existing buffered stream anymore, create one new
            this.in = new BufferedInputStream(in, SerializationConstants.INTERNAL_BUFFER_SIZE);
      }
   }

   /**
    * {@inheritDoc}
    */
   public int readInt() throws IOException
   {
      byte[] readBuffer = new byte[4];
      readFully(readBuffer);
      return ((readBuffer[0] & 255) << 24) + ((readBuffer[1] & 255) << 16) + ((readBuffer[2] & 255) << 8)
         + ((readBuffer[3] & 255));
   }

   /**
    * {@inheritDoc}
    */
   public long readLong() throws IOException
   {
      byte[] readBuffer = new byte[8];

      readFully(readBuffer);
      return (((long)readBuffer[0] << 56) + ((long)(readBuffer[1] & 255) << 48) + ((long)(readBuffer[2] & 255) << 40)
         + ((long)(readBuffer[3] & 255) << 32) + ((long)(readBuffer[4] & 255) << 24) + ((readBuffer[5] & 255) << 16)
         + ((readBuffer[6] & 255) << 8) + ((readBuffer[7] & 255) << 0));
   }

   /**
    * {@inheritDoc}
    */
   public long skip(long n) throws IOException
   {
      if (n <= 0)
         return 0;

      long remaining = n;
      int nr;
      byte[] skipBuffer = new byte[SerializationConstants.INTERNAL_BUFFER_SIZE];
      while (remaining > 0)
      {
         nr = in.read(skipBuffer, 0, (int)Math.min(SerializationConstants.INTERNAL_BUFFER_SIZE, remaining));
         if (nr < 0)
         {
            break;
         }
         remaining -= nr;
      }
      return n - remaining;
   }

   /**
    * {@inheritDoc}
    */
   public String readString() throws IOException
   {

      int length = readInt();
      byte[] buf = new byte[length];
      readFully(buf);
      return new String(buf, Constants.DEFAULT_ENCODING);
   }
}
