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

import java.io.File;
import java.text.Format;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.valueobject.BlancoCgClass;
import blanco.cg.valueobject.BlancoCgField;
import blanco.cg.valueobject.BlancoCgMethod;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoBigDecimalUtil;
import blanco.commons.util.BlancoJavaSourceUtil;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.commons.util.BlancoStringUtil;
import blanco.resourcebundle.BlancoResourceBundleUtil;
import blanco.resourcebundle.resourcebundle.BlancoResourceBundleResourceBundle;
import blanco.resourcebundle.valueobject.BlancoResourceBundleBundleItemStructure;
import blanco.resourcebundle.valueobject.BlancoResourceBundleBundleResourceStringStructure;
import blanco.resourcebundle.valueobject.BlancoResourceBundleBundleStructure;

/**
 * 中間XMLファイルから プロパティファイルを生成する処理の内部処理です。
 * 
 * このソースコードはblancoResourceBundleの一部です。<br>
 * XMLのDOMエレメントを入力としてファイルを出力します。 <br>
 * 
 * 動作の前提条件:BlancoResourceBundleXmlValidatorクラスで事前にチェックされているものと想定します。
 * 
 * @author IGA Tosiki
 */
public class BlancoResourceBundleExpandResourceBundle {
    /**
     * リソースバンドルアクセサのインスタンス。
     */
    private final BlancoResourceBundleResourceBundle fBundle = new BlancoResourceBundleResourceBundle();

    /**
     * 内部的に利用するblancoCg用ファクトリ。
     */
    private BlancoCgObjectFactory fCgFactory = null;

    /**
     * 内部的に利用するblancoCg用ソースファイル情報。
     */
    private BlancoCgSourceFile fCgSourceFile = null;

    /**
     * 内部的に利用するblancoCg用クラス情報。
     */
    private BlancoCgClass fCgClass = null;

    /**
     * 指定の情報をソースコードに展開します。
     * 
     * ソースコード出力フラグによって、ソースコードを出力するかどうかが切り替わります。
     * 
     * @param argStructure
     *            構造。
     * @param argDirectoryTarget
     *            出力先のターゲットディレクトリ。
     * @param argIsFailOnMessageFormatError
     *            メッセージ文字列をMessageFormatによるパースを行った際の例外が発生したら処理を中断するかどうかのフラグを設定します。
     * @param argIsLog
     *            ログ出力を自動生成されるソースコードに含めるかどうか。
     */
    public void expand(final BlancoResourceBundleBundleStructure argStructure,
            final File argDirectoryTarget,
            final boolean argIsFailOnMessageFormatError, final boolean argIsLog) {
        // クラス名は名前変形させます。
        final String className = BlancoNameAdjuster.toClassName(argStructure
                .getName())
                + (argStructure.getSuffix() == null ? "" : argStructure
                        .getSuffix());

        final ArrayList listKnownLocale = new ArrayList();
        final HashMap mapBundle = new HashMap();

        // 従来と互換性を持たせるため、/mainサブフォルダに出力します。
        final File fileBlancoMain = new File(argDirectoryTarget
                .getAbsolutePath()
                + "/main");

        fCgFactory = BlancoCgObjectFactory.getInstance();
        fCgSourceFile = fCgFactory.createSourceFile(argStructure.getPackage(),
                "このソースコードは blanco Frameworkによって自動生成されています。");
        fCgSourceFile.getLangDoc().getDescriptionList().add(
                fBundle.getExpandresourceSrc001());

        fCgClass = fCgFactory.createClass(className, fBundle
                .getExpandresourceSrc011(argStructure.getName()
                        + (argStructure.getDescription() == null ? "" : "/"
                                + argStructure.getDescription())));
        fCgSourceFile.getClassList().add(fCgClass);

        // アクセスを設定します。
        fCgClass.setAccess(argStructure.getAccess());

        fCgClass.getLangDoc().getDescriptionList().add(
                fBundle.getExpandresourceSrc012());

        {
            final int sizeListLocale = argStructure.getListLocale().size();
            if (sizeListLocale > 0) {
                fCgClass.getLangDoc().getDescriptionList().add(
                        fBundle.getExpandresourceSrc013());
                fCgClass.getLangDoc().getDescriptionList().add("<UL>");
                for (int indexLocale = 0; indexLocale < sizeListLocale; indexLocale++) {
                    final String locale = (String) argStructure.getListLocale()
                            .get(indexLocale);
                    listKnownLocale.add(locale);
                    fCgClass.getLangDoc().getDescriptionList().add(
                            "<LI>" + locale);
                }
                fCgClass.getLangDoc().getDescriptionList().add("</UL>");
            }

            final BlancoCgField field1 = fCgFactory.createField(
                    "fResourceBundle", "java.util.ResourceBundle", fBundle
                            .getExpandresourceSrc014());
            fCgClass.getFieldList().add(field1);
            field1.getLangDoc().getDescriptionList().add(
                    fBundle.getExpandresourceSrc015());

            {
                final BlancoCgMethod method = fCgFactory.createMethod(
                        className, fBundle.getExpandresourceSrc021(className));
                fCgClass.getMethodList().add(method);
                method.setConstructor(true);
                method.getLangDoc().getDescriptionList()
                        .add(
                                fBundle.getExpandresourceSrc022(argStructure
                                        .getName()));

                // 常にインポートが必要です。
                fCgSourceFile.getImportList().add(
                        "java.util.MissingResourceException");

                final List listLine = method.getLineList();

                listLine.add("try {");
                listLine.add("fResourceBundle = ResourceBundle.getBundle(\""
                        + argStructure.getName() + "\");");
                listLine.add("} catch (MissingResourceException ex) {");
                if (argIsLog) {
                    listLine.add("final String message = \""
                            + fBundle.getExpandresourceSrc023(argStructure
                                    .getName()) + "\" + ex.toString();");
                    fCgSourceFile.getImportList().add(
                            "java.util.logging.Logger");
                    listLine.add("Logger.getLogger(\""
                            + argStructure.getPackage() + "\").fine(message);");
                }
                listLine.add("}");
            }

            {
                final BlancoCgMethod method = fCgFactory.createMethod(
                        className, fBundle.getExpandresourceSrc031(className));
                fCgClass.getMethodList().add(method);
                method.setConstructor(true);
                method.getLangDoc().getDescriptionList()
                        .add(
                                fBundle.getExpandresourceSrc032(argStructure
                                        .getName()));
                method.getParameterList().add(
                        fCgFactory.createParameter("locale",
                                "java.util.Locale", fBundle
                                        .getExpandresourceSrc033()));

                final List listLine = method.getLineList();

                listLine.add("try {");
                listLine.add("fResourceBundle = ResourceBundle.getBundle(\""
                        + argStructure.getName() + "\", locale);");
                listLine.add("} catch (MissingResourceException ex) {");
                if (argIsLog) {
                    listLine.add("final String message = \""
                            + fBundle.getExpandresourceSrc034(argStructure
                                    .getName()) + "\" + ex.toString();");
                    fCgSourceFile.getImportList().add(
                            "java.util.logging.Logger");
                    listLine.add("Logger.getLogger(\""
                            + argStructure.getPackage() + "\").fine(message);");
                }
                listLine.add("}");
            }

            {
                final BlancoCgMethod method = fCgFactory.createMethod(
                        className, fBundle.getExpandresourceSrc041(className));
                fCgClass.getMethodList().add(method);
                method.setConstructor(true);
                method.getLangDoc().getDescriptionList()
                        .add(
                                fBundle.getExpandresourceSrc042(argStructure
                                        .getName()));

                method.getParameterList().add(
                        fCgFactory.createParameter("locale",
                                "java.util.Locale", fBundle
                                        .getExpandresourceSrc043()));
                method.getParameterList().add(
                        fCgFactory.createParameter("loader",
                                "java.lang.ClassLoader", fBundle
                                        .getExpandresourceSrc044()));

                final List listLine = method.getLineList();

                listLine.add("try {");
                listLine.add("fResourceBundle = ResourceBundle.getBundle(\""
                        + argStructure.getName() + "\", locale, loader);");
                listLine.add("} catch (MissingResourceException ex) {");
                if (argIsLog) {
                    listLine.add("final String message = \""
                            + fBundle.getExpandresourceSrc045(argStructure
                                    .getName()) + "\" + ex.toString();");
                    fCgSourceFile.getImportList().add(
                            "java.util.logging.Logger");
                    listLine.add("Logger.getLogger(\""
                            + argStructure.getPackage() + "\").fine(message);");
                }
                listLine.add("}");
            }

            {
                // getResourceBundleメソッド
                final BlancoCgMethod method = fCgFactory.createMethod(
                        "getResourceBundle", "内部的に保持しているリソースバンドルオブジェクトを取得します。");
                fCgClass.getMethodList().add(method);

                method.setReturn(fCgFactory
                        .createReturn("java.util.ResourceBundle",
                                "内部的に保持しているリソースバンドルオブジェクト。"));

                final List listLine = method.getLineList();

                listLine.add("return fResourceBundle;");
            }

            final int sizeListResource = argStructure.getItemList().size();
            for (int indexResource = 0; indexResource < sizeListResource; indexResource++) {
                final BlancoResourceBundleBundleItemStructure itemStructure = (BlancoResourceBundleBundleItemStructure) argStructure
                        .getItemList().get(indexResource);

                final BlancoCgMethod methodGet = fCgFactory.createMethod("get"
                        + BlancoNameAdjuster
                                .toClassName(itemStructure.getKey()), "bundle["
                        + argStructure.getName() + "], key["
                        + itemStructure.getKey() + "]");
                fCgClass.getMethodList().add(methodGet);

                final HashMap mapProcessedLocale = new HashMap();
                for (int indexResourceString = 0; indexResourceString < itemStructure
                        .getResourceStringList().size(); indexResourceString++) {

                    final BlancoResourceBundleBundleResourceStringStructure resourceStringStructure = (BlancoResourceBundleBundleResourceStringStructure) itemStructure
                            .getResourceStringList().get(indexResourceString);

                    methodGet
                            .getLangDoc()
                            .getDescriptionList()
                            .add(
                                    "["
                                            + BlancoJavaSourceUtil
                                                    .escapeStringAsJavaDoc(BlancoStringUtil
                                                            .null2Blank(resourceStringStructure
                                                                    .getResourceString()))
                                            + "] ("
                                            + resourceStringStructure
                                                    .getLocale() + ")<br>");

                    // 処理済みのロケールであることを記憶します。
                    mapProcessedLocale.put(resourceStringStructure.getLocale(),
                            resourceStringStructure.getLocale());
                    if (mapBundle.get(itemStructure.getKey()) == null) {
                        mapBundle.put(itemStructure.getKey(),
                                resourceStringStructure.getResourceString());
                    }

                    // フォーマット例外は発生することは想定しません。なぜなら事前チェックにより既に解決されているからです。
                    final Format[] formatList = BlancoResourceBundleUtil
                            .getFormatsByArgumentIndex(BlancoStringUtil
                                    .null2Blank(resourceStringStructure
                                            .getResourceString()),
                                    argIsFailOnMessageFormatError);
                    for (int indexFormat = 0; indexFormat < formatList.length; indexFormat++) {
                        String strArgType = "java.lang.String";
                        if (formatList[indexFormat] == null) {
                            // フォーマットなし:
                            // java.lang.Stringへのマップが妥当
                            strArgType = "java.lang.String";
                        } else if (formatList[indexFormat] instanceof java.text.NumberFormat) {
                            // java.math.BigDecimalへのマップが妥当
                            strArgType = "java.math.BigDecimal";
                        } else if (formatList[indexFormat] instanceof java.text.DateFormat) {
                            // java.util.Dateへのマップが妥当
                            strArgType = "java.util.Date";
                        } else if (formatList[indexFormat] instanceof java.text.ChoiceFormat) {
                            // intへのマップが妥当
                            strArgType = "int";
                        } else {
                            strArgType = "java.lang.String";
                        }
                        if (indexResourceString == 0) {
                            // 初回のみメソッド引数を生成
                            methodGet
                                    .getParameterList()
                                    .add(
                                            fCgFactory
                                                    .createParameter(
                                                            "arg" + indexFormat,
                                                            strArgType,
                                                            fBundle
                                                                    .getExpandresourceSrc101(
                                                                            BlancoBigDecimalUtil
                                                                                    .toBigDecimal(indexFormat),
                                                                            strArgType)));
                        }
                    }
                    methodGet.setReturn(fCgFactory.createReturn(
                            "java.lang.String", fBundle
                                    .getExpandresourceSrc102(itemStructure
                                            .getKey())));
                }

                // ロケールが全て揃っているかどうかのチェックを行います。
                for (int indexCheck = 0; indexCheck < listKnownLocale.size(); indexCheck++) {
                    final String localeCheck = (String) listKnownLocale
                            .get(indexCheck);
                    final Object objCheck = mapProcessedLocale.get(localeCheck);
                    if (objCheck == null) {
                        // そろっていない場合であっても、JavaDocへの警告出力にとどめます。
                        methodGet.getLangDoc().getDescriptionList().add(
                                fBundle.getExpandresourceSrc103(localeCheck));
                    }
                }

                final String resourceString = (String) mapBundle
                        .get(itemStructure.getKey());

                final List listLine = methodGet.getLineList();

                listLine.add("// " + fBundle.getExpandresourceSrc104());
                listLine.add("String strFormat = \""
                        + BlancoJavaSourceUtil
                                .escapeStringAsJavaSource(BlancoStringUtil
                                        .null2Blank(resourceString)) + "\";");
                listLine.add("try {");
                listLine.add("if (fResourceBundle != null) {");
                listLine.add("strFormat = fResourceBundle.getString(\""
                        + BlancoJavaSourceUtil.escapeStringAsJavaSource(fBundle
                                .getKeyPrefix()
                                + itemStructure.getKey()) + "\");");
                listLine.add("}");
                listLine.add("} catch (MissingResourceException ex) {");
                if (argIsLog) {
                    listLine.add("final String message = \""
                            + fBundle.getExpandresourceSrc105(argStructure
                                    .getName(), fBundle.getKeyPrefix()
                                    + itemStructure.getKey())
                            + "\" + ex.toString();");
                    fCgSourceFile.getImportList().add(
                            "java.util.logging.Logger");
                    listLine.add("Logger.getLogger(\""
                            + argStructure.getPackage() + "\").fine(message);");
                }
                listLine.add("}");

                final Format[] formatList = BlancoResourceBundleUtil
                        .getFormatsByArgumentIndex(BlancoStringUtil
                                .null2Blank(resourceString),
                                argIsFailOnMessageFormatError);
                if (formatList.length > 0) {
                    String strArgForFormat = "";
                    for (int index = 0; index < formatList.length; index++) {
                        if (index != 0) {
                            strArgForFormat += ", ";
                        }
                        strArgForFormat += ("arg" + index);
                    }

                    fCgSourceFile.getImportList()
                            .add("java.text.MessageFormat");

                    listLine
                            .add("final MessageFormat messageFormat = new MessageFormat(strFormat);");
                    listLine
                            .add("final StringBuffer strbuf = new StringBuffer();");
                    listLine.add("// " + fBundle.getExpandresourceSrc106());
                    listLine.add("messageFormat.format(new Object[] {"
                            + strArgForFormat + "}, strbuf, null);");

                    listLine.add("return strbuf.toString();");
                } else {
                    listLine.add("// " + fBundle.getExpandresourceSrc107());
                    listLine.add("return strFormat;");
                }

            }
        }

        // ソースコードを実際に生成します。
        BlancoCgTransformerFactory.getJavaSourceTransformer().transform(
                fCgSourceFile, fileBlancoMain);
    }
}