/*
 * blanco Framework
 * Copyright (C) 2004-2005 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.constants;

import java.io.File;
import java.io.IOException;

import javax.xml.transform.dom.DOMResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.valueobject.BlancoCgClass;
import blanco.cg.valueobject.BlancoCgField;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoJavaSourceUtil;
import blanco.commons.util.BlancoNameUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.commons.util.BlancoXmlUtil;
import blanco.constants.resourcebundle.BlancoConstantsResourceBundle;
import blanco.constants.valueobject.BlancoConstantsFieldStructure;
import blanco.constants.valueobject.BlancoConstantsStructure;

/**
 * 中間XMLファイルからJavaソースコードを自動生成するクラスです。
 * 
 * 定数定義書(XLS)からソースコードを自動生成するプロジェクトの一部です。
 * 
 * @author IGA Tosiki
 */
public class BlancoConstantsXml2JavaClass {
    /**
     * リソースバンドルへのアクセサクラスのオブジェクト。
     */
    private final BlancoConstantsResourceBundle fBundle = new BlancoConstantsResourceBundle();

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

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

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

    /**
     * 中間XMLファイルからJavaソースコードを自動生成します。
     * 
     * @param metaXmlSourceFile
     *            ValueObjectに関するメタ情報が含まれているXMLファイル
     * @param directoryTarget
     *            ソースコード生成先ディレクトリ
     * @throws IOException
     *             入出力例外が発生した場合
     */
    public void process(final File metaXmlSourceFile, final File directoryTarget)
            throws IOException {

        final DOMResult result = BlancoXmlUtil
                .transformFile2Dom(metaXmlSourceFile);

        final Node rootNode = result.getNode();
        if (rootNode instanceof Document) {
            // これが正常系。ドキュメントルートを取得
            final Document rootDocument = (Document) rootNode;
            final NodeList listSheet = rootDocument
                    .getElementsByTagName("sheet");
            final int sizeListSheet = listSheet.getLength();
            for (int index = 0; index < sizeListSheet; index++) {
                final Element elementCommon = BlancoXmlUtil.getElement(
                        listSheet.item(index), fBundle
                                .getMeta2xmlElementCommon());
                if (elementCommon == null) {
                    // commonが無い場合にはスキップします。
                    continue;
                }

                final String name = BlancoXmlUtil.getTextContent(elementCommon,
                        "name");
                if (name == null || name.trim().length() == 0) {
                    continue;
                }

                final Element elementList = BlancoXmlUtil.getElement(listSheet
                        .item(index), fBundle.getMeta2xmlElementList());

                expandSheet(elementCommon, elementList, directoryTarget);
            }
        }
    }

    /**
     * シートエレメントを展開します。
     * 
     * @param elementCommon
     *            現在処理しているCommonノード。
     * @param elementList
     *            現在処理しているListノード。
     * @param directoryTarget
     *            出力先フォルダ。
     */
    private void expandSheet(final Element elementCommon,
            final Element elementList, final File directoryTarget) {
        final BlancoConstantsStructure processStructure = new BlancoConstantsStructure();
        processStructure.setName(BlancoXmlUtil.getTextContent(elementCommon,
                "name"));
        processStructure.setPackage(BlancoXmlUtil.getTextContent(elementCommon,
                "package"));
        if (processStructure.getPackage() == null
                || processStructure.getPackage().trim().length() == 0) {
            throw new IllegalArgumentException(fBundle
                    .getXml2javaclassErr001(processStructure.getName()));
        }

        if (BlancoXmlUtil.getTextContent(elementCommon, "description") != null) {
            processStructure.setDescription(BlancoXmlUtil.getTextContent(
                    elementCommon, "description"));
        }
        if (BlancoXmlUtil.getTextContent(elementCommon, "fileDescription") != null) {
            processStructure.setFileDescription(BlancoXmlUtil.getTextContent(
                    elementCommon, "fileDescription"));
        }
        if (BlancoXmlUtil.getTextContent(elementCommon, "suffix") != null) {
            processStructure.setSuffix(BlancoXmlUtil.getTextContent(
                    elementCommon, "suffix"));
        }

        if (elementList != null) {
            final NodeList listField = elementList
                    .getElementsByTagName("field");
            for (int indexField = 0; indexField < listField.getLength(); indexField++) {
                final Node nodeField = listField.item(indexField);
                if (nodeField instanceof Element == false) {
                    continue;
                }
                final Element elementField = (Element) nodeField;
                BlancoConstantsFieldStructure field = new BlancoConstantsFieldStructure();
                field.setNo(BlancoXmlUtil.getTextContent(elementField, "no"));

                field.setName(BlancoXmlUtil
                        .getTextContent(elementField, "name"));
                if (field.getName() == null) {
                    continue;
                }

                field.setType(BlancoXmlUtil
                        .getTextContent(elementField, "type"));
                if (field.getType() == null) {
                    throw new IllegalArgumentException(fBundle
                            .getXml2javaclassErr002(processStructure.getName(),
                                    field.getName()));
                }

                field.setValue(BlancoXmlUtil.getTextContent(elementField,
                        "value"));
                if (field.getValue() == null) {
                    throw new IllegalArgumentException(fBundle
                            .getXml2javaclassErr003(processStructure.getName(),
                                    field.getName()));
                }

                field.setDescription(BlancoXmlUtil.getTextContent(elementField,
                        "description"));

                processStructure.getListField().add(field);
            }
        }

        expandJavaSource(processStructure, directoryTarget);
    }

    /**
     * 収集された情報を元に、Javaソースコードを出力します。
     * 
     * @param processStructure
     *            処理構造
     * @param directoryTarget
     *            出力先フォルダ。
     */
    private void expandJavaSource(
            final BlancoConstantsStructure processStructure,
            final File directoryTarget) {

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

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

        if (processStructure.getFileDescription() != null) {
            String lines = BlancoJavaSourceUtil
                    .escapeStringAsJavaDoc(processStructure
                            .getFileDescription());
            fCgSourceFile.getLangDoc().getDescriptionList().add(lines);
        }

        for (int indexField = 0; indexField < processStructure.getListField()
                .size(); indexField++) {
            BlancoConstantsFieldStructure fieldLook = (BlancoConstantsFieldStructure) processStructure
                    .getListField().get(indexField);
            final BlancoCgField field = fCgFactory.createField(fieldLook
                    .getName(), fieldLook.getType(), "");
            fCgClass.getFieldList().add(field);

            field.setAccess("public");
            field.setFinal(true);
            field.setStatic(true);

            if (fieldLook.getNo() != null) {
                field.getLangDoc().getDescriptionList().add(
                        fBundle.getXml2javaclassFieldNo(fieldLook.getNo()));
            }
            if (fieldLook.getDescription() != null) {
                field.getLangDoc().getDescriptionList().add(
                        fieldLook.getDescription());
            }

            // 型によってデフォルト値を書き分けます。
            // 対応しない型が与えられた場合には例外を発生します。
            if (fieldLook.getType().equals("java.lang.String")) {
                field.setDefault("\""
                        + BlancoJavaSourceUtil
                                .escapeStringAsJavaSource(fieldLook.getValue())
                        + "\"");
            } else if (fieldLook.getType().equals("boolean")
                    || fieldLook.getType().equals("short")
                    || fieldLook.getType().equals("int")
                    || fieldLook.getType().equals("long")) {
                field.setDefault(fieldLook.getValue());
            } else if (fieldLook.getType().equals("java.lang.Boolean")
                    || fieldLook.getType().equals("java.lang.Integer")
                    || fieldLook.getType().equals("java.lang.Long")) {
                field.setDefault("new "
                        + BlancoNameUtil.trimJavaPackage(fieldLook.getType())
                        + "(" + fieldLook.getValue() + ")");
            } else if (fieldLook.getType().equals("java.lang.Short")) {
                field.setDefault("new "
                        + BlancoNameUtil.trimJavaPackage(fieldLook.getType())
                        + "((short) " + fieldLook.getValue() + ")");
            } else if (fieldLook.getType().equals("java.math.BigDecimal")) {
                fCgSourceFile.getImportList().add("java.math.BigDecimal");
                field.setDefault("new BigDecimal(\"" + fieldLook.getValue()
                        + "\")");
            } else if (fieldLook.getType().equals("java.util.ArrayList")) {
                // ArrayListの場合には、与えられた文字をそのまま採用します。
                fCgSourceFile.getImportList().add("java.util.ArrayList");
                field.setDefault(fieldLook.getValue());
            } else {
                throw new IllegalArgumentException(fBundle
                        .getXml2javaclassErr004(processStructure.getName(),
                                fieldLook.getName(), fieldLook.getValue(),
                                fieldLook.getType()));
            }
        }

        BlancoCgTransformerFactory.getJavaSourceTransformer().transform(
                fCgSourceFile, fileBlancoMain);
    }
}
