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

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

import blanco.commons.util.BlancoStringUtil;
import blanco.jsf.message.BlancoJsfMessage;
import blanco.jsf.valueobject.BlancoJsfProcessBeanItemStructure;
import blanco.jsf.valueobject.BlancoJsfProcessSqlItemStructure;
import blanco.jsf.valueobject.BlancoJsfProcessStructure;
import blanco.jsf.valueobject.BlancoJsfStructure;
import blanco.xml.bind.BlancoXmlBindingUtil;
import blanco.xml.bind.BlancoXmlUnmarshaller;
import blanco.xml.bind.valueobject.BlancoXmlDocument;
import blanco.xml.bind.valueobject.BlancoXmlElement;

/**
 * 定義書の XML から情報を抽出します。
 * 
 * @author IGA Tosiki
 */
public class BlancoJsfXmlParser {
    /**
     * メッセージクラス。
     */
    protected final BlancoJsfMessage fMsg = new BlancoJsfMessage();

    /**
     * 中間XMLファイルのXMLドキュメントをパースして、情報の配列を取得します。
     * 
     * @param argMetaXmlSourceFile
     *            中間XMLファイル。
     * @return パースの結果得られた情報の配列。
     */
    public List<BlancoJsfStructure> parse(final File argMetaXmlSourceFile) {
        final BlancoXmlDocument documentMeta = new BlancoXmlUnmarshaller()
                .unmarshal(argMetaXmlSourceFile);
        if (documentMeta == null) {
            return null;
        }

        return parse(documentMeta);
    }

    /**
     * 中間XMLファイル形式のXMLドキュメントをパースして、バリューオブジェクト情報の配列を取得します。
     * 
     * @param argXmlDocument
     *            中間XMLファイルのXMLドキュメント。
     * @return パースの結果得られたバリューオブジェクト情報の配列。
     */
    public List<BlancoJsfStructure> parse(final BlancoXmlDocument argXmlDocument) {
        final List<BlancoJsfStructure> structureList = new ArrayList<BlancoJsfStructure>();

        final List<BlancoJsfProcessStructure> processStructureList = new ArrayList<BlancoJsfProcessStructure>();

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

        // sheet(Excelシート)のリストを取得します。
        final List<blanco.xml.bind.valueobject.BlancoXmlElement> listSheet = BlancoXmlBindingUtil
                .getElementsByTagName(elementRoot, "sheet");

        for (int index = 0; index < listSheet.size(); index++) {
            final BlancoXmlElement elementSheet = listSheet.get(index);

            final List<blanco.xml.bind.valueobject.BlancoXmlElement> listCommon = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSheet, "blancojsf-common");
            if (listCommon.size() == 0) {
                // commonが無い場合にはスキップします。
                continue;
            }

            // 最初のアイテムのみ処理しています。
            final BlancoXmlElement elementCommon = listCommon.get(0);
            final String name = BlancoXmlBindingUtil.getTextContent(
                    elementCommon, "name");
            if (BlancoStringUtil.null2Blank(name).trim().length() == 0) {
                // nameが空の場合には処理をスキップします。
                continue;
            }

            final BlancoJsfStructure structure = parseSheet(elementSheet,
                    elementCommon);
            if (structure != null) {
                // 得られた情報を記憶します。
                structureList.add(structure);
            }
        }

        for (int index = 0; index < listSheet.size(); index++) {
            final BlancoXmlElement elementSheet = listSheet.get(index);

            final List<blanco.xml.bind.valueobject.BlancoXmlElement> listAction = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSheet,
                            "blancojsfprocess-initialize");
            if (listAction.size() == 0) {
                // commonが無い場合にはスキップします。
                continue;
            }

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

                final BlancoJsfProcessStructure processStructure = parseProcessSheet(
                        elementSheet, elementCommon, "initialize");
                if (processStructure != null) {
                    // 得られた情報を記憶します。
                    processStructureList.add(processStructure);
                }
            }
        }

        for (int index = 0; index < listSheet.size(); index++) {
            final BlancoXmlElement elementSheet = listSheet.get(index);

            final List<blanco.xml.bind.valueobject.BlancoXmlElement> listAction = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSheet, "blancojsfprocess-view");
            if (listAction.size() == 0) {
                // commonが無い場合にはスキップします。
                continue;
            }

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

                final BlancoJsfProcessStructure processStructure = parseProcessSheet(
                        elementSheet, elementCommon, "view");
                if (processStructure != null) {
                    // 得られた情報を記憶します。
                    processStructureList.add(processStructure);
                }
            }
        }

        for (int index = 0; index < listSheet.size(); index++) {
            final BlancoXmlElement elementSheet = listSheet.get(index);

            final List<blanco.xml.bind.valueobject.BlancoXmlElement> listAction = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSheet,
                            "blancojsfprocess-action");
            if (listAction.size() == 0) {
                // commonが無い場合にはスキップします。
                continue;
            }

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

                final BlancoJsfProcessStructure processStructure = parseProcessSheet(
                        elementSheet, elementCommon, "action");
                if (processStructure != null) {
                    // 得られた情報を記憶します。
                    processStructureList.add(processStructure);
                }
            }
        }

        for (int index = 0; index < listSheet.size(); index++) {
            final BlancoXmlElement elementSheet = listSheet.get(index);

            final List<blanco.xml.bind.valueobject.BlancoXmlElement> listAction = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSheet,
                            "blancojsfprocess-download");

            if (listAction.size() == 0) {
                // commonが無い場合にはスキップします。
                continue;
            }

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

                final BlancoJsfProcessStructure processStructure = parseProcessSheet(
                        elementSheet, elementCommon, "download");
                if (processStructure != null) {
                    // 得られた情報を記憶します。
                    processStructureList.add(processStructure);
                }
            }
        }

        for (int index = 0; index < listSheet.size(); index++) {
            final BlancoXmlElement elementSheet = listSheet.get(index);

            final List<blanco.xml.bind.valueobject.BlancoXmlElement> listAction = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSheet,
                            "blancojsfprocess-pfAutoComplete");

            if (listAction.size() == 0) {
                // commonが無い場合にはスキップします。
                continue;
            }

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

                final BlancoJsfProcessStructure processStructure = parseProcessSheet(
                        elementSheet, elementCommon, "pfAutoComplete");
                if (processStructure != null) {
                    // 得られた情報を記憶します。
                    processStructureList.add(processStructure);
                }
            }
        }

        // 得られた JSF処理定義を JSF定義に割り当て。
        // ★ポイント: 同じブック内に記述されていることが前提条件。
        for (BlancoJsfStructure structure : structureList) {
            for (BlancoJsfProcessStructure processStructure : processStructureList) {
                if (structure.getName().equals(processStructure.getName())) {
                    structure.getProcessList().add(processStructure);
                }
            }
        }
        // TODO どこにもひもづかなかった JSF 処理定義書があればエラーとして通知する機能が必要。

        return structureList;
    }

    /**
     * 中間XMLファイル形式の「sheet」XMLエレメントをパースして、バリューオブジェクト情報を取得します。
     * 
     * @param argElementSheet
     *            中間XMLファイルの「sheet」XMLエレメント。
     * @return パースの結果得られたバリューオブジェクト情報。「name」が見つからなかった場合には nullを戻します。
     */
    public BlancoJsfStructure parseSheet(
            final BlancoXmlElement argElementSheet,
            final BlancoXmlElement argElementCommon) {
        final BlancoJsfStructure structure = new BlancoJsfStructure();
        // 入力パラメータ情報を取得します。

        // シートから詳細な情報を取得します。
        structure.setName(BlancoXmlBindingUtil.getTextContent(argElementCommon,
                "name"));
        structure.setPackage(BlancoXmlBindingUtil.getTextContent(
                argElementCommon, "package"));

        if (BlancoStringUtil.null2Blank(structure.getPackage()).trim().length() == 0) {
            throw new IllegalArgumentException(fMsg.getMbjfi001(structure
                    .getName()));
        }

        if (BlancoXmlBindingUtil.getTextContent(argElementCommon, "bean") != null) {
            structure.setBean(BlancoXmlBindingUtil.getTextContent(
                    argElementCommon, "bean"));
        }

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

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

        return structure;
    }

    /**
     * 中間XMLファイル形式の「sheet」XMLエレメントをパースして、バリューオブジェクト情報を取得します。
     * 
     * @param argElementSheet
     *            中間XMLファイルの「sheet」XMLエレメント。
     * @return パースの結果得られたバリューオブジェクト情報。「name」が見つからなかった場合には nullを戻します。
     */
    public BlancoJsfProcessStructure parseProcessSheet(
            final BlancoXmlElement argElementSheet,
            final BlancoXmlElement argElementCommon, final String type) {
        final BlancoJsfProcessStructure processStructure = new BlancoJsfProcessStructure();
        processStructure.setType(type);

        // 入力パラメータ情報を取得します。

        // シートから詳細な情報を取得します。
        processStructure.setName(BlancoXmlBindingUtil.getTextContent(
                argElementCommon, "name"));
        processStructure.setMethod(BlancoXmlBindingUtil.getTextContent(
                argElementCommon, "method"));

        if ("action".equals(type) || "download".equals(type)
                || "pfAutoComplete".equals(type)) {
            // アクション名が予約語と衝突していないことを確認。
            if (BlancoStringUtil.null2Blank(processStructure.getMethod())
                    .trim().length() == 0) {
                throw new IllegalArgumentException(fMsg
                        .getMbjfi001(processStructure.getMethod()));
            }
            if ("initialize".equals(processStructure.getMethod())) {
                throw new IllegalArgumentException(fMsg
                        .getMbjfi003(processStructure.getMethod()));
            } else if ("view".equals(processStructure.getMethod())) {
                throw new IllegalArgumentException(fMsg
                        .getMbjfi004(processStructure.getMethod()));
            }
        } else {
            processStructure.setMethod(processStructure.getType());
        }

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

        if (BlancoXmlBindingUtil.getTextContent(argElementCommon, "forward") != null) {
            processStructure.setForward("true".equals(BlancoXmlBindingUtil
                    .getTextContent(argElementCommon, "forward")));
        }

        if (BlancoXmlBindingUtil
                .getTextContent(argElementCommon, "actionevent") != null) {
            processStructure.setActionEvent("true".equals(BlancoXmlBindingUtil
                    .getTextContent(argElementCommon, "actionevent")));
        }

        if (BlancoXmlBindingUtil.getTextContent(argElementCommon,
                "facesmessage") != null) {
            processStructure.setFacesMessage("true".equals(BlancoXmlBindingUtil
                    .getTextContent(argElementCommon, "facesmessage")));
        }

        if (BlancoXmlBindingUtil.getTextContent(argElementCommon,
                "facescontext") != null) {
            processStructure.setFacesContext("true".equals(BlancoXmlBindingUtil
                    .getTextContent(argElementCommon, "facescontext")));
        }

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

        final BlancoXmlElement elementBeanList = BlancoXmlBindingUtil
                .getElement(argElementSheet, "blancojsfprocess-bean-list");

        if (elementBeanList != null) {
            // 一覧の内容を取得します。
            final List<BlancoXmlElement> listField = BlancoXmlBindingUtil
                    .getElementsByTagName(elementBeanList, "bean");
            for (int indexField = 0; indexField < listField.size(); indexField++) {
                final BlancoXmlElement elementField = listField.get(indexField);

                final BlancoJsfProcessBeanItemStructure inputItem = new BlancoJsfProcessBeanItemStructure();

                inputItem.setName(BlancoXmlBindingUtil.getTextContent(
                        elementField, "name"));
                if (BlancoStringUtil.null2Blank(inputItem.getName()).length() == 0) {
                    continue;
                }

                processStructure.getBeanList().add(inputItem);
            }
        }

        final BlancoXmlElement elementSqlList = BlancoXmlBindingUtil
                .getElement(argElementSheet, "blancojsfprocess-sql-list");

        if (elementSqlList != null) {
            // 一覧の内容を取得します。
            final List<BlancoXmlElement> listSqlField = BlancoXmlBindingUtil
                    .getElementsByTagName(elementSqlList, "sql");
            for (int indexField = 0; indexField < listSqlField.size(); indexField++) {
                final BlancoXmlElement elementField = listSqlField
                        .get(indexField);

                final BlancoJsfProcessSqlItemStructure inputItem = new BlancoJsfProcessSqlItemStructure();

                inputItem.setName(BlancoXmlBindingUtil.getTextContent(
                        elementField, "name"));
                if (BlancoStringUtil.null2Blank(inputItem.getName()).length() == 0) {
                    continue;
                }

                processStructure.getSqlList().add(inputItem);
            }

        }
        return processStructure;
    }
}
