package com.fp.dto.json;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Date;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.sf.ezmorph.Morpher;
import net.sf.ezmorph.MorpherRegistry;
import net.sf.ezmorph.bean.MorphDynaBean;
import net.sf.json.JSONObject;
import net.sf.json.JsonConfig;
import net.sf.json.util.JSONUtils;
import net.sf.json.xml.XMLSerializer;

import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtils;

import com.fp.common.helper.BeanManager;
import com.fp.common.logger.APPLogger;
import com.fp.dto.AbstractData;
import com.fp.dto.AbstractDataTransport;
import com.fp.dto.Response;
import com.fp.dto.json.morpher.DateMorpher;
import com.fp.dto.json.morpher.TimestampMorpher;
import com.fp.dto.save.SaveBean;
import com.fp.dto.save.SaveRequest;

public class Serializer {

	private Serializable data;

	private String json;

	private JSONObject jsonObj;

	private JsonConfig jsonConfig;

	private MorpherRegistry morpherRegistry;

	public Serializer(Serializable pData) throws Exception {
		if (pData instanceof Response) {
			((Response) pData).changeAbstractBeanToMap();
		}
		this.data = pData;
		this.config();
	}

	public Serializer(String pXML, boolean pIndex) {
		this("" + new XMLSerializer().read(pXML));
	}

	public Serializer(String pJSON) {
		this.json = pJSON.trim();
		this.config();
		if ((this.json.charAt(0) == '"') && (this.json.charAt(this.json.length() - 1) == '"')) {
			this.json = this.json.substring(1, this.json.length() - 1);
		}
		this.jsonObj = JSONObject.fromObject(this.json, this.jsonConfig);
	}

	private void config() {
		this.jsonConfig = new JsonConfig();
		this.jsonConfig.registerJsonValueProcessor(Date.class, new DateValueProcessor());
		this.jsonConfig.registerJsonValueProcessor(Timestamp.class, new DateValueProcessor());
		this.jsonConfig.registerDefaultValueProcessor(Double.class, new ObjectValueProcessor());
		this.morpherRegistry = JSONUtils.getMorpherRegistry();
		Morpher dynaMorpher = new TimestampMorpher();
		this.morpherRegistry.registerMorpher(dynaMorpher);
		dynaMorpher = new DateMorpher();
		this.morpherRegistry.registerMorpher(dynaMorpher);
	}

	/**
	 * Entrega un string en formato json.
	 * 
	 * @return String
	 * @throws Exception
	 */
	public final String toJSON() throws Exception {
		try {
			JSONObject jsonData = JSONObject.fromObject(this.data, this.jsonConfig);
			return jsonData.toString();
		} catch (Exception e) {
			APPLogger.getLogger().error(e, e);
			throw e;
		}
	}

	public String toXML() throws Exception {
		try {
			JSONObject jsonData = JSONObject.fromObject(this.data, this.jsonConfig);
			XMLSerializer xml = new XMLSerializer();
			xml.setElementName("REGISTRO");
			xml.setObjectName("FP");
			xml.setTrimSpaces(true);
			xml.setRemoveNamespacePrefixFromElements(true);
			xml.setSkipWhitespace(true);
			xml.setSkipNamespaces(true);
			String sXML = xml.write(jsonData);
			while (sXML.indexOf("null=\"true\"/>") > 0) {
				String aux1 = sXML.substring(0, sXML.indexOf("null=\"true\"/>"));
				String aux2 = sXML.substring(sXML.indexOf("null=\"true\"/>") + 13);
				sXML = aux1.substring(0, aux1.lastIndexOf("<")) + aux2;
			}
			return sXML;
		} catch (Exception e) {
			e.printStackTrace();
			throw e;
		}
	}

	@SuppressWarnings("unchecked")
	public Object toObject(Class pType) throws Exception {
		Object obj = JSONObject.toBean(this.jsonObj, pType);
		if (obj instanceof Map) {
			Map m = (Map) obj;
			if (!m.containsKey("transportBeanClass")) {
				m.put("transportBeanClass", pType.getName());
			}
			// m=this.correctMap(m);
			obj = this.manageMapBean(m);
		}

		return obj;
	}

	@SuppressWarnings("unchecked")
	public <T> T toObjectGenerics(T pType) throws Exception {
		T obj = (T) JSONObject.toBean(this.jsonObj, pType.getClass());
		if (obj instanceof Map) {
			Map m = (Map) obj;
			if (!m.containsKey("transportBeanClass")) {
				m.put("transportBeanClass", pType.getClass().getName());
			}
			// m=this.correctMap(m);
			obj = (T) this.manageMapBean(m);
		}

		return obj;
	}

	private List<Object> manageList(List<Object> pList) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
	InvocationTargetException, NoSuchMethodException, ParseException {
		List<Object> dataList = new ArrayList<Object>();
		for (Object obj : pList) {
			dataList.add(this.manageValue(obj, null));
		}
		return dataList;
	}

	@SuppressWarnings("unchecked")
	private Object manageValue(Object pObject, Class pType) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
	InvocationTargetException, NoSuchMethodException, ParseException {
		if (pObject == null) {
			return null;
		}
		if (pObject instanceof String) {
			if (((String) pObject).compareTo("null") == 0) {
				return null;
			}
		}
		if (pObject instanceof JSONObject) {
			JSONObject obj = (JSONObject) pObject;
			if (obj.isNullObject()) {
				return null;
			}
		}
		if (pType != null) {
			// System.out.println(pObject+" "+"->"+pType.getName());
			return BeanManager.convertObject(pObject, pType);
		}
		if (pObject instanceof Double) {
			return new BigDecimal("" + pObject);
		}
		if (pObject instanceof Map) {
			Map m = (Map) pObject;// this.correctMap((Map)pObject);
			return this.manageMapBean(m);
		}
		if (pObject instanceof MorphDynaBean) {
			return this.manageBean((MorphDynaBean) pObject);
		}
		if (pObject instanceof List) {
			return this.manageList((List) pObject);
		}
		if (pObject instanceof String) {
			String sObj = (String) pObject;
			if (sObj.length() == DateValueProcessor.DATE_TRANSPORT.length()) {
				try {
					return new Date(new SimpleDateFormat(DateValueProcessor.DATE_TRANSPORT).parse(sObj).getTime());
				} catch (Exception e) {
				}
			}
			if (sObj.length() == DateValueProcessor.TIMESTAMP_TRANSPORT.length()) {
				try {
					return new Timestamp(new SimpleDateFormat(DateValueProcessor.TIMESTAMP_TRANSPORT).parse(sObj).getTime());
				} catch (Exception e) {
				}
			}
			return pObject;
		}
		return pObject;
	}

	@SuppressWarnings("unchecked")
	private Object manageBean(MorphDynaBean pMorphDynaBean) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
	InvocationTargetException, NoSuchMethodException {
		DynaProperty[] prop = pMorphDynaBean.getDynaClass().getDynaProperties();
		Map mapBean = new HashMap();
		for (DynaProperty dynaProperty : prop) {
			String name = dynaProperty.getName();
			mapBean.put(name, pMorphDynaBean.get(name));
		}
		if (!mapBean.containsKey("transportBeanClass")) {
			mapBean.put("transportBeanClass", "java.util.HashMap");
		}
		return this.manageMapBean(mapBean);
	}

	private String correctType(String pType) {
		if (pType.compareTo("java.util.List") == 0) {
			return "java.util.ArrayList";
		}
		if (pType.compareTo("java.util.Map") == 0) {
			return "java.util.HashMap";
		}
		return pType;
	}

	@SuppressWarnings("unchecked")
	private Object manageMapBean(Map pMapBean) throws ClassNotFoundException, InstantiationException, IllegalAccessException,
	InvocationTargetException, NoSuchMethodException {
		String classType = (String) pMapBean.get("transportBeanClass");
		if (classType == null) {
			return pMapBean;
		}
		classType = this.correctType(classType);
		Object obj = Class.forName(classType).newInstance();
		Set keys = pMapBean.keySet();
		for (Object key : keys) {
			String name = (String) key;
			if (name.compareTo("transportBeanClass") == 0) {
				continue;
			}
			try {
				Class type = PropertyUtils.getPropertyType(obj, name);
				try {

					if (type.getPackage().getName().compareTo("java.util") == 0) {
						type = null;
					}
				} catch (Exception e) {
				}
				Object value = this.manageValue(pMapBean.get(name), type);
				try {
					// PropertyUtils.setProperty(obj, name, value);
					if (classType.compareTo("java.util.HashMap") == 0) {
						((Map) obj).put(name, value);
					} else {
						if (value instanceof MorphDynaBean) {
							value = this.manageBean((MorphDynaBean) value);
						}
						if (value instanceof Map) {
							value = this.manageMapBean((Map) value);
						}
						BeanManager.setBeanAttributeValue(obj, name,
								BeanManager.convertObject(value, BeanManager.getBeanGetterMethod(obj, name).getReturnType()));
					}
				} catch (Exception eadd) {
					if (name.indexOf("transportBeanClass") > 0) {
						String prop = name.replaceAll("_transportBeanClass", "");
						Object objProp = this.prepareBean(prop, (String) value, pMapBean);
						try {
							// PropertyUtils.setProperty(obj, prop, objProp);
							BeanManager.setBeanAttributeValue(obj, prop,
									BeanManager.convertObject(objProp, BeanManager.getBeanGetterMethod(obj, prop).getReturnType()));
						} catch (Exception e) {
							try {
								AbstractData dt = (AbstractData) obj;
								dt.addAddtionalInfo(prop, objProp);
							} catch (Exception e1) {
							}
						}
					} else {
						try {
							if ((obj instanceof SaveRequest) && (value instanceof SaveBean)) {
								((SaveRequest) obj).putSaveBean(((SaveBean) value).getBeanAlias(), value);
							} else {
								//JVC 22 octubre 2012 todo el if
								if(obj instanceof AbstractDataTransport){
									((AbstractDataTransport)obj).put(name, value);
								}else{
									AbstractData dt = (AbstractData) obj;
									dt.addAddtionalInfo(name, value);
								}

							}
						} catch (Exception e1) {
						}
					}
				}
			} catch (Exception e) {
				e.printStackTrace();
				continue;
			}
		}
		return obj;
	}

	@SuppressWarnings("unchecked")
	private Object prepareBean(String pPrefix, String pClass, Map pMapBean) throws ClassNotFoundException, InstantiationException,
	IllegalAccessException {
		Class c = Class.forName(pClass);
		Object obj = c.newInstance();
		Field[] fs = c.getDeclaredFields();
		for (Field field : fs) {
			try {
				String name = field.getName();
				if (name.compareTo("serialVersionUID") == 0) {
					continue;
				}
				Object val = null;
				try {
					val = pMapBean.get(pPrefix + "_" + name);
				} catch (Exception e1) {
					continue;
				}
				BeanManager.setBeanAttributeValue(obj, name,
						BeanManager.convertObject(val, BeanManager.getBeanGetterMethod(obj, name).getReturnType()));
			} catch (Exception e) {
				APPLogger.getLogger().debug(e);
				continue;
			}
		}
		return obj;
	}
}
