/*
 * blanco Framework
 * Copyright (C) 2004-2007 IGA Tosiki
 * 
 * This library 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.
 */
package blanco.encryption;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;

import blanco.commons.util.BlancoStreamUtil;

/**
 * トリプルDES (Triple DES) アルゴリズムによるファイル暗号化ツール。
 * 
 * ファイルの拡張子により、暗号化／復号化の判断をおこなうようになっています。
 * 
 * @author IGA Tosiki
 */
public class BlancoEncryptionDesede {
    /**
     * 利用するアルゴリズム。
     */
    private static final String ALGORITHM = "DESede";

    /**
     * 暗号化をおこないます。
     * 
     * 内部的には GZIP圧縮を行います。
     * 
     * @param argFileInput
     *            入力ファイル。
     * @param argFileOutput
     *            出力ファイル(暗号化)。
     * @param argPassword
     *            暗号化パスワード。
     * @throws IOException
     *             入出力例外 が発生した場合。
     */
    public void encrypt(final File argFileInput, final File argFileOutput,
            final String argPassword) throws IOException {
        // TODO 入力パラメータチェックの実装。
        // TODO ファイル存在チェック。

        try {
            final InputStream inStream = new BufferedInputStream(
                    new FileInputStream(argFileInput));

            final Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, BlancoEncryptionSecretKeyUtil
                    .getSha256KeyForDesede(argPassword));

            final OutputStream outStream = new GZIPOutputStream(
                    new CipherOutputStream(new BufferedOutputStream(
                            new FileOutputStream(argFileOutput)), cipher));

            BlancoStreamUtil.copy(inStream, outStream);

            outStream.flush();
            outStream.close();
            // TODO 確実なclose処理の実装。

            inStream.close();

            // 最後にファイルの日付をコピーします。
            if (argFileOutput.setLastModified(argFileInput.lastModified()) == false) {
                throw new IllegalArgumentException("ファイルの最終更新日時のコピーに失敗しました。");
            }
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("アルゴリズムの取得に失敗しました。:"
                    + e.toString());
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("パディング不正で失敗しました。:"
                    + e.toString());
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("秘密鍵不正で失敗しました。:" + e.toString());
        }
    }

    /**
     * 復号化を行います。
     * 
     * 内部的には GZIP伸張を行います。
     * 
     * @param argFileInput
     *            入力ファイル(暗号化)。
     * @param argFileOutput
     *            出力ファイル。
     * @param argPassword
     *            暗号化パスワード。
     * @throws IOException
     *             入出力例外 が発生した場合。
     */
    public void decrypt(final File argFileInput, final File argFileOutput,
            final String argPassword) throws IOException {
        // TODO 入力パラメータチェックの実装。
        // TODO ファイル存在チェック。

        try {
            final Cipher cipher = Cipher.getInstance(ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, BlancoEncryptionSecretKeyUtil
                    .getSha256KeyForDesede(argPassword));
            final InputStream inStream = new GZIPInputStream(
                    new CipherInputStream(new BufferedInputStream(
                            new FileInputStream(argFileInput)), cipher));

            final OutputStream outStream = new BufferedOutputStream(
                    new FileOutputStream(argFileOutput));

            BlancoStreamUtil.copy(inStream, outStream);

            inStream.close();

            outStream.flush();
            outStream.close();
            // TODO 確実なclose処理の実装。

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("アルゴリズムの取得に失敗しました。:"
                    + e.toString());
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("パディング不正で失敗しました。:"
                    + e.toString());
        } catch (InvalidKeyException e) {
            e.printStackTrace();
            throw new IllegalArgumentException("秘密鍵不正で失敗しました。:" + e.toString());
        } catch (IOException e) {
            throw new IllegalArgumentException(
                    "入出力例外が発生しました。パスワード間違いの可能性もあります:" + e.toString());
        }
    }
}
