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

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;

import blanco.commons.io.Native2AsciiWriter;
import blanco.commons.util.BlancoFileUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.nlpack.generator.BlancoNLpackGeneratorConstants;
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.csv.record.BlancoNLpackGeneratorResultDetailCsvRecord;
import blanco.nlpack.generator.message.BlancoNLpackGeneratorMessage;
import blanco.nlpack.generator.resourcebundle.BlancoNLpackGeneratorResourceBundle;
import blanco.nlpack.generator.task.valueobject.BlancoNLpackGeneratorConvertXliff2PropertiesProcessInput;
import blanco.properties.BlancoPropertiesDirectoryParser;
import blanco.properties.BlancoPropertiesUtil;
import blanco.properties.manifest.BlancoPropertiesManifestWriter;
import blanco.properties.manifest.valueobject.BlancoPropertiesManifest;
import blanco.xliff.BlancoXliffUtil;
import blanco.xliff.stringgroup.BlancoXliffStateStringGroup;
import blanco.xliff.valueobject.BlancoXliff;
import blanco.xliff.valueobject.BlancoXliffTransUnit;

public class BlancoNLpackGeneratorConvertXliff2PropertiesProcessImpl extends
        BlancoPropertiesDirectoryParser implements
        BlancoNLpackGeneratorConvertXliff2PropertiesProcess {
    private BlancoNLpackGeneratorConvertXliff2PropertiesProcessInput fInput = null;

    /**
     * メッセージオブジェクト。
     */
    private final BlancoNLpackGeneratorMessage fMsg = new BlancoNLpackGeneratorMessage();

    private final BlancoNLpackGeneratorResourceBundle fBundle = new BlancoNLpackGeneratorResourceBundle();

    private String fPrevBundleSymbolicName = null;

    private BlancoXliff fXliff = null;

    private BlancoNLpackGeneratorResultCsvWriter fLogSummaryWriter = null;

    private BlancoNLpackGeneratorResultDetailCsvWriter fLogDetailWriter = null;

    private String fPropertiesNameWithPath = null;

    /**
     * ヒットした件数。
     */
    private int fReplaceHitCount = 0;

    /**
     * ニモーニック変換をした件数。
     */
    @SuppressWarnings("unused")
    private int fReplaceMnemonicCount = 0;

    /**
     * 除外した件数。
     */
    private int fReplaceOmitCount = 0;

    /**
     * ミスした件数。
     */
    private int fReplaceMissCount = 0;

    /**
     * クラスをインスタンス化して処理を実行する際のエントリポイントです。
     * 
     * @param input
     *            処理の入力パラメータ。
     * @return 処理の実行結果。
     * @throws IOException
     *             入出力例外が発生した場合。
     * @throws IllegalArgumentException
     *             入力値に不正が見つかった場合。
     */
    public int execute(
            final BlancoNLpackGeneratorConvertXliff2PropertiesProcessInput input)
            throws IOException, IllegalArgumentException {
        fInput = input;

        // キャッシュのクリア。
        fPrevBundleSymbolicName = null;

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

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

            final File fileTmpDir = new File(fInput.getTmpdir() + "/log");
            fileTmpDir.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();
        } 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()));
            }

            parse(new File(fInput.getSourcedir()));
        } finally {
            if (fLogSummaryWriter != null) {
                fLogSummaryWriter.close();
                fLogSummaryWriter = null;
            }

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

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

        return 0;
    }

    @Override
    protected void beginProperties(final String bundleURI,
            final Properties inputProps,
            final String argPropertiesNameWithPath,
            final BufferedWriter bufPropsWriter) throws IOException {
        fReplaceHitCount = 0;
        fReplaceMissCount = 0;
        fReplaceMnemonicCount = 0;
        fReplaceOmitCount = 0;

        final String nextBundleSymbolicName = BlancoPropertiesUtil
                .getHostFromBabelURI(bundleURI);
        if (nextBundleSymbolicName.equals(fPrevBundleSymbolicName)) {
            // キャッシュがヒットしました。リロードしません。
        } else {
            fXliff = BlancoXliffUtil.getXliffInstance(new File(fInput
                    .getXliffdir()), nextBundleSymbolicName);
            fPrevBundleSymbolicName = nextBundleSymbolicName;
        }

        fPropertiesNameWithPath = argPropertiesNameWithPath;
        if (BlancoStringUtil.null2Blank(fBundle.getPropertiesHeader()).length() > 0) {
            // 人工的にヘッダーを付与します。
            bufPropsWriter.write(fBundle.getPropertiesHeader());
            bufPropsWriter.newLine();
            bufPropsWriter.newLine();
        }
    }

    @Override
    protected boolean processPropertiesKey(String bundleURI,
            Properties inputProps, String key, BufferedWriter bufPropsWriter)
            throws IOException {
        boolean isReplaced = false;
        final BlancoNLpackGeneratorResultDetailCsvRecord record = new BlancoNLpackGeneratorResultDetailCsvRecord();
        record.setBundleUri(bundleURI + "?key=" + key);
        record.setKey(key);
        record.setInput((String) inputProps.get(key));

        final BlancoXliffTransUnit transUnit = getTransUnitInstance(fXliff,
                bundleURI + "?key=" + key);
        if (transUnit == null) {
            // 見つけられず
            fReplaceMissCount++;
            record.setAction("miss.n");
        } else if (transUnit.getTranslate() == false) {
            // 翻訳対象外。
            fReplaceOmitCount++;
            record.setAction("omit");
        } else if (BlancoStringUtil.null2Blank(transUnit.getSource()).equals(
                (String) inputProps.get(key)) == false) {
            // せっかく trans-unit が見つかったのに、source が異なります。
            fReplaceMissCount++;
            record.setAction("miss.s");

            // FIXME source異なりの場合の処理について、どのような実装がベストなのか検討をおこなうこと。
            System.out
                    .println("trace: せっかく trans-unit が見つかったのに、source が異なります。: "
                            + record.getBundleUri());
        } else if (transUnit.getTarget() != null
                && transUnit.getTarget().getTarget() != null) {
            isReplaced = true;

            // state による動作変更を実施します。
            // ※基本的に target の文字列は利用します。
            switch (new BlancoXliffStateStringGroup()
                    .convertToInt(BlancoStringUtil.null2Blank(transUnit
                            .getTarget().getState()))) {
            case BlancoXliffStateStringGroup.NEW:
            case BlancoXliffStateStringGroup.NEEDS_TRANSLATION:
                // 未翻訳のものについて、target の文字列は利用するものの miss としてカウントします。
                fReplaceMissCount++;
                record.setAction("miss.y");
                break;
            default:
                // 翻訳できました。
                fReplaceHitCount++;
                record.setAction("hit");
                break;
            }

        } else {
            fReplaceMissCount++;
            record.setAction("miss.t");
        }

        if (isReplaced) {
            bufPropsWriter
                    .write(Native2AsciiWriter.encodeNative2AsciiValue(transUnit
                            .getTarget().getTarget()));
            record.setOutput(transUnit.getTarget().getTarget());
        } else {
            bufPropsWriter.write(Native2AsciiWriter
                    .encodeNative2AsciiValue((String) inputProps.get(key)));
        }
        bufPropsWriter.newLine();

        try {
            fLogDetailWriter.writeRecord(record);
        } catch (BlancoCsvIOException e) {
            throw new IllegalArgumentException("CSV入出力例外", e);
        }

        return isReplaced;
    }

    @Override
    protected void endProperties(String bundleURI, Properties inputProps,
            BlancoPropertiesManifest manifest, byte[] bufPropsBytes,
            boolean isReplaced) throws IOException {
        final BlancoNLpackGeneratorResultCsvRecord record = new BlancoNLpackGeneratorResultCsvRecord();
        record.setBundleSymbolicName(BlancoPropertiesUtil
                .getHostFromBabelURI(bundleURI));
        record.setBundleUri(bundleURI);
        record.setReplaceHitCount(fReplaceHitCount);
        record.setReplaceMissCount(fReplaceMissCount);
        record.setReplaceOmitCount(fReplaceOmitCount);

        final String displayPath = BlancoPropertiesUtil
                .getHostFromBabelURI(bundleURI)
                + ": " + BlancoPropertiesUtil.getPathFromBabelURI(bundleURI);

        if (isReplaced) {
            final File fileTarget = new File(fInput.getTargetdir()
                    + "/"
                    + BlancoPropertiesUtil.getPluginNameWithSuffix(manifest,
                            fBundle.getTargetLocale())
                    + fPropertiesNameWithPath.substring(0,
                            fPropertiesNameWithPath.length()
                                    - ".properties".length()) + "_"
                    + fBundle.getTargetLocale() + ".properties");
            fileTarget.getParentFile().mkdirs();
            switch (BlancoFileUtil.bytes2FileIfNecessary(bufPropsBytes,
                    fileTarget)) {
            case 1:
                if (fInput.getVerbose()) {
                    System.out.println("xliff: x2p: create: " + displayPath);
                }
                record.setAction("create");
                break;
            case 2:
                if (fInput.getVerbose()) {
                    System.out.println("xliff: x2p: update: " + displayPath);
                }
                record.setAction("update");
                break;
            case 0:
                if (fInput.getVerbose()) {
                    System.out.println("xliff: x2p: none  : " + displayPath);
                }
                record.setAction("none");
                break;
            }

            // 翻訳処理が実施された場合には MANIFEST.MF関連ファイルを生成します。
            new BlancoPropertiesManifestWriter().write(manifest, new File(
                    fInput.getTargetdir()
                            + "/"
                            + BlancoPropertiesUtil.getPluginNameWithSuffix(
                                    manifest, fBundle.getTargetLocale())));
        } else {
            if (fInput.getVerbose()) {
                System.out.println("xliff: x2p: skip  : " + displayPath);
            }
            record.setAction("skip");
        }

        try {
            fLogSummaryWriter.writeRecord(record);
        } catch (BlancoCsvIOException e) {
            throw new IOException(fMsg.getMbebc001(e.toString()));
        }
    }

    private BlancoXliffTransUnit getTransUnitInstance(final BlancoXliff xliff,
            final String id) {
        BlancoXliffTransUnit transUnit = BlancoXliffUtil
                .getTransUnit(xliff, id);
        if (transUnit != null) {
            // 指定されたidによる trans-unit が既にあれば それを返却。
            return transUnit;
        }

        // 存在しない場合は処理しません。
        return null;
    }
}
