package com.eactive.eai.message;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;

import com.eactive.eai.common.util.StringUtil;
import com.eactive.eai.data.entity.onl.stdmessage.StandardMessageLayoutItem;
import com.eactive.eai.transformer.util.ByteUtil;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.commons.lang3.StringUtils;
import org.json.simple.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class StandardItem implements Serializable, Cloneable {
	private static final long serialVersionUID = -4430144517271735912L;
	static Logger logger = LoggerFactory.getLogger(StandardItem.class);
	LinkedHashMap<String , StandardItem> childs = new LinkedHashMap<String , StandardItem>();
	ArrayList<LinkedHashMap<String , StandardItem>> list = new ArrayList<LinkedHashMap<String , StandardItem>>();
	
	ArrayList<String> values = new ArrayList<String>();
	
	// FIX-ME : check encoding
	// String <-> byte[] charset
//	private static String encode = "ms949"; //"euc-kr";
//	private static String encodeXml = "utf-8";
//	private static String encodeJson = "utf-8";
	
	public static int ITEM_COUNT = 11;
	String name;
	int level; // 0 : root  ~ n
	int type; // 1: Message, 2: Field, 3:Group,  4 : Grid, 5 : Field Array, 9 : BIZ DATA	
	int size; // array size
	int fieldType; // 1: ELEMENT, 2: ATTRIBUTE,
	int length;
	int dataType; // 1: String, 2: Number, 11: ZZ String, 12: Length Number
	String refPath;
	String refValue;
	String value;
	String desc = "";
	boolean isHidden =false;
	
	byte[] bytesValue = null;
	
	// configuration options
	// if data is null, convert to empty-space
	private static boolean NULL_TO_SPACE = true;
	
	// if add json root item with message name 
	private static boolean ADD_JSON_ROOT = false;
	
	static {
		resetSystemParameters();
	}
	public static void resetSystemParameters() {
		try {
			NULL_TO_SPACE = Boolean.parseBoolean( System.getProperty("standard.message.nulltospace", "true") );
		}
		catch(Exception ex) {
			;
		}
		try {
			ADD_JSON_ROOT = Boolean.parseBoolean( System.getProperty("standard.message.addjsonroot", "false") );
		}
		catch(Exception ex) {
			;
		}
	}
	
	public StandardItem() {

	}
	
	public StandardItem(String name, int level, int type, int size, int fieldType, int length, 
			int dataType, String refPath, String refValue, String value, String desc) {
		super();
		initValues(name, level, type, size, fieldType, length, dataType, refPath, refValue, value, desc);
	}
	
	public StandardItem(String name, int level, int type, int size, int length, int dataType, String value, String desc) {
		super();
		initValues(name, level, type, size, 0, length, dataType, null, null, value, desc);
	}
	public StandardItem(String name, int level, int type, int size, int length, int dataType, String value) {
		super();
		initValues(name, level, type, size, 0, length, dataType, null, null, value, null);
	}
	
	public StandardItem(String name, int level, int type, int length, int dataType, String value, String desc) {
		super();
		initValues(name, level, type, 1, 0, length, dataType, null, null, value, desc);
	}
	
	public StandardItem(String name, int level, int type, int length, int dataType, String value) {
		super();
		initValues(name, level, type, 1, 0, length, dataType, null, null, value, null);
	}
	
	public void initValues(String name, int level, int type, int size, int fieldType, int length, 
			int dataType, String refPath, String refValue, String value, String desc) {
		this.name = name;
		this.level = level;
		this.type = type;
		this.size = size;
		this.fieldType = fieldType;
		this.length = length;
		this.dataType = dataType;
		this.refPath = refPath;
		this.refValue = refValue;
		setValue(value);
		if(desc != null) this.desc = desc;
	}
	
	public StandardItem(String[] values) throws Exception {
		if(values == null || values.length < ITEM_COUNT-1) {
			throw new Exception("invalid StandardItem defination values : " + (values==null? "":values.length)); 
		}		
		this.name = values[0].trim();
		this.level = Integer.parseInt(values[1].trim());
		this.type = Integer.parseInt(values[2].trim());
		this.size = Integer.parseInt(values[3].trim());;
		this.fieldType = Integer.parseInt(values[4].trim());
		this.length = Integer.parseInt(values[5].trim());;
		this.dataType = Integer.parseInt(values[6].trim());
		this.refPath = values[7].trim();
		this.refValue = values[8].trim();
		setValue(values[9]);
		if(values.length == ITEM_COUNT) {
			setDesc(values[10].trim());
		}
		else {
			setDesc(values[0].trim());
		}
	}

	public StandardItem(StandardMessageLayoutItem entityItem) {
		this.name = entityItem.getItemName();
		this.level = entityItem.getItemLevel();
		this.type = entityItem.getItemType();
		this.size = entityItem.getArraySize();
		this.fieldType = entityItem.getFieldType();
		this.length = entityItem.getDataLength();
		this.dataType = entityItem.getDataType();
		this.refPath = entityItem.getRefFieldPath();
		this.refValue = entityItem.getRefValue();
		setValue(entityItem.getDefaultValue());
		setDesc(entityItem.getItemDesc());
	}
	
	public void addItem (StandardItem item) {
		childs.put(item.getName(), item);
	}
	
	public void addArrayItem(int index, StandardItem item) {
		if( !childs.containsKey(item.getName()) ) {
			childs.put(item.getName(), item);
		}
		// add last
		if(list.size() == index) {
			LinkedHashMap<String , StandardItem> items = new LinkedHashMap<String , StandardItem>();
			items.put(item.getName(), item);
			list.add(items);
		}
		else if(list.size() > index) {
			list.get(index).put(item.getName(), item);
		}
		else {
			logger.debug("index too big : index={} / array size={}", index, list.size());
		}
	}
	public void addArray(LinkedHashMap<String , StandardItem> items) {
		list.add(items);
	}
	
	public LinkedHashMap<String, StandardItem> getArrayChilds(int index, boolean createNew) {
		if(index == list.size()) {
//			logger.debug("@@ add cloned childs in array item={}, index={}", this.name, index);
			if(createNew) {
				list.add((LinkedHashMap<String , StandardItem>)(SerializationUtils.clone(childs)) );
			}
			else {
				return null;
			}
		}
		else if(index > list.size()) {
			return null;
		}
		return list.get(index);
	}
	
	public LinkedHashMap<String, StandardItem> getChilds() {
		return childs;
	}
	
	public ArrayList<StandardItem> getChildsList() {
		ArrayList<StandardItem> childList = new ArrayList<StandardItem>(childs.values());
		return childList;
	}
	
	public void setChilds(LinkedHashMap<String, StandardItem> childs) {
		this.childs = childs;
	}
	
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	public int getLevel() {
		return level;
	}

	public void setLevel(int level) {
		this.level = level;
	}

	public int getType() {
		return type;
	}
	public void setType(int type) {
		this.type = type;
	}
	
	public int getFieldType() {
		return fieldType;
	}

	public void setFieldType(int fieldType) {
		this.fieldType = fieldType;
	}
	
	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public int getLength() {
		return length;
	}
	public void setLength(int length) {
		this.length = length;
	}
	public int getDataType() {
		return dataType;
	}
	public void setDataType(int dataType) {
		this.dataType = dataType;
	}
	
	public String getRefPath() {
		return refPath;
	}

	public void setRefPath(String refPath) {
		this.refPath = refPath;
	}

	public String getRefValue() {
		return refValue;
	}

	public void setRefValue(String refValue) {
		this.refValue = refValue;
	}
	
	public String getDesc() {
		return desc;
	}

	public void setDesc(String desc) {
		this.desc = desc;
	}

	public String getValue() {
		return value;
	}
	
	public boolean isHidden() {
		return isHidden;
	}

	public void setHidden(boolean isHidden) {
		this.isHidden = isHidden;
	}
	
	protected String toTypeValue(String svalue) {
		if(svalue == null) return svalue;

		if(dataType == StandardDataType.STRING || dataType == StandardDataType.ZZ_STRING) {
			return svalue;
		}
		else {
			char[] charValues = svalue.trim().toCharArray();
			int i = 0;
			for(; i< charValues.length; i++) {
				if(charValues[i] == '0' || charValues[i] == ' ') {
					continue;
				}
				break;
			}
			return new String(charValues, i, (charValues.length - i));
		}
	}
	
	public void setValue(String value) {
		this.value = toTypeValue(value);
		this.bytesValue = null; //value와 bytesValue의 불일치 때문 jun's 20231101
	}

	public void addValue(String value) {
		values.add(toTypeValue(value));		
	}

	public void addBytesValue(byte[] bytesValue) {
//		this.bytesValue = bytesValue;
		String svalue = null;
		try {
			String encoding = EncodingVar.flatEncoding;
//			logger.debug("Encoding : {}", encoding );
			svalue = new String(bytesValue, encoding);				
		} catch (UnsupportedEncodingException e) {
			svalue = new String(bytesValue);
		}
		addValue(toTypeValue(svalue));
	}
	
//	public byte[] getBytesValue() {
//		if(bytesValue != null) {
//			return bytesValue;
//		}
//		else {
//			try {
//				String encoding = EncodingVar.flatEncoding;
////				logger.debug("Encoding : {}", encoding );
//				this.bytesValue = value.getBytes(encoding);
//			} catch (UnsupportedEncodingException e) {
//				this.bytesValue = value.getBytes();
//			}
//			return bytesValue;
//		}
//	}
	
	public byte[] getBytesValue(String charset) {
		if (bytesValue == null || bytesValue.length == 0) {
			if(value == null) return new byte[0];
			try {
//				logger.debug("Encoding : {}", charset );
				this.bytesValue = value.getBytes(charset);
			} catch (UnsupportedEncodingException e) {
				this.bytesValue = value.getBytes();
			}
			return bytesValue;
		}
		else {
			return bytesValue;
		}		
	}
	
	public void setBytesValue(byte[] bytesValue) {
		this.bytesValue = bytesValue;
	}
	
	public void setBytesValue(byte[] bytesValue, String charset) {
		try {			
			logger.debug("charset : {}", charset );
			this.value = toTypeValue(new String(bytesValue, charset));
		} catch (UnsupportedEncodingException e) {
			this.value = toTypeValue(new String(bytesValue));
		}
		
	}
	
	protected String getLevelIndent() {
		StringBuilder sb = new StringBuilder();
		for(int i=0; i<level; i++) {
			sb.append("  ");
		}
		return sb.toString();
	}
	
	protected String getLevelTreeIndent() {
		StringBuilder sb = new StringBuilder();
		if(level > 0) {
			for(int i=0; i<level-1; i++) {
				sb.append("┃");
			}
			sb.append("┣");
		}
		return sb.toString();
	}
	
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append(getLevelTreeIndent()).append("StandardItem [name=" + name + ", level=" + level 
				+ ", type=" + type +", size=" + size + ", fieldType=" + fieldType +", length=" + length + ", dataType=" + dataType
				+ ", refPath=" + refPath + ", refValue=" + refValue
				+ ", value=" + value
				+ ", isHidden=" + isHidden
				+ ", desc=" + desc + "]");
		Iterator<String> keyIter = null;;
		switch(getType())  {
			case StandardType.MESSAGE :
				keyIter = childs.keySet().iterator();
				while(keyIter.hasNext()) {
					String key = keyIter.next();
					StandardItem item = childs.get(key);
					sb.append("\n").append(item.toString());
				}
				break;
//			case StandardType.FIELD :
//				break;
			case StandardType.GROUP :
				if (isHidden) break;				
				keyIter = childs.keySet().iterator();
				while(keyIter.hasNext()) {
					String key = keyIter.next();
					StandardItem item = childs.get(key);
					sb.append("\n").append(item.toString());
				}
				break;
			case StandardType.GRID :
				if (isHidden) break;				
				for(int p=0; p<list.size(); p++) {
					LinkedHashMap<String , StandardItem> group = list.get(p);
					keyIter = group.keySet().iterator();
					while(keyIter.hasNext()) {
						String key = keyIter.next();
						StandardItem item = group.get(key);
						sb.append("\n").append(item.toString());
					}				
				}
				break;
//			case StandardType.FARRAY :
//				break;
//			case StandardType.BIZDATA :
//				break;
			default :
				break;
		}
		return sb.toString();
	}
	
	public String toCSVString() {
		StringBuilder sb = new StringBuilder();
		
		if(level == 0) {
			sb.append("#name,level,type,size,fieldType,length,dataType,refPath,refValue,value,desc\n");
		}
		sb.append(name);
		sb.append(",");
		sb.append(level);
		sb.append(",");
		sb.append(type);
		sb.append(",");
		sb.append(size);
		sb.append(",");
		sb.append(fieldType);
		sb.append(",");
		sb.append(length);
		sb.append(",");
		sb.append(dataType);
		sb.append(",");
		sb.append(refPath);
		sb.append(",");
		sb.append(refValue);
		sb.append(",");
		sb.append(value);
		sb.append(",");
		sb.append(desc);
		sb.append("\n");
		Iterator<String> keyIter = childs.keySet().iterator();
		while(keyIter.hasNext()) {
			String key = keyIter.next();
			StandardItem item = childs.get(key);
			sb.append(item.toCSVString());
		}
		return sb.toString();
	}
	
	public ArrayList<StandardItem> toList() {
		ArrayList<StandardItem> list = new ArrayList<StandardItem>();
		ArrayList<StandardItem> childList = null;
		switch(getType())  {
			case StandardType.MESSAGE :
				list.add(this);
				childList = new ArrayList<StandardItem>(childs.values());
				for(int i=0; i<childList.size(); i++) {
					list.addAll(childList.get(i).toList());
				}
				break;
			case StandardType.FIELD :
				list.add(this);
				break;
			case StandardType.GROUP :
				if (isHidden) break;
				list.add(this);
				childList = new ArrayList<StandardItem>(childs.values());
				for(int i=0; i<childList.size(); i++) {
					list.addAll(childList.get(i).toList());
				}
				break;
			case StandardType.GRID :
				if (isHidden) break;
				list.add(this);
				int arraySize = getSize();
				for(int ai=0;ai<arraySize;ai++) {
					LinkedHashMap<String, StandardItem> map = getArrayChilds(ai, false);
					childList = new ArrayList<StandardItem>(map.values());
					for(int i=0; i<childList.size(); i++) {
						list.addAll(childList.get(i).toList());
					}
				}
				break;
			case StandardType.FARRAY :
				list.add(this);
				break;
			case StandardType.BIZDATA :
				list.add(this);
				break;
			default :
				break;
		}
		return list;
	}
	
	private StandardLogItem toLogItem(StandardItem item) {
		StandardLogItem logItem = new StandardLogItem();
		logItem.setName       ( item.getName     ()  );
		logItem.setLevel      ( item.getLevel    ()  );
		logItem.setType       ( item.getType     ()  );
		logItem.setSize       ( item.getSize     ()  );
		logItem.setFieldType  ( item.getFieldType()  );
		logItem.setLength     ( item.getLength   ()  );
		logItem.setDataType   ( item.getDataType ()  );
		logItem.setRefPath    ( item.getRefPath  ()  );
		logItem.setRefValue   ( item.getRefValue ()  );
		logItem.setValue      ( item.getValue    ()  );
		logItem.setDesc       ( item.getDesc     ()  );
		return logItem;
	}

	private StandardLogItem toLogFlatItem(StandardItem item) {
		StandardLogItem logItem = new StandardLogItem();
		logItem.setName(item.getName());
		logItem.setLevel(item.getLevel());
		logItem.setType(item.getType());
		logItem.setSize(item.getSize());
		logItem.setFieldType(item.getFieldType());
		logItem.setLength(item.getLength());
		logItem.setDataType(item.getDataType());
		logItem.setRefPath(item.getRefPath());
		logItem.setRefValue(item.getRefValue());
		if (item.getDataType() == StandardDataType.LL_NUMBER || item.getDataType() == StandardDataType.NUMBER) {
			logItem.setValue(StringUtil.stringFormat(item.getValue(), true, '0', getLength()));
		} else {
			logItem.setValue(item.getValue());
		}
		logItem.setDesc(item.getDesc());
		return logItem;
	}
	   
	public ArrayList<StandardLogItem> toLogList() {
		return toLogList(false);
	}
	
	public ArrayList<StandardLogItem> toLogList(boolean withBizData) {
		ArrayList<StandardLogItem> list = new ArrayList<StandardLogItem>();
		ArrayList<StandardItem> childList = null;
		switch(getType())  {
			case StandardType.MESSAGE :
//				list.add(toLogItem(this));
				childList = new ArrayList<StandardItem>(childs.values());
				for(int i=0; i<childList.size(); i++) {
					list.addAll(childList.get(i).toLogList(withBizData));
				}
				break;
			case StandardType.FIELD :
				list.add(toLogItem(this));
				break;
			case StandardType.GROUP :
				if (isHidden) break;
				list.add(toLogItem(this));
				childList = new ArrayList<StandardItem>(childs.values());
				for(int i=0; i<childList.size(); i++) {
					list.addAll(childList.get(i).toLogList(withBizData));
				}
				break;
			case StandardType.GRID :
				if (isHidden) break;
				list.add(toLogItem(this));
				int arraySize = getSize();
				for(int ai=0;ai<arraySize;ai++) {
					LinkedHashMap<String, StandardItem> map = getArrayChilds(ai, withBizData);
					childList = new ArrayList<StandardItem>(map.values());
					for(int i=0; i<childList.size(); i++) {
						list.addAll(childList.get(i).toLogList(withBizData));
					}
				}
				break;
			case StandardType.FARRAY :
				list.add(toLogItem(this));
				break;
			case StandardType.BIZDATA :
				if(withBizData)
					list.add(toLogItem(this));
				break;
			default :
				break;
		}
		return list;
	}
	
	public ArrayList<StandardLogItem> toLogFlatList() {
		return toLogFlatList(false);
	}

	public ArrayList<StandardLogItem> toLogFlatList(boolean withBizData) {
		ArrayList<StandardLogItem> list = new ArrayList<StandardLogItem>();
		ArrayList<StandardItem> childList = null;
		switch (getType()) {
		case StandardType.MESSAGE:
			childList = new ArrayList<StandardItem>(childs.values());
			for (int i = 0; i < childList.size(); i++) {
				list.addAll(childList.get(i).toLogFlatList(withBizData));
			}
			break;
		case StandardType.FIELD:
			list.add(toLogFlatItem(this));
			break;
		case StandardType.GROUP:
			if (isHidden)
				break;
			list.add(toLogFlatItem(this));
			childList = new ArrayList<StandardItem>(childs.values());
			for (int i = 0; i < childList.size(); i++) {
				list.addAll(childList.get(i).toLogFlatList(withBizData));
			}
			break;
		case StandardType.GRID:
			if (isHidden)
				break;
			list.add(toLogFlatItem(this));
			int arraySize = getSize();
			for (int ai = 0; ai < arraySize; ai++) {
				LinkedHashMap<String, StandardItem> map = getArrayChilds(ai, withBizData);
				childList = new ArrayList<StandardItem>(map.values());
				for (int i = 0; i < childList.size(); i++) {
					list.addAll(childList.get(i).toLogFlatList(withBizData));
				}
			}
			break;
		case StandardType.FARRAY:
			list.add(toLogFlatItem(this));
			break;
		case StandardType.BIZDATA:
			if (withBizData)
				list.add(toLogFlatItem(this));
			break;
		default:
			break;
		}
		return list;
	}
	
	protected String toJsonValue() {
		return toJsonValue(this.value);
	}
	
	protected String toJsonValue(String svalue) {
		if(dataType == StandardDataType.STRING || dataType == StandardDataType.ZZ_STRING) {
			if(svalue == null) {
				if(NULL_TO_SPACE) return "\"\""; 
				else return null;
			}
			return "\"" + JSONObject.escape(svalue)  + "\"";
		}
		else {
			if(StringUtils.isEmpty(svalue)) {
				return "0";
			}
			else {
				return JSONObject.escape(svalue) ;
			}
		}
	}
	
	public String toJson() {
		return toJson(false, true);
	}
	public String toPrettyJson() {
		return toJson(true, true);
	}
	
	public String toJson(boolean showPretty, boolean withBizData) {
		StringBuilder sb = new StringBuilder();
		Iterator<String> keyIter = null;
		switch(getType())  {
		case StandardType.MESSAGE :
			sb.append("{");
// if root name add			
			if(ADD_JSON_ROOT) sb.append("\"").append( name ).append("\"").append(":{");
//			if(showPretty) sb.append("\n");
			keyIter = childs.keySet().iterator();
			int i=0;
			String part = null;
			while(keyIter.hasNext()) {
				String key = keyIter.next();
				StandardItem item = childs.get(key);
				
				part = item.toJson(showPretty, withBizData);
				if(StringUtils.isNotEmpty(part)) {
					if(i>0) sb.append(",");
					if(showPretty) sb.append("\n");
					sb.append(part);
					i++;
				}
			}
			if(ADD_JSON_ROOT) sb.append("}");
			if(showPretty) sb.append("\n");
			sb.append("}");
			break;
		case StandardType.FIELD :
			if(StringUtils.isEmpty(this.value)) break;
			if(showPretty) sb.append(getLevelIndent());
			sb.append("\"").append(name).append("\":").append(toJsonValue());
			break;
		case StandardType.GROUP :
			// skip variable group
			if( getSize() == 0) {
				break;
			}
			if (isHidden) break;
			if(showPretty) sb.append(getLevelIndent());
			sb.append("\"").append(name).append("\":{");
			keyIter = childs.keySet().iterator();
			int g=0;
			while(keyIter.hasNext()) {
				String key = keyIter.next();
				StandardItem item = childs.get(key);
				part = item.toJson(showPretty, withBizData);
				if(StringUtils.isNotEmpty(part)) {
					if(g>0) sb.append(",");
					if(showPretty) sb.append("\n");
					sb.append(part);
					g++;
				}
			}
			if(showPretty) {
				sb.append("\n");
				sb.append(getLevelIndent());
			}
			sb.append("}");
			break;
		case StandardType.GRID :
			if(showPretty) {				
				sb.append(getLevelIndent());
			}
			if (isHidden) break;
			sb.append("\"").append(name).append("\":[");
//			if(showPretty) sb.append("\n");
			for(int p=0; p<list.size(); p++) {
				LinkedHashMap<String , StandardItem> group = list.get(p);
				keyIter = group.keySet().iterator();
				int ai=0;
				if(showPretty) {
					sb.append("\n");
					sb.append(getLevelIndent());
				}
				if(p>0) sb.append(",");
				sb.append("{");
				while(keyIter.hasNext()) {
					String key = keyIter.next();
					StandardItem item = group.get(key);
					String itemPart = item.toJson(showPretty, withBizData);
					if(StringUtils.isNotEmpty(itemPart)) {
						if(ai>0) sb.append(",");
						if(showPretty) sb.append("\n");
						sb.append(itemPart);
						ai++;
					}
				}
				
				if(showPretty) {
					sb.append("\n");
					sb.append(getLevelIndent()).append("}");					
				}
				else {
					sb.append("}");
				}
			}
//			if(showPretty) {
//				sb.append("\n");
//				sb.append(getLevelIndent());
//			}
			sb.append("]");
			break;
		case StandardType.FARRAY :
			if(showPretty) {				
				sb.append(getLevelIndent());
			}
			sb.append("\"").append(name).append("\":[");
//			if(showPretty) sb.append("\n");
			for(int p=0; p<values.size(); p++) {				
				if(p>0) sb.append(",");
				sb.append(toJsonValue(values.get(p)));
			}
//			if(showPretty) {
//				sb.append("\n");
//				sb.append(getLevelIndent());
//			}
			sb.append("]");
			break;	
		case StandardType.BIZDATA :
			if(withBizData) {
				if(showPretty) sb.append(getLevelIndent());
				sb.append("\"").append(name).append("\":");
				if(StringUtils.isEmpty(getValue())) {
//					sb.append(toJsonValue(null));
					sb.append("{}");
				}
				else {
					// 업무데이터가 Json인지 확인
					String jsonString = getValue();
					if(jsonString.startsWith("{") || jsonString.startsWith("[")) {//배열로 시작하는게 있어서
						sb.append(jsonString);
					}
					else {
						sb.append(toJsonValue());
					}
				}
			}
			else {
				return null;
			}
			break;
		default :
			break;
		}
		return sb.toString();
	}
	
	private String toStartTag(String nodeName) {
		StringBuilder sb = new StringBuilder();
		sb.append("<").append(nodeName).append(">");
		return sb.toString();
	}
	private String toEndTag(String nodeName) {
		StringBuilder sb = new StringBuilder();
		sb.append("</").append(nodeName).append(">");
		return sb.toString();
	}
	
	protected String toXmlValue() {
		return value;
	}
	
	public String toXML() {
		String encoding = EncodingVar.xmlEncoding;
//		logger.debug("Encoding : {}", encoding );
		return toXML(encoding, false);
	}
	
	public String toPrettyXML() {
		String encoding = EncodingVar.xmlEncoding;
//		logger.debug("Encoding : {}", encoding );
		return toXML(encoding, true);
	}
	
	// FIX-ME : Attribute와 Element가 혼합된 Group/Array는 현재 처리 불가함.
	//            XML의 Attribute Group일 경우 Group과 Item의 FieldType은 모두 1 로 설정되어야 함.
	//            향후 XMLParser로 생성하는 방안은 고려해 봐야 함.
	public String toXML(String encode, boolean showPretty) {
		StringBuilder sb = new StringBuilder();
		Iterator<String> keyIter = null;
		switch(getType()) {
			case StandardType.MESSAGE :
				sb.append("<?xml version=\"1.0\" encoding=\""+encode+"\"?>");
				if(showPretty) sb.append("\n");
				
				sb.append(toStartTag(name));
				
				keyIter = childs.keySet().iterator();
				while(keyIter.hasNext()) {
					String key = keyIter.next();
					StandardItem item = childs.get(key);
					sb.append(item.toXML(encode, showPretty));
				}
				
				if(showPretty) sb.append("\n");
				sb.append(toEndTag(name));
				break;
			case StandardType.FIELD :
				if(fieldType == StandardFieldType.ELEMENT) {
					if(showPretty) {
						sb.append("\n");
						sb.append(getLevelIndent());
					}
					sb.append(toStartTag(name));
					sb.append(toXmlValue());
					sb.append(toEndTag(name));
				}
				else {
					sb.append(" ").append(name).append("=\"");
					sb.append(toXmlValue()).append("\"");;
				}
				break;
			case StandardType.GROUP :
				// skip variable group
				if( getSize() == 0) {
					break;
				}
				if (isHidden) break;
				if(showPretty) {
					sb.append("\n");
					sb.append(getLevelIndent());
				}
				if(fieldType == 0) {
					sb.append(toStartTag(name));
				}
				else {
					sb.append("<").append(name);
				}
				keyIter = childs.keySet().iterator();
				while(keyIter.hasNext()) {
					String key = keyIter.next();
					StandardItem item = childs.get(key);
					sb.append(item.toXML(encode, showPretty));
				}
				
				if(fieldType == 0) {
					if(showPretty) { 
						sb.append("\n");
						sb.append(getLevelIndent());
					}
					sb.append(toEndTag(name));
				}
				else {
					sb.append(" />");;
				}
				break;
			case StandardType.GRID :
				if(showPretty) {
					sb.append(getLevelIndent());
				}
				if (isHidden) break;
				for(int p=0; p<list.size(); p++) {
					if(showPretty) { 
						sb.append("\n");
						sb.append(getLevelIndent());
					}	
					sb.append(toStartTag(name));
					LinkedHashMap<String , StandardItem> group = list.get(p);
					keyIter = group.keySet().iterator();
					while(keyIter.hasNext()) {
						String key = keyIter.next();
						StandardItem item = group.get(key);
						sb.append(item.toXML(encode, showPretty));
					}
					if(showPretty) {
						sb.append("\n");
						sb.append(getLevelIndent());
					}
					sb.append(toEndTag(name));												
				}
				break;
			case StandardType.FARRAY :
				if(showPretty) {
					sb.append(getLevelIndent());
				}
				for(int p=0; p<values.size(); p++) {
					if(showPretty) { 
						sb.append("\n");
						sb.append(getLevelIndent());
					}	
					sb.append(toStartTag(name));
					sb.append(values.get(p));
					sb.append(toEndTag(name));												
				}
				break;	
			case StandardType.BIZDATA :
				if(showPretty) {
					sb.append("\n");
					sb.append(getLevelIndent());
				}
				sb.append(toStartTag(name));
				sb.append(getValue());
				sb.append(toEndTag(name));
				break;
			default :
				break;			
		}
		return sb.toString();
	}
	
	public int getBytesDataLength(String charset) {
		int totalSize = 0;
		Iterator<String> keyIter = null;
		try {
			switch(getType()) {
				case StandardType.MESSAGE :
					keyIter = childs.keySet().iterator();
					while(keyIter.hasNext()) {
						String key = keyIter.next();
						StandardItem item = childs.get(key);
						totalSize +=item.getBytesDataLength(charset);
					}
					break;
				case StandardType.FIELD :
					totalSize += getLength();
					break;
				case StandardType.GROUP :
					// skip inactive/conditional group (size==0 means not activated)
					if( getSize() == 0 ) {
						break;
					}
					if (isHidden) break;
					keyIter = childs.keySet().iterator();
					while(keyIter.hasNext()) {
						String key = keyIter.next();
						StandardItem item = childs.get(key);
						totalSize +=item.getBytesDataLength(charset);
					}			
					break;
				case StandardType.GRID :	
					if( getSize() == 0
						//반복그리드 관련해서 추가되야됩
						&& StringUtils.isBlank(getRefPath())
						&& StringUtils.isBlank(getRefValue())
						) {
						break;
					}
					if (isHidden) break;
					for(int p=0; p<list.size(); p++) {					
						LinkedHashMap<String , StandardItem> group = list.get(p);
						keyIter = group.keySet().iterator();
						while(keyIter.hasNext()) {
							String key = keyIter.next();
							StandardItem item = group.get(key);
							totalSize +=item.getBytesDataLength(charset);
						}												
					}
					break;
				case StandardType.FARRAY :			
					for(int p=0; p<values.size(); p++) {					
						totalSize += getLength();												
					}
					break;	
				case StandardType.BIZDATA :							
					totalSize += getBytesValue(charset).length;
		            
					break;	
				default :
					break;
			}		
			return totalSize;
		} catch(Exception e) {
    		logger.error("getBytesDataLength failed", e);
    		return totalSize;
    	}
    	finally {
    	}
	}
	

	
// deprecated	
//	public byte[] toByteArray() {
//		return toByteArray(true, EncodingVar.flatEncoding);
//	}
	
	public byte[] toByteArray(String charset) {
		return toByteArray(true, charset);
	}
	public byte[] toByteArray(boolean withBizData, String charset) {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		Iterator<String> keyIter = null;		
//		logger.debug("Encoding : {}", charset);
		try {
			switch(getType()) {
				case StandardType.MESSAGE :
					keyIter = childs.keySet().iterator();
					while(keyIter.hasNext()) {
						String key = keyIter.next();
						StandardItem item = childs.get(key);
						bos.write(item.toByteArray(withBizData,charset)); //하위로 내려갈때 withBizDat 값이 없어져서 추가 jun's 20231101
					}
					break;
				case StandardType.FIELD :
					if (getValue() == null) {
						//데이타 타입에 따라서 패딩 변경
		            	if (dataType == StandardDataType.STRING || dataType == StandardDataType.ZZ_STRING) {	            	
		            		bos.write( ByteUtil.padding("", getLength(), (byte)' '));
		            	} else if (dataType == StandardDataType.NUMBER || dataType == StandardDataType.LL_NUMBER) {
		            		bos.write( ByteUtil.padding("", getLength(), (byte)'0'));
		            	}else {
		            		bos.write( ByteUtil.padding(new byte[0], getLength()) );
		            	}
		            } else if (dataType == StandardDataType.STRING || dataType == StandardDataType.ZZ_STRING) {	            	
		            	bos.write( ByteUtil.padding(getBytesValue(charset), getLength()) );
		            } else if (dataType == StandardDataType.NUMBER || dataType == StandardDataType.LL_NUMBER) {
		            	bos.write( ByteUtil.padding(getBytesValue(charset), getLength(), (byte)'0', true) );
		            } else {	            	
		            	bos.write( ByteUtil.padding(getBytesValue(charset), getLength()) );
		            }
					break;
				case StandardType.GROUP :
					// skip inactive/conditional group (size==0 means not activated)
					if( getSize() == 0 ) {
						break;
					}
					if (isHidden) break;
					keyIter = childs.keySet().iterator();
					while(keyIter.hasNext()) {
						String key = keyIter.next();
						StandardItem item = childs.get(key);
						bos.write(item.toByteArray(withBizData,charset)); //하위로 내려갈때 withBizDat 값이 없어져서 추가 jun's 20231101
					}			
					break;
				case StandardType.GRID :			
					if( getSize() == 0
						//반복그리드 관련해서 추가되야됩
						&& StringUtils.isBlank(getRefPath())
						&& StringUtils.isBlank(getRefValue())
						) {
						break;
					}
					if (isHidden) break;
					for(int p=0; p<list.size(); p++) {					
						LinkedHashMap<String , StandardItem> group = list.get(p);
						keyIter = group.keySet().iterator();
						while(keyIter.hasNext()) {
							String key = keyIter.next();
							StandardItem item = group.get(key);
							bos.write(item.toByteArray(withBizData, charset)); //하위로 내려갈때 withBizDat 값이 없어져서 추가 jun's 20231101
						}												
					}
					break;
				case StandardType.FARRAY :			
					for(int p=0; p<values.size(); p++) {					
						if (values.get(p) == null) {
			            	bos.write( new byte[getLength()] );	            	
			            } else if (dataType == StandardDataType.STRING || dataType == StandardDataType.ZZ_STRING) {	            	
			            	bos.write( ByteUtil.padding(values.get(p).getBytes(charset), getLength()) );
			            } else if (dataType == StandardDataType.NUMBER || dataType == StandardDataType.LL_NUMBER) {
			            	bos.write( ByteUtil.padding(values.get(p).getBytes(charset), getLength(), (byte)'0', true) );
			            } else {	            	
			            	bos.write( ByteUtil.padding(values.get(p).getBytes(charset), getLength()) );
			            }												
					}
					break;	
				case StandardType.BIZDATA :
					if(withBizData) {
						if (getValue() == null) {
			            	bos.write( new byte[getLength()] );
			            } else {	            	
			            	bos.write( getBytesValue(charset) );
			            }
					}
					break;	
				default :
					break;
			}		
			return bos.toByteArray();
		} catch(Exception e) {
    		logger.error("toByteArray failed", e);
    		return new byte[0];
    	}
    	finally {
    		if(bos != null) {
				try {
					bos.close();
				} catch (IOException e) {
					;
				}
    		}
    	}
	}
	
    public ArrayList<LinkedHashMap<String, StandardItem>> getList() {
		return list;
	}

	public void setList(ArrayList<LinkedHashMap<String, StandardItem>> list) {
		this.list = list;
	}

	public ArrayList<String> getValues() {
		return values;
	}

	public void setValues(ArrayList<String> values) {
		this.values = values;
	}
	
	private LinkedHashMap<String, StandardItem> cloneMap(LinkedHashMap<String, StandardItem> childs) {
		if(childs == null) return childs;
		LinkedHashMap<String, StandardItem> newMap = new LinkedHashMap<String, StandardItem>();
		for(String key:childs.keySet())
			try {
				newMap.put(key, (StandardItem)childs.get(key).clone());
			} catch (CloneNotSupportedException e) {
				;
			}
		return newMap; 
	}
	@Override
    public Object clone() throws CloneNotSupportedException {
    	StandardItem cloned = (StandardItem)super.clone();
    	
    	if(childs != null) {
    		cloned.childs = cloneMap(childs);    		
    	}
    	
    	if(list != null) {
    		ArrayList<LinkedHashMap<String , StandardItem>> clist = new ArrayList<>();
    		for(int idx=0; idx<list.size(); idx++) {
    			clist.add(cloneMap(list.get(idx)));
    		}
    		cloned.setList(clist);
    	}

    	if(values != null) {
    		cloned.setValues((ArrayList<String>)values.clone());
    	}
    	
    	if(value != null) {    		
    		cloned.setValue(String.copyValueOf(value.toCharArray()));
    	}
    	
    	if(bytesValue != null) {
    		byte[] clonedBytes = new byte[bytesValue.length];
    		System.arraycopy(bytesValue, 0, clonedBytes, 0, bytesValue.length);
    		cloned.setBytesValue(clonedBytes);
    	}
    	return cloned;
    }
}