package jp.co.headwaters.webappos.generator.web;

import static jp.co.headwaters.webappos.generator.Main.*;

import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.co.headwaters.webappos.controller.ControllerConstants;
import jp.co.headwaters.webappos.controller.cache.ActionCache;
import jp.co.headwaters.webappos.controller.cache.UrlPatternCache;
import jp.co.headwaters.webappos.controller.cache.bean.AbstractFunctionBean;
import jp.co.headwaters.webappos.controller.cache.bean.ActionBean;
import jp.co.headwaters.webappos.controller.cache.bean.BindBean;
import jp.co.headwaters.webappos.controller.cache.bean.CaseBean;
import jp.co.headwaters.webappos.controller.cache.bean.ConditionBean;
import jp.co.headwaters.webappos.controller.cache.bean.ExecuteBean;
import jp.co.headwaters.webappos.controller.cache.bean.LoadFunctionBean;
import jp.co.headwaters.webappos.controller.cache.bean.ResultBean;
import jp.co.headwaters.webappos.controller.cache.bean.SubmitFunctionBean;
import jp.co.headwaters.webappos.controller.cache.bean.UrlPatternBean;
import jp.co.headwaters.webappos.controller.enumation.CrudEnum;
import jp.co.headwaters.webappos.controller.utils.ControllerUtils;
import jp.co.headwaters.webappos.generator.GeneratorConstants;
import jp.co.headwaters.webappos.generator.utils.FileUtils;
import jp.co.headwaters.webappos.generator.utils.GeneratorUtils;
import jp.co.headwaters.webappos.generator.utils.MessageUtils;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Attribute;
import org.jsoup.nodes.Attributes;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Document.OutputSettings;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.mybatis.generator.api.dom.OutputUtilities;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

// TODO:ページャー
public class JspGenerator {

	private static final Log _logger = LogFactory.getLog(JspGenerator.class);
	private static String _webRootPath;
	private static String _htmlRootPath;
	private ActionCache _actionCache;
	private UrlPatternCache _urlPatternCache;
	private List<String> _iteratorResultNames;

	public boolean generate() {
		List<String> warnings = new ArrayList<String>();

		// create action cache
		this._actionCache = ActionCache.getInstance();
		createActionCache(new File(GeneratorUtils.getInputHtmlPath()), null);

		// create url pattern cache
		this._urlPatternCache = UrlPatternCache.getInstance();

		// create jsp directory
		createJspDirectory();

		// setting web and html root path
		setRootPath();

		for (ActionBean actionBean : this._actionCache.getActionMap().values()) {
			File[] htmlFiles = new File(getTargetHtmlPath(actionBean)).listFiles(FileUtils.getHtmlFileFilter());
			for (File file : htmlFiles) {
				try {
					// parse html and generate jsp file
					_logger.debug(file.getAbsoluteFile());
					outputJspFile(actionBean, file, warnings);
				} catch (IOException e) {
					_logger.error(MessageUtils.getString("err.400", file.getAbsolutePath()), e); //$NON-NLS-1$
					return false;
				}
			}
		}

		if (warnings.size() != 0) {
			for (String warning : warnings){
				_logger.error(warning);
			}
			return false;
		}
		return true;
	}

	private void createActionCache(File dir, String parent) {
		File[] files = dir.listFiles();
		if (files == null) {
			return;
		}
		String actionName = null;
		String actionPath = null;

		// for index.html
		ActionBean indexAction = new ActionBean("", ""); //$NON-NLS-1$ //$NON-NLS-2$
		indexAction.setSubmitExecuteMap(new HashMap<String, ExecuteBean>());
		indexAction.setLoadExecuteMap(new HashMap<String, ExecuteBean>());
		this._actionCache.getActionMap().put("", indexAction); //$NON-NLS-1$

		for (File file : files) {
			if (file.isDirectory()) {
				actionName = getActionName(parent, file.getName());

				// relative action directory path from context root
				actionPath = file.getAbsolutePath().replace(GeneratorUtils.getInputHtmlPath(), ""); //$NON-NLS-1$

				ActionBean action = new ActionBean(actionName, actionPath);
				action.setSubmitExecuteMap(new HashMap<String, ExecuteBean>());
				action.setLoadExecuteMap(new HashMap<String, ExecuteBean>());
				this._actionCache.getActionMap().put(actionName, action);

				createActionCache(file, actionName);
			}
		}
	}

	private String getActionName(String parent, String fileName){
		StringBuilder sb = new StringBuilder();
		if (parent != null) {
			sb.append(parent);
			sb.append(GeneratorConstants.ACTION_NAME_DELIMITER);
		}
		sb.append(fileName);
		return sb.toString();
	}

	private void createJspDirectory() {
		StringBuilder sb = new StringBuilder();
		for (ActionBean actionBean : this._actionCache.getActionMap().values()) {
			sb.setLength(0);
			sb.append(GeneratorUtils.getOutputJspPath());
			sb.append(actionBean.getHtmlPath());
			File file = new File(sb.toString());
			file.mkdirs();
		}
	}

	private static void setRootPath() {
		_htmlRootPath = getHtmlRootPath();
		_webRootPath = getWebRootPath();
	}

	private static String getHtmlRootPath() {
		StringBuilder sb = new StringBuilder();
		sb.append(ControllerConstants.PATH_DELIMITER);
		sb.append(GeneratorConstants.INPUT_HTML_DIR);
		sb.append(ControllerConstants.PATH_DELIMITER);
		return sb.toString();
	}

	private static String getWebRootPath() {
		StringBuilder sb = new StringBuilder();
		sb.append(ControllerConstants.PATH_DELIMITER);
		sb.append(GeneratorConstants.OUTPUT_WEB_ROOT_DIR);
		sb.append(ControllerConstants.PATH_DELIMITER);
		return sb.toString();
	}

	private static String getTargetHtmlPath(ActionBean actionBean) {
		StringBuilder sb = new StringBuilder();
		sb.append(GeneratorUtils.getInputHtmlPath());
		sb.append(actionBean.getHtmlPath());
		return sb.toString();
	}

	private static String getOutputPath(ActionBean actionBean, String fileName) {
		StringBuilder sb = new StringBuilder();
		if (!StringUtils.isEmpty(actionBean.getHtmlPath())){
			sb.append(actionBean.getHtmlPath());
			sb.append(ControllerUtils.getFileSparator());
		}
		sb.append(FileUtils.convertExtensionHtmlToJsp(fileName));
		return sb.toString();
	}

	private void outputJspFile(ActionBean actionBean, File inputFile, List<String> warnings) throws IOException {
		String outputPath = getOutputPath(actionBean, inputFile.getName());
		Document document = Jsoup.parse(inputFile, GeneratorConstants.INPUT_HTML_FILE_ENCODING);

		// iterator name list clear
		this._iteratorResultNames = new ArrayList<String>();

		// replace path
		replacePath(document);

		// parse data-erasure
		parseDataErasure(document);

		// parse data-load
		if (!GeneratorConstants.INPUT_HTML_ERROR_PAGE_DIR.equalsIgnoreCase(actionBean.getName())) {
			parseDataLoad(actionBean, outputPath, document);
		}

		// parse data-func
		parseDataFunc(actionBean, outputPath, document);

		// parse data-bind
		parseDataBind(document);

		// parse data-iterator
		parseDataIterator(document);

		// parse data-case
		parseDataCase(document);

		// parse data-url
		if (!GeneratorConstants.INPUT_HTML_ERROR_PAGE_DIR.equalsIgnoreCase(actionBean.getName())) {
			parseDataUrl(document, actionBean.getName(), inputFile.getName());
		}

		// check Untreated data- Attribute
		checkUnParsedAttribute(document, inputFile.getAbsolutePath(), warnings);

		// save jsp file
		saveJspFile(document, outputPath);
	}

	private static void replacePath(Document document) {
		replaceWebPath(document);
		replaceHtmlPath(document.select("a[href]"), "href"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private static void replaceWebPath(Document document) {
		StringBuilder sb = new StringBuilder();
		String attributeValue = null;
		for (String attributeKey : GeneratorConstants.HTML_REPLACE_ATTR_KEY) {
			for (Element element : document.getElementsByAttributeValueContaining(attributeKey, _webRootPath)) {
				sb.setLength(0);
				if (GeneratorUtils.getContextMode()) {
					sb.append(ControllerConstants.PATH_DELIMITER);
					sb.append(getContextName());
				}
				attributeValue = element.attr(attributeKey);
				sb.append(ControllerConstants.PATH_DELIMITER);
				sb.append(attributeValue.substring(attributeValue.indexOf(_webRootPath) + _webRootPath.length()));
				element.attr(attributeKey, sb.toString());
			}
		}
	}

	private static void replaceHtmlPath(Elements elements, String attributeName) {
		StringBuilder sb = new StringBuilder();
		String value = null;
		String resultName = null;
		for (Element element : elements) {
			sb.setLength(0);
			value = element.attr(attributeName);
			if (value.indexOf(_htmlRootPath) > 0) {
				if (GeneratorUtils.getContextMode()) {
					sb.append(ControllerConstants.PATH_DELIMITER);
					sb.append(getContextName());
				}
				value = value.substring(value.indexOf(_htmlRootPath) + _htmlRootPath.length());
				String[] path = value.split(ControllerConstants.PATH_DELIMITER);
				for (int i = 0; i < path.length; i++) {
					if (i == path.length - 1){
						resultName = FileUtils.removeFileExtension(path[i]);
						break;
					}
					sb.append(ControllerConstants.PATH_DELIMITER);
					sb.append(path[i]);
				}
				sb.append(ControllerConstants.PATH_DELIMITER);
				if (!ControllerConstants.DEFAULT_RESULT_NAME.equalsIgnoreCase(resultName)) {
					sb.append(resultName);
					sb.append(ControllerConstants.PATH_DELIMITER);
				}
				element.attr(attributeName, sb.toString());
			}
		}
	}

	private void parseDataErasure(Document document) {
		Elements elements = document.getElementsByAttribute(GeneratorConstants.HTML_DATA_ATTR_NAME_ERASURE);
		String type = null;
		for (Element element : elements) {
			type = element.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_ERASURE);
			if (GeneratorConstants.HTML_ERASURE_TYPE_OWN.equalsIgnoreCase(type)) {
				// remove own element
				elements.remove();
			} else if (GeneratorConstants.HTML_ERASURE_TYPE_CHILD.equalsIgnoreCase(type)) {
				// remove child element
				element.children().remove();
				// remove attribute
				element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_ERASURE);
			}
		}
	}

	private void parseDataLoad(ActionBean actionBean, String outputPath, Document document)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		List<AbstractFunctionBean> functions = null;
		String params = null;

		ExecuteBean executeInfo = new ExecuteBean();
		executeInfo.setResult(createResultForLoad(outputPath));
		executeInfo.setFunctions(new ArrayList<AbstractFunctionBean>());
		actionBean.getLoadExecuteMap().put(outputPath, executeInfo);

		Element element = document.select(getQueryForDataLoad()).first();
		if (element != null) {
			// ---------------------------------------
			// parse data-load parameters
			// ---------------------------------------
			params = element.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_LOAD);
			if (params.startsWith(GeneratorConstants.HTML_ARRAY_START_STRING)) {
				functions = mapper.readValue(params, new TypeReference<List<LoadFunctionBean>>() {});
			} else {
				functions = new ArrayList<AbstractFunctionBean>();
				functions.add(mapper.readValue(params, LoadFunctionBean.class));
			}

			// ---------------------------------------
			// set execute bean
			// ---------------------------------------
			executeInfo.getFunctions().addAll(functions);

			addIteratorResultNames(functions);

			// ---------------------------------------
			// remove attribute
			// ---------------------------------------
			element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_LOAD);
		}
	}

	private void parseDataFunc(ActionBean actionBean, String outputPath, Document document)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		String params = null;
		Elements elements = document.select(getQueryForDataFunc());
		for (Element element : elements) {
			ExecuteBean executeInfo = new ExecuteBean();
			executeInfo.setResult(createResultForSubmit(actionBean, element));
			executeInfo.setFunctions(new ArrayList<AbstractFunctionBean>());

			// ---------------------------------------
			// update action attribute
			// ---------------------------------------
			replaceHtmlPath(document.select("form[action]"), "action"); //$NON-NLS-1$ //$NON-NLS-2$

			// ---------------------------------------
			// append form id element
			// ---------------------------------------
			String formId = addFormIdElement(outputPath, element);
			actionBean.getSubmitExecuteMap().put(formId, executeInfo);

			// ---------------------------------------
			// parse data-func parameters
			// ---------------------------------------
			List<AbstractFunctionBean> functions = null;
			params = element.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_FUNC);
			if (params.startsWith(GeneratorConstants.HTML_ARRAY_START_STRING)) {
				functions = mapper.readValue(params, new TypeReference<List<SubmitFunctionBean>>() {});
			} else {
				functions = new ArrayList<AbstractFunctionBean>();
				functions.add(mapper.readValue(params, SubmitFunctionBean.class));
			}

			// ---------------------------------------
			// parse data-cond parameters
			// ---------------------------------------
			// form child element
			Elements condElements = element.select(getQueryForDataCond());
			for (Element condElement : condElements) {
				params = condElement.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_COND);
				ConditionBean condition = mapper.readValue(params, ConditionBean.class);
				updateAttributeForCondition(condition, condElement);
				condElement.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_COND);

				// create operator map for request
				for (AbstractFunctionBean function : functions) {
					if (function.getResult().equals(condition.getResult())) {
						SubmitFunctionBean submitFunction = (SubmitFunctionBean)function;
						if (submitFunction.getOperatorMap() == null) {
							submitFunction.setOperatorMap(new HashMap<String, String>());
						}
						submitFunction.getOperatorMap().putAll(createOperatorMap(condition));
					}
				}
			}

			// ---------------------------------------
			// add sort and page element
			// ---------------------------------------
			for (AbstractFunctionBean function : functions) {
				if (function.getSorts() != null && function.getSorts().size() > 0) {
					// generate sort element
					addSortElement(function, element);
				}
				if (function.getPager() != null) {
					// generate sort element
					addPageElement(function, element);
				}
			}

			// ---------------------------------------
			// set execute bean
			// ---------------------------------------
			executeInfo.getFunctions().addAll(functions);

			addIteratorResultNames(functions);

			// ---------------------------------------
			// remove attribute
			// ---------------------------------------
			element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_FUNC);
		}
	}

	private void parseDataBind(Document document)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		String params = null;
		List<BindBean> binds = null;

		Elements elements = document.getElementsByAttribute(GeneratorConstants.HTML_DATA_ATTR_NAME_BIND);
		for (Element element : elements) {
			params = element.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_BIND);
			if (params.startsWith(GeneratorConstants.HTML_ARRAY_START_STRING)) {
				binds = mapper.readValue(params, new TypeReference<List<BindBean>>() {});
			} else {
				binds = new ArrayList<BindBean>();
				binds.add(mapper.readValue(params, BindBean.class));
			}

			for (BindBean bind : binds) {
				updateAttributeForBind(bind, element);
			}

			// remove attribute
			element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_BIND);
		}
	}

	private void parseDataIterator(Document document) {
		for (String resultName : this._iteratorResultNames) {
			Elements elements = document.select(getQueryForDataIterator(resultName));
			for (Element element : elements) {
				removeSiblingElement(element);
				addIteratorElement(element, resultName);
				// remove attribute
				element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_ITERATOR);
			}
		}
	}

	private void parseDataCase(Document document)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		String params = null;
		List<CaseBean> cases = null;

		Elements elements = document.getElementsByAttribute(GeneratorConstants.HTML_DATA_ATTR_NAME_CASE);
		for (Element element : elements) {
			params = element.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_CASE);
			if (params.startsWith(GeneratorConstants.HTML_ARRAY_START_STRING)) {
				cases = mapper.readValue(params, new TypeReference<List<CaseBean>>() {});
			} else {
				cases = new ArrayList<CaseBean>();
				cases.add(mapper.readValue(params, CaseBean.class));
			}

			for (CaseBean caseInfo : cases) {
				addCaseElement(caseInfo, element);
			}

			// remove attribute
			element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_CASE);
		}
	}

	private void parseDataUrl(Document document, String actionName, String fileName)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		List<String> patterns = null;
		String params = null;
		String defaultUrlPattern = null;
		UrlPatternBean urlPatternBean = null;
		String resultName = FileUtils.removeFileExtension(fileName);

		defaultUrlPattern = getDefaultUrlPattern(actionName, resultName);

		Element element = document.select(getQueryForDataUrl()).first();
		if (element != null) {
			params = element.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_URL);
			if (params.startsWith(GeneratorConstants.HTML_ARRAY_START_STRING)) {
				patterns = mapper.readValue(params, new TypeReference<List<String>>() {});
			} else {
				patterns = new ArrayList<String>();
				patterns.add(mapper.readValue(params, String.class));
			}

			for (String pattern : patterns) {
				if (pattern.contains(GeneratorConstants.HTML_ORIGINAL)) {
					// replace {_ORIGINAL}
					pattern = pattern.replace(GeneratorConstants.HTML_ORIGINAL, defaultUrlPattern);
				}
				urlPatternBean = new UrlPatternBean(getUrlPatternRegex(pattern), actionName, resultName);
				if (this._urlPatternCache.getUrlPatternMap().containsKey(pattern)) {
					_logger.warn(MessageUtils.getString("err.403", pattern)); //$NON-NLS-1$
				} else {
					this._urlPatternCache.getUrlPatternMap().put(pattern, urlPatternBean);
				}
			}

			// remove attribute
			element.removeAttr(GeneratorConstants.HTML_DATA_ATTR_NAME_URL);
		} else {
			urlPatternBean = new UrlPatternBean(getUrlPatternRegex(defaultUrlPattern), actionName, resultName);
			if (this._urlPatternCache.getUrlPatternMap().containsKey(defaultUrlPattern)) {
				_logger.warn(MessageUtils.getString("err.403", defaultUrlPattern)); //$NON-NLS-1$
			} else {
				this._urlPatternCache.getUrlPatternMap().put(defaultUrlPattern, urlPatternBean);
			}
		}
	}

	private String getDefaultUrlPattern(String actionName, String resultName){
		// set default url pattern
		StringBuilder sb = new StringBuilder();
		sb.append(ControllerConstants.PATH_DELIMITER);
		if (!StringUtils.isEmpty(actionName)) {
			sb.append(actionName);
			sb.append(ControllerConstants.PATH_DELIMITER);
			if (!ControllerConstants.DEFAULT_RESULT_NAME.equalsIgnoreCase(resultName)) {
				sb.append(resultName);
				sb.append(ControllerConstants.PATH_DELIMITER);
			}
		}
		return sb.toString();
	}

	private String getUrlPatternRegex(String pattern) {
		StringBuilder sb = new StringBuilder();
		sb.append("^"); //$NON-NLS-1$
		sb.append(pattern);
		sb.append("$"); //$NON-NLS-1$
		return sb.toString();
	}

	private void checkUnParsedAttribute(Document document, String inputFilePath, List<String> warnings) {
		Elements elements = document.select(getQueryForUnParsedAttribute());
		String key = null;
		for (Element element : elements) {
			Attributes attributes = element.attributes();
			for (Attribute attribute : attributes){
				key = attribute.getKey();
				if (key.startsWith(GeneratorConstants.HTML_DATA_ATTR_PREFIX)) {
					warnings.add(MessageUtils.getString("err.401", inputFilePath, key, attribute.getValue())); //$NON-NLS-1$
				}
			}
		}
	}

	private static String getQueryForDataLoad() {
		StringBuilder sb = new StringBuilder();
		sb.append("head["); //$NON-NLS-1$
		sb.append(GeneratorConstants.HTML_DATA_ATTR_NAME_LOAD);
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}

	private static String getQueryForDataFunc() {
		StringBuilder sb = new StringBuilder();
		sb.append("form["); //$NON-NLS-1$
		sb.append(GeneratorConstants.HTML_DATA_ATTR_NAME_FUNC);
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}

	private static String getQueryForDataCond() {
		StringBuilder sb = new StringBuilder();
		sb.append("input["); //$NON-NLS-1$
		sb.append(GeneratorConstants.HTML_DATA_ATTR_NAME_COND);
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}

	private static String getQueryForDataIterator(String resultName) {
		StringBuilder sb = new StringBuilder();
		sb.append("["); //$NON-NLS-1$
		sb.append(GeneratorConstants.HTML_DATA_ATTR_NAME_ITERATOR);
		sb.append("="); //$NON-NLS-1$
		sb.append(resultName);
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}

	private static String getQueryForUnParsedAttribute() {
		StringBuilder sb = new StringBuilder();
		sb.append("[^"); //$NON-NLS-1$
		sb.append(GeneratorConstants.HTML_DATA_ATTR_PREFIX);
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}

	private static String getQueryForDataUrl() {
		StringBuilder sb = new StringBuilder();
		sb.append("head["); //$NON-NLS-1$
		sb.append(GeneratorConstants.HTML_DATA_ATTR_NAME_URL);
		sb.append("]"); //$NON-NLS-1$
		return sb.toString();
	}

	private static ResultBean createResultForLoad(String outputPath) {
		StringBuilder sb = new StringBuilder();
		sb.append(ControllerUtils.getFileSparator());
		sb.append(GeneratorConstants.OUTPUT_JSP_DIR);
		sb.append(ControllerUtils.getFileSparator());
		sb.append(outputPath);

		ResultBean result = new ResultBean();
		String fileName = outputPath.substring(outputPath.indexOf(ControllerUtils.getFileSparator()) + 1);
		result.setName(fileName.replace(ControllerConstants.JSP_EXTENSION, "")); //$NON-NLS-1$
		result.setValue(sb.toString());
		return result;
	}

	private static ResultBean createResultForSubmit(ActionBean actionBean, Element formElement) {
		ResultBean result = new ResultBean();
		StringBuilder sb = new StringBuilder();

		String value = formElement.attr("action"); //$NON-NLS-1$
		int beginIndex = value.indexOf(_htmlRootPath) + _htmlRootPath.length();
		value = value.substring(beginIndex);
		value = FileUtils.convertExtensionHtmlToJsp(value);
		value = value.replace(ControllerConstants.PATH_DELIMITER, ControllerUtils.getFileSparator());

		sb.append(ControllerUtils.getFileSparator());
		sb.append(GeneratorConstants.OUTPUT_JSP_DIR);
		sb.append(ControllerUtils.getFileSparator());
		sb.append(value);
		result.setValue(sb.toString());

		sb.setLength(0);
		sb.append(GeneratorConstants.STRUTS_RESULT_PREFIX);
		sb.append(actionBean.getSubmitExecuteMap().size());
		result.setName(sb.toString());

		return result;
	}

	private static String addFormIdElement(String outputPath, Element formElement) {
		StringBuilder sb = new StringBuilder();
		sb.append(outputPath);
		sb.append(formElement.attr("id")); //$NON-NLS-1$
		String formId = String.valueOf(sb.toString().hashCode());

		Element element = formElement.appendElement("input"); //$NON-NLS-1$
		element.attr("type", "hidden");  //$NON-NLS-1$//$NON-NLS-2$
		element.attr("name", ControllerConstants.ELEMENT_NAME_FORM_ID); //$NON-NLS-1$
		element.attr("value", formId); //$NON-NLS-1$

		return formId;
	}

	private static void addSortElement(AbstractFunctionBean function, Element formElement) {
		Element element = formElement.appendElement("input"); //$NON-NLS-1$
		element.attr("type", "hidden"); //$NON-NLS-1$//$NON-NLS-2$
		element.attr("name", ControllerUtils.getSortElementName(function)); //$NON-NLS-1$
		element.attr("value", StringUtils.join(function.getSorts(), ",")); //$NON-NLS-1$//$NON-NLS-2$
	}

	private static void addPageElement(AbstractFunctionBean function, Element formElement) {
		Element element = formElement.appendElement("input"); //$NON-NLS-1$
		element.attr("type", "hidden"); //$NON-NLS-1$//$NON-NLS-2$
		element.attr("name", ControllerUtils.getPageElementName(function)); //$NON-NLS-1$
		element.attr("value", String.valueOf(ControllerConstants.DEFAULT_PEGE_NO)); //$NON-NLS-1$
	}

	private static void updateAttributeForCondition(ConditionBean condition, Element element) {
		// table.column
		String[] column = condition.getColumnName().split(GeneratorConstants.HTML_GENERATOR_DELIMITER);
		StringBuilder sb = new StringBuilder();
		sb.append(condition.getResult());
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_CRUD_CONDITION);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
		sb.append(column[0]);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
		sb.append(column[1]);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
		if (!StringUtils.isEmpty(condition.getOption())) {
			sb.append(condition.getOption());
		}
		element.attr("name", sb.toString()); //$NON-NLS-1$

		sb.setLength(0);
		sb.append("<s:property value=\""); //$NON-NLS-1$
		sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
		sb.append('.');
		sb.append(condition.getResult());
		sb.append('.');
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_CRUD_CONDITION);
		sb.append('.');
		sb.append(condition.getColumnName());
		if (!StringUtils.isEmpty(condition.getOption())) {
			sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
			sb.append(condition.getOption());
		}
		sb.append("\"/>"); //$NON-NLS-1$
		element.attr("value", sb.toString()); //$NON-NLS-1$
	}

	private static Map<String, String> createOperatorMap(ConditionBean condition) {
		Map<String, String> operatorMap = new HashMap<String, String>();
		StringBuilder sb = new StringBuilder();
		sb.setLength(0);
		sb.append(condition.getResult());
		sb.append('.');
		sb.append(condition.getColumnName());
		operatorMap.put(sb.toString(), condition.getOperator());
		return operatorMap;
	}

	private void updateAttributeForBind(BindBean bind, Element element) {
		String format = null;
		boolean isEscape = true;

		if (element.tagName().equalsIgnoreCase("input") || element.tagName().equalsIgnoreCase("textarea")) { //$NON-NLS-1$ //$NON-NLS-2$
			setBindInputTagName(bind, element);
		}

		// format
		format = bind.getFormat();
		if (element.hasText()) {
			if (element.text().contains(GeneratorConstants.HTML_REPLACE_STRING)) {
				format = element.text();
			}
		}
		if (StringUtils.isEmpty(format)) {
			format = GeneratorConstants.HTML_REPLACE_STRING;
		}
		if (format.contains(GeneratorConstants.HTML_ORIGINAL)) {
			// replace {_ORIGINAL}
			if (GeneratorConstants.HTML_BIND_TARGET_TEXT.equalsIgnoreCase(bind.getTarget())) {
				format = format.replace(GeneratorConstants.HTML_ORIGINAL, element.text());
			} else {
				format = format.replace(GeneratorConstants.HTML_ORIGINAL, element.attr(bind.getTarget()));
			}
		}

		// escape
		isEscape = Boolean.valueOf(bind.getEscape());

		// arg or args
		StringBuilder sb = new StringBuilder();
		String value = null;
		if (bind.getArgs() != null && bind.getArgs().size() > 0) {
			String[] args = new String[bind.getArgs().size()];
			for (int i = 0; i < bind.getArgs().size(); i++) {
				args[i] = getBindProperty(bind.getArgs().get(i), isEscape);
			}
			value = MessageFormat.format(format, (Object[])args);
		} else {
			if (bind.getArg() != null) {
				value = MessageFormat.format(format, getBindProperty(bind.getArg(), isEscape));
			}else{
				value = format;
			}
		}

		// update text or attribute
		if (!StringUtils.isEmpty(bind.getTarget())) {
			if (GeneratorConstants.HTML_BIND_TARGET_TEXT.equalsIgnoreCase(bind.getTarget())) {
				if (!element.text().contains("s:property")){ //$NON-NLS-1$
					// clear text
					element.text(""); //$NON-NLS-1$
				}
				sb.setLength(0);
				sb.append(element.text());
				sb.append(value);
				element.text(sb.toString());
			} else {
				if (GeneratorConstants.HTML_BIND_TARGET_INNER.equalsIgnoreCase(bind.getTarget())) {
					element.html(value);
				} else {
					if (!element.attr(bind.getTarget()).contains("s:property")){ //$NON-NLS-1$
						// clear attribute
						element.removeAttr(bind.getTarget());
					}
					sb.setLength(0);
					sb.append(element.attr(bind.getTarget()));
					sb.append(value);
					element.attr(bind.getTarget(), sb.toString());
				}
			}
		}
	}

	private static void setBindInputTagName(BindBean bind, Element element) {
		// result.table.column
		String[] args = bind.getArg().split(GeneratorConstants.HTML_GENERATOR_DELIMITER);
		StringBuilder sb = new StringBuilder();
		sb.append(args[0]);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_CRUD_COLUMN);
		sb.append(ControllerConstants.REQUEST_PARAM_NAME_DELIMITER);
		sb.append(args[2]);
		element.attr("name", sb.toString()); //$NON-NLS-1$
	}

	private String getBindProperty(String arg, boolean isEscape) {
		StringBuilder sb = new StringBuilder();
		String[] args = arg.split(GeneratorConstants.HTML_GENERATOR_DELIMITER);
		if (args.length == 2) {
			sb.append("<s:property value=\""); //$NON-NLS-1$
			sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
			sb.append('.');
			sb.append(arg);
			sb.append("\""); //$NON-NLS-1$
			if (!isEscape){
				sb.append(GeneratorConstants.JSP_PROPERTY_ESCAPE);
			}
			sb.append("/>"); //$NON-NLS-1$
		} else {
			if (!this._iteratorResultNames.contains(args[0])) {
				sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
				sb.append('.');
				sb.append(args[0]);
			}
			if (sb.length() > 0) {
				sb.append('.');
			}
			sb.append(args[2]);

			sb.append("\""); //$NON-NLS-1$
			if (!isEscape){
				sb.append(GeneratorConstants.JSP_PROPERTY_ESCAPE);
			}
			sb.append("/>"); //$NON-NLS-1$
			sb.insert(0, "<s:property value=\""); //$NON-NLS-1$
		}

		return sb.toString();
	}

	private void addIteratorResultNames(List<AbstractFunctionBean> functions) {
		for (AbstractFunctionBean function : functions) {
			String method = function.getMethod();
			if (CrudEnum.CRUD_SELECT_ALL_BY_EXAMPLE.getMethod().equalsIgnoreCase(method)
					|| CrudEnum.CRUD_SELECT_BY_EXAMPLE.getMethod().equalsIgnoreCase(method)) {
				// Multiple Result
				if (!this._iteratorResultNames.contains(function.getResult())) {
					this._iteratorResultNames.add(function.getResult());
				}
			}
		}
	}

	private static void addIteratorElement(Element element, String resultName) {
		StringBuilder sb = new StringBuilder();
		sb.setLength(0);
		sb.append("<s:iterator value=\"%{"); //$NON-NLS-1$
		sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
		sb.append('.');
		sb.append(resultName);
		sb.append("}\" "); //$NON-NLS-1$
		sb.append("status=\""); //$NON-NLS-1$
		sb.append(GeneratorConstants.JSP_ITERATOR_STATUS);
		sb.append("\" "); //$NON-NLS-1$
		sb.append("var=\""); //$NON-NLS-1$
		sb.append(GeneratorConstants.JSP_ITERATOR_VAR);
		sb.append("\">"); //$NON-NLS-1$
		sb.append("</s:iterator>"); //$NON-NLS-1$
		element.wrap(sb.toString());
	}

	private void addCaseElement(CaseBean caseInfo, Element element) {
		StringBuilder sb = new StringBuilder();
		if (GeneratorConstants.HTML_CASE_TYPE_SIZE.equalsIgnoreCase(caseInfo.getType())) {
			sb.append("<s:if test=\""); //$NON-NLS-1$
			sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
			sb.append('.');
			sb.append(caseInfo.getArgs().get(0));
			sb.append(".size"); //$NON-NLS-1$
			sb.append(getOperatorString(caseInfo.getArgs().get(1)));
			sb.append(caseInfo.getArgs().get(2));
			sb.append("\">"); //$NON-NLS-1$
			sb.append("</s:if>"); //$NON-NLS-1$
		} else {
			String[] target = caseInfo.getArgs().get(0).split(GeneratorConstants.HTML_GENERATOR_DELIMITER);
			String key = null;

			sb.append("<s:if test=\""); //$NON-NLS-1$
			if (target.length == 2) {
				key = target[1];
				sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
				sb.append('.');
				sb.append(target[0]);
				sb.append('.');
			} else {
				key = target[2];
				if (!this._iteratorResultNames.contains(target[0])) {
					sb.append(GeneratorConstants.JSP_PROPERTY_ROOT_NAME);
					sb.append('.');
					sb.append(target[0]);
					sb.append('.');
				}
				sb.append(target[1]);
				sb.append('.');
			}

			if (GeneratorConstants.HTML_CASE_NULL.equalsIgnoreCase(caseInfo.getArgs().get(2).trim())) {
				sb.append("containsKey('"); //$NON-NLS-1$
				sb.append(key);
				sb.append("')"); //$NON-NLS-1$
			} else {
				sb.append(key);
				sb.append(getOperatorString(caseInfo.getArgs().get(1)));
				sb.append(caseInfo.getArgs().get(2));
			}
			sb.append("\">"); //$NON-NLS-1$
			sb.append("</s:if>"); //$NON-NLS-1$
		}
		// TODO:wrap だけなく、innerも必要かもしれない
		element.wrap(sb.toString());
	}

	private static void removeSiblingElement(Element element) {
		Elements siblings = element.siblingElements();
		for (Element e : siblings) {
			if (StringUtils.isEmpty(e.attr(GeneratorConstants.HTML_DATA_ATTR_NAME_ITERATOR))) {
				e.remove();
			}
		}
	}

	private static String getOperatorString(String operator) {
		String result = "="; //$NON-NLS-1$
		if ("eq".equalsIgnoreCase(operator)) { //$NON-NLS-1$
			result = " == "; //$NON-NLS-1$
		} else if ("ne".equalsIgnoreCase(operator)) { //$NON-NLS-1$
			result = " != "; //$NON-NLS-1$
		} else if ("gt".equalsIgnoreCase(operator)) { //$NON-NLS-1$
			result = " > "; //$NON-NLS-1$
		} else if ("ge".equalsIgnoreCase(operator)) { //$NON-NLS-1$
			result = " >= "; //$NON-NLS-1$
		} else if ("lt".equalsIgnoreCase(operator)) { //$NON-NLS-1$
			result = " < "; //$NON-NLS-1$
		} else if ("le".equalsIgnoreCase(operator)) { //$NON-NLS-1$
			result = " <= "; //$NON-NLS-1$
		}
		return result;
	}

	private void saveJspFile(Document document, String outputPath) throws IOException {
		File outputFile = getOutputFile(outputPath);
		OutputSettings outputSettings = new OutputSettings();
		outputSettings.indentAmount(2);
		outputSettings.prettyPrint(false);
		document.outputSettings(outputSettings);
		FileUtils.writeFile(outputFile, getContent(document), GeneratorConstants.OUTPUT_JSP_FILE_ENCODING);
	}

	private static File getOutputFile(String outputRelativePath) {
		StringBuilder sb = new StringBuilder();
		sb.append(GeneratorUtils.getOutputJspPath());
		sb.append(outputRelativePath);
		return new File(sb.toString());
	}

	private static String getContent(Document document) {
		StringBuilder sb = new StringBuilder();

		// jsp directive
		sb.append("<%@ page contentType=\"text/html; charset="); //$NON-NLS-1$
		sb.append(GeneratorConstants.OUTPUT_JSP_FILE_ENCODING);
		sb.append("\" "); //$NON-NLS-1$
		sb.append("pageEncoding=\""); //$NON-NLS-1$
		sb.append(GeneratorConstants.OUTPUT_JSP_FILE_ENCODING);
		sb.append("\"%>"); //$NON-NLS-1$
		OutputUtilities.newLine(sb);
		sb.append("<%@ taglib prefix=\"s\" uri=\"/struts-tags\"%>"); //$NON-NLS-1$
		OutputUtilities.newLine(sb);

		// from html
		sb.append(StringEscapeUtils.unescapeXml(document.outerHtml()));

		return sb.toString();
	}
}
