/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package peaconnector;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author Csubák Péter
 */
public class XmlSerializer {

    public static <E extends Collection, T extends Class<Collection>> E deserializeList(Node xmlNode, T className, SchemaListElement sle) throws SerializerException {
        E res = null;
        try {
            res = (E) className.newInstance();
        } catch (Exception e) {
            return null;
        }
        NodeList nl = xmlNode.getChildNodes();

        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node.getNodeName().equals(sle.getElementsName())) {
                if (sle.getElementsType().isPrimitive()) {
                    //primitivek
                    String cn = sle.getElementsType().getCanonicalName();
                    if (cn.equals("int")) {
                        res.add(Integer.parseInt(node.getFirstChild().getNodeValue()));
                    } else if (cn.equals("boolean")) {
                        res.add(node.getFirstChild().getNodeValue().equals("1"));
                    } else if (cn.equals("double")) {
                        res.add(Double.parseDouble(node.getFirstChild().getNodeValue()));
                    } else if (cn.equals("float")) {
                        res.add(Float.parseFloat(node.getFirstChild().getNodeValue()));
                    } else if (cn.equals("byte")) {
                        res.add(Byte.parseByte(node.getFirstChild().getNodeValue()));
                    } else if (cn.equals("long")) {
                        res.add(Long.parseLong(node.getFirstChild().getNodeValue()));
                    } else if (cn.equals("short")) {
                        res.add(Short.parseShort(node.getFirstChild().getNodeValue()));
                    } else if (cn.equals("char")) {
                        res.add(node.getFirstChild().getNodeValue());
                    } else {
                        throw new SerializerException("Unsupported primitive type (It's an impossible Exception :-) )");
                    }
                } else {
                    //collection és map már nem lehet. (lehet, hogy még le lehetne írni, de a php
                    //nem enné már meg.
                    if (String.class.isAssignableFrom(sle.getElementsType())) {
                        res.add(node.getFirstChild().getNodeValue());
                    }
                    res.add(deserializeObject(node.getFirstChild(), sle.getElementsType(), sle.getSchemaName()));
                }
            } else {
                throw new SerializerException("The collections schema and the givven xml node are incompatibles.");
            }
        }
        return res;
    }

    private static String serializeList(SchemaListElement sle, Collection list) throws SerializerException {
        if (list == null) {
            return "";
        }

        String res = "";
        for (Object o : list) {
            res += "<" + sle.getElementsName() + ">";
            if (o.getClass().isAnnotationPresent(XmlSerializerSchemas.class)) {
                res += serializeObject(sle.getSchemaName(), o);
            } else {
                res += o.toString();
            }
            res += "</" + sle.getElementsName() + ">";
        }
        return res;
    }

    public static <E extends Map, T extends Class<Map>> E deserializeMap(Node xmlNode, T className, SchemaMapElement sme) throws SerializerException {
        E res = null;
        try {
            res = (E) className.newInstance();
        } catch (Exception e) {
            return null;
        }

        NodeList nl = xmlNode.getChildNodes();

        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node.getNodeName().equals(sme.getElementsName())) {
                Node nkey = node.getFirstChild();
                Node nvalue = node.getLastChild();
                if (sme.getKeysName().equals(nkey.getNodeName()) && sme.getValuesName().equals(nvalue.getNodeName())) {
                    Object key = null;
                    Object value = null;
                    if (sme.getKeysType().isPrimitive()) {
                        //primitivek
                        String cn = sme.getKeysType().getCanonicalName();
                        if (cn.equals("int")) {
                            key = Integer.parseInt(nkey.getFirstChild().getNodeValue());
                        } else if (cn.equals("boolean")) {
                            key = nkey.getFirstChild().getNodeValue().equals("1"); //It would be a very short map :-)
                        } else if (cn.equals("double")) {
                            key = Double.parseDouble(nkey.getFirstChild().getNodeValue());
                        } else if (cn.equals("float")) {
                            key = Float.parseFloat(nkey.getFirstChild().getNodeValue());
                        } else if (cn.equals("byte")) {
                            key = Byte.parseByte(nkey.getFirstChild().getNodeValue());
                        } else if (cn.equals("long")) {
                            key = Long.parseLong(nkey.getFirstChild().getNodeValue());
                        } else if (cn.equals("short")) {
                            key = Short.parseShort(nkey.getFirstChild().getNodeValue());
                        } else if (cn.equals("char")) {
                            key = nkey.getFirstChild().getNodeValue();
                        } else {
                            throw new SerializerException("Unsupported primitive type (It's an impossible Exception :-) )");
                        }
                    } else {
                        //collection és map már nem lehet. (lehet, hogy még le lehetne írni, de a php
                        //nem enné már meg.
                        if (String.class.isAssignableFrom(sme.getElementsType())) {
                            key = nkey.getFirstChild().getNodeValue();
                        }
                        key = deserializeObject(nkey.getFirstChild(), sme.getKeysType(), sme.getSchemaName());
                    }
                    if (sme.getValuesType().isPrimitive()) {
                        //primitivek
                        String cn = sme.getValuesType().getCanonicalName();
                        if (cn.equals("int")) {
                            value = Integer.parseInt(nvalue.getFirstChild().getNodeValue());
                        } else if (cn.equals("boolean")) {
                            value = nvalue.getFirstChild().getNodeValue().equals("1");
                        } else if (cn.equals("double")) {
                            value = Double.parseDouble(nvalue.getFirstChild().getNodeValue());
                        } else if (cn.equals("float")) {
                            value = Float.parseFloat(nvalue.getFirstChild().getNodeValue());
                        } else if (cn.equals("byte")) {
                            value = Byte.parseByte(nvalue.getFirstChild().getNodeValue());
                        } else if (cn.equals("long")) {
                            value = Long.parseLong(nvalue.getFirstChild().getNodeValue());
                        } else if (cn.equals("short")) {
                            value = Short.parseShort(nvalue.getFirstChild().getNodeValue());
                        } else if (cn.equals("char")) {
                            value = nvalue.getFirstChild().getNodeValue();
                        } else {
                            throw new SerializerException("Unsupported primitive type (It's an impossible Exception :-) )");
                        }
                    } else {
                        //collection és map már nem lehet. (lehet, hogy még le lehetne írni, de a php
                        //nem enné már meg.
                        if (String.class.isAssignableFrom(sme.getValuesType())) {
                            value = nvalue.getFirstChild().getNodeValue();
                        }
                        value = deserializeObject(nvalue.getFirstChild(), sme.getValuesType(), sme.getSchemaName());
                    }
                    res.put(key, value);
                } else {
                    throw new SerializerException("The keys name, or the values name is not compatible with the givven xml nodes keys, or values.");
                }
            } else {
                throw new SerializerException("The collections schema and the givven xml node are incompatibles.");
            }
        }
        return res;
    }

    private static String serializeMap(SchemaMapElement sme, Map map) throws SerializerException {
        if (map == null) {
            return "";
        }

        String res = "";
        Set<Entry> s = map.entrySet();
        for (Entry e : s) {
            res += "<" + sme.getElementsName() + ">";
            Object key = e.getKey();
            Object value = e.getValue();

            if (key.getClass().isAnnotationPresent(XmlSerializerSchemas.class)) {
                res += "<" + sme.getKeysName() + ">" + serializeObject(sme.getSchemaName(), key) + "</" + sme.getKeysName() + ">";
            } else {
                res += "<" + sme.getKeysName() + ">" + key.toString() + "</" + sme.getKeysName() + ">";
            }

            if (value.getClass().isAnnotationPresent(XmlSerializerSchemas.class)) {
                res += "<" + sme.getValuesName() + ">" + serializeObject(sme.getSchemaName(), value) + "</" + sme.getValuesName() + ">";
            } else {
                res += "<" + sme.getValuesName() + ">" + value.toString() + "</" + sme.getValuesName() + ">";
            }

            res += "</" + sme.getElementsName() + ">";
        }
        return res;
    }

    public static <E> String serializeMethod(String methodName, Pea object, SchemaMethodElement sme, E... params) throws SerializerException {
        String res = "<"+sme.getName()+">";
        int i = 0;
        for (SchemaElement ps : sme.getParameters()){
            res += "<"+ps.getName()+">";
            E param = params[i++];
            if (param instanceof Map){
                res += serializeMap((SchemaMapElement) ps, (Map) param);
            }else if (param instanceof Collection){
                res += serializeList((SchemaListElement) ps, (Collection) param);
            }else{
                if (param.getClass().isAnnotationPresent(XmlSerializerSchemas.class)){ //serializálható objektum
                    res += serializeObject(sme.getSchemaName(), param);
                }else{ //primitiv
                    res += param.toString();
                }
            }
            res += "</"+ps.getName()+">";
        }
        res += "</"+sme.getName()+">";
        return res;
    }

    /**
     * UNDORÍTÓ ÉS TÚL BONYOLULT, ÉS TÚL HOSSZÚ, SZÓVAL MAJD REFACTOR KELL!!!!!
     * @param <E>
     * @param <T>
     * @param xml
     * @param className
     * @param schemaName
     * @return
     * @throws peaconnector.SerializerException
     */
    public static <E, T extends Class> E deserializeObject(Node xml, T className, String schemaName) throws SerializerException {
        if (className.isAnnotationPresent(XmlSerializerSchemas.class)) {
            XmlSerializerSchemas xs = (XmlSerializerSchemas) className.getAnnotation(XmlSerializerSchemas.class);
            SchemaElement se = findBySchemaName(xs, schemaName);
            if (se == null) {
                throw new SerializerException("Could not find schema definition for schema name: " + schemaName);
            }

            E res = null;
            try {
                res = (E) className.newInstance();
            } catch (Exception ex) {
                throw new SerializerException("Could not create an instance of the class named: " + className.getCanonicalName());
            }

            if (xml.getNodeName().equals(se.getName())) {
                NodeList nl = xml.getChildNodes();
                for (int i = 0; i < nl.getLength(); ++i) {
                    Node node = nl.item(i);
                    for (Field f : className.getDeclaredFields()) {
                        if (f.isAnnotationPresent(XmlSerializerSchemas.class)) {
                            xs = f.getAnnotation(XmlSerializerSchemas.class);
                            se = findBySchemaName(xs, schemaName);
                            if (se != null) {
                                if (se.getName().equals(node.getNodeName())) {
                                    Class clazz = f.getType();
                                    if (clazz.isPrimitive()) {
                                        String cn = clazz.getCanonicalName();
                                        if (cn.equals("int")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, Integer.parseInt(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, Integer.parseInt(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("boolean")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, node.getFirstChild().getNodeValue().equals("1"));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, node.getFirstChild().getNodeValue().equals("1"));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("double")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, Double.parseDouble(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, Double.parseDouble(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("float")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, Float.parseFloat(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, Float.parseFloat(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("byte")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, Byte.parseByte(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, Byte.parseByte(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("long")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, Long.parseLong(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, Long.parseLong(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("short")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, Short.parseShort(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, Short.parseShort(node.getFirstChild().getNodeValue()));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (cn.equals("char")) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, node.getFirstChild().getNodeValue());
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, node.getFirstChild().getNodeValue());
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else {
                                            throw new SerializerException("Unsupported primitive type (It's an impossible Exception :-) )");
                                        }
                                    } else { //nem primitív
                                        if (String.class.isAssignableFrom(clazz)) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, node.getFirstChild().getNodeValue());
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, node.getFirstChild().getNodeValue());
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (Map.class.isAssignableFrom(clazz)) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, deserializeMap(node, clazz, (SchemaMapElement) se));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, deserializeMap(node, clazz, (SchemaMapElement) se));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else if (Collection.class.isAssignableFrom(clazz)) {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, deserializeList(node, clazz, (SchemaListElement) se));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, deserializeList(node, clazz, (SchemaListElement) se));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        } else {
                                            if (Modifier.isPublic(f.getModifiers())) {
                                                try {
                                                    f.set(res, deserializeObject(node, clazz, schemaName));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            } else {
                                                try {
                                                    setBySetter(clazz, f, res, deserializeObject(node, clazz, schemaName));
                                                } catch (Exception e) {
                                                    continue;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            } else {
                throw new SerializerException("The given xml node is not compatible with the schema.");
            }
            return res;
        } else {
            throw new SerializerException("Could not find any schema definition in class named: " + className.getCanonicalName());
        }
    }

    private static SchemaElement findBySchemaName(XmlSerializerSchemas xs, String sName) {
        for (SEAnnot se : xs.value()) {
            if (se.schemaName().equals(sName)) {
                return SchemaElement.getByAnnot(se);
            }
        }
        return null;
    }

    private static void setBySetter(Class clazz, Field f, Object obj, Object value) throws Exception {
        //EZT MAJD MÉG MEG KELL TAKARÍTGATNI, MERT EZ A CLAZZ PL TÖK FÖLÖSLEGES, DE A GETTERESBEN IS!!!
        String sName = "set" + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1);
        clazz = obj.getClass();
        clazz.getMethod(sName, f.getType()).invoke(obj, value);
    }

    private static <E> E getByGetter(Class clazz, Field f, Object obj) throws Exception {
        String gName = "";
        if (f.getType().getCanonicalName().equals("boolean")){
            gName = "is" + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1);
        }else{
            gName = "get" + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1);
        }
        return (E) clazz.getMethod(gName).invoke(obj);
    }

    public static <E> String serializeObject(String schemaName, E object) throws SerializerException {
        Class clazz = object.getClass();
        String res = "";
        if (clazz.isAnnotationPresent(XmlSerializerSchemas.class)) {
            XmlSerializerSchemas xs = (XmlSerializerSchemas) clazz.getAnnotation(XmlSerializerSchemas.class);
            SchemaElement se = findBySchemaName(xs, schemaName);
            if (se == null) {
                throw new SerializerException("Could not find schema definition for schema name: " + schemaName);
            } else {
                res += "<" + se.getName() + ">";
            }

            String nodeName = se.getName();

            for (Field f : clazz.getDeclaredFields()) {
                if (f.isAnnotationPresent(XmlSerializerSchemas.class)) {
                    xs = f.getAnnotation(XmlSerializerSchemas.class);
                    se = findBySchemaName(xs, schemaName);
                    if (se != null) {
                        if (se instanceof SchemaMapElement) {
                            SchemaMapElement me = (SchemaMapElement) se;
                            Object obj = null;
                            if (Modifier.isPublic(f.getModifiers())) {
                                try {
                                    obj = f.get(object);
                                } catch (Exception ex) {
                                    continue;
                                }
                            } else {
                                try {
                                    obj = getByGetter(clazz, f, object);
                                } catch (Exception e) {
                                    continue;
                                }
                            }
                            if (obj instanceof Map) {
                                res += "<" + me.getName() + ">" + serializeMap(me, (Map) obj) + "</" + me.getName() + ">";
                            } else {
                                throw new SerializerException("Incompatible schema type and field type, field name: " + f.getName());
                            }
                        } else if (se instanceof SchemaListElement) {
                            SchemaListElement le = (SchemaListElement) se;
                            Object obj = null;
                            if (Modifier.isPublic(f.getModifiers())) {
                                try {
                                    obj = f.get(object);
                                } catch (Exception ex) {
                                    continue;
                                }
                            } else {
                                try {
                                    obj = getByGetter(clazz, f, object);
                                } catch (Exception e) {
                                    continue;
                                }
                            }
                            if (obj instanceof Collection) {
                                res += "<" + le.getName() + ">" + serializeList(le, (Collection) obj) + "</" + le.getName() + ">";
                            } else {
                                throw new SerializerException("Incompatible schema type and field type, field name: " + f.getName());
                            }
                        } else {
                            Object obj = null;
                            if (Modifier.isPublic(f.getModifiers())) {
                                try {
                                    obj = f.get(object);
                                } catch (Exception ex) {
                                    continue;
                                }
                            } else {
                                try {
                                    obj = getByGetter(clazz, f, object);
                                } catch (Exception e) {
                                    continue;
                                }
                            }

                            if ((obj != null) && (obj.getClass().isAnnotationPresent(XmlSerializerSchemas.class))) {
                                res += "<" + se.getName() + ">" + serializeObject(schemaName, obj) + "</" + se.getName() + ">";
                            } else { //Nem biztos, hogy ez a legjobb megoldás, de majd menetközben úgy is fog fejlődni, és akkor majd kialakulnak a best practice-ek
                                res += "<" + se.getName() + ">" + ((obj == null) ? "null" : obj.toString()) + "</" + se.getName() + ">";
                            }
                        }
                    }
                }
            }
            res += "</" + nodeName + ">";
        } else {
            throw new SerializerException("Could not find any schema definition in class named: " + clazz.getCanonicalName());
        }
        return res;
    }

    public static RemoteException deserializeException(Node xml){
        if (xml.getNodeName().equals("exception")){
            String type = null;
            String msg = null;
            int code = 0;
            NodeList nl = xml.getChildNodes();
            for (int i = 0; i < nl.getLength(); i++){
                Node node = nl.item(i);
                if (node.getNodeName().equals("type")){
                    type = node.getFirstChild().getNodeValue();
                }
                if (node.getNodeName().equals("message")){
                    msg = node.getFirstChild().getNodeValue();
                }
                if (node.getNodeName().equals("code")){
                    code = Integer.parseInt(node.getFirstChild().getNodeValue());
                }
            }
            return new RemoteException(msg, code, type);
        }else{
            return null;
        }
    }
}
