/*
 * blancoStruts Copyright (C) 2005 Tosiki Iga
 * 
 * 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.struts;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;

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

import blanco.ig.collector.ResourceBundleLoader;
import blanco.ig.expander.NameAdjuster;
import blanco.ig.expander.Type;
import blanco.ig.generator.ImplementGenerator;
import blanco.ig.generator.RuntimeGenerator;
import blanco.ig.service.ServiceClass;
import blanco.struts.action.AbstractBaseAction;
import blanco.struts.action.AbstractDbAction;
import blanco.struts.definition.form.FieldValidation;
import blanco.struts.definition.form.Form;
import blanco.struts.definition.form.FormEvent;
import blanco.struts.definition.form.FormField;
import blanco.struts.definition.form.FormTable;
import blanco.struts.definition.form.SizeValidationType;
import blanco.struts.exception.BlancoStrutsValidateException;
import blanco.struts.exception.BusinessException;
import blanco.struts.exception.ValidateException;
import blanco.struts.expander.action.AbstractActionClass;
import blanco.struts.expander.form.ActionFormClass;
import blanco.struts.expander.form.TableItemClass;
import blanco.struts.generator.StrutsSetting;
import blanco.struts.metadata.StrutsMetadata;
import blanco.struts.runtime.validator.Validator;
import blanco.struts.runtime.validator.message.ErrorMessageKey;
import blanco.struts.util.BlancoStrutsExpandUtil;

public class BlancoStrutsXml2JavaClass {

    public static void main(String[] args) {
        new BlancoStrutsXml2JavaClass().process(
                new File("./tmp/struts/aaa.xml"), new File("blanco"), "myapp", true);
        System.out.println("全ての処理を完了");
    }

    private StrutsSetting setting;

    private StrutsMetadata metadata;

    private HashMap mapTable = new HashMap();

    private HashMap mapFieldEvent = new HashMap();

    public void process(final File fileSource, final File directoryTarget,
            final String rootPackage, final boolean isCreateRuntime) {
        DOMResult result = new DOMResult();
        InputStream inStream = null;
        try {
            inStream = new FileInputStream(fileSource);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(new StreamSource(inStream), result);
        } catch (TransformerConfigurationException e) {
            e.printStackTrace();
        } catch (TransformerException e) {
            e.printStackTrace();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (inStream != null) {
                try {
                    inStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        setting = new StrutsSetting();
        // mainは固定値とします。
        setting.setMainOutputDirectory("main");
        setting.setRootNameSpace(rootPackage);
        setting.setWorkDirectory(directoryTarget.getAbsolutePath());

        Node rootNode = result.getNode();
        if (rootNode instanceof Document) {
            // これが正常系。ドキュメントルートを取得
            Document rootDocument = (Document) rootNode;
            NodeList listSheet = rootDocument.getElementsByTagName("sheet");
            int sizeListSheet = listSheet.getLength();
            for (int index = 0; index < sizeListSheet; index++) {
                final Element elementSheet = (Element) listSheet.item(index);
                System.out.println("シート[" + elementSheet.getAttribute("name")
                        + "]を処理中");

                NodeList listCommon = elementSheet
                        .getElementsByTagName("common");
                if (listCommon.getLength() == 0) {
                    // commonが無い場合にはスキップします。
                    continue;
                }

                NodeList listActionFormSetting = elementSheet
                        .getElementsByTagName("actionFormSetting");
                if (listActionFormSetting.getLength() == 0) {
                    continue;
                }

                NodeList listActionSetting = elementSheet
                        .getElementsByTagName("actionSetting");
                if (listActionSetting.getLength() == 0) {
                    continue;
                }

                NodeList listFieldList = elementSheet
                        .getElementsByTagName("fieldList");
                if (listFieldList.getLength() == 0) {
                    continue;
                }

                Element elementCommon = (Element) listCommon.item(0);
                if (BlancoStrutsExpandUtil.readStringByElementTagName(
                        elementCommon, "screenId") == null) {
                    continue;
                }
                Element elementActionFormSetting = (Element) listActionFormSetting
                        .item(0);
                Element elementActionSetting = (Element) listActionSetting
                        .item(0);
                Element elementFieldList = (Element) listFieldList.item(0);
                try {
                    expandSheet(elementSheet, elementCommon,
                            elementActionFormSetting, elementActionSetting,
                            elementFieldList, setting);
                } catch (SecurityException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }

		if(isCreateRuntime){
	        RuntimeGenerator rg = new RuntimeGenerator(setting);
	
	        rg.addSourceDirectory("src/main");
	        rg.addRuntime(ResourceBundleLoader.class);
	        rg.addRuntime(Validator.class);
	        rg.addRuntime(ErrorMessageKey.class);
	
	        String target = "";
	        target = setting.getRootNameSpace() + ".runtime";
	        if (target.equals(".runtime")) {
	            target = "blanco.struts.runtime";
	        }
	        rg.addPackagePair("blanco.struts.runtime", target);
	        rg.addPackagePair("blanco.ig.collector", target);
	        try {
	            rg.generate();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        RuntimeGenerator rgT = new RuntimeGenerator(setting);
			rgT.addSourceDirectory("src/main");
			rgT.addRuntime(AbstractBaseAction.class);
			rgT.addRuntime(AbstractDbAction.class);
	
	        String targetT = "";
			targetT = setting.getRootNameSpace() + ".action";
			rgT.addPackagePair("blanco.struts.action", targetT);
			rgT.addPackagePair("blanco.struts.exception", setting.getRootNameSpace() + ".exception");
	        try {
				rgT.generate();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	
	        RuntimeGenerator rgE = new RuntimeGenerator(setting);
			rgE.addSourceDirectory("src/main");
			rgE.addRuntime(BlancoStrutsValidateException.class);
			rgE.addRuntime(BusinessException.class);
			rgE.addRuntime(ValidateException.class);
	
	        String targetE = "";
			targetE = setting.getRootNameSpace() + ".exception";
			rgE.addPackagePair("blanco.struts.exception", targetE);
	        try {
				rgE.generate();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
		}

	}

    private String screenId;

    private String actionFormPackageName;

    void expandSheet(final Element elementSheet, final Element elementCommon,
            final Element elementActionFormSetting,
            final Element elementActionSetting, final Element elementFieldList,
            final StrutsSetting setting) throws SecurityException,
            NoSuchMethodException {
        screenId = BlancoStrutsExpandUtil.readStringByElementTagName(
                elementCommon, "screenId");
        actionFormPackageName = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementActionFormSetting,
                        "packageName");
        final String screenName = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementCommon, "screenName");
        final String actionPackageName = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementActionSetting, "packageName");
        final String screenAutoValidation = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementCommon,
                        "screenAutoValidation");
        final String screenUseDatabase = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementCommon, "screenUseDatabase");
        final String saveToken = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementActionSetting, "saveToken");

        Form form = new Form(actionFormPackageName, screenId + "Form");
        form.setFormClassName((screenName == null ? screenId : screenName + "("
                + screenId + ")")
                + "のアクションフォームクラスです。<br>");
        form.setFormClassOutLine("バリデーションなどの処理が実装されます。");
        // 自動針デーションについて、true/falseが反転している点に注意。
        form.setAutoValidation(screenAutoValidation == null
                || screenAutoValidation.equals("false") ? true : false);
        form.setEnebledJavaDoc(true);
        form
                .setSaveToken(saveToken == null || saveToken.equals("false") ? false
                        : true);
        form.setUsingDb(screenUseDatabase == null
                | screenUseDatabase.equals("false") ? false : true);

        metadata = new StrutsMetadata();

        // イベントの振り分けに使用するパラメータ名。
        // サンプルの通りならいじらないようにしてください。
        metadata.setEventParameterName("eventId");

        ImplementGenerator ig = new ImplementGenerator(new ServiceClass(
                "Struts"), setting);

        NodeList listField = elementFieldList.getElementsByTagName("field");
        for (int index = 0; index < listField.getLength(); index++) {
            final Element elementField = (Element) listField.item(index);
            final String fieldName = BlancoStrutsExpandUtil
                    .readStringByElementTagName(elementField, "name");
            if (fieldName == null) {
                continue;
            }

            final String description = BlancoStrutsExpandUtil
                    .readStringByElementTagName(elementField, "description");

            final String isSubmit = BlancoStrutsExpandUtil
                    .readStringByElementTagName(elementField, "isSubmit");
            if (isSubmit != null && isSubmit.equals("true")) {
                // メソッド処理
                final String nameLocal = BlancoStrutsExpandUtil
                        .readStringByElementTagName(elementField, "nameLocal");
                final String isCheckToken = BlancoStrutsExpandUtil
                        .readStringByElementTagName(elementField,
                                "isCheckToken");

                FormEvent event = new FormEvent(fieldName);
                if (description != null) {
                    event.setDescription(description);
                }
                if (nameLocal != null) {
                    event.setKoumokuNihongoMei(nameLocal);
                }
                if (isCheckToken != null && isCheckToken.equals("true")) {
                    // Token確認
                    event.setCheckToken(true);
                }
                form.addEvent(event);
                Type type = new Type(actionPackageName, "Abstract"
                        + new NameAdjuster().toClassName(screenId) + "Action");
                AbstractActionClass abstractActionClass = new AbstractActionClass(
                        type, metadata, form, setting);
                abstractActionClass.getJavaDoc().addLine(
                        "画面定義ID[" + screenId + "]の抽象アクションクラス");
                // 親クラスをセットします。
                // TODO この指定は外部化の必要がありそうです。
                ig.addMain(abstractActionClass);
            } else {
                // ここからはフィールド処理。
                expandField(ig, elementField, form);
            }
        }

        for (Iterator ite = mapTable.values().iterator(); ite.hasNext();) {
            FormTable formTable = (FormTable) ite.next();
            ig.addMain(new TableItemClass(formTable.getType(), formTable));
        }

        for (Iterator ite = form.getEventIterator(); ite.hasNext();) {
            FormEvent formEvent = (FormEvent) ite.next();
            ArrayList list = (ArrayList) mapFieldEvent.get(formEvent.getName());
            // TODO きついチェックを行うためには、mapFieldEventマップあるが利用されなかったものをエラーとして扱うべき
            if (list == null) {
                // ボタンはあるのだが、これに関連するチェックは無い場合にここを通過します。
                // 正常系です！
                continue;
            }
            for (int index = 0; index < list.size(); index++) {
                FormField field = (FormField) list.get(index);
                formEvent.addValidationField(field);
            }
        }

        try {
            ig.addMain(new ActionFormClass(setting, metadata, form));
            ig.generate();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * フィールドを展開します。
     */
    private void expandField(final ImplementGenerator ig,
            final Element elementField, final Form form) {

        final String fieldName = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "name");
        final String fieldDescription = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "description");
        final String fieldTypeOnUI = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "typeOnUI");
        String fieldType = BlancoStrutsExpandUtil.readStringByElementTagName(
                elementField, "type");
        final String minLength = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "minLength");
        final String maxLength = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "maxLength");
        final String lengthValidationType = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField,
                        "lengthValidationType");

        final String format = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "format");
        final String minRange = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "minRange");
        final String maxRange = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "maxRange");
        final String fielddefault = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "default");
        final String regex = BlancoStrutsExpandUtil.readStringByElementTagName(
                elementField, "regex");
        // チェック項目。
        final String checkExecute = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "checkExecute");
        final String isCheckRequire = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "isCheckRequire");
        final String regexMessageKey = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "regexMessageKey");

        if (fieldType == null) {
            // ほんとうはここでエラー処理。
            return;
        }

        // System.out.println("fieldName:" + fieldName);
	
        if (fieldTypeOnUI.equals("Label")) {
			// Labelでは、java.lang.String型のみサポート。
	        if (!fieldType.equals("java.lang.String")) {
	            throw new IllegalArgumentException(
	                    "Labelではサポートしない型です。java.lang.String を指定してください。" + " 画面定義ID["
	                            + screenId + "] フィールド名[" + fieldName + "] 型["
	                            + fieldType + "]");
	        }
        }
		
        if (fieldTypeOnUI.equals("DropdownList")) {
			// DropdownListでは、java.util.Collection型のみサポート。
	        if (!fieldType.equals("java.util.Collection")) {
	            throw new IllegalArgumentException(
	                    "DropdownListではサポートしない型です。java.util.Collection を指定してください。" + " 画面定義ID["
	                            + screenId + "] フィールド名[" + fieldName + "] 型["
	                            + fieldType + "]");
	        }
			
            // もとのblancoStrutsに下記と同等のコーディングが実在。
            fieldType = "java.lang.String";
        }

        FormField formField = new FormField(new Type(fieldType), fieldName);

        final String tableGroup = BlancoStrutsExpandUtil
                .readStringByElementTagName(elementField, "tableGroup");
        if (tableGroup == null) {
            form.addField(formField);
        } else {
            Object objTableExist = mapTable.get(tableGroup);
            if (objTableExist == null) {
                mapTable.put(tableGroup, new FormTable(screenId,
                        actionFormPackageName, tableGroup, 0));
            }
            FormTable formTable = (FormTable) mapTable.get(tableGroup);
            formTable.addField(formField);
            form.addTable(formTable);
        }

        if (fieldDescription != null) {
            // 項目の説明 ※JavaDocに利用
            formField.setDescription(fieldDescription);
        }

        // 項目型
        formField.setUiType(fieldTypeOnUI);

        // 「型」はコンストラクタで提供済み。

        FieldValidation validation = new FieldValidation();
        formField.setValidation(validation);

        // MIN桁、MAX桁
        if (minLength != null || maxLength != null) {
            if (!fieldType.equals("java.lang.String")) {
                throw new IllegalArgumentException(
                        "桁数チェックをサポートしない型です。桁数チェックの指定を削除してください。" + " 画面定義ID["
                                + screenId + "] フィールド名[" + fieldName + "] 型["
                                + fieldType + "]");
            }

            validation.setCheckSize(true);
            if (minLength != null) {
                validation.setMinSize(minLength);
            }
            if (maxLength != null) {
                validation.setMaxSize(maxLength);
            }
            if (lengthValidationType == null
                    || lengthValidationType.equals("byteCount")) {
                validation.setSizeValidationType(SizeValidationType.BYTES);
            } else {
                validation.setSizeValidationType(SizeValidationType.LENGTH);
            }
        }

        // 規定値
        if (fielddefault != null) {
            formField.setDefault(fielddefault);
        } else if (fieldType.equals("java.lang.String")) {
            formField.setDefault("");
        }

        // チェック項目
        // TODO 実装不明。
        if (checkExecute != null) {
            for (StringTokenizer token = new StringTokenizer(checkExecute,
                    " ,\\."); token.hasMoreElements();) {
                String verb = token.nextToken();
                if (mapFieldEvent.get(verb) == null) {
                    mapFieldEvent.put(verb, new ArrayList());
                }
                ArrayList list = (ArrayList) mapFieldEvent.get(verb);
                list.add(formField);
            }
        }

        // 必須
        if (isCheckRequire != null && isCheckRequire.equals("true")) {
            // System.out.println("必須");
            validation.setCheckRequire(true);
        }

        // 形式タイプ
        if (format != null && format.equals("なし") == false) {
            validation.setCheckFormat(true);
            validation.setFormat(format);
        }

        // 正規表現
        if (regex != null) {
            validation.setCheckRegex(true);
            validation.setRegex(regex);
            if (regexMessageKey != null) {
                validation.setRegexMessageKey(regexMessageKey);
            }
        }

        // 範囲Validate (数値)
        if (minRange != null || maxRange != null) {
            if (!fieldType.equals("int")) {
                throw new IllegalArgumentException(
                        "範囲チェックをサポートしない型です。範囲チェックの指定を削除してください。" + " 画面定義ID["
                                + screenId + "] フィールド名[" + fieldName + "] 型["
                                + fieldType + "]");
            }

            validation.setCheckRange(true);
            if (minRange != null) {
                validation.setMinRange(minRange);
            }
            if (maxRange != null) {
                validation.setMaxRange(maxRange);
            }
        }
    }
}
