/*
 * 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.ext.initializer.impl;

import org.exoplatform.commons.utils.PrivilegedFileHelper;
import org.exoplatform.services.jcr.ext.initializer.NoMemberToSendException;
import org.exoplatform.services.jcr.ext.initializer.RemoteTransport;
import org.exoplatform.services.jcr.ext.initializer.RemoteWorkspaceInitializationException;
import org.exoplatform.services.jcr.ext.replication.ReplicationException;
import org.exoplatform.services.jcr.ext.replication.storage.InvalidChecksumException;
import org.exoplatform.services.jcr.ext.replication.transport.ChannelManager;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CountDownLatch;

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

   /**
    * BUFFER_SIZE. The constant for buffer size.
    */
   private static final int BUFFER_SIZE = 20 * 1024;

   /**
    * The channel manager.
    */
   private final ChannelManager channelManager;

   /**
    * The data transmitter.
    */
   private final RemoteTransmitter remoteTransmitter;

   /**
    * The data receiver.
    */
   private final RemoteReceiver remoteReceiver;

   /**
    * The temporary folder.
    */
   private final File tempDir;

   /**
    * Data source url.
    */
   private final String sourceUrl;

   /**
    * The synchronization latch.
    */
   private CountDownLatch latch;

   /**
    * RemoteTransportImpl constructor.
    * 
    */
   /**
    * RemoteTransportImpl constructor.
    * 
    * @param channelManager
    *          the channel manager.
    * @param tempDir
    *          the temporary folder.
    * @param sourceUrl
    *          data source url.
    */
   public RemoteTransportImpl(ChannelManager channelManager, File tempDir, String sourceUrl)
   {
      this.latch = new CountDownLatch(1);
      this.channelManager = channelManager;
      this.tempDir = tempDir;
      this.sourceUrl = sourceUrl;
      this.remoteTransmitter = new RemoteTransmitter(this.channelManager);
      this.remoteReceiver = new RemoteReceiver(this.tempDir, latch);
      this.channelManager.addPacketListener(remoteReceiver);
      this.channelManager.addStateListener(remoteReceiver);
   }

   /**
    * {@inheritDoc}
    */
   public void close() throws RemoteWorkspaceInitializationException
   {
      channelManager.disconnect();
   }

   /**
    * {@inheritDoc}
    */
   public File getWorkspaceData(String repositoryName, String workspaceName, String id)
      throws RemoteWorkspaceInitializationException
   {
      RemoteHttpClient client = new RemoteHttpClient(sourceUrl);
      String result = client.execute(repositoryName, workspaceName, id);

      if (result.startsWith("FAIL"))
         throw new RemoteWorkspaceInitializationException("Can not getting the remote workspace data : " + result);

      try
      {
         latch.await();
      }
      catch (InterruptedException e)
      {
         throw new RemoteWorkspaceInitializationException("The interapted : " + e.getMessage(), e);
      }

      if (remoteReceiver.getException() != null)
         throw new RemoteWorkspaceInitializationException("Can not getting the remote workspace data : "
            + remoteReceiver.getException().getMessage(), remoteReceiver.getException());

      try
      {
         remoteReceiver.getContext().getChangesFile().validate();
      }
      catch (InvalidChecksumException e)
      {
         new RemoteWorkspaceInitializationException("Can not getting the remote workspace data : " + e.getMessage(), e);
      }

      String filePath = remoteReceiver.getContext().getChangesFile().toString();

      return new File(filePath);
   }

   /**
    * {@inheritDoc}
    */
   public void init() throws RemoteWorkspaceInitializationException
   {
      try
      {
         channelManager.connect();
      }
      catch (ReplicationException e)
      {
         throw new RemoteWorkspaceInitializationException("Can not  initialize the transport : " + e.getMessage(), e);
      }
   }

   /**
    * {@inheritDoc}
    */
   public void sendWorkspaceData(File workspaceData) throws RemoteWorkspaceInitializationException,
      NoMemberToSendException
   {

      if (!PrivilegedFileHelper.exists(workspaceData))
         throw new RemoteWorkspaceInitializationException("The file with workspace data not exists.");

      byte[] crc;
      try
      {
         crc = getCheckSum(workspaceData);

         try
         {
            if (channelManager.getOtherMembers().size() == 0)
               throw new NoMemberToSendException("No member to sent, member list : "
                  + channelManager.getOtherMembers().size());

            remoteTransmitter.sendChangesLogFile(channelManager.getOtherMembers().get(0), workspaceData, crc);
         }
         catch (IOException e)
         {
            throw new RemoteWorkspaceInitializationException(
               "Can not send the workspace data file : " + e.getMessage(), e);
         }
      }
      catch (NoSuchAlgorithmException e)
      {
         throw new RemoteWorkspaceInitializationException("Can not calculate the checksum for workspace data file : "
            + e.getMessage(), e);
      }
      catch (IOException e)
      {
         throw new RemoteWorkspaceInitializationException("Can not calculate the checksum for workspace data file : "
            + e.getMessage(), e);
      }

   }

   /**
    * {@inheritDoc}
    */
   public void sendError(String message) throws RemoteWorkspaceInitializationException, NoMemberToSendException
   {
      try
      {
         InitializationErrorPacket packet =
            new InitializationErrorPacket(InitializationErrorPacket.INITIALIZATION_ERROR_PACKET, message);
         channelManager.sendPacket(packet, channelManager.getOtherMembers().get(0));
      }
      catch (IOException e)
      {
         throw new RemoteWorkspaceInitializationException("Cannot send export data " + e.getMessage(), e);
      }
   }

   /**
    * getCheckSum.
    * 
    * @param f
    *          the File
    * @return byte[] the checksum
    * @throws NoSuchAlgorithmException
    *           will be generated the NoSuchAlgorithmException
    * @throws IOException
    *           will be generated the IOException
    */
   private byte[] getCheckSum(File f) throws NoSuchAlgorithmException, IOException
   {
      MessageDigest digest = MessageDigest.getInstance("MD5");
      InputStream in = PrivilegedFileHelper.fileInputStream(f);

      long length = PrivilegedFileHelper.length(f);

      byte[] buff = new byte[BUFFER_SIZE];
      for (; length >= BUFFER_SIZE; length -= BUFFER_SIZE)
      {
         in.read(buff);
         digest.update(buff);
      }

      if (length > 0)
      {
         buff = new byte[(int)length];
         in.read(buff);
         digest.update(buff);
      }

      return digest.digest();
   }

}
