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

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.BlancoCgSupportedLang;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.util.BlancoCgLineUtil;
import blanco.cg.util.BlancoCgSourceUtil;
import blanco.cg.valueobject.BlancoCgClass;
import blanco.cg.valueobject.BlancoCgField;
import blanco.cg.valueobject.BlancoCgMethod;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.commons.util.BlancoStringUtil;
import blanco.hashtable.resourcebundle.BlancoHashtableResourceBundle;
import blanco.hashtable.valueobject.BlancoHashtableInputStructure;
import blanco.hashtable.valueobject.BlancoHashtableStructure;
import blanco.xml.bind.BlancoXmlBindingUtil;
import blanco.xml.bind.BlancoXmlUnmarshaller;
import blanco.xml.bind.valueobject.BlancoXmlDocument;
import blanco.xml.bind.valueobject.BlancoXmlElement;

/**
 * 「ハッシュテーブル定義書」Excel様式からHashMapを処理するクラス・ソースコードを生成。
 * 
 * このクラスは、中間XMLファイルからソースコードを自動生成する機能を担います。
 * 
 * @author IGA Tosiki
 */
public class BlancoHashtableXml2SourceFile {
    /**
     * このプロダクトのリソースバンドルへのアクセスオブジェクト。
     */
    private final BlancoHashtableResourceBundle fBundle = new BlancoHashtableResourceBundle();

    /**
     * 出力対象となるプログラミング言語。
     */
    private int fTargetLang = BlancoCgSupportedLang.NOT_DEFINED;

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

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

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

    /**
     * 中間XMLファイルからソースコードを自動生成します。
     * 
     * @param argMetaXmlSourceFile
     *            メタ情報が含まれているXMLファイル。
     * @param argTargetLang
     *            出力対象プログラミング言語。
     * @param argDirectoryTarget
     *            ソースコード生成先ディレクトリ (/mainを除く部分を指定します)。
     * @throws IOException
     *             入出力例外が発生した場合。
     */
    public void process(final File argMetaXmlSourceFile,
            final String argTargetLang, final File argDirectoryTarget)
            throws IOException {

        fTargetLang = new BlancoCgSupportedLang().convertToInt(argTargetLang);
        switch (fTargetLang) {
        case BlancoCgSupportedLang.JAVA:
            break;
        default:
            throw new IllegalArgumentException(fBundle
                    .getXml2sourceFileErr002(argTargetLang));
        }

        // メタ情報を解析してバリューオブジェクトのツリーを取得します。
        final BlancoXmlDocument documentMeta = new BlancoXmlUnmarshaller()
                .unmarshal(argMetaXmlSourceFile);

        // ルートエレメントを取得します。
        final BlancoXmlElement elementRoot = BlancoXmlBindingUtil
                .getDocumentElement(documentMeta);
        if (elementRoot == null) {
            // ルートエレメントが無い場合には処理中断します。
            return;
        }

        // sheet(Excelシート)のリストを取得します。
        final List listSheet = BlancoXmlBindingUtil.getElementsByTagName(
                elementRoot, "sheet");
        final int sizeListSheet = listSheet.size();
        for (int index = 0; index < sizeListSheet; index++) {
            // おのおののシートを処理します。
            final BlancoXmlElement elementSheet = (BlancoXmlElement) listSheet
                    .get(index);

            // 共通情報を取得します。
            final BlancoXmlElement elementCommon = BlancoXmlBindingUtil
                    .getElement(elementSheet, fBundle
                            .getMeta2xmlElementCommon());
            if (elementCommon == null) {
                // commonが無い場合には、このシートの処理をスキップします。
                continue;
            }

            final String name = BlancoXmlBindingUtil.getTextContent(
                    elementCommon, "name");
            if (BlancoStringUtil.null2Blank(name).trim().length() == 0) {
                // nameが空の場合には処理をスキップします。
                continue;
            }

            // 一覧情報を取得します。
            final BlancoXmlElement elementList = BlancoXmlBindingUtil
                    .getElement(elementSheet, fBundle.getMeta2xmlElementList());

            // シートから詳細な情報を取得します。
            final BlancoHashtableStructure processStructure = parseSheet(
                    elementCommon, elementList, argDirectoryTarget);

            if (processStructure != null) {
                // メタ情報の解析結果をもとにソースコード自動生成を実行します。
                expandSourceFile(processStructure, argDirectoryTarget);
            }
        }
    }

    /**
     * sheetエレメントを展開します。
     * 
     * @param argElementCommon
     *            現在処理しているCommonノード。
     * @param argElementList
     *            現在処理しているListノード。
     * @param argDirectoryTarget
     *            ソースコードの出力先フォルダ。
     * @return 収集されたメタ情報構造データ。
     */
    private BlancoHashtableStructure parseSheet(
            final BlancoXmlElement argElementCommon,
            final BlancoXmlElement argElementList, final File argDirectoryTarget) {

        final BlancoHashtableStructure processStructure = new BlancoHashtableStructure();
        processStructure.setName(BlancoXmlBindingUtil.getTextContent(
                argElementCommon, "name"));
        processStructure.setPackage(BlancoXmlBindingUtil.getTextContent(
                argElementCommon, "package"));

        if (BlancoStringUtil.null2Blank(processStructure.getPackage()).trim()
                .length() == 0) {
            throw new IllegalArgumentException(fBundle
                    .getXml2sourceFileErr001(processStructure.getName()));
        }

        if (BlancoXmlBindingUtil
                .getTextContent(argElementCommon, "description") != null) {
            processStructure.setDescription(BlancoXmlBindingUtil
                    .getTextContent(argElementCommon, "description"));
        }

        if (BlancoXmlBindingUtil.getTextContent(argElementCommon, "suffix") != null) {
            processStructure.setSuffix(BlancoXmlBindingUtil.getTextContent(
                    argElementCommon, "suffix"));
        }

        if (argElementList == null) {
            return null;
        }

        // ハッシュテーブル定義・入力の内容を取得します。
        final List listField = BlancoXmlBindingUtil.getElementsByTagName(
                argElementList, "field");
        for (int indexField = 0; indexField < listField.size(); indexField++) {
            final Object nodeField = listField.get(indexField);

            if (nodeField instanceof BlancoXmlElement == false) {
                continue;
            }

            final BlancoXmlElement elementField = (BlancoXmlElement) nodeField;
            BlancoHashtableInputStructure input = new BlancoHashtableInputStructure();
            input
                    .setNo(BlancoXmlBindingUtil.getTextContent(elementField,
                            "no"));

            input
                    .setId(BlancoXmlBindingUtil.getTextContent(elementField,
                            "id"));
            if (BlancoStringUtil.null2Blank(input.getId()).length() == 0) {
                continue;
            }
            //
            // field.setValue(BlancoXmlBindingUtil.getTextContent(elementField,
            // "value"));
            // if (BlancoStringUtil.null2Blank(field.getValue()).length() == 0)
            // {
            // // ここで警告。
            // continue;
            // }
            //
            // // 既に同じ内容が登録されていないかどうかのチェック。
            // for (int indexPast = 0; indexPast <
            // processStructure.getListField()
            // .size(); indexPast++) {
            // final BlancoMessageFieldStructure fieldPast =
            // (BlancoMessageFieldStructure) processStructure
            // .getListField().get(indexPast);
            // if (fieldPast.getKey().equals(field.getKey())) {
            // throw new IllegalArgumentException(fBundle
            // .getXml2sourceFileErr003(
            // processStructure.getName(), field.getKey()));
            // }
            // }

            processStructure.getInput().add(input);
        }

        return processStructure;
    }

    /**
     * 収集された情報を元に、ソースコードを自動生成します。
     * 
     * @param argProcessStructure
     *            メタファイルから収集できた処理構造データ。
     * @param argDirectoryTarget
     *            ソースコードの出力先フォルダ。
     */
    private void expandSourceFile(
            final BlancoHashtableStructure argProcessStructure,
            final File argDirectoryTarget) {

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

        fCgFactory = BlancoCgObjectFactory.getInstance();
        fCgSourceFile = fCgFactory.createSourceFile(argProcessStructure
                .getPackage(), "このソースコードは blanco Frameworkによって自動生成されています。");
        fCgClass = fCgFactory.createClass(argProcessStructure.getName()
                + BlancoStringUtil.null2Blank(argProcessStructure.getSuffix()),
                BlancoStringUtil.null2Blank(argProcessStructure
                        .getDescription()));
        fCgSourceFile.getClassList().add(fCgClass);

        expandField(argProcessStructure);
        expandMethodGet(argProcessStructure);
        expandMethodGetMap(argProcessStructure);

        BlancoCgTransformerFactory.getSourceTransformer(fTargetLang).transform(
                fCgSourceFile, fileBlancoMain);
    }

    /**
     * フィールドを展開します。
     * 
     * @param argProcessStructure
     *            メタファイルから収集できた処理構造データ。
     */
    private void expandField(final BlancoHashtableStructure argProcessStructure) {

        final BlancoCgField cgField = fCgFactory.createField("fMap",
                "java.util.HashMap", "内部的に保持しているマップオブジェクト。");
        fCgClass.getFieldList().add(cgField);
        cgField.setAccess("protected");
        cgField.setDefault("new HashMap()");

    }

    /**
     * get メソッドを展開します。
     * 
     * @param argProcessStructure
     *            メタファイルから収集できた処理構造データ。
     */
    private void expandMethodGet(
            final BlancoHashtableStructure argProcessStructure) {

        BlancoHashtableInputStructure input = (BlancoHashtableInputStructure) argProcessStructure
                .getInput().get(0);

        final String methodName = "get";

        final BlancoCgMethod cgMethod = fCgFactory.createMethod(
                getMethodName(methodName), BlancoCgSourceUtil
                        .escapeStringAsLangDoc(fTargetLang, argProcessStructure
                                .getName()
                                + "に対応する値を取得します。"));
        fCgClass.getMethodList().add(cgMethod);

        ArrayList parameterList = new ArrayList();
        parameterList.add(fCgFactory.createParameter(input.getId(),
                getTypeString(), input.getName(), true));
        cgMethod.setParameterList(parameterList);
        cgMethod
                .setReturn(fCgFactory.createReturn(getTypeString(), "キーに対応する値"));

        final List lineList = cgMethod.getLineList();

        lineList.add("final Object objFound = fMap.get(" + input.getId() + ")"
                + BlancoCgLineUtil.getTerminator(fTargetLang));

        lineList.add(BlancoCgLineUtil.getIfBegin(fTargetLang,
                "objFound == null"));

        lineList.add("throw new IllegalArgumentException(\"指定されたキー(\" + "
                + input.getId() + " + \")に対応する値は存在しません。\")"
                + BlancoCgLineUtil.getTerminator(fTargetLang));
        lineList.add(BlancoCgLineUtil.getIfEnd(fTargetLang));

        lineList.add("");
        lineList.add("return (String) objFound"
                + BlancoCgLineUtil.getTerminator(fTargetLang));
    }

    /**
     * getMap メソッドを展開します。
     * 
     * @param argProcessStructure
     *            メタファイルから収集できた処理構造データ。
     */
    private void expandMethodGetMap(
            final BlancoHashtableStructure argProcessStructure) {

        final String methodName = "getMap";

        final BlancoCgMethod cgMethod = fCgFactory.createMethod(
                getMethodName(methodName), BlancoCgSourceUtil
                        .escapeStringAsLangDoc(fTargetLang,
                                "内部的に保持しているマップを暴露します。"));
        cgMethod.getLangDoc().getDescriptionList().add("マップを構築する際などに利用してください。");
        cgMethod.setReturn(fCgFactory.createReturn("java.util.HashMap",
                "内部に保持しているマップ"));
        fCgClass.getMethodList().add(cgMethod);

        final List lineList = cgMethod.getLineList();
        lineList.add("return fMap;");
    }

    /**
     * プログラミング言語処理系に合った String 型の名称を取得します。
     * 
     * 型の読み替え。
     * 
     * @return
     */
    private final String getTypeString() {
        switch (fTargetLang) {
        case BlancoCgSupportedLang.JAVA:
        default:
            return "java.lang.String";
        }
    }

    /**
     * プログラミング言語処理系に合った メソッドの名前変形をおこないます。
     * 
     * メソッド名の読み替え。
     * 
     * @return
     */
    private final String getMethodName(final String argMethodName) {
        switch (fTargetLang) {
        case BlancoCgSupportedLang.JAVA:
        default:
            return argMethodName;
        case BlancoCgSupportedLang.CS:
        case BlancoCgSupportedLang.VB:
            return BlancoNameAdjuster.toUpperCaseTitle(argMethodName);
        case BlancoCgSupportedLang.JS:
            return argMethodName;
        }
    }
}
