/**
 * Copyright (C) 2012 Orbeon, Inc.
 *
 * This program is free software; you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation; either version
 * 2.1 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
 */
package org.orbeon.oxf.processor.XQuery;

import org.orbeon.oxf.xml.XMLReceiver;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


public class ObjectReceiver implements XMLReceiver {


    private XMLReceiver currentReceiver = null;
    private Field currentField = null;
    private Class currentMemberType = null;
    private StringBuilder buffer = null;
    private int level = 0;

    public void setDocumentLocator(Locator locator) {
        if (currentReceiver != null) {
            currentReceiver.setDocumentLocator(locator);
        }
    }

    public void startDocument() throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.startDocument();
        }
    }

    public void endDocument() throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.endDocument();
        }
    }

    public void startPrefixMapping(String s, String s1) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.startPrefixMapping(s, s1);
        }
    }

    public void endPrefixMapping(String s) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.endPrefixMapping(s);
        }
    }

    private XMLReceiver instantiateXMLReceiver(Class klass) throws SAXException {
        try {
            return (XMLReceiver) (klass.newInstance());
        } catch (InstantiationException e) {
            // If we can't instantiate the class, it may be because it is an inner class of the current one...
            try {
                Constructor constructor = klass.getConstructor(this.getClass());
                return (XMLReceiver) constructor.newInstance(this);
            } catch (NoSuchMethodException e1) {
                throw new SAXException(e1);
            } catch (InstantiationException e1) {
                throw new SAXException(e1);
            } catch (InvocationTargetException e1) {
                throw new SAXException(e1);
            } catch (IllegalAccessException e1) {
                throw new SAXException(e1);
            }
        } catch (IllegalAccessException e) {
            throw new SAXException(e);
        }
    }

    private static boolean classIsCollection(Class klass) {
        return Collection.class.isAssignableFrom(klass);
    }

    private static boolean classIsXMLReceiver(Class klass) {
        return XMLReceiver.class.isAssignableFrom(klass);
    }

    private static Class classMemberType(Field field) {
        // Can't believe there isn't some smarter way to do that...
        String genericType = field.getGenericType().toString();
        Pattern memberPattern = Pattern.compile(".*<(.*)>");
        Matcher matcher = memberPattern.matcher(genericType);
        if (matcher.matches()) {
            String memberTypeName = matcher.group(1);
            try {
                return Class.forName(memberTypeName);
            } catch (ClassNotFoundException e) {
                return null;
            }
        }
        return null;
    }

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        try {
            if (currentReceiver != null) {
                /* Forwarding state */
                currentReceiver.startElement(uri, localName, qName, attributes);
            } else if (currentField == null) {
                /* Need to create a new object to receive the content of a new element */
                currentField = this.getClass().getField(localName);
                Class klass = currentField.getType();
                if (classIsXMLReceiver(klass)) {
                    currentReceiver = instantiateXMLReceiver(klass);
                } else if (classIsCollection(klass)) {
                    currentMemberType = classMemberType(currentField);
                    if (classIsXMLReceiver(currentMemberType)) {
                        currentReceiver = instantiateXMLReceiver(currentMemberType);
                    }
                }
                buffer = null;
            } else {
                /* Sub elements are not expected here! */
                throw new SAXException("Sub element " + localName + " was not expected here!");
            }
        } catch (NoSuchFieldException e) {
            throw new SAXException(e);
        }
        level++;
    }


    public void endElement(String s, String s1, String s2) throws SAXException {
        try {
            level--;
            if (level == 0) {
                /* We have finished to receive an object */
                if (currentReceiver == null && currentMemberType == null) {
                    /* This was a non repeatable simple type element */
                    Class klass = currentField.getType();
                    String className = klass.getName();
                    String value = buffer == null ? "" : buffer.toString();
                    if (className.equals("int")) {
                        currentField.setInt(this, Integer.parseInt(value));
                    } else if (className.equals("boolean")) {
                        currentField.setBoolean(this, value.equals("true"));
                    } else if (klass.equals(value.getClass())) {
                        currentField.set(this, value);
                    } else {
                        currentField.set(this, klass.getConstructor(value.getClass()).newInstance(value));
                    }
                    //Method method = this.getClass().getMethod("addValue", Class.forName("java.lang.String"));
                    //method.invoke(buffer.toString());
                } else if (currentReceiver == null && currentMemberType != null) {
                    /* This was a repeatable simple type element */
                    String value = buffer == null ? "" : buffer.toString();
                    Class klass = currentField.getType();
                    Collection collection = (Collection) currentField.get(this);
                    if (collection == null) {
                        collection = (Collection) klass.newInstance();
                        currentField.set(this, collection);
                    }
                    if (currentMemberType.equals(value.getClass())) {
                        collection.add(value);
                    } else {
                        collection.add(currentMemberType.getConstructor(value.getClass()).newInstance(value));
                    }
                } else if (currentMemberType != null) {
                    Class klass = currentField.getType();
                    Collection collection = (Collection) currentField.get(this);
                    if (collection == null) {
                        collection = (Collection) klass.newInstance();
                        currentField.set(this, collection);
                    }
                    collection.add(currentReceiver);
                } else {
                    /* This was not a simple type element */
                    currentField.set(this, currentReceiver);
                }
                currentReceiver = null;
                currentField = null;
                currentMemberType = null;
                buffer = null;
            } else {
                currentReceiver.endElement(s, s1, s2);
            }
        } catch (IllegalAccessException e) {
            throw new SAXException(e);
        } catch (NoSuchMethodException e) {
            throw new SAXException(e);
        } catch (InstantiationException e) {
            throw new SAXException(e);
        } catch (InvocationTargetException e) {
            throw new SAXException(e);
        }
    }

    public void characters(char[] chars, int i, int i1) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.characters(chars, i, i1);
        } else if (buffer == null) {
            buffer = new StringBuilder(new String(chars, i, i1));
        } else {
            buffer.append(chars, i, i1);
        }
    }

    public void ignorableWhitespace(char[] chars, int i, int i1) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.ignorableWhitespace(chars, i, i1);
        }
    }

    public void processingInstruction(String s, String s1) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.processingInstruction(s, s1);
        }
    }

    public void skippedEntity(String s) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.skippedEntity(s);
        }
    }

    public void startDTD(String s, String s1, String s2) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.startDTD(s, s1, s2);
        }
    }

    public void endDTD() throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.endDTD();
        }
    }

    public void startEntity(String s) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.startEntity(s);
        }
    }

    public void endEntity(String s) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.endEntity(s);
        }
    }

    public void startCDATA() throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.startCDATA();
        }
    }

    public void endCDATA() throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.endCDATA();
        }
    }

    public void comment(char[] chars, int i, int i1) throws SAXException {
        if (currentReceiver != null) {
            currentReceiver.comment(chars, i, i1);
        }
    }
}