package com.udapsoft.modular.web.ibsheet7;

import java.beans.PropertyEditor;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import kr.co.hsnc.common.config.WAFConfig;
import kr.co.hsnc.common.logger.Logger;
import kr.co.hsnc.common.sql.Row;
import kr.co.hsnc.common.sql.RowSet;
import kr.co.hsnc.common.util.ValueObject;

import org.jdom.CDATA;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.web.bind.ServletRequestUtils;

import com.udapsoft.modular.core.exception.ModularException;
import com.udapsoft.modular.core.util.StringUtils;
import com.udapsoft.modular.web.ibsheet7.exception.IBSheet7MessageException;
import com.udapsoft.modular.web.ibsheet7.exception.IBSheet7RenderingException;
import com.udapsoft.modular.web.ibsheet7.util.IBSheet7Utils;


public class IBSheet7Factory {
	private static final String TAG_MULTI_SHEET = "MULTISHEET";
	private static final String TAG_SHEET = "SHEET";
	private static final String TAG_ETCDATA = "ETC-DATA";
	private static final String TAG_ETC = "ETC";
	private static final String TAG_MULTI_RESULT = "MULTIDATA";
	private static final String TAG_MESSAGE = "MESSAGE";
	private static final String TAG_DATA = "DATA";
	private static final String ATTR_TOTAL = "TOTAL";
	private static final String ATTR_COLORDER = "COLORDER";
	private static final String ATTR_COLSEPARATOR = "COLSEPARATOR";
	private static final String ATTR_KEY = "KEY";
	private static final String TAG_TR = "TR";
	private static final String TAG_TD = "TD";
	private static final String TAG_RESULT = "RESULT";
	private static final String ATTR_CODE = "CODE";
	private static final String ATTR_MESSAGE = "MESSAGE";
	private static final String VAL_OK = "0";
	private String encoding = "utf-8";
	private String colSeparator;
	private Map<?, ?> customEditors;
	
	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	public void setColSeparator(String colSeparator) {
		this.colSeparator = colSeparator;
	}

	public Map<?, ?> getCustomEditors() {
		return this.customEditors;
	}

	public void setCustomEditors(Map<?, ?> customEditors) {
		this.customEditors = customEditors;
	}
	
	public static Document createIBSheet7(ValueObject model, HttpServletRequest request) throws Exception {
		IBSheet7Factory factory = new IBSheet7Factory();
		return factory.renderMergedOutputModel(model, request);
	}
	
	  
	protected Document renderMergedOutputModel(ValueObject model, HttpServletRequest request) throws Exception {
		Format format = Format.getRawFormat();
		format.setEncoding(this.encoding);

		XMLOutputter outputter = new XMLOutputter();
		outputter.setFormat(format);
		Document doc = new Document();

		String commandType = ServletRequestUtils.getStringParameter(request, "modular.web.ibsheet7.command", "search");
		String sheetId = ServletRequestUtils.getStringParameter(request, "modular.web.ibsheet7.id", "");
		String extProps = ServletRequestUtils.getStringParameter(request, "modular.web.ibsheet7.extend", "");
		
		if (commandType.equals("search")) {
			renderSearchResult(doc, model, request, sheetId, extProps);
			
		} else if (commandType.equals("multiSearch")) {
			String[] sheetIdArray = sheetId.split("\\^");
			renderSearchResult(doc, model, request, sheetIdArray, extProps, true);
			
		} else if (commandType.equals("save")) {
			renderSaveResult(doc, model, request, sheetId);
			
		} else if (commandType.equals("multiSave")) {
			String[] sheetIdArray = sheetId.split("\\^");
			renderSaveResult(doc, model, request, sheetIdArray, true);
			
		} else {
			throw new ModularException("can't handle a command type [" + commandType + "]");
		}
		
		if (kr.co.hsnc.common.util.tracer.Logger.isLogging) {
			IBSheet7Utils.printDebugXml(doc, outputter, this.encoding);
		}
		
		return doc;
	}

	protected void renderSearchResult(Document doc, ValueObject model, HttpServletRequest request, String sheetId, String extendProps) {
		String[] sheetIdArray = { sheetId };
		renderSearchResult(doc, model, request, sheetIdArray, extendProps, false);
	}

	protected void renderSearchResult(Document doc, ValueObject model, HttpServletRequest request, String[] sheetIdArray, String extendProps, boolean isMulti) {
		if ((isMulti) && (sheetIdArray.length <= 1)) {
			throw new IBSheet7RenderingException("MultiSearch 모드에는 sheetId가 2개 이상 필요합니다.");
		}

		Map<String, String> extendPropsMap = bindExtendProperties(extendProps);
		if ((model != null) && (model.size() > 0)) {
			Element root = null;
			if (isMulti) {
				root = new Element(TAG_MULTI_SHEET);
				doc.addContent(root);
			}
			String columns = "";
			for (int i = 0; i < sheetIdArray.length; i++) {
				Element sheet = new Element(TAG_SHEET);
				if (isMulti) {
					root.addContent(sheet);
					columns = ServletRequestUtils.getStringParameter(request, sheetIdArray[i] + "." + "modular.web.ibsheet7.columns", "");
				} else {
					doc.addContent(sheet);
					columns = ServletRequestUtils.getStringParameter(request, "modular.web.ibsheet7.columns", "");
				}
				
				if (i == 0) {
					Object messageValue = model.getObject("modular.web.ibsheet7.message");
					if ((messageValue != null) && ((messageValue instanceof IBSheet7MessageException))) {
						Element message = new Element(TAG_MESSAGE);
						sheet.addContent(message);

						IBSheet7MessageException mex = (IBSheet7MessageException) messageValue;
						message.addContent(mex.getMessage());
					}
				}
				
				Object etcValue = model.getObject(sheetIdArray[i]+ ".modular.web.ibsheet7.etcdata");
				if (etcValue != null) {
					Element etcData = new Element(TAG_ETCDATA);
					sheet.addContent(etcData);
					makeEtcDataElement(etcData, etcValue);
				}
				Element data = new Element(TAG_DATA);
				sheet.addContent(data);
				if (StringUtils.hasText(this.colSeparator)) {
					data.setAttribute(ATTR_COLSEPARATOR, this.colSeparator);
				}
				
				Object value = model.getObject(sheetIdArray[i]);
				if (value != null) {
					RowSet rs = (RowSet) value;
					data.setAttribute(ATTR_TOTAL, String.valueOf(rs.size()));
					for (int rsIdx = 0; rsIdx < rs.size(); rsIdx++) {
						makeDataElement(data, columns, rs.getRow(rsIdx), extendPropsMap);
					}
					
				} else {
					Logger.warn.print("can't find a sheetId[" + sheetIdArray[i] + "] form result object");
				}
			}
		}
	}

	
	protected void renderSaveResult(Document doc, ValueObject model, HttpServletRequest request, String sheetId) {
		String[] sheetIdArray = { sheetId };
		renderSaveResult(doc, model, request, sheetIdArray, false);
	}
	
	protected void renderSaveResult(Document doc, ValueObject model, HttpServletRequest request, String[] sheetIdArray, boolean isMulti) {
		Element root = null;
		if (isMulti) {
			root = new Element(TAG_MULTI_RESULT);
			doc.addContent(root);
		}
		for (int i = 0; i < sheetIdArray.length; i++) {
			Element sheet = new Element(TAG_SHEET);
			if (isMulti) {
				root.addContent(sheet);
			} else {
				doc.addContent(sheet);
			}
			Object etcValue = model.getObject(sheetIdArray[i]+ ".modular.web.ibsheet7.etcdata");
			if (etcValue != null) {
				Element etcData = new Element(TAG_ETCDATA);
				sheet.addContent(etcData);
				makeEtcDataElement(etcData, etcValue);
			}
			Element result = new Element(TAG_RESULT);
			sheet.addContent(result);

			Object messageValue = model.get("modular.web.ibsheet7.message");
			if ((messageValue != null) && ((messageValue instanceof IBSheet7MessageException))) {
				IBSheet7MessageException mex = (IBSheet7MessageException) messageValue;
				result.setAttribute(ATTR_CODE, Integer.toString(mex.getCode()));
				result.setAttribute(ATTR_MESSAGE, mex.getMessage());
			} else {
				result.setAttribute(ATTR_CODE, VAL_OK);
				result.setAttribute(ATTR_MESSAGE, "");
			}
		}
	}
	
	private void makeDataElement(Element data, String columns, Object obj, Map<String, String> extendPropsMap) {
		String[] columnsArray = columns.split(",");

		String colorder = columns.replaceAll(",", "|");
		data.setAttribute(ATTR_COLORDER, colorder);

		Element tr = new Element(TAG_TR);
		
		if (obj instanceof Row) {
			Row row = (Row) obj;
			makeExtendAttribute(tr, extendPropsMap, row);
			
			for (int i = 0; i < columnsArray.length; i++) {
				Element td = new Element(TAG_TD);
				String columnNm = columnsArray[i];
				if (("sStatus".equals(columnNm)) || (columnNm.startsWith(WAFConfig.get("modular.ibsheet7.column.attribute_prefix")))) {
					td.setText("");
				} else {
					Object columnVal = null;
					try {
						columnVal = row.get(columnNm);
					} catch (IllegalArgumentException e) {
						Logger.warn.print(e.getMessage());
					}
					if (columnVal != null) {
						Class<? extends Object> columnType = columnVal.getClass();
						bindColumnValue(td, columnNm, columnVal, columnType);
					}
				}
				tr.addContent(td);
			}
		}
		data.addContent(tr);
	}

	private void makeExtendAttribute(Element tr, Map<String, String> extendPropsMap, Object obj) {
		for (Iterator<?> iterator = extendPropsMap.entrySet().iterator(); iterator.hasNext();) {
			Map.Entry entry = (Map.Entry) iterator.next();
			String attributeName = (String) entry.getKey();
			String propertyName = (String) entry.getValue();
			PropertyEditor pe = null;
			Object val = null;
			if (!attributeName.startsWith("format.")) {
				try {
					Object columnVal = null;
					Class<? extends Object> columnType = null;
					if (obj instanceof Row) {
						Row row = (Row) obj;
						columnVal = row.get(propertyName);
						columnType = columnVal == null ? Object.class : columnVal.getClass();
					} else {
						BeanWrapper bean = PropertyAccessorFactory.forBeanPropertyAccess(obj);
						columnVal = bean.getPropertyValue(propertyName);
						columnType = bean.getPropertyType(propertyName);
					}
					if (this.customEditors != null) {
						pe = (PropertyEditor) this.customEditors.get(columnType);
					}
					val = columnVal;
					
				} catch (Exception e) {
					Logger.err.print("IBSheet7 View 결과 처리 중 오류가 발생하였습니다." + e.getMessage());
				}
				
				if (pe != null) {
					pe.setValue(val);
					tr.setAttribute(attributeName, val == null ? "" : pe.getAsText());
				} else {
					tr.setAttribute(attributeName, val == null ? "" : val.toString());
				}
			}
		}
	}

	private void bindColumnValue(Element td, String columnNm, Object columnVal, Class<? extends Object> columnType) {
		PropertyEditor pe = null;
		try {
			if (this.customEditors != null) {
				pe = (PropertyEditor) this.customEditors.get(columnType);
			}
		} catch (Exception e) {
			Logger.err.print("IBSheet7 View 결과 처리 [" + columnNm + "]중 오류가 발생하였습니다." + e.getMessage());
		}
		if (pe != null) {
			pe.setValue(columnVal);
			td.setText(columnVal == null ? "" : pe.getAsText());
		} else {
			String tdVal = columnVal == null ? "" : columnVal.toString();
			tdVal = tdVal.replaceAll("\r\n", "\n");

			td.setContent(new CDATA(tdVal));
		}
	}

	private Map<String, String> bindExtendProperties(String extendProps) {
		Map<String, String> extendPropsMap = new HashMap<String, String>();
		String[] values = StringUtils.commaDelimitedListToStringArray(extendProps);
		for (int i = 0; i < values.length; i++) {
			String value = values[i];
			if (StringUtils.hasText(value)) {
				String[] v = StringUtils.delimitedListToStringArray(value.trim(), "=");
				if ((v.length >= 2) && (StringUtils.hasText(v[0]))) {
					extendPropsMap.put(v[0].trim(), v[1]);
				}
			}
		}
		return extendPropsMap;
	}

	private void makeEtcDataElement(Element etcdata, Object obj) {
		if (obj instanceof Map) {
			Map<?, ?> map = (Map<?, ?>) obj;
			for (Iterator<?> it = map.entrySet().iterator(); it.hasNext();) {
				Element etc = new Element(TAG_ETC);
				
				Map.Entry entry = (Map.Entry) it.next();
				String attributeName = (String) entry.getKey();
				String propertyName = (String) entry.getValue();
				
				etc.setAttribute(ATTR_KEY, attributeName);
				etc.setText(propertyName);
				
				etcdata.addContent(etc);
			}
		}
	}

	private String getColumnValue(String columnNm, Object columnVal, Class<?> columnType) {
		PropertyEditor pe = null;
		try {
			if (this.customEditors != null) {
				pe = (PropertyEditor) this.customEditors.get(columnType);
			}
		} catch (Exception e) {
			Logger.err.print("IBSheet7 View 결과 처리 [" + columnNm + "]중 오류가 발생하였습니다." + e.getMessage());
		}
		String tdVal = "";
		if (pe != null) {
			pe.setValue(columnVal);
			tdVal = columnVal == null ? "" : pe.getAsText();
		} else {
			tdVal = columnVal == null ? "" : columnVal.toString();
			tdVal = tdVal.replaceAll("\r\n", "\n");
		}
		return tdVal;
	}
}
