/*******************************************************************************
 * Copyright (c) 2008 IGA Tosiki, NTT DATA BUSINESS BRAINS Corp.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    IGA Tosiki (NTT DATA BUSINESS BRAINS Corp.) - initial API and implementation
 *******************************************************************************/
/*
 * blanco Framework
 * Copyright (C) 2008 NTT DATA BUSINESS BRAINS CORPORATION
 * 
 * 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.nlpack.generator;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Properties;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import blanco.commons.util.BlancoFileUtil;
import blanco.nlpack.generator.csv.io.BlancoCsvIOException;
import blanco.nlpack.generator.csv.io.BlancoNLpackGeneratorResultCsvWriter;
import blanco.nlpack.generator.csv.io.BlancoNLpackGeneratorResultDetailCsvWriter;
import blanco.nlpack.generator.csv.record.BlancoNLpackGeneratorResultCsvRecord;
import blanco.nlpack.generator.manifest.BlancoNLpackGeneratorManifestReader;
import blanco.nlpack.generator.manifest.BlancoNLpackGeneratorManifestWriter;
import blanco.nlpack.generator.message.BlancoNLpackGeneratorMessage;
import blanco.nlpack.generator.resourcebundle.BlancoNLpackGeneratorResourceBundle;
import blanco.nlpack.generator.task.valueobject.BlancoNLpackGeneratorProcessInput;
import blanco.nlpack.generator.valueobject.BlancoNLpackGeneratorManifest;

/**
 * Javaアプリケーションを入力して、言語パックを出力するクラス。
 * 
 * @author IGA Tosiki
 */
public class BlancoNLpackGenerator {
    /**
     * メッセージオブジェクト。
     */
    private final BlancoNLpackGeneratorMessage fMsg = new BlancoNLpackGeneratorMessage();

    /**
     * リソースバンドルオブジェクト。
     */
    private final BlancoNLpackGeneratorResourceBundle fBundle = new BlancoNLpackGeneratorResourceBundle();

    /**
     * 変換のためのインスタンス。
     */
    private final BlancoNLpackGeneratorPropertiesConverter fReplace = new BlancoNLpackGeneratorPropertiesConverter();

    private BlancoNLpackGeneratorProcessInput fInput = null;

    private BlancoNLpackGeneratorResultCsvWriter fLogSummaryWriter = null;

    private BlancoNLpackGeneratorResultDetailCsvWriter fLogDetailWriter = null;

    /**
     * 指定されたディレクトリのプラグインについて各国語化処理を実行します。
     * 
     * @param argInput
     *            処理への入力パラメータ。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public void process(final BlancoNLpackGeneratorProcessInput argInput)
            throws IOException {
        fInput = argInput;

        System.out.println("babel: begin: [" + fInput.getSourcedir() + "]->["
                + fInput.getTargetdir() + "].");

        new File(fInput.getTmpdir() + "/log").mkdirs();

        {
            // 出力先ディレクトリを作成。
            final File fileTarget = new File(fInput.getTargetdir());
            if (fileTarget.exists() == false) {
                fileTarget.mkdirs();
            }
        }

        // TODO ログファイル名の外部化。
        final File fileLogSummary = new File(fInput.getTmpdir()
                + "/log/blancoNLpackGeneratorResult.csv");
        try {
            final BufferedWriter writer = new BufferedWriter(
                    new BufferedWriter(new FileWriter(fileLogSummary, true)));
            writer.write("[[" + BlancoNLpackGeneratorConstants.PRODUCT_NAME
                    + " (" + BlancoNLpackGeneratorConstants.VERSION + ")"
                    + "]]: " + fInput.getSourcedir());
            writer.newLine();

            fLogSummaryWriter = new BlancoNLpackGeneratorResultCsvWriter(writer);
            fLogSummaryWriter.writeTitle();
        } catch (IOException ex) {
            throw new IllegalArgumentException(fMsg.getMbebi004(fileLogSummary
                    .getAbsolutePath(), ex.toString()));
        }

        // TODO ログファイル名の外部化。
        final File fileLogDetail = new File(fInput.getTmpdir()
                + "/log/blancoNLpackGeneratorResultDetail.csv");
        try {
            final BufferedWriter writer = new BufferedWriter(
                    new BufferedWriter(new FileWriter(fileLogDetail, true)));
            writer.write("[[" + BlancoNLpackGeneratorConstants.PRODUCT_NAME
                    + " (" + BlancoNLpackGeneratorConstants.VERSION + ")"
                    + "]]: " + fInput.getSourcedir());
            writer.newLine();

            fLogDetailWriter = new BlancoNLpackGeneratorResultDetailCsvWriter(
                    writer);
            fLogDetailWriter.writeTitle();

            fReplace.setLogDetailWriter(fLogDetailWriter);
        } catch (IOException ex) {
            throw new IllegalArgumentException(fMsg.getMbebi005(fileLogDetail
                    .getAbsolutePath(), ex.toString()));
        }

        try {
            if (new File(fInput.getSourcedir()).exists() == false) {
                throw new IllegalArgumentException(fMsg.getMbebi001(fInput
                        .getSourcedir()));
            }

            final File[] files = new File(fInput.getSourcedir()).listFiles();
            if (files != null) {
                for (int index = 0; index < files.length; index++) {
                    if (files[index].isDirectory()) {
                        // 展開されたプラグインディレクトリを処理。
                        processPluginExpandedDir(files[index].getName());
                    } else {
                        if (files[index].getName().endsWith(".jar")) {
                            // jarファイル形式のプラグインを処理。
                            processPluginJarFile(files[index].getName());
                        }
                    }
                }
            }
        } finally {
            if (fLogSummaryWriter != null) {
                fLogSummaryWriter.close();
                fLogSummaryWriter = null;
            }

            if (fLogDetailWriter != null) {
                fLogDetailWriter.close();
                fLogDetailWriter = null;
            }
        }

        System.out.println("babel: end.");
    }

    /**
     * プラグインのjarファイルを処理します。
     * 
     * TODO 入力を String から File に変更することを検討したい。
     * 
     * @param targetPluginFullPlusJar
     * @throws IOException
     */
    private void processPluginJarFile(final String targetPluginFullPlusJar)
            throws IOException {
        final BlancoNLpackGeneratorManifest manifest = new BlancoNLpackGeneratorManifestReader()
                .readPluginJar(new File(fInput.getSourcedir() + "/"
                        + targetPluginFullPlusJar));

        String actualTargetPluginFull = targetPluginFullPlusJar;
        if (actualTargetPluginFull.endsWith(".jar")) {
            // .jar の指定箇所を除去します。
            actualTargetPluginFull = actualTargetPluginFull.substring(0,
                    actualTargetPluginFull.length() - ".jar".length());
        } else {
            throw new IllegalArgumentException(fMsg
                    .getMbebi003(targetPluginFullPlusJar));
        }

        // 出力先となるプラグインのサブディレクトリ。
        final String outputPluginSubDir = fInput.getTargetdir() + "/"
                + BlancoNLpackGeneratorUtil.getPluginNameWithSuffix(manifest);

        final InputStream inStream = new BufferedInputStream(
                new FileInputStream(fInput.getSourcedir() + "/"
                        + actualTargetPluginFull + ".jar"));
        if (processJarFileInternal(manifest, inStream, new File(
                outputPluginSubDir))) {
            // 翻訳処理が実施された場合には MANIFEST.MF関連ファイルを生成します。
            new BlancoNLpackGeneratorManifestWriter().write(manifest, new File(
                    outputPluginSubDir));
        }
        inStream.close();
    }

    /**
     * プラグインの展開後ディレクトリを処理します。
     * 
     * @param targetPluginFullPlus
     * @throws IOException
     */
    private void processPluginExpandedDir(final String targetPluginFullPlus)
            throws IOException {
        boolean isReplaced = false;

        final BlancoNLpackGeneratorManifest manifest = new BlancoNLpackGeneratorManifestReader()
                .readPluginDirectory(new File(fInput.getSourcedir() + "/"
                        + targetPluginFullPlus));

        final String outputPluginSubDir = fInput.getTargetdir() + "/"
                + BlancoNLpackGeneratorUtil.getPluginNameWithSuffix(manifest);

        final File[] files = new File(fInput.getSourcedir() + "/"
                + targetPluginFullPlus).listFiles();
        if (files != null) {
            for (int index = 0; index < files.length; index++) {
                if (files[index].isDirectory()) {
                } else {
                    if (files[index].getName().endsWith(".jar")) {
                        final InputStream inStream = new BufferedInputStream(
                                new FileInputStream(fInput.getSourcedir() + "/"
                                        + targetPluginFullPlus + "/"
                                        + files[index].getName()));
                        if (processJarFileInternal(manifest, inStream,
                                new File(outputPluginSubDir))) {
                            isReplaced = true;
                        }
                        inStream.close();
                    } else if (checkFilenameForTarget(files[index].getName())) {
                        final InputStream inStream = new BufferedInputStream(
                                new FileInputStream(files[index]));

                        String target = files[index].getName().substring(
                                0,
                                files[index].getName().length()
                                        - ".properties".length())
                                + "_"
                                + fBundle.getTargetLocale()
                                + ".properties";
                        target = outputPluginSubDir + "/" + target;

                        // 報告用ベースディレクトリ:begin
                        final String wrkBase = new File(fInput.getSourcedir()
                                + "/" + targetPluginFullPlus)
                                .getCanonicalPath();
                        final String strPropertyFilePath = files[index]
                                .getCanonicalPath().substring(
                                        wrkBase.length() + 1);
                        // 報告用ベースディレクトリ:end

                        if (processPropertiesFile(manifest,
                                BlancoNLpackGeneratorUtil.getBabelURI(manifest,
                                        strPropertyFilePath), inStream,
                                new File(target))) {
                            isReplaced = true;
                        }

                        inStream.close();
                    }
                }
            }
            if (isReplaced) {
                // 翻訳処理が実施された場合には MANIFEST.MF関連ファイルを生成します。
                new BlancoNLpackGeneratorManifestWriter().write(manifest,
                        new File(outputPluginSubDir));
            }
        }
    }

    /**
     * jarファイル処理のための内部メソッドです。
     * 
     * @param manifest
     *            マニフェスト。
     * @param inStreamJarTarget
     *            jarファイルの入力ストリーム。
     * @param fileOutputBaseDir
     *            出力先ディレクトリ。
     * @return 処理したかどうか。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    private boolean processJarFileInternal(
            final BlancoNLpackGeneratorManifest manifest,
            final InputStream inStreamJarTarget, final File fileOutputBaseDir)
            throws IOException {
        boolean isReplaced = false;
        final JarInputStream jarInStream = new JarInputStream(
                new BufferedInputStream(inStreamJarTarget));
        for (;;) {
            final JarEntry entry = jarInStream.getNextJarEntry();
            if (entry == null) {
                break;
            }

            if (entry.isDirectory()) {
            } else {
                if (entry.getName().endsWith(".jar")) {
                    if (processJarFileInternal(manifest, jarInStream,
                            fileOutputBaseDir)) {
                        isReplaced = true;
                    }
                } else {
                    if (checkFilenameForTarget(entry.getName()) == false) {
                        continue;
                    }
                    String target = entry.getName().substring(0,
                            entry.getName().length() - ".properties".length())
                            + "_" + fBundle.getTargetLocale() + ".properties";
                    target = fileOutputBaseDir + "/" + target;

                    if (processPropertiesFile(manifest,
                            BlancoNLpackGeneratorUtil.getBabelURI(manifest,
                                    entry.getName()), jarInStream, new File(
                                    target))) {
                        isReplaced = true;
                    }
                }
            }
            jarInStream.closeEntry();
        }
        return isReplaced;

        // 申し送り。入力ストリーム(jarInStream)は、敢えてクローズしません！これは Java
        // APIの特性を考慮したうえで必要な措置です。
    }

    /**
     * プロパティファイルを処理します。
     * 
     * @param bundleURI
     * @param jarInStream
     * @param fileTarget
     * @return 処理したかどうか。
     * @throws IOException
     */
    private boolean processPropertiesFile(
            final BlancoNLpackGeneratorManifest manifest,
            final String bundleURI, final InputStream jarInStream,
            final File fileTarget) throws IOException {
        // バイトとして読み込み
        final byte[] bytesProperties = BlancoNLpackGeneratorUtil
                .readBytes(jarInStream);

        final Properties props = new Properties();
        props.load(new ByteArrayInputStream(bytesProperties));

        final BufferedReader reader = new BufferedReader(new InputStreamReader(
                new ByteArrayInputStream(bytesProperties)));
        final ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        final BufferedWriter writer = new BufferedWriter(
                new OutputStreamWriter(outStream));

        final boolean isReplaced = fReplace.process(bundleURI, props, reader,
                writer);

        writer.close();

        final BlancoNLpackGeneratorResultCsvRecord record = new BlancoNLpackGeneratorResultCsvRecord();
        record.setBundleSymbolicName(manifest.getBundleSymbolicName());
        record.setBundleUri(bundleURI);

        if (isReplaced) {
            fileTarget.getParentFile().mkdirs();
            switch (BlancoFileUtil.bytes2FileIfNecessary(outStream
                    .toByteArray(), fileTarget)) {
            case 1:
                if (fInput.getVerbose()) {
                    System.out
                            .println(BlancoNLpackGeneratorConstants.PRODUCT_SHORT_NAME
                                    + ": create: " + bundleURI);
                }
                record.setAction("create");
                break;
            case 2:
                if (fInput.getVerbose()) {
                    System.out
                            .println(BlancoNLpackGeneratorConstants.PRODUCT_SHORT_NAME
                                    + ": update: " + bundleURI);
                }
                record.setAction("update");
                break;
            case 0:
                if (fInput.getVerbose()) {
                    System.out
                            .println(BlancoNLpackGeneratorConstants.PRODUCT_SHORT_NAME
                                    + ": none  : " + bundleURI);
                }
                record.setAction("none");
                break;
            }
        } else {
            if (fInput.getVerbose()) {
                System.out
                        .println(BlancoNLpackGeneratorConstants.PRODUCT_SHORT_NAME
                                + ": skip  : " + bundleURI);
            }
            record.setAction("skip");
        }

        record.setReplaceHitCount(fReplace.getReplaceHitCount());
        record.setReplaceMnemonicCount(fReplace.getReplaceMnemonicCount());
        record.setReplaceOmitCount(fReplace.getReplaceOmitCount());
        record.setReplaceMissCount(fReplace.getReplaceMissCount());
        try {
            fLogSummaryWriter.writeRecord(record);
        } catch (BlancoCsvIOException e) {
            throw new IOException(fMsg.getMbebc001(e.toString()));
        }

        return isReplaced;
    }

    /**
     * 与えられたファイルが処理すべきプロパティファイルであるかどうかを判定します。
     * 
     * @param argFilename
     * @return
     */
    private boolean checkFilenameForTarget(final String argFilename) {
        // TODO 改善の余地あり。そもそもアンダーバーで区切られているプロパティファイルは無視すべきかも知れません。
        return argFilename.endsWith(".properties")
                && (argFilename.endsWith("_" + fBundle.getTargetLocale()
                        + ".properties") == false
                        && argFilename.endsWith("_fr.properties") == false
                        && argFilename.endsWith("_de.properties") == false
                        && argFilename.endsWith("_es.properties") == false
                        && argFilename.endsWith("_it.properties") == false
                        && argFilename.endsWith("_ko.properties") == false
                        && argFilename.endsWith("_pt_BR.properties") == false
                        && argFilename.endsWith("_zh.properties") == false
                        && argFilename.endsWith("_zh_HK.properties") == false && argFilename
                        .endsWith("_zh_TW.properties") == false);
    }
}
