/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * http://aipostyle.com/
 *
 * Copyright(C) 2010 avanza Co.,Ltd. All rights reserved.
 * http://www.avnz.co.jp/
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package com.aimluck.eip.webmail.util;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;

import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipOutputStream;

/**
 * ファイルを圧縮するクラス
 * 
 * 
 */
public class FileZip {

  /** logger */
  private static final JetspeedLogger logger =
    JetspeedLogFactoryService.getLogger(FileZip.class.getName());

  /** 読込み終了値 */
  private static final int EOF = -1;

  /** 格納バイト数の最大値 */
  // private static final int ZIP_BUFF_SIZE = 1024;
  private static final int ZIP_BUFF_SIZE = 4096;

  /**
   * デフォルト(ファイル名解析)エンコーダを使用してファイル及びディレクトリをZIP圧縮します。
   * 
   * @param zipFilename
   *          作成されるZIPファイル名
   * @param targetFiles
   *          圧縮対象のファイル及びディレクトリ名列
   * @return 作成されたZIPファイル
   * @throws Exception
   */
  public static File zip(String zipFilename, String... targetFiles)
      throws Exception {
    return zip(zipFilename, targetFiles, "MS932");
  }

  /**
   * ファイル及びディレクトリをZIP圧縮します。
   * 
   * @param zipFilename
   *          作成されるZIPファイル名
   * @param targetFiles
   *          targetFiles 圧縮対象のファイル及びディレクトリ名配列
   * @param encoding
   *          ファイル名解析エンコーダ名（<A HREF=
   *          "http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html"
   *          >一覧</A>）
   * @return 作成されたZIPファイル
   * @throws Exception
   */
  public static File zip(String zipFilename, String[] targetFiles,
      String encoding) throws Exception {
    int n = targetFiles.length;
    File[] files = new File[n];
    for (int i = 0; i < n; i++) {
      files[i] = new File(targetFiles[i]);
    }
    return zip(new File(zipFilename), files, encoding);
  }

  /**
   * ファイル及びディレクトリをZIP圧縮します。
   * 
   * @param zipFile
   *          作成されるZIPファイル
   * @param targetFiles
   *          targetFiles 圧縮対象のファイル及びディレクトリ配列
   * @param encoding
   *          ファイル名解析エンコーダ名（<A HREF=
   *          "http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html"
   *          >一覧</A>）
   * @return 作成されたZIPファイル
   * @throws Exception
   */
  public static File zip(File zipFile, File[] targetFiles, String encoding)
      throws Exception {
    ZipOutputStream out = null;
    try {
      out = new ZipOutputStream(new FileOutputStream(zipFile));
      out.setEncoding(encoding);
      for (int i = 0; i < targetFiles.length; i++) {
        int deleteLength =
          targetFiles[i].getPath().length() - targetFiles[i].getName().length();
        zip(out, targetFiles[i], deleteLength);
      }
    } catch (Exception e) {
      logger.error("ZIP処理で例外が発生しました。", e);
      throw e;
    } finally {
      if (out != null) {
        out.close();
      }
    }

    return zipFile;
  }

  /**
   * ZIP圧縮ファイルを作成します。
   * 
   * @param out
   *          ZIP ファイル形式でファイルを書き込むための出力ストリーム
   * @param targetFile
   *          圧縮対象のファイル及びディレクトリ
   * @param deleteLength
   *          圧縮対象のファイル及びディレクトリパスから名称を取得するための切取位置
   * @throws IOException
   * @throws IOException
   *           IO例外
   */
  private static void zip(ZipOutputStream out, File targetFile, int deleteLength)
      throws IOException {
    if (targetFile.isDirectory()) {
      // File[] files = targetFile.listFiles();
      String[] files = targetFile.list();
      for (int i = 0; i < files.length; i++) {
        // zip(out, files[i], deleteLength);
        File f =
          new File(targetFile.getAbsolutePath() + File.separator + files[i]);
        zip(out, f, deleteLength);
        files[i] = null;
      }
    } else {
      BufferedInputStream in = null;
      try {
        ZipEntry target =
          new ZipEntry(targetFile.getPath().substring(deleteLength));
        out.putNextEntry(target);
        byte buf[] = new byte[ZIP_BUFF_SIZE];
        int count;
        in = new BufferedInputStream(new FileInputStream(targetFile));
        while ((count = in.read(buf, 0, ZIP_BUFF_SIZE)) != EOF) {
          out.write(buf, 0, count);
        }
      } catch (FileNotFoundException e) {
        logger.error("ZIP処理で例外が発生しました。", e);
        throw e;
      } catch (IOException e) {
        logger.error("ZIP処理で例外が発生しました。", e);
        throw e;
      } finally {
        try {
          out.closeEntry();
        } catch (IOException e) {
          logger.error("ZIPエントリのクローズ処理で例外が発生しました。", e);
        }
        try {
          out.flush();
        } catch (IOException e) {
          logger.error("ZIPエントリのフラッシュ処理で例外が発生しました。", e);
        }
        if (in != null) {
          try {
            in.close();
          } catch (IOException e) {
            logger.error("メールファイルクローズ処理で例外が発生しました。", e);
          }
        }

      }
    }
  }

  /**
   * ファイル及びディレクトリをZIP圧縮します。
   * 
   * @param zipFile
   *          作成されるZIPファイル
   * @param targetFiles
   *          targetFiles 圧縮対象のファイル及びディレクトリ配列
   * @param encoding
   *          ファイル名解析エンコーダ名（<A HREF=
   *          "http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html"
   *          >一覧</A>）
   * @return 作成されたZIPファイル
   * @throws Exception
   */
  public static File zipDir(String zipFilePath, String targetDirPath)
      throws Exception {
    ZipOutputStream out = null;
    File zipFile = null;
    try {
      zipFile = new File(zipFilePath);
      File targetDir = new File(targetDirPath);

      out = new ZipOutputStream(new FileOutputStream(zipFile));
      out.setEncoding("MS932");
      int deleteLength =
        targetDir.getPath().length() - targetDir.getName().length();
      zipFile(out, targetDir, deleteLength);
    } catch (Exception e) {
      logger.error("ZIP処理で例外が発生しました。", e);
      throw e;
    } finally {
      if (out != null) {
        out.close();
      }
    }

    return zipFile;
  }

  /**
   * ZIP圧縮ファイルを作成します。
   * 
   * @param out
   *          ZIP ファイル形式でファイルを書き込むための出力ストリーム
   * @param targetFile
   *          圧縮対象のファイル及びディレクトリ
   * @param deleteLength
   *          圧縮対象のファイル及びディレクトリパスから名称を取得するための切取位置
   * @throws IOException
   * @throws IOException
   *           IO例外
   */
  private static void zipFile(ZipOutputStream out, File targetFile,
      int deleteLength) throws IOException {
    if (targetFile.isDirectory()) {

      Runtime runtime0 = Runtime.getRuntime();
      long max0 = runtime0.maxMemory();
      long free0 = runtime0.freeMemory();
      long used0 = max0 - free0;

      logger.info("メールZIP処理モニター 処理開始直前状態["
        + "　メモリMAX:"
        + max0
        / 1024
        / 1024
        + "M メモリFREE:"
        + free0
        / 1024
        / 1024
        + "M メモリUSED:"
        + used0
        / 1024
        / 1024);

      BufferedReader in = null;
      try {
        in =
          new BufferedReader(new InputStreamReader(new FileInputStream(
            new File(targetFile.getParentFile().getAbsolutePath()
              + File.separator
              + "file.list")), "UTF-8"));
        int i = 0;
        long memoryMax = 0;
        String line;
        while ((line = in.readLine()) != null) {

          if (i % 500 == 0) {

            Runtime runtime = Runtime.getRuntime();
            long max = runtime.maxMemory();
            long free = runtime.freeMemory();
            long used = max - free;

            if (memoryMax < used) {
              memoryMax = used;
            }

            logger.info("メールZIP処理モニター"
              + " ここまでの最大メモリ使用量:"
              + memoryMax
              / 1024
              / 1024
              + "　初期使用メモリ:"
              + used0
              / 1024
              / 1024
              + " メモリMAX:"
              + max
              / 1024
              / 1024
              + "M メモリFREE:"
              + free
              / 1024
              / 1024
              + "M メモリUSED:"
              + used
              / 1024
              / 1024
              + "M 現在処理件数:"
              + i
              + "件 ");

          }
          File f =
            new File(targetFile.getAbsolutePath() + File.separator + line);
          // 運用課題No.43
          // ファイルが見つからない場合エラー
          if (!f.exists()) {
            throw new FileNotFoundException("圧縮対象のファイルが見つかりません。["
              + f.getAbsolutePath()
              + "]");
          }
          zipFile(out, f, deleteLength);

          i++;
        }

      } catch (FileNotFoundException e) {
        logger.error("ディレクトリZIP圧縮で例外が発生しました。", e);
      } catch (Exception e) {
        logger.error("ディレクトリZIP圧縮で例外が発生しました。", e);
      } finally {
        // 運用課題No.43
        // ZIPファイルのエントリクローズ・フラッシュを追加
        try {
          out.closeEntry();
        } catch (IOException e) {
          logger.error("ディレクトリZIP圧縮：ZIPエントリのクローズ処理で例外が発生しました。", e);
        }
        try {
          out.flush();
        } catch (IOException e) {
          logger.error("ディレクトリZIP圧縮：ZIPエントリのフラッシュ処理で例外が発生しました。", e);
        }
        try {
          if (in != null) {
            in.close();
          }
        } catch (IOException e) {
          logger.error("メールファイルクローズ処理で例外が発生しました。", e);
        }
      }
    } else {
      BufferedInputStream in = null;
      try {
        ZipEntry target =
          new ZipEntry(targetFile.getPath().substring(deleteLength));
        out.putNextEntry(target);
        byte buf[] = new byte[ZIP_BUFF_SIZE];
        int count;
        in = new BufferedInputStream(new FileInputStream(targetFile));
        while ((count = in.read(buf, 0, ZIP_BUFF_SIZE)) != EOF) {
          out.write(buf, 0, count);
        }
      } catch (FileNotFoundException e) {
        logger.error("ZIP処理で例外が発生しました。", e);
        throw e;
      } catch (IOException e) {
        logger.error("ZIP処理で例外が発生しました。", e);
        throw e;
      } finally {
        try {
          out.closeEntry();
        } catch (IOException e) {
          logger.error("ZIPエントリのクローズ処理で例外が発生しました。", e);
        }
        try {
          out.flush();
        } catch (IOException e) {
          logger.error("ZIPエントリのフラッシュ処理で例外が発生しました。", e);
        }
        if (in != null) {
          try {
            in.close();
          } catch (IOException e) {
            logger.error("メールファイルクローズ処理で例外が発生しました。", e);
          }
        }

      }
    }
  }
}
