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

import org.exoplatform.commons.utils.PrivilegedFileHelper;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.dataflow.ChangesLogIterator;
import org.exoplatform.services.jcr.dataflow.ItemStateChangesLog;
import org.exoplatform.services.jcr.dataflow.TransactionChangesLog;
import org.exoplatform.services.jcr.ext.backup.BackupConfig;
import org.exoplatform.services.jcr.ext.backup.impl.AbstractIncrementalBackupJob;
import org.exoplatform.services.jcr.ext.backup.impl.FileNameProducer;
import org.exoplatform.services.jcr.ext.backup.impl.FixupStream;
import org.exoplatform.services.jcr.ext.backup.impl.PendingChangesLog;
import org.exoplatform.services.jcr.impl.util.io.FileCleaner;
import org.exoplatform.services.jcr.impl.util.io.FileCleanerHolder;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

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.ObjectOutputStream;
import java.net.URL;
import java.util.Calendar;
import java.util.List;

/**
 * Created by The eXo Platform SARL Author : Alex Reshetnyak alex.reshetnyak@exoplatform.com.ua Nov
 * 20, 2007
 */
public class IncrementalBackupJob extends AbstractIncrementalBackupJob
{

   protected static final Log LOG = ExoLogger.getLogger("exo.jcr.component.ext.IncrementalBackupJob");

   private ObjectOutputStream oosFileData;

   private FileCleaner fileCleaner;

   public IncrementalBackupJob()
   {
   }

   public void init(ManageableRepository repository, String workspaceName, BackupConfig config, Calendar timeStamp)
   {
      this.repository = repository;
      this.workspaceName = workspaceName;
      this.config = config;
      this.timeStamp = timeStamp;

      FileCleanerHolder cleanerHolder =
         (FileCleanerHolder)repository.getWorkspaceContainer(workspaceName).getComponent(FileCleanerHolder.class);

      this.fileCleaner = cleanerHolder.getFileCleaner();

      try
      {
         url = createStorage();
      }
      catch (FileNotFoundException e)
      {
         LOG.error("Incremental backup initialization failed ", e);
         notifyError("Incremental backup initialization failed ", e);
      }
      catch (IOException e)
      {
         LOG.error("Incremental backup initialization failed ", e);
         notifyError("Incremental backup initialization failed ", e);
      }
   }

   public void stop()
   {
      state = FINISHED;
      LOG.info("Stop requested " + getStorageURL().getPath());

      notifyListeners();
   }

   @Override
   protected URL createStorage() throws FileNotFoundException, IOException
   {
      FileNameProducer fnp =
         new FileNameProducer(config.getRepository(), config.getWorkspace(),
            PrivilegedFileHelper.getAbsolutePath(config.getBackupDir()), super.timeStamp, false);

      File backupFileData = fnp.getNextFile();

      oosFileData = new ObjectOutputStream(PrivilegedFileHelper.fileOutputStream(backupFileData));

      return new URL("file:" + backupFileData.getAbsoluteFile());
   }

   @Override
   protected void save(ItemStateChangesLog persistentLog) throws IOException
   {

      TransactionChangesLog changesLog = (TransactionChangesLog)persistentLog;

      if (changesLog != null && !isSessionNull(changesLog))
      {
         long start = System.currentTimeMillis();

         writeExternal(oosFileData, changesLog, fileCleaner);

         long total = System.currentTimeMillis() - start;

         if (LOG.isDebugEnabled())
            LOG.debug("Time : " + total + " ms" + "    Itemstates count : " + changesLog.getAllStates().size());
      }
   }

   public void writeExternal(ObjectOutputStream out, TransactionChangesLog changesLog, FileCleaner fileCleaner)
      throws IOException
   {

      PendingChangesLog pendingChangesLog = new PendingChangesLog(changesLog, fileCleaner);

      synchronized (out)
      {
         if (pendingChangesLog.getConteinerType() == PendingChangesLog.Type.CHANGESLOG_WITH_STREAM)
         {

            out.writeInt(PendingChangesLog.Type.CHANGESLOG_WITH_STREAM);
            out.writeObject(changesLog);

            // Write FixupStream
            List<FixupStream> listfs = pendingChangesLog.getFixupStreams();
            out.writeInt(listfs.size());

            for (int i = 0; i < listfs.size(); i++)
            {
               listfs.get(i).writeExternal(out);
            }

            // write stream data
            List<InputStream> listInputList = pendingChangesLog.getInputStreams();

            // write file count
            out.writeInt(listInputList.size());

            for (int i = 0; i < listInputList.size(); i++)
            {
               File tempFile = getAsFile(listInputList.get(i));
               FileInputStream fis = PrivilegedFileHelper.fileInputStream(tempFile);

               // write file size
               out.writeLong(PrivilegedFileHelper.length(tempFile));

               // write file content
               writeContent(fis, out);

               fis.close();
               fileCleaner.addFile(tempFile);
            }
         }
         else
         {
            out.writeInt(PendingChangesLog.Type.CHANGESLOG_WITHOUT_STREAM);
            out.writeObject(changesLog);
         }

         out.flush();
      }
   }

   private File getAsFile(InputStream is) throws IOException
   {
      byte[] buf = new byte[1024 * 20];

      File tempFile = PrivilegedFileHelper.createTempFile("" + System.currentTimeMillis(), "" + System.nanoTime());
      FileOutputStream fos = PrivilegedFileHelper.fileOutputStream(tempFile);
      int len;

      while ((len = is.read(buf)) > 0)
         fos.write(buf, 0, len);

      fos.flush();
      fos.close();

      return tempFile;
   }

   private void writeContent(InputStream is, ObjectOutputStream oos) throws IOException
   {
      byte[] buf = new byte[1024 * 8];
      int len;

      int size = 0;

      while ((len = is.read(buf)) > 0)
      {
         oos.write(buf, 0, len);
         size += len;
      }

      oos.flush();
   }

   private boolean isSessionNull(TransactionChangesLog changesLog)
   {
      boolean isSessionNull = false;

      ChangesLogIterator logIterator = changesLog.getLogIterator();
      while (logIterator.hasNextLog())
         if (logIterator.nextLog().getSessionId() == null)
         {
            isSessionNull = true;
            break;
         }

      return isSessionNull;
   }

   /**
    * {@inheritDoc}
    */
   public boolean isTXAware()
   {
      return false;
   }

}
