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

import org.exoplatform.commons.utils.PrivilegedFileHelper;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileFilter;
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.util.ArrayList;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Created by The eXo Platform SAS Author : Chung Nguyen
 * nguyenchung136@yahoo.com Feb 10, 2006
 */
public class CompressData
{
   /**
    * The logger
    */
   private static final Log LOG = ExoLogger.getLogger("exo.kernel.component.common.CompressData");

   private String base_;

   private List<DataInstance> datas_ = new ArrayList<DataInstance>();

   protected static final int EOF = -1;

   protected static final int BUFFER = 2048;

   public CompressData()
   {

   }

   public CompressData(String base)
   {
      base_ = base;
   }

   public String getBase()
   {
      return base_;
   }

   public void addFile(String entryName, File file)
   {
      try
      {
         InputStream is = PrivilegedFileHelper.fileInputStream(file);
         datas_.add(new InputStreamDataInstance(entryName, is));
      }
      catch (FileNotFoundException e)
      {
         LOG.error(e.getLocalizedMessage(), e);
      }
   }

   public void addDir(File srcDir)
   {
      if (srcDir.isFile())
      {
         addFile(srcDir.getName(), srcDir);
      }
      else
      {
         datas_.add(new FileDataInstance(srcDir.getName(), srcDir));
      }
      // create recursive loop to go through all files in the srcDir.....
   }

   public void addInputStream(String entryName, InputStream is) throws Exception
   {
      datas_.add(new InputStreamDataInstance(entryName, is));
   }

   public void createZipFile(String fileName) throws Exception
   {
      File fileZip = new File(fileName + ".zip");
      FileOutputStream out = PrivilegedFileHelper.fileOutputStream(fileZip);
      ZipOutputStream zos = new ZipOutputStream(out);
      int size = datas_.size();
      byte InputData[] = new byte[BUFFER];
      if (size < 0)
      {
         throw new Exception("Data size is null");
      }
      for (int i = 0; i < size; i++)
      {
         DataInstance di = datas_.get(i);
         if (di instanceof InputStreamDataInstance)
         {
            InputStream is = di.getInputStream();
            zos.putNextEntry(new ZipEntry(di.getEntryName()));
            int len;
            while ((len = is.read(InputData)) != EOF)
            {
               zos.write(InputData, 0, len);
            }
            zos.closeEntry();
         }
         else if (di instanceof FileDataInstance)
         {
            di.getZipOut(true, zos);
         }
      }
      zos.close();
      out.close();
   }

   public void createZip(OutputStream os) throws Exception
   {
      int size = datas_.size();
      ZipOutputStream zos = new ZipOutputStream(os);
      if (size == 0)
         throw new Exception("Data is null");
      for (int i = 0; i < size; i++)
      {
         DataInstance di = datas_.get(i);
         if (di instanceof InputStreamDataInstance)
         {
            InputStream is = di.getInputStream();
            zos.putNextEntry(new ZipEntry(di.getEntryName()));
            int len;
            byte InputData[] = new byte[BUFFER];
            while ((len = is.read(InputData)) != EOF)
            {
               zos.write(InputData, 0, len);
            }
            zos.closeEntry();
         }
         else if (di instanceof FileDataInstance)
         {
            di.setType("Zip");
            InputStream is = di.getInputStream();
            int len;
            byte[] data = new byte[BUFFER];
            while ((len = is.read(data)) != EOF)
            {
               os.write(data, 0, len);
            }
            is.close();
         }
      }
      zos.close();
      os.close();
   }

   public void createJarFile(String fileName) throws Exception
   {
      File fileZip = new File(fileName + ".jar");
      FileOutputStream out = PrivilegedFileHelper.fileOutputStream(fileZip);
      JarOutputStream jos = new JarOutputStream(out);
      int size = datas_.size();
      if (size < 0)
         throw new Exception("Data size is null");
      for (int i = 0; i < size; i++)
      {
         DataInstance di = datas_.get(i);

         if (di instanceof InputStreamDataInstance)
         {
            String entryName = di.getEntryName();
            InputStream is = di.getInputStream();
            jos.putNextEntry(new ZipEntry(entryName));
            int len;
            byte InputData[] = new byte[BUFFER];
            while ((len = is.read(InputData)) != EOF)
            {
               jos.write(InputData, 0, len);
            }
            jos.closeEntry();
         }
         else if (di instanceof FileDataInstance)
         {
            di.getJarOut(true, jos);
         }
      }
      jos.close();
      out.close();
   }

   public void createJar(OutputStream os) throws Exception
   {
      int size = datas_.size();
      JarOutputStream jos = new JarOutputStream(os);
      if (size == 0)
         throw new Exception("Data is null");
      for (int i = 0; i < size; i++)
      {
         DataInstance di = datas_.get(i);
         if (di instanceof InputStreamDataInstance)
         {
            InputStream is = di.getInputStream();

            jos.putNextEntry(new ZipEntry(di.getEntryName()));
            int len;
            byte InputData[] = new byte[BUFFER];
            while ((len = is.read(InputData)) != EOF)
            {
               jos.write(InputData, 0, len);
            }
            jos.closeEntry();
            is.close();
         }
         else if (di instanceof FileDataInstance)
         {
            di.setType("Jar");
            InputStream is = di.getInputStream();
            int len;
            byte[] data = new byte[BUFFER];
            while ((len = is.read(data)) != EOF)
            {
               os.write(data, 0, len);
            }
            is.close();
         }
      }
      jos.close();
      os.close();
   }

   public void cleanDataInstance()
   {
      int count = datas_.size();
      for (int i = 0; i < count; i++)
      {
         datas_.remove(i);
      }
   }

   abstract public static class DataInstance
   {
      protected String entryName_;

      protected String typeZip_;

      abstract public InputStream getInputStream();

      abstract public void getJarOut(boolean containParent, JarOutputStream jos) throws Exception;

      abstract public void getZipOut(boolean containParent, ZipOutputStream zos) throws Exception;

      public String getEntryName()
      {
         return entryName_;
      }

      public void setType(String typeZip)
      {
         typeZip_ = typeZip;
      }

      public String getType()
      {
         return typeZip_;
      }

   }

   public static class FileDataInstance extends DataInstance
   {
      private File file_;

      public FileDataInstance(String entryName, File file)
      {
         entryName_ = entryName;
         file_ = file;
      }

      @Override
      public InputStream getInputStream()
      {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();

         if (getType().equals("Zip"))
         {
            ZipOutputStream zos = new ZipOutputStream(baos);
            try
            {
               getZipOut(true, zos);
            }
            catch (Exception e)
            {
               LOG.error(e.getLocalizedMessage(), e);
            }
         }
         else
         {
            JarOutputStream jos;
            try
            {
               jos = new JarOutputStream(baos);
               getJarOut(true, jos);
            }
            catch (Exception e)
            {
               LOG.error(e.getLocalizedMessage(), e);
            }
         }
         InputStream is = new ByteArrayInputStream(baos.toByteArray());

         try
         {
            baos.close();
         }
         catch (IOException e)
         {
            LOG.error(e.getLocalizedMessage(), e);
         }

         return is;
      }

      @Override
      public void getJarOut(boolean containParent, JarOutputStream jos) throws Exception
      {
         String path = file_.getAbsolutePath();
         InputStream bufInput = null;
         List<File> list = listFile(file_);
         if (file_.isDirectory())
            list.remove(file_);
         if (list == null || list.size() < 1)
            throw new Exception("nothing in the list");
         for (File f : list)
         {
            StringBuilder filePath = new StringBuilder(f.getAbsolutePath());

            if (f.getAbsolutePath().startsWith(path))
            {
               if (containParent && file_.isDirectory())
               {
                  filePath = new StringBuilder(file_.getName());
                  filePath.append(File.separator);
                  filePath.append(f.getAbsolutePath().substring(path.length() + 1));
               }
               else if (file_.isDirectory())
               {
                  filePath = new StringBuilder(f.getAbsolutePath().substring(path.length() + 1));
               }
               else
               {
                  filePath = new StringBuilder(file_.getName());
               }
            }

            if (f.isFile())
            {
               bufInput = PrivilegedFileHelper.fileInputStream(f);
            }
            else
            {
               filePath.append("/");
            }

            addToArchive(jos, bufInput, filePath.toString());
         }
      }

      private List<File> listFile(File dir)
      {
         final List<File> list = new ArrayList<File>();
         if (dir.isFile())
         {
            list.add(dir);
            return list;
         }
         dir.listFiles(new FileFilter()
         {
            public boolean accept(File f)
            {
               if (f.isDirectory())
                  list.addAll(listFile(f));
               list.add(f);
               return true;
            }
         });
         return list;
      }

      public ZipOutputStream addToArchive(ZipOutputStream zipOutput, InputStream input, String entryName1)
         throws Exception
      {
         byte data[] = new byte[BUFFER];
         ZipEntry entry = new ZipEntry(entryName1);
         zipOutput.putNextEntry(entry);
         if (input != null)
         {
            int count;
            while ((count = input.read(data, 0, BUFFER)) != EOF)
               zipOutput.write(data, 0, count);
         }
         zipOutput.closeEntry();
         return zipOutput;
      }

      public JarOutputStream addToArchive(JarOutputStream jarOutput, InputStream input, String entryName1)
         throws Exception
      {
         byte data[] = new byte[BUFFER];
         JarEntry entry = new JarEntry(entryName1);
         jarOutput.putNextEntry(entry);
         if (input != null)
         {
            int count;
            while ((count = input.read(data, 0, BUFFER)) != EOF)
               jarOutput.write(data, 0, count);
         }
         jarOutput.closeEntry();
         return jarOutput;
      }

      @Override
      public void getZipOut(boolean containParent, ZipOutputStream zos) throws Exception
      {
         String path = file_.getAbsolutePath();
         InputStream bufInput = null;
         List<File> list = listFile(file_);
         if (file_.isDirectory())
            list.remove(file_);
         if (list == null || list.size() < 1)
            throw new Exception("nothing in the list");
         for (File f : list)
         {
            StringBuilder filePath = new StringBuilder(f.getAbsolutePath());

            if (f.getAbsolutePath().startsWith(path))
            {
               if (containParent && file_.isDirectory())
               {
                  filePath = new StringBuilder(file_.getName());
                  filePath.append(File.separator);
                  filePath.append(f.getAbsolutePath().substring(path.length() + 1));
               }
               else if (file_.isDirectory())
               {
                  filePath = new StringBuilder(f.getAbsolutePath().substring(path.length() + 1));
               }
               else
               {
                  filePath = new StringBuilder(file_.getName());
               }
            }

            if (f.isFile())
            {
               bufInput = new FileInputStream(f);
            }
            else
            {
               filePath.append("/");
            }

            addToArchive(zos, bufInput, filePath.toString());
         }
      }
   }

   public static class InputStreamDataInstance extends DataInstance
   {
      private InputStream is_;

      public InputStreamDataInstance(String entryName, InputStream is)
      {
         entryName_ = entryName;
         is_ = is;
      }

      @Override
      public InputStream getInputStream()
      {
         return is_;
      }

      @Override
      public void getJarOut(boolean containParent, JarOutputStream jos) throws Exception
      {

      }

      @Override
      public void getZipOut(boolean containParent, ZipOutputStream zos) throws Exception
      {

      }

   }

}
