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

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.text.SimpleDateFormat;
import java.util.Date;

import blanco.commons.util.BlancoStreamUtil;
import blanco.log.resourcebundle.BlancoLogProgramMessage;

/**
 * blancoLog: バイナリログを出力するためのクラス。
 * 
 * @author IGA Tosiki
 */
public class BlancoLogBinaryLogger {
    /**
     * blancoLogメッセージクラス。
     */
    protected final BlancoLogProgramMessage fMsg = new BlancoLogProgramMessage();

    /**
     * 日時からファイル名の一部を命名するときに利用します。
     * 
     * このオブジェクトは このクラスのロックオブジェクトとしても利用されています。
     */
    private static final SimpleDateFormat sdFormat = new SimpleDateFormat(
            "yyyyMMddHHmmss.SSS");

    /**
     * ディレクトリ名に利用されます。
     */
    private static final SimpleDateFormat sdYmdFormat = new SimpleDateFormat(
            "yyyyMMdd");

    /**
     * 基準ディレクトリ。このディレクトリ配下に年月日ディレクトリが作成される。
     */
    protected String fBaseDirectory;

    /**
     * ログファイル名のプレフィックス。
     */
    protected String fPrefix;

    /**
     * ログファイル名のサフィックス。
     */
    protected String fSuffix;

    /**
     * ログファイル名の拡張子。
     */
    protected String fExt;

    /**
     * 
     * @param argBaseDirectory
     * @param argPrefix
     * @param argSuffix
     * @param argExt
     */
    public BlancoLogBinaryLogger(final String argBaseDirectory,
            final String argPrefix, final String argSuffix, final String argExt) {
        // これらフィールドを構造体化するかどうかを検討すること。
        fBaseDirectory = argBaseDirectory;
        fPrefix = argPrefix;
        fSuffix = argSuffix;
        fExt = argExt;
    }

    /**
     * バイナリデータを所定のディレクトリの所定の物理ファイルへと書き込みます。
     * 
     * @param argBinaryData
     *            書き込みを行いたいバイナリデータ。
     * @return ログとして書き出した物理ファイル。
     * @throws IOException
     */
    public File log(final byte[] argBinaryData) throws IOException {
        // 今回の処理で利用する現在時刻を決定します。
        final Date dateCurrent = new Date();

        final File fileBaseDirectory = new File(fBaseDirectory);
        if (fileBaseDirectory.exists() == false) {
            if (fileBaseDirectory.mkdirs() == false) {
                throw new IOException(fMsg.getMblgb001(fileBaseDirectory
                        .getAbsolutePath()));
            }
        }

        synchronized (sdFormat) {
            // 注意: この箇所はシングルに動作するようにブロックを実施しています。
            // JavaVMが同一の場合には、ファイル名衝突の問題を解決します。

            final String baseYmdDirectory = fileBaseDirectory.getAbsolutePath()
                    + "/" + sdYmdFormat.format(dateCurrent);
            final File fileBaseYmdDirectory = new File(baseYmdDirectory);
            if (fileBaseYmdDirectory.exists() == false) {
                if (fileBaseYmdDirectory.mkdirs() == false) {
                    throw new IOException(fMsg.getMblgb002(fileBaseYmdDirectory
                            .getAbsolutePath()));
                }
            }

            final String targetFilenameBase = baseYmdDirectory + "/" + fPrefix
                    + sdFormat.format(dateCurrent) + fSuffix + ".";

            File fileOutputLogfile = new File(targetFilenameBase + fExt);
            if (fileOutputLogfile.exists()) {
                // ファイルが既にあれば、連番処理で衝突を避ける
                for (int index = 1;; index++) {
                    // ※注意。現在は無限ループ系の実装です。
                    fileOutputLogfile = new File(targetFilenameBase
                            + String.valueOf(index) + "." + fExt);
                    if (fileOutputLogfile.exists() == false) {
                        break;
                    }
                }
            }

            // TODO この間にファイルが新規作成されると例外で吹っ飛びます。その点に注意。

            try {
                if (fileOutputLogfile.createNewFile() == false) {
                    // createNewFile は新しいファイルを atomic に生成します。
                    throw new IOException(fMsg.getMblgb003(fileOutputLogfile
                            .getAbsolutePath()));
                }
            } catch (IOException ex) {
                throw new IOException(fMsg.getMblgb004(fileOutputLogfile
                        .getAbsolutePath(), ex.toString()));
            } catch (SecurityException ex) {
                throw new IOException(fMsg.getMblgb005(fileOutputLogfile
                        .getAbsolutePath(), ex.toString()));
            }

            try {
                if (fileOutputLogfile.canWrite() == false) {
                    throw new IOException(fMsg.getMblgb006(fileOutputLogfile
                            .getAbsolutePath()));
                }
            } catch (SecurityException ex) {
                throw new IOException(fMsg.getMblgb007(fileOutputLogfile
                        .getAbsolutePath(), ex.toString()));

            }

            // 取得したファイルにログ内容を一括転送。

            try {
                final FileOutputStream outStream = new FileOutputStream(
                        fileOutputLogfile);
                final FileChannel outputChannel = outStream.getChannel();
                FileLock lock = null;
                try {
                    lock = outputChannel.lock();
                } catch (IOException ex) {
                    throw new IOException(fMsg.getMblgb009(fileOutputLogfile
                            .getAbsolutePath(), ex.toString()));
                }

                try {
                    BlancoStreamUtil.copy(new ByteArrayInputStream(
                            argBinaryData), outStream);
                    outStream.flush();
                } finally {
                    try {
                        lock.release();
                    } catch (IOException ex) {
                        throw new IOException(fMsg.getMblgb010(
                                fileOutputLogfile.getAbsolutePath(), ex
                                        .toString()));
                    }

                    try {
                        outputChannel.close();
                    } catch (IOException ex) {
                        throw new IOException(fMsg.getMblgb011(
                                fileOutputLogfile.getAbsolutePath(), ex
                                        .toString()));
                    }

                    try {
                        outStream.close();
                    } catch (IOException ex) {
                        throw new IOException(fMsg.getMblgb012(
                                fileOutputLogfile.getAbsolutePath(), ex
                                        .toString()));
                    }
                }
            } catch (IOException ex) {
                throw new IOException(fMsg.getMblgb008(fileOutputLogfile
                        .getAbsolutePath(), ex.toString()));
            }

            return fileOutputLogfile;
        }
    }
}
