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

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import blanco.commons.util.BlancoStringUtil;
import blanco.properties.BlancoPropertiesUtil;
import blanco.xliff.BlancoXliffParser;
import blanco.xliff.BlancoXliffUtil;
import blanco.xliff.util.BlancoXliffUtilConstants;
import blanco.xliff.util.task.valueobject.BlancoXliffUtilMergeProcessInput;
import blanco.xliff.valueobject.BlancoXliff;
import blanco.xliff.valueobject.BlancoXliffAltTrans;
import blanco.xliff.valueobject.BlancoXliffFile;
import blanco.xliff.valueobject.BlancoXliffNote;
import blanco.xliff.valueobject.BlancoXliffTarget;
import blanco.xliff.valueobject.BlancoXliffTransUnit;

public class BlancoXliffUtilMergeProcessImpl implements
        BlancoXliffUtilMergeProcess {
    private BlancoXliffUtilMergeProcessInput fInput = null;

    private String fPrevBundleSymbolicName = null;

    private BlancoXliff fXliffTarget = null;

    private int fUpdateXliffCount = 0;

    private int fSkipXliffCount = 0;

    private int fTotalUnitCount = 0;

    private int fMissUnitCount = 0;

    public int execute(final BlancoXliffUtilMergeProcessInput input)
            throws IOException, IllegalArgumentException {
        fInput = input;
        fPrevBundleSymbolicName = null;

        final boolean isSourceDir = BlancoStringUtil.null2Blank(
                fInput.getSourcedir()).length() > 0;
        final boolean isSourceFile = BlancoStringUtil.null2Blank(
                fInput.getSourcefile()).length() > 0;
        if (isSourceDir == false && isSourceFile == false) {
            throw new IllegalArgumentException(
                    "sourcedir または sourcefile のいずれか一方は指定してください。");
        }
        if (isSourceDir && isSourceFile) {
            throw new IllegalArgumentException(
                    "sourcedir または sourcefile のいずれか一方のみ指定してください。");
        }

        final File targetDir = new File(fInput.getTargetdir());
        if (targetDir.exists() == false) {
            throw new IllegalArgumentException("指定された targetdir["
                    + targetDir.getAbsolutePath() + "]は存在しません。");
        }
        if (targetDir.isDirectory() == false) {
            throw new IllegalArgumentException("指定された targetdir["
                    + targetDir.getAbsolutePath() + "]はディレクトリではありません。");
        }

        if (isSourceDir) {
            final File sourceDir = new File(fInput.getSourcedir());
            if (sourceDir.exists() == false) {
                throw new IllegalArgumentException("指定された sourcedir["
                        + sourceDir.getAbsolutePath() + "]は存在しません。");
            }
            if (sourceDir.isDirectory() == false) {
                throw new IllegalArgumentException("指定された sourcedir["
                        + sourceDir.getAbsolutePath() + "]はディレクトリではありません。");
            }

            if (sourceDir.getCanonicalPath().equals(
                    targetDir.getCanonicalPath())) {
                throw new IllegalArgumentException("sourcedir["
                        + sourceDir.getAbsolutePath() + "] と targetdir["
                        + targetDir.getAbsolutePath() + "] とは同じディレクトリです。");
            }
        } else {
            // ファイル存在チェック。
        }

        if (fInput.getSettarget() == false && fInput.getSettargetoverwrite()) {
            throw new IllegalArgumentException(
                    "settargetoverwrite を利用する場合には settarget を true に設定してください。");
        }

        if (fInput.getSettarget() == false && fInput.getSetalttrans() == false) {
            throw new IllegalArgumentException(
                    "settarget または setalttrans のいずれか一方は true に設定してください。");
        }

        if (isSourceDir) {
            final File[] targetFiles = targetDir.listFiles();
            if (targetFiles == null) {
                return 0;
            }
            for (int index = 0; index < targetFiles.length; index++) {
                if (targetFiles[index].isFile() == false) {
                    continue;
                }
                if (targetFiles[index].getName().endsWith(".xlf")) {
                    processXliffWithDir(targetFiles[index], new File(fInput
                            .getSourcedir()));
                }
            }
        } else {
            // 入力はファイル。
            processXliffWithFile(targetDir, new File(fInput.getSourcefile()));
        }

        System.out.println("xliff: merge: update=" + fUpdateXliffCount
                + ", skip=" + fSkipXliffCount);
        System.out.println("- trans-unit (total=" + fTotalUnitCount + ", miss="
                + fMissUnitCount + ")");

        return 0;
    }

    /**
     * ディレクトリによるXLIFFの更新。
     * 
     * @param fileTarget
     * @param sourceDir
     * @throws IOException
     */
    private void processXliffWithDir(final File fileTarget, final File sourceDir)
            throws IOException {
        final File fileSource = new File(sourceDir.getAbsoluteFile() + "/"
                + fileTarget.getName());
        if (fileSource.exists() == false) {
            // 同名の XLIFF は存在しません。
            return;
        }

        final String bundleSymbolicName = fileTarget.getName().substring(0,
                fileTarget.getName().length() - ".xlf".length());
        final BlancoXliff xliffTarget = BlancoXliffUtil.getXliffInstance(
                fileTarget.getParentFile(), bundleSymbolicName);
        final BlancoXliff xliffSource = BlancoXliffUtil.getXliffInstance(
                fileSource.getParentFile(), bundleSymbolicName);
        final Map<String, BlancoXliffTransUnit> mapSource = new HashMap<String, BlancoXliffTransUnit>();
        buildMap(xliffSource, mapSource);

        for (int indexFile = 0; indexFile < xliffTarget.getFileList().size(); indexFile++) {
            final BlancoXliffFile file = xliffTarget.getFileList().get(
                    indexFile);
            for (int indexTransUnit = 0; indexTransUnit < file.getBody()
                    .getTransUnitList().size(); indexTransUnit++) {
                final BlancoXliffTransUnit targetUnit = file.getBody()
                        .getTransUnitList().get(indexTransUnit);
                final BlancoXliffTransUnit sourceUnit = mapSource
                        .get(targetUnit.getId());
                if (sourceUnit == null) {
                    // 発見できず
                    continue;
                }

                // この trans-unit をマージします。
                mergeTransUnit(targetUnit, sourceUnit);
            }
        }

        // マージ結果を保存します。
        switch (BlancoXliffUtil.saveXliffInstance(xliffTarget, fileTarget
                .getParentFile(), bundleSymbolicName)) {
        case 1:
            // 基本的に、こちらには入らない見込み。
            if (fInput.getVerbose()) {
                System.out.println("xliff: merge: create: "
                        + bundleSymbolicName);
            }
            break;
        case 2:
            if (fInput.getVerbose()) {
                System.out.println("xliff: merge: update: "
                        + bundleSymbolicName);
            }
            fUpdateXliffCount++;
            break;
        case 0:
            if (fInput.getVerbose()) {
                System.out.println("xliff: merge: skip  : "
                        + bundleSymbolicName);
            }
            fSkipXliffCount++;
            break;
        }
    }

    /**
     * ファイルによるXLIFFの更新。
     * 
     * @param dirTarget
     * @param sourceFile
     * @throws IOException
     */
    private void processXliffWithFile(final File dirTarget,
            final File sourceFile) throws IOException {
        final BlancoXliff xliffSource = new BlancoXliffParser()
                .parse(sourceFile);

        for (int indexFile = 0; indexFile < xliffSource.getFileList().size(); indexFile++) {
            final BlancoXliffFile file = xliffSource.getFileList().get(
                    indexFile);
            for (int indexTransUnit = 0; indexTransUnit < file.getBody()
                    .getTransUnitList().size(); indexTransUnit++) {
                final BlancoXliffTransUnit sourceUnit = file.getBody()
                        .getTransUnitList().get(indexTransUnit);

                final String bundleSymbolicName = BlancoPropertiesUtil
                        .getHostFromBabelURI(sourceUnit.getId());

                // XLIFFロードのキャッシュ化。
                if (bundleSymbolicName.equals(fPrevBundleSymbolicName) == false) {
                    fXliffTarget = BlancoXliffUtil.getXliffInstance(dirTarget,
                            bundleSymbolicName);
                }

                boolean isUpdated = false;

                final Map<String, BlancoXliffTransUnit> mapTarget = new HashMap<String, BlancoXliffTransUnit>();
                buildMap(fXliffTarget, mapTarget);

                final BlancoXliffTransUnit targetTransUnit = mapTarget
                        .get(sourceUnit.getId());
                if (targetTransUnit != null) {
                    // この trans-unit をマージします。
                    isUpdated = mergeTransUnit(targetTransUnit, sourceUnit);
                } else {
                    fMissUnitCount++;
                    System.out.println("trace: マージ先に該当 Babel URI がありません。["
                            + sourceUnit.getId() + "]");
                }

                if (isUpdated) {
                    // マージ結果を保存します。
                    switch (BlancoXliffUtil.saveXliffInstance(fXliffTarget,
                            dirTarget, bundleSymbolicName)) {
                    case 1:
                        // 基本的に、こちらには入らない見込み。
                        if (fInput.getVerbose()) {
                            System.out.println("xliff: merge: create: "
                                    + bundleSymbolicName);
                        }
                        break;
                    case 2:
                        if (fInput.getVerbose()) {
                            System.out.println("xliff: merge: update: "
                                    + bundleSymbolicName);
                        }
                        fUpdateXliffCount++;
                        break;
                    case 0:
                        if (fInput.getVerbose()) {
                            System.out.println("xliff: merge: skip  : "
                                    + bundleSymbolicName);
                        }
                        fSkipXliffCount++;
                        break;
                    }
                }
            }
        }
    }

    private boolean mergeTransUnit(final BlancoXliffTransUnit targetUnit,
            final BlancoXliffTransUnit sourceUnit) {
        boolean isUpdate = false;

        fTotalUnitCount++;

        // state がもしあれば、これを転送
        if (targetUnit.getTarget() != null
                && sourceUnit.getTarget() != null
                && BlancoStringUtil.null2Blank(
                        sourceUnit.getTarget().getState()).length() > 0) {
            if (BlancoStringUtil.null2Blank(targetUnit.getTarget().getState())
                    .length() > 0) {
                // 既に state が存在する場合
                if (fInput.getSettargetoverwrite()) {
                    targetUnit.getTarget().setState(
                            sourceUnit.getTarget().getState());
                }
            } else {
                // 既に state が存在しない場合
                targetUnit.getTarget().setState(
                        sourceUnit.getTarget().getState());
            }
        }

        // noteがもしあれば、これを転送: 開始
        for (int indexNote = 0; indexNote < sourceUnit.getNoteList().size(); indexNote++) {
            final BlancoXliffNote note = sourceUnit.getNoteList()
                    .get(indexNote);
            if (BlancoStringUtil.null2Blank(note.getFrom()).equals(
                    "blanconlpackgenerator.version")
                    || BlancoStringUtil.null2Blank(note.getFrom()).equals(
                            "blanconlpackgenerator.action")) {
                continue;
            }

            // 同じ内容の note が targetに存在するかどうかチェック。
            boolean isEqualExist = false;
            for (int indexTargetNote = 0; indexTargetNote < targetUnit
                    .getNoteList().size(); indexTargetNote++) {
                final BlancoXliffNote targetNote = targetUnit.getNoteList()
                        .get(indexTargetNote);
                if (BlancoStringUtil.null2Blank(note.getFrom()).equals(
                        BlancoStringUtil.null2Blank(targetNote.getFrom())) == false) {
                    // from がそもそも異なるものは、違うものと判断。
                    continue;
                }
                if (BlancoStringUtil.null2Blank(note.getText()).equals(
                        BlancoStringUtil.null2Blank(targetNote.getText())) == false) {
                    // テキスト内容が異なれば、違うものと判断。
                    continue;
                }
                // 既に登録があります。
                isEqualExist = true;
            }

            if (isEqualExist == false) {
                targetUnit.getNoteList().add(note);
            }
        }
        // noteがもしあれば、これを転送: 終了

        if (targetUnit.getTranslate() == false) {
            // 翻訳不要。
            // TODO 注意：では ターゲットが translate="no" だと情報は転送されません。
            return false;
        }

        if (sourceUnit.getTranslate() == false) {
            // 必要に応じて、source の translate="no" を転送します。
            if (BlancoStringUtil.null2Blank(sourceUnit.getSource()).equals(
                    BlancoStringUtil.null2Blank(targetUnit.getSource()))) {
                // translate="no"で 且つ sourceが ソース・ターゲットで一致する場合には翻訳対象外。
                targetUnit.setTranslate(false);
                // 変更実施！
                return true;
            } else {
                System.out.println("trace: 文字列変わっています[" + sourceUnit.getId()
                        + "]");
            }
        }

        // それ以外は翻訳についての考慮が必要な trans-unit

        if (BlancoStringUtil.null2Blank(sourceUnit.getSource()).length() == 0
                || sourceUnit.getTarget() == null
                || BlancoStringUtil.null2Blank(
                        sourceUnit.getTarget().getTarget()).length() == 0) {
            // source 側が空の場合などには処理できないので、処理スキップします。
            return false;
        }

        // ここまできたら処理済！
        isUpdate = true;

        if (fInput.getCopyalttrans()) {
            // alt-transをコピーします。
            copyAltTrans(sourceUnit, targetUnit);
        }

        if (fInput.getSetalttrans()) {
            // alt-transをセットします。
            setAltTrans(fInput.getToolid(), targetUnit, sourceUnit.getSource(),
                    sourceUnit.getTarget().getTarget());
        }

        if (fInput.getSettarget()) {
            // 本命となる翻訳を実施します。
            translate(targetUnit, sourceUnit, fInput.getSettargetoverwrite());
        }

        return isUpdate;
    }

    /**
     * 翻訳を実施します。
     * 
     * @param targetUnit
     * @param sourceUnit
     */
    public static void translate(final BlancoXliffTransUnit targetUnit,
            final BlancoXliffTransUnit sourceUnit,
            final boolean isSettargetoverwrite) {
        final String sourceSourceText = BlancoStringUtil.null2Blank(sourceUnit
                .getSource());
        String sourceTargetText = "";
        if (sourceUnit.getTarget() != null) {
            sourceTargetText = BlancoStringUtil.null2Blank(sourceUnit
                    .getTarget().getTarget());
        }
        final String targetSourceText = BlancoStringUtil.null2Blank(targetUnit
                .getSource());
        String targetTargetText = "";
        if (targetUnit.getTarget() != null) {
            targetTargetText = BlancoStringUtil.null2Blank(targetUnit
                    .getTarget().getTarget());
        }

        // 対象trans-unitと source, target がどのくらい異なるのかチェック。
        final boolean isSourceEqual = sourceSourceText.equals(targetSourceText);
        final boolean isTargetEqual = sourceTargetText.equals(targetTargetText);
        if (isSourceEqual && isTargetEqual) {
            // equal
            // 翻訳元、翻訳後の両方が一致する状態。
        } else if (isSourceEqual && !isTargetEqual) {
            // change.o
            // 翻訳後のみ異なる状態。

            if (isSettargetoverwrite || targetUnit.getTarget() == null) {
                // 上書きモード または target が無い場合。
                // 入力した target をそのまま活用。
                targetUnit.setTarget(sourceUnit.getTarget());
            }
        } else if (!isSourceEqual && isTargetEqual) {
            // change.i
            // 翻訳元のみ異なる状態。
        } else {
            // change.io
            // 翻訳元、翻訳後の両方が異なる状態。
        }
    }

    private void buildMap(final BlancoXliff xliffInput,
            final Map<String, BlancoXliffTransUnit> mapOutput) {
        for (int indexFile = 0; indexFile < xliffInput.getFileList().size(); indexFile++) {
            final BlancoXliffFile file = xliffInput.getFileList()
                    .get(indexFile);
            for (int indexTransUnit = 0; indexTransUnit < file.getBody()
                    .getTransUnitList().size(); indexTransUnit++) {
                final BlancoXliffTransUnit unit = file.getBody()
                        .getTransUnitList().get(indexTransUnit);
                mapOutput.put(unit.getId(), unit);
            }
        }
    }

    /**
     * alt-trans をセットします。
     * 
     * @param transUnit
     * @param targetString
     * @return
     */
    private boolean setAltTrans(final String tool_id,
            final BlancoXliffTransUnit transUnit, final String sourceString,
            final String targetString) {
        boolean isModified = false;

        if (BlancoXliffUtil.getAltTrans(transUnit, tool_id, BlancoStringUtil
                .null2Blank(sourceString), BlancoStringUtil
                .null2Blank(targetString)) == null) {
            isModified = true;
            final BlancoXliffAltTrans altTransNew = new BlancoXliffAltTrans();
            transUnit.getAltTransList().add(altTransNew);
            altTransNew.setToolId(tool_id);
            altTransNew.setSource(sourceString);
            final BlancoXliffTarget target = new BlancoXliffTarget();
            altTransNew.setTarget(target);
            target.setTarget(targetString);

            final BlancoXliffNote note = new BlancoXliffNote();
            note.setFrom(BlancoXliffUtilConstants.PRODUCT_NAME_LOWER
                    + ".version");
            note.setText(BlancoXliffUtilConstants.VERSION);
            altTransNew.getNoteList().add(note);
        }

        return isModified;
    }

    /**
     * alt-trans をコピーします。
     * 
     * @param transUnit
     * @param targetString
     * @return
     */
    private boolean copyAltTrans(final BlancoXliffTransUnit sourceTransUnit,
            final BlancoXliffTransUnit targetTransUnit) {
        boolean isModified = false;

        for (int sourceIndex = 0; sourceIndex < sourceTransUnit
                .getAltTransList().size(); sourceIndex++) {
            final BlancoXliffAltTrans altTrans = sourceTransUnit
                    .getAltTransList().get(sourceIndex);

            // 重複は気にせず とりあえずセットします。
            targetTransUnit.getAltTransList().add(altTrans);
        }

        return isModified;
    }
}
