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

import blanco.cg.BlancoCgObjectFactory;
import blanco.cg.transformer.BlancoCgTransformerFactory;
import blanco.cg.valueobject.BlancoCgInterface;
import blanco.cg.valueobject.BlancoCgMethod;
import blanco.cg.valueobject.BlancoCgSourceFile;
import blanco.commons.util.BlancoNameAdjuster;
import blanco.commons.util.BlancoNameUtil;
import blanco.commons.util.BlancoStringUtil;
import blanco.jsf.task.valueobject.BlancoJsfProcessInput;
import blanco.jsf.valueobject.BlancoJsfProcessBeanItemStructure;
import blanco.jsf.valueobject.BlancoJsfProcessStructure;
import blanco.jsf.valueobject.BlancoJsfStructure;

/**
 * 処理(blancoJsf)のためのメインクラス。
 * 
 * 処理インタフェースを展開します。
 */
class BlancoJsfExpandLogic {
    /**
     * 内部的に利用するblancoCg用ファクトリ。
     */
    private BlancoCgObjectFactory fCgFactory = null;

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

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

    private BlancoJsfProcessInput fInput = null;

    /**
     * 収集された情報を元に、ソースコードを自動生成します。
     * 
     * @param processStructure
     *            メタファイルから収集できた処理構造データ。
     * @param argRuntimePackage
     *            ランタイムパッケージ。nullおよび長さ0の文字列の場合は定義書ごとにランタイムクラスを生成。
     * @param argTargetLang
     *            出力対象プログラミング言語。
     * @param argDirectoryTarget
     *            ソースコードの出力先フォルダ。
     */
    public void expandSourceFile(final BlancoJsfStructure structure,
            final List<BlancoJsfStructure> listJsfStructure,
            final BlancoJsfProcessStructure processStructure,
            final List<String> sqlClassNameList,
            final BlancoJsfProcessInput input) {
        fInput = input;

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

        fCgFactory = BlancoCgObjectFactory.getInstance();
        fCgSourceFile = fCgFactory.createSourceFile(structure.getPackage(),
                null);
        fCgSourceFile.setEncoding(input.getEncoding());
        fCgInterface = fCgFactory.createInterface(getProcessInterfaceName(
                structure, processStructure), BlancoStringUtil
                .null2Blank(processStructure.getDescription()));
        fCgSourceFile.getInterfaceList().add(fCgInterface);

        fCgInterface.setDescription("処理 ["
                + getProcessInterfaceName(structure, processStructure)
                + "]インタフェース。");
        fCgInterface.getLangDoc().getDescriptionList().add(
                "このインタフェースを実装して [" + structure.getPackage() + "]パッケージに["
                        + getProcessInterfaceName(structure, processStructure)
                        + "Impl]クラスを作成して実際の処理を実装してください。");
        fCgInterface.getLangDoc().getDescriptionList().add("");

        expandMethodExecute(structure, listJsfStructure, processStructure,
                sqlClassNameList);

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

    /**
     * execute メソッドを展開します。
     * 
     * @param processStructure
     *            メタファイルから収集できた処理構造データ。
     */
    private void expandMethodExecute(final BlancoJsfStructure structure,
            final List<BlancoJsfStructure> listJsfStructure,
            final BlancoJsfProcessStructure processStructure,
            final List<String> sqlClassNameList) {
        final BlancoCgMethod method = fCgFactory.createMethod("execute",
                "クラスをインスタンス化して処理を実行する際のエントリポイントです。");
        fCgInterface.getMethodList().add(method);
        method.setFinal(true);

        if (BlancoStringUtil.null2Blank(structure.getBean()).length() > 0) {
            addBeanParameter(fCgFactory, method, structure.getBean(),
                    "メインの Managed Bean。");
        }

        for (BlancoJsfProcessBeanItemStructure bean : processStructure
                .getBeanList()) {
            String managedBeanClassName = null;

            // まずは管理Bean名が入っているものと仮定。
            final BlancoJsfStructure jsfStructure = BlancoJsfExpandLogicProxy
                    .getJsfStructureByManagedBeanName(bean.getName(),
                            listJsfStructure);
            if (jsfStructure == null) {
                // blancoJsf 定義には見つかりませんでした。
                // この場合は、クラスそのままです。
                managedBeanClassName = bean.getName();
            } else {
                // blancoJsf 定義に見つかりました。
                managedBeanClassName = jsfStructure.getBean();
            }

            addBeanParameter(fCgFactory, method, managedBeanClassName,
                    "session から取得した Managed Bean。");
        }

        if ("pfAutoComplete".equals(processStructure.getType())) {
            method.getParameterList().add(
                    fCgFactory.createParameter("argPfAutoCompleteInput",
                            "java.lang.String",
                            "PrimeFaces の AutoComplete リクエスト。"));
            method.getParameterList().add(
                    fCgFactory.createParameter("argPfAutoCompleteOutput",
                            "java.util.List<java.lang.String>",
                            "PrimeFaces の AutoComplete レスポンス。"));
        }

        for (String sqlClassName : sqlClassNameList) {
            addSqlParameter(fCgFactory, method, sqlClassName);
        }

        if (sqlClassNameList.size() > 0) {
            method.getParameterList().add(
                    fCgFactory.createParameter("conn", "java.sql.Connection",
                            "データベース接続。基本的に、このパラメータは利用しないでください。"));
        }

        if (processStructure.getForward()) {
            // 戻り値ありなら String
            method.setReturn(fCgFactory.createReturn("java.lang.String",
                    "処理の実行結果。success, error などの遷移先に関する文字列を返却。"));
        }

        if (processStructure.getActionEvent()) {
            method.getParameterList().add(
                    fCgFactory.createParameter("actionEvent",
                            "javax.faces.event.ActionEvent",
                            "JSF の ActionEvent。"));
        }

        if (processStructure.getFacesMessage()) {
            method
                    .getParameterList()
                    .add(
                            fCgFactory
                                    .createParameter(
                                            "facesMessageList",
                                            "java.util.List<javax.faces.application.FacesMessage>",
                                            "FacesMessage のリスト。JSF に <h:messages /> と記述することで利用可能"));
        }

        if (processStructure.getFacesContext()) {
            method.getParameterList().add(
                    fCgFactory.createParameter("ctx",
                            "javax.faces.context.FacesContext",
                            "Faces で利用することができるコンテキスト。この引数は、なるべく利用しないでください。"));
        }

        if ("download".equals(processStructure.getType())) {
            String runtimePackage = structure.getPackage();
            if (BlancoStringUtil.null2Blank(fInput.getRuntimepackage())
                    .length() > 0) {
                runtimePackage = fInput.getRuntimepackage();
            }
            method.getParameterList().add(
                    fCgFactory.createParameter("downloadInfo", runtimePackage
                            + ".BlancoJsfDownloadInfo", "ダウンロード情報。"));

            method.getParameterList().add(
                    fCgFactory.createParameter("outStream",
                            "java.io.OutputStream", "ダウンロードの出力ストリーム。"));
        }

        method.getThrowList()
                .add(
                        fCgFactory.createException("java.lang.Exception",
                                "例外が発生した場合。"));
    }

    private String getProcessInterfaceName(final BlancoJsfStructure structure,
            final BlancoJsfProcessStructure processStructure) {
        return BlancoNameAdjuster.toClassName(processStructure.getName())
                + BlancoNameAdjuster.toClassName(processStructure.getMethod())
                + structure.getSuffix();
    }

    static void addBeanParameter(final BlancoCgObjectFactory cgFactory,
            final BlancoCgMethod method, final String managedBeanClassName,
            final String description) {
        final String name = "arg"
                + BlancoNameAdjuster.toClassName(BlancoNameUtil
                        .trimJavaPackage(managedBeanClassName));
        method.getParameterList().add(
                cgFactory.createParameter(name, managedBeanClassName,
                        description));
    }

    static void addSqlParameter(final BlancoCgObjectFactory cgFactory,
            final BlancoCgMethod method, final String sqlClassName) {
        final String name = BlancoNameUtil.trimJavaPackage(sqlClassName);
        method.getParameterList().add(
                cgFactory.createParameter("arg" + name, sqlClassName,
                        "SQL定義から自動生成されたデータベース・アクセサー。"));
    }
}
