/**
 * Copyright (c) 2003-2004 System Integrator Corporation.
 *                 All Rights Reserved.
 */
package jp.co.sint.upload;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.ResourceBundle;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import jp.co.sint.basic.SILogin;
import jp.co.sint.beans.mallmgr.UIIndividualAttachedFile;
import jp.co.sint.config.SIConfig;
import jp.co.sint.database.SIDBAccessException;
import jp.co.sint.database.SIDBUtil;
import jp.co.sint.database.SIDuplicateKeyException;
import jp.co.sint.database.SIInsertRec;
import jp.co.sint.servlet.SIServlet;
import jp.co.sint.tools.SIFileUploadException;
import jp.co.sint.tools.SIHTMLUtil;
import jp.co.sint.tools.SIUtil;

import org.apache.commons.fileupload.DefaultFileItem;
import org.apache.commons.fileupload.DiskFileUpload;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.log4j.Category;

/**
 * サーバーにファイルをアップロードすることを処理します
 */
public class SIFileUploadSrv extends SIServlet {

  // ログ用のインスタンスの生成
  private static Category log = Category.getInstance(SIConfig.SILOG4J_WEBSHOP_CATEGORY_NAME);

  /* (非 Javadoc)
   * @see jp.co.sint.servlet.SIServlet#doUpdate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  public void doUpdate(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
  }

  /**
   * 資料のアップロード、削除、ダウンロードに関する処理
   *
   * @param request
   * @param response
   * @param lParaMap
   * @param conn コネクション
   * @return action文字列
   * @throws FileNotFoundException
   * @throws SQLException
   * @throws FileNotFoundException
   * @throws SIFileUploadException
   */
  public String doFileUploadBG(HttpServletRequest request, HttpServletResponse response, HashMap lParaMap,
      Connection conn)
      throws FileNotFoundException, SQLException, FileNotFoundException, SIFileUploadException {

    HttpSession session = request.getSession();
    HashMap lMultiMap = new HashMap();
    DiskFileUpload diskFile = new DiskFileUpload();
    // ログイン情報
    SILogin siLogin = SIHTMLUtil.getLogin(request);
    // アップロード可能なファイルサイズ上限=5M
    final int FILE_MAX_SIZE = 5242880;
    // アップロードファイルのフルパス
    File lFullDestFile = null;
    // ディレクトリのパス
    String lDestPath = "";
    // ファイルの拡張子
    String extension = ".";
    // アップロード用のファイルリストのデータ取得用
    DefaultFileItem[] fileItemList;

    // serverの文字コード設定
    ResourceBundle rb = ResourceBundle.getBundle(SIConfig.SICONFIG_ENCODING_FILE_NAME);
    String clientCode = rb.getString("clientCode");

    try {
      // FileItemクラスのオブジェクトとしてファイル情報を分割して取得
      fileItemList = (DefaultFileItem[]) (diskFile.parseRequest(request).toArray(new DefaultFileItem[0]));

    } catch (FileUploadException e) {
      // リクエストの読み込み、処理またはファイルの保持にて 問題が発生した場合。
      log.error("ERROR : DiskFileUpload -> parseRequest .");
      throw new SIFileUploadException("ファイル情報の取得に失敗しました。");
    }

    // ファイル情報
    for (int i = 0; i < fileItemList.length; i++) {
      if (fileItemList[i].isFormField()) {
        byte tempByte[] = fileItemList[i].get();
        String str;
        try {
          str = new String(tempByte, clientCode);
        } catch (UnsupportedEncodingException e) {
          log.error("ERROR : UnsupportedEncodingException");
          throw new SIFileUploadException("ファイル情報の取得に失敗しました。");
        }
        lMultiMap.put(fileItemList[i].getFieldName(), str);

      } else {
        // ファイル名から拡張子を取得
        if (fileItemList[i].getName() != null) {
          int point = fileItemList[i].getName().lastIndexOf(".");
          if (point != -1) {
            extension += fileItemList[i].getName().substring(point + 1);
          }
        }
      }
    }

    // 親コード
    String cmdtyCode = (String) lMultiMap.get("cmdtyCode");
    // 在庫コード
    String individualCode = (String) lMultiMap.get("individualCode");
    // ファイル名
    String fileName = (String) lMultiMap.get("fileName");
    // アクション名（"upload" or "delete" or "download"）
    String action = (String) lMultiMap.get("actionNameTxt");
    // 資料番号
    String attachedFileCodeStr = (String) lMultiMap.get("attachedFileCodeTxt");

    if (cmdtyCode == null || cmdtyCode.length() < 1 || individualCode == null || individualCode.length() < 1) {
      log.error("ERROR : 親コードまたは在庫コードが取得できませんでした。"
          + "cmdtyCode=[" + cmdtyCode + "], individualCode=[" + individualCode + "]");
      throw new SIFileUploadException("ファイル情報の取得に失敗しました。");
    }

    // ディレクトリのパス
    lDestPath = SIConfig.SIUPLOAD_FOLDER[0] + cmdtyCode + "/";
    log.debug("Directory Path : [" + lDestPath + "]");

    // //////////////////////////////////////////////////////////
    // アップロード
    // //////////////////////////////////////////////////////////
    if (action.equals(SIConfig.SIACTION_UPLOAD)) {

      log.debug("File Upload Start!!! "
          + "cmdtyCode=[" + cmdtyCode + "], individualCode=[" + individualCode + "], fileName=[" + fileName + "]");

      // ファイル表示名が空
      if (fileName == null || fileName.length() == 0) {
        log.error("ERROR : ファイル名が取得できませんでした。fileName=[" + fileName + "]");
        throw new FileNotFoundException();
      }

      // ディレクトリの作成
      createDirectory(lDestPath);

      // 現在の資料番号の最大
      int lastIndex = 0;

      try {
        // 資料番号の最大数を取得する
        String lastIndexStr = getLastIndex(conn, individualCode);
        log.debug("individualCode=[" + individualCode + "], lastIndexStr = [" + lastIndexStr + "]");

        try {
          lastIndex = Integer.parseInt(lastIndexStr);
        } catch (NumberFormatException e) {
          log.error("ERROR : 資料番号の数値変換でエラーが発生しました。lastIndexStr=[" + lastIndexStr + "]");
          throw new SIFileUploadException("ファイル情報の取得に失敗しました。");
        }

        // 商品添付資料
        UIIndividualAttachedFile attachedFile = new UIIndividualAttachedFile();
        attachedFile.setIndividualCode(individualCode);
        attachedFile.setAttachedfilecode((lastIndex + 1));
        attachedFile.setAttachedfilename(fileName);
        attachedFile.setExtension(extension);

        // 1件登録
        insertTableDataIndividualattachedfile(conn, attachedFile, siLogin.getUserCode());

      } catch (SIDBAccessException e) {
        e.printStackTrace();
        try {
          conn.rollback();
        } catch (SQLException se) {
          se.printStackTrace();
        }
        throw new SQLException();

      } catch (SIFileUploadException e) {
        e.printStackTrace();
        try {
          conn.rollback();
        } catch (SQLException se) {
          se.printStackTrace();
        }
        throw e;
      }

      try {
        for (int ii = 0; ii < fileItemList.length; ii++) {
          if (!fileItemList[ii].isFormField()) {// アップロードファイルの項目であれば

            lFullDestFile = getAbsFileName(lDestPath, cmdtyCode + "_"
                + individualCode + "_" + "document_" + (lastIndex + 1) + extension);

            log.debug("lFullDestFile = [" + lFullDestFile + "]");
            log.debug("fileItemList[ii].getName() = [" + fileItemList[ii].getName() + "]");

            if (SIUtil.isNotNull(fileItemList[ii].getName())) {
              // ファイルを保存
              fileItemList[ii].write(lFullDestFile);// 実行
              log.debug("ファイルを保存しました。ファイルサイズ =[" + getSizeStr(lFullDestFile.length()) + "]");
            }

            if (!lFullDestFile.exists() || !lFullDestFile.isFile()) {
              // ファイルが存在しない場合のエラー処理
              log.error("ファイルが存在していません。ファイル=[" + lFullDestFile + "]");
              lFullDestFile.delete();
              session.setAttribute(SIConfig.SISESSION_FILE_UPLOAD_NAME, lMultiMap);
              throw new SIFileUploadException("ファイルが存在しないため、アップロードできませんでした。");
            }

            // ファイルのサイズが0であれば削除してエラーを表示
            if (lFullDestFile.length() == 0) {
              log.error("ファイルサイズが0byteです。ファイルサイズ=[" + getSizeStr(lFullDestFile.length()) + "]");
              lFullDestFile.delete();
              session.setAttribute(SIConfig.SISESSION_FILE_UPLOAD_NAME, lMultiMap);
              throw new SIFileUploadException("空のファイルのため、アップロードできませんでした。");
            }

            if (lFullDestFile.length() > FILE_MAX_SIZE) {// 5M超えてたらエラー
              log.error("ファイルサイズが大きすぎます。ファイルサイズ=[" + getSizeStr(lFullDestFile.length()) + "]");
              lFullDestFile.delete();
              session.setAttribute(SIConfig.SISESSION_FILE_UPLOAD_NAME, lMultiMap);
              throw new SIFileUploadException("ファイルサイズが大きすぎるため、アップロードできませんでした。");
            }
          }
        }
      } catch (SIFileUploadException e) {
        try {
          conn.rollback();
        } catch (SQLException se) {
          log.error("rollbackでSQLExceptionが発生しました。");
          se.printStackTrace();
        }
        throw e;
      } catch (Exception e) {// ファイルの保存でエラー
        e.printStackTrace();
        try {
          conn.rollback();
        } catch (SQLException se) {
          log.error("rollbackでSQLExceptionが発生しました。");
          se.printStackTrace();
        }
        throw new SIFileUploadException("ファイルの保存に失敗しました。");
      }

      // //////////////////////////////////////////////////////////
      // 削除
      // //////////////////////////////////////////////////////////
    } else if (action.equals(SIConfig.SIACTION_DELETE)) {

      // 拡張子
      extension = (String) lMultiMap.get("extensionTxt");
      // ファイル名
      String targetFileName = cmdtyCode + "_"
          + individualCode + "_" + "document_" + attachedFileCodeStr + extension;

      log.debug("File Delete Start!!! cmdtyCode=[" + cmdtyCode + "], individualCode=[" + individualCode + "], "
              + "targetFileName=[" + targetFileName + "]");

      lFullDestFile = getAbsFileName(lDestPath, targetFileName);
      log.debug("lFullDestFile=[" + lFullDestFile + "]");

      try {
        // レコード削除
        deleteTableDataIndividualattachedfile(conn, individualCode, attachedFileCodeStr);
      } catch (SIDBAccessException e) {
        e.printStackTrace();
        try {
          conn.rollback();
        } catch (SQLException se) {
          log.error("rollbackでSQLExceptionが発生しました。");
          se.printStackTrace();
        }
        throw new SQLException();
      }
      // ファイル削除
      if (lFullDestFile.delete()) {
        log.debug("ファイルを削除しました。lFullDestFile=[" + lFullDestFile + "]");
      }else{
        // ファイル削除に失敗した場合
        log.error("ファイルの削除に失敗しました。lFullDestFile=[" + lFullDestFile + "]");
        try {
          conn.rollback();
        } catch (SQLException se) {
          log.error("rollbackでSQLExceptionが発生しました。");
          se.printStackTrace();
        }
        throw new SIFileUploadException("ファイルの削除に失敗しました。");
      }

      // //////////////////////////////////////////////////////////
      // ダウンロード
      // //////////////////////////////////////////////////////////
    } else if (action.equals(SIConfig.SIACTION_DOWNLOAD)) {

      // 拡張子
      extension = (String) lMultiMap.get("extensionTxt");
      // ファイル名
      String targetFileName = cmdtyCode + "_" + individualCode + "_" + "document_" + attachedFileCodeStr + extension;

      log.debug("File Download Start!!! cmdtyCode=[" + cmdtyCode + "], individualCode=[" + individualCode + "], "
          + "targetFileName=[" + targetFileName + "]");

      lFullDestFile = getAbsFileName(lDestPath, targetFileName);
      log.debug("lFullDestFile=[" + lFullDestFile + "]");

      // ダウンロード対象ファイルの読み込み用オブジェクト
      FileInputStream fis = null;
      InputStreamReader isr = null;

      // ダウンロードファイルの書き出し用オブジェクト
      OutputStream os = null;
      OutputStreamWriter osw = null;

      try {
        if (!lFullDestFile.exists() || !lFullDestFile.isFile()) {
          // ファイルが存在しない場合のエラー処理
          log.error("ファイルが存在していないためダウンロードに失敗しました。lFullDestFile=[" + lFullDestFile + "]");
          throw new SIFileUploadException("ファイルが存在していないためダウンロードに失敗しました。");
        }

        // レスポンスオブジェクトのヘッダー情報を設定
        response.setContentType("application/octet-stream");
        response.setHeader("Content-Disposition",
            "attachment; filename="
                + new String(targetFileName.getBytes("Windows-31J"), "ISO-8859-1"));

        // ダウンロード対象ファイルの読み込み用オブジェクトを生成
        fis = new FileInputStream(lFullDestFile);
        isr = new InputStreamReader(fis, "ISO-8859-1");

        // ダウンロードファイルの書き出し用オブジェクトを生成
        os = response.getOutputStream();
        osw = new OutputStreamWriter(os, "ISO-8859-1");

        // IOストリームを用いてファイルダウンロードを行う
        int i;
        while ((i = isr.read()) != -1) {
          osw.write(i);
        }
        log.debug("ファイルをダウンロードしました。lFullDestFile=[" + lFullDestFile + "]");
      } catch (FileNotFoundException e) {
        log.error("[FileNotFoundException]ファイルのダウンロードに失敗しました。lFullDestFile=[" + lFullDestFile + "]");
        throw new SIFileUploadException("ファイルのダウンロードに失敗しました。");
      } catch (UnsupportedEncodingException e) {
        log.error("[UnsupportedEncodingException]ファイルのダウンロードに失敗しました。lFullDestFile=[" + lFullDestFile + "]");
        throw new SIFileUploadException("ファイルのダウンロードに失敗しました。");
      } catch (IOException e) {
        log.error("[IOException]ファイルのダウンロードに失敗しました。lFullDestFile=[" + lFullDestFile + "]");
        throw new SIFileUploadException("ファイルのダウンロードに失敗しました。");
      } finally {

        try {
          // 各オブジェクトをクローズ
          if (osw != null) {
            osw.close();
          }
          if (os != null) {
            os.close();
          }
          if (isr != null) {
            isr.close();
          }
          if (fis != null) {
            fis.close();
          }
        } catch (IOException e) {
          log.error("[IOException]オブジェクトのクローズに失敗しました。lFullDestFile=[" + lFullDestFile + "]");
          throw new SIFileUploadException("ファイルのダウンロードに失敗しました。");
        }
      }
    }
    try {
      conn.commit();
    } catch (SQLException sqle) {
      // システムエラー
    } finally {
      session.setAttribute(SIConfig.SISESSION_FILE_UPLOAD_NAME, lMultiMap);
    }
    return action;
  }

  /**
   * <b>getMultiParameter </b> ファイルをアップロードするフォームに、ほかの項目に対する値を取得します。
   *
   * @param request リクエスト
   * @param lName 対象項目
   * @return
   */
  public String getMultiParameter(HttpServletRequest request, String lName) {

    HttpSession session = request.getSession();
    HashMap lMultiMap = (HashMap) session.getAttribute(SIConfig.SISESSION_FILE_UPLOAD_NAME);
    if (lMultiMap == null)
      return "";
    Object lValue = lMultiMap.get(lName);

    // パラメータのデータ
    if (lValue == null) {
      return "";
    } else if (lValue instanceof String) {
      return (String) lValue;
    } else {
      log.warn("not string!!!!");
      return "";
    }
  }

  /**
   * <b>getAbsFileName </b> アップロード先に目的ファイル名を生成します。
   *
   * @param lDestPath アップロード先のパス
   * @param lDestFileName 目的ファイル
   * @return 目的ファイルのオブジェクト
   */
  private File getAbsFileName(String lDestPath, String lDestFileName) {

    lDestPath = SIUtil.getOsFileName(lDestPath);
    lDestFileName = SIUtil.getOsFileName(lDestFileName);

    if (!SIConfig.SIOS_CURRENT_NAME.equals(SIConfig.SIOS_WINDOW_SERIES_NAME)
        && SIUtil.isNotNull(lDestFileName)) {

      int ii = lDestFileName.lastIndexOf("/");
      if (ii > 0) {
        lDestFileName = lDestFileName.substring(ii + 1);
      }
    }

    File lResFile = new File(lDestPath, lDestFileName);
    if (lResFile.isAbsolute()) {
      return lResFile;
    }
    return new File(getServletContext().getRealPath(lDestPath), lDestFileName);
  }

  /**
   * ディレクトリを作成します
   * @param lFolderName
   */
  private void createDirectory(String directoryName) {

    log.debug("createDirectory : directoryName=[" + directoryName + "]");
    String lAbsPath = getServletContext().getRealPath(directoryName);
    File lFile = new File(lAbsPath);
    lFile.mkdirs();
  }

  /**
   * 対象在庫に登録されているアップロードファイルの最後の資料番号（インデックス）を取得します
   * @param conn コネクション
   * @param individualcode 在庫コード
   * @return 資料番号
   * @throws SIDBAccessException
   */
  private String getLastIndex(Connection conn, String individualcode) throws SIDBAccessException {

    String lSql = "SELECT attachedfilecode FROM individualattachedfiletbl WHERE individualcode = '"
        + individualcode + "' ORDER BY attachedfilecode DESC";
    String lastIndex = "0";
    log.debug("sql.toString()=" + lSql.toString());

    try {
      if (SIDBUtil.hasData(conn, lSql)) {
        lastIndex = SIDBUtil.getFirstData(conn, lSql);
      }
    } catch (SIDBAccessException e) {
      log.error("[SIDBAccessException] 資料番号の取得中にエラーが発生しました。: " + lSql.toString());
      throw new SIDBAccessException(e);
    }
    return lastIndex;
  }

  /**
   * 商品添付資料テーブルにデータを登録します
   * @param lConnection コネクション
   * @param attachedFile 商品添付資料オブジェクト
   * @param userCode 登録者コード
   * @throws SIDBAccessException
   */
  private void insertTableDataIndividualattachedfile(
      Connection lConnection, UIIndividualAttachedFile attachedFile, String userCode) throws SIDBAccessException {

    SIInsertRec lRec = new SIInsertRec("individualattachedfiletbl");
    lRec.add("individualcode", attachedFile.getIndividualCode()); // 在庫コード
    lRec.add("attachedfilecode", attachedFile.getAttachedfilecode()); // 資料番号
    lRec.add("attachedfilename", attachedFile.getAttachedfilename()); // ファイル名
    lRec.add("extension", attachedFile.getExtension()); // 拡張子
    lRec.add("registusercode", userCode); // 登録者コード

    try {
      // 商品添付資料テーブルに登録
      lRec.execute(lConnection);
      log.debug("[individualattachedfiletbl]レコードを1件登録しました。"
          + "individualcode=[" + attachedFile.getIndividualCode() + "], "
          + "attachedfilecode=[" + attachedFile.getAttachedfilecode() + "], "
          + "attachedfilename=[" + attachedFile.getAttachedfilename() + "]");

    } catch (SIDuplicateKeyException e) {
      log.error("[individualattachedfiletbl]レコード登録処理で失敗しました。"
          + "individualcode=[" + attachedFile.getIndividualCode() + "], "
          + "attachedfilecode=[" + attachedFile.getAttachedfilecode() + "], "
          + "attachedfilename=[" + attachedFile.getAttachedfilename() + "]");
      throw new SIDBAccessException(e);

    } catch (SIDBAccessException e) {
      log.error("[individualattachedfiletbl]レコード登録処理で失敗しました。"
          + "individualcode=[" + attachedFile.getIndividualCode() + "], "
          + "attachedfilecode=[" + attachedFile.getAttachedfilecode() + "], "
          + "attachedfilename=[" + attachedFile.getAttachedfilename() + "]");
      throw new SIDBAccessException(e);
    }
  }

  /**
   * 商品添付資料のレコードを１件削除します
   * @param connection コネクション
   * @param individualcode 在庫コード
   * @param code 資料番号
   * @throws SIDBAccessException DB接続エラー
   */
  private void deleteTableDataIndividualattachedfile(Connection connection, String individualcode, String code)
      throws SIDBAccessException {

    StringBuffer sbSql = new StringBuffer();

    sbSql.append("DELETE FROM individualattachedfiletbl");
    sbSql.append(" WHERE individualcode = ").append(SIDBUtil.SQL2Str(individualcode));
    sbSql.append(" AND attachedfilecode = ").append(code);
    log.debug("sql.toString()=" + sbSql.toString());

    try {
      SIDBUtil.execSQL(connection, sbSql.toString());
      log.debug("[individualattachedfiletbl]1レコード削除しました。individualcode=[" + individualcode + "], attachedfilecode=[" + code + "]");

    } catch (SIDBAccessException e) {
      log.error("[individualattachedfiletbl]削除処理で失敗しました。individualcode=[" + individualcode + "], attachedfilecode=[" + code + "]");
      throw new SIDBAccessException();
    }
  }

  /**
   * ファイルサイズを適切な単位に修正して返します。(debug用)
   * @param size 単位がbyteのファイルサイズ
   * @return
   */
  private String getSizeStr(long size) {

    if (1024 > size) {
      return size + " Byte";
    } else if (1024 * 1024 > size) {
      double dsize = size;
      dsize = dsize / 1024;
      BigDecimal bi = new BigDecimal(String.valueOf(dsize));
      double value = bi.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
      return value + " KByte";
    } else {
      double dsize = size;
      dsize = dsize / 1024 / 1024;
      BigDecimal bi = new BigDecimal(String.valueOf(dsize));
      double value = bi.setScale(1, BigDecimal.ROUND_HALF_UP).doubleValue();
      return value + " MB";
    }
  }
}