package com.adobe.epubcheck.util;

import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.zip.CRC32;
import java.util.zip.CheckedInputStream;

public class Archive
{
  private ArrayList<String> paths;

  private ArrayList<String> names;

  private final File baseDir;

  private File epubFile;

  private String epubName;


  public Archive(String base, boolean save)
  {
    boolean deleteOnExit = !save;
    baseDir = makeCanonical(new File(base));

    if (!baseDir.exists() || !baseDir.isDirectory())
    {
      throw new RuntimeException("The path specified for the archive is invalid");
    }
    epubName = baseDir.getName() + ".epub";
		epubFile = new File(baseDir.getParent() + File.separator + epubName);
    if (deleteOnExit)
    {
      epubFile.deleteOnExit();
    }

    paths = new ArrayList<String>();
    names = new ArrayList<String>();
  }

  public Archive(String base)
  {
    this(base, false);
  }

  public String getEpubName()
  {
    return epubName;
  }

  public File getEpubFile()
  {
    return epubFile;
  }

  public void deleteEpubFile()
  {
    if (!epubFile.delete())
    {
      System.err.println("Unable to delete generated archive.");
    }
  }

  public void createArchive()
  {
    // using commons compress to allow setting filename encoding pre java7
    ZipArchiveOutputStream out = null;
    try
    {
      collectFiles(baseDir, "");

      //make mimetype the first entry
      int mimetype = names.indexOf("mimetype");
      if (mimetype > -1)
      {
        String name = names.remove(mimetype);
        String path = paths.remove(mimetype);
        names.add(0, name);
        paths.add(0, path);
      }
      else
      {
        System.err.println("No mimetype file found in expanded publication, output archive will be invalid");
      }

      out = new ZipArchiveOutputStream(epubFile);
      out.setEncoding("UTF-8");

      for (int i = 0; i < paths.size(); i++)
      {
        ZipArchiveEntry entry = new ZipArchiveEntry(new File(paths.get(i)), names.get(i));
        if (i == 0 && mimetype > -1)
        {
          entry.setMethod(ZipArchiveEntry.STORED);
          entry.setSize(getSize(paths.get(i)));
          entry.setCrc(getCRC(paths.get(i)));
        }
        else
        {
          entry.setMethod(ZipArchiveEntry.DEFLATED);
        }
        out.putArchiveEntry(entry);
        FileInputStream in = null;
        try
        {
          in = new FileInputStream(paths.get(i));

          byte[] buf = new byte[1024];
          int len;
          while ((len = in.read(buf)) > 0)
          {
            out.write(buf, 0, len);
          }
          out.closeArchiveEntry();
        }
        finally
        {
          if (in != null)
          {
            in.close();
          }
        }
      }
    }
    catch (Exception e)
    {
      throw new RuntimeException(e.getMessage());
    }
    finally
    {
      try
      {
        if (out != null)
        {
          out.flush();
          out.finish();
          out.close();
        }
      }
      catch (IOException ignored)
      {
      }
    }
  }
  private File makeCanonical(File f)
  {
    if (f == null)
    {
      return null;
    }
    try
    {
      return f.getCanonicalFile();
    }
    catch (IOException ignored)
    {
      return f.getAbsoluteFile();
    }
  }

  private long getSize(String path) throws IOException
  {
    FileInputStream in = null;
    try
    {
      in = new FileInputStream(path);
      byte[] buf = new byte[1024];
      int len;
      int size = 0;
      while ((len = in.read(buf)) > 0)
      {
        size += len;
      }
      return size;
    }
    finally
    {
      if (in != null)
      {
        in.close();
      }
    }
  }

  private long getCRC(String path) throws IOException
  {
    CheckedInputStream cis = null;
    FileInputStream fis = null;
    try
    {
      fis = new FileInputStream(path);
      cis = new CheckedInputStream(fis, new CRC32());
      byte[] buf = new byte[128];
      while (cis.read(buf) >= 0)
      {
      }
    }
    finally
    {
      if (fis != null)
      {
        fis.close();
      }
      if (cis != null)
      {
        cis.close();
      }
    }
    return cis.getChecksum().getValue();
  }

//	public void createArchiveOld() {
//		collectFiles(baseDir, "");
//		byte[] buf = new byte[1024];
//		try {
//
//			ZipOutputStream out = new ZipOutputStream((new FileOutputStream(
//					epubName)));
//
//			int index = names.indexOf("mimetype");
//			if (index >= 0) {
//				FileInputStream in = new FileInputStream(paths.get(index));
//
//				ZipEntry entry = new ZipEntry(names.get(index));
//				entry.setMethod(ZipEntry.STORED);
//				int len, size = 0;
//				while ((len = in.read(buf)) > 0)
//					size += len;
//
//				in = new FileInputStream(paths.get(index));
//
//				entry.setCompressedSize(size);
//				entry.setSize(size);
//
//				CRC32 crc = new CRC32();
//				entry.setCrc(crc.getValue());
//				out.putNextEntry(entry);
//
//				while ((len = in.read(buf)) > 0) {
//					crc.update(buf, 0, len);
//					out.write(buf, 0, len);
//				}
//
//				entry.setCrc(crc.getValue());
//
//				paths.remove(index);
//				names.remove(index);
//			}
//
//			for (int i = 0; i < paths.size(); i++) {
//				FileInputStream in = new FileInputStream(paths.get(i));
//
//				out.putNextEntry(new ZipEntry(names.get(i)));
//
//				int len;
//				while ((len = in.read(buf)) > 0) {
//					out.write(buf, 0, len);
//				}
//
//				out.closeEntry();
//				in.close();
//			}
//
//			out.close();
//		} catch (IOException e) {
//		}
//	}

  private void collectFiles(File dir, String dirName)
  {
    if (!dirName.equals("") && !dirName.endsWith("/"))
    {
      dirName = dirName + "/";
    }

    File files[] = dir.listFiles();
    assert files != null;

	for (int i = 0; i < files.length; i++)
    {
	  // issue 256: ignore '.DS_Store', '._DS_Store', 'Thumbs.db' and 'ehthumbs.db' files
	  if (files[i].isFile() &&
		 !files[i].getName().equals(".DS_Store") && !files[i].getName().equals("._DS_Store") &&
		 !files[i].getName().equals("Thumbs.db") && !files[i].getName().equals("ehthumbs.db"))
	  {
        names.add(dirName + files[i].getName());
		paths.add(files[i].getAbsolutePath());
			
			// issue 256: ignore .git/ and .svn/ folders
			
	   }
	   else if (files[i].isDirectory() && !files[i].getName().equals(".svn") && !files[i].getName().equals(".git")) 
	   {
	      collectFiles(files[i], dirName + files[i].getName() + "/");
	   }
   }

 }

  public void listFiles()
  {
    for (String name : names)
    {
      outWriter.println(name);
    }
  }
}
