package org.jboss.as.console.mbui.marshall;

import com.google.gwt.user.client.Window;
import com.google.gwt.xml.client.Document;
import com.google.gwt.xml.client.Element;
import com.google.gwt.xml.client.Node;
import com.google.gwt.xml.client.NodeList;
import com.google.gwt.xml.client.XMLParser;
import com.google.gwt.xml.client.impl.DOMUtils;
import org.jboss.as.console.mbui.marshall.adapters.DMRAdapter;
import org.jboss.as.console.mbui.marshall.adapters.EditorPanelAdapter;
import org.jboss.as.console.mbui.marshall.adapters.FormAdapter;
import org.jboss.as.console.mbui.marshall.adapters.PagesAdapter;
import org.jboss.as.console.mbui.marshall.adapters.PropertiesAdapter;
import org.jboss.as.console.mbui.marshall.adapters.PulldownAdapter;
import org.jboss.as.console.mbui.marshall.adapters.TodoAdapter;
import org.jboss.as.console.mbui.marshall.adapters.ToolstripAdapter;
import org.jboss.as.console.mbui.marshall.adapters.UsewareAdapter;
import org.jboss.as.console.mbui.model.StereoTypes;
import org.jboss.as.console.mbui.model.mapping.DMRMapping;
import org.useware.kernel.model.Dialog;
import org.useware.kernel.model.mapping.MappingType;
import org.useware.kernel.model.structure.Container;
import org.useware.kernel.model.structure.InteractionUnit;
import org.useware.kernel.model.structure.QName;
import org.useware.kernel.model.structure.builder.Builder;
import org.useware.kernel.model.structure.builder.InteractionUnitVisitor;

import java.util.HashSet;
import java.util.Set;
import java.util.Stack;

/**
 * @author Heiko Braun
 * @date 6/13/13
 */
public class DialogXML {

    private Set<String> stereoTypes;
    private static Set<ElementAdapter> adapters = new HashSet<ElementAdapter>();

    static {

        adapters.add(new UsewareAdapter("container"));
        adapters.add(new UsewareAdapter("trigger"));
        adapters.add(new UsewareAdapter("select"));
        adapters.add(new UsewareAdapter("input"));
        adapters.add(new UsewareAdapter("output"));
        adapters.add(new UsewareAdapter("link"));

        adapters.add(new PropertiesAdapter());
        adapters.add(new FormAdapter());
        adapters.add(new ToolstripAdapter());
        adapters.add(new PagesAdapter());
        adapters.add(new PulldownAdapter());
        adapters.add(new EditorPanelAdapter());
        adapters.add(new TodoAdapter());

        adapters.add(new DMRAdapter());
    }

    public DialogXML() {

        // known stereotypes
        this.stereoTypes = new HashSet<String>();
        for(StereoTypes type : StereoTypes.values())
        {
            stereoTypes.add(type.name().toLowerCase());
        }
    }


    public Dialog unmarshall(String xml)
    {

        try {
            Document document = XMLParser.parse(xml);
            Element root = document.getDocumentElement();

            // model
            Builder builder = new Builder();
            dfsElement(builder, DOMUtils.getFirstChildElement(root));

            // dialog
            Dialog dialog = new Dialog(new QName(root.getNamespaceURI(), root.getAttribute("id")), builder.build());

            return dialog;
        } catch (RuntimeException e) {
            Window.alert("Faile to parse XML: "+e.getMessage());
            throw e;
        }
    }



    private void dfsElement(Builder builder, Node root) {

        // the node itself
        ElementAdapter adapter = getAdapter(root.getNodeName());

        if(InteractionUnit.class == adapter.getType())
        {
            // interaction unit contents
            InteractionUnit unit = (InteractionUnit)adapter.fromXML(root);

            if(unit instanceof Container)
                builder.start((Container)unit);
            else
                builder.add(unit);

            // it's children
            NodeList children = root.getChildNodes();

            for(int i=0; i<children.getLength(); i++)
            {

                Node child = children.item(i);

                // skip anything except elements
                if(! (Node.ELEMENT_NODE == child.getNodeType()))
                    continue;

                if(child.hasChildNodes())
                {
                    dfsElement(builder, child);
                }
                else
                {
                    ElementAdapter childAdapter = getAdapter(child.getNodeName());
                    if(DMRMapping.class==childAdapter.getType())
                        builder.mappedBy((DMRMapping)childAdapter.fromXML(child));
                    else if(InteractionUnit.class==childAdapter.getType())
                        builder.add((InteractionUnit)childAdapter.fromXML(child));
                }
            }

            if(unit instanceof Container)
                builder.end();
        }
        else if (DMRMapping.class == adapter.getType())
        {
            // dmr mapping contents
            DMRMapping mapping = (DMRMapping) adapter.fromXML(root);
            builder.mappedBy(mapping);
        }

    }

    ElementAdapter getAdapter(String name) {
        ElementAdapter match = null;
        for(ElementAdapter adapter : adapters)
        {
            if(adapter.getElementName().equals(name))
            {
                match = adapter;
                break;
            }
        }

        if(null==match)
            throw new IllegalArgumentException("Invalid element name: "+ name);

        return match;
    }


    // ----------------------------


    public Document marshall(Dialog dialog) {

        DMRMarshallRoutine marshaller = new DMRMarshallRoutine(dialog);
        dialog.getInterfaceModel().accept(marshaller);
        return marshaller.getResult();
    }

    class DMRMarshallRoutine implements InteractionUnitVisitor
    {

        private final Dialog dialog;
        private Document document;
        private Stack<Node> stack = new Stack<Node>();

        DMRMarshallRoutine(Dialog dialog) {
            this.dialog = dialog;
            this.document = XMLParser.createDocument();
        }

        Document getResult() {
            return document;
        }

        @Override
        public void startVisit(Container container) {

            String name = container.getStereotype()!=null ?
                    container.getStereotype().toString().toLowerCase() : "container";

            Element el = getAdapter(name).toXML(document, container);
            marshallMapping(container, el);

            stack.push(el);
        }

        @Override
        public void visit(InteractionUnit unit) {

            String name = unit.getStereotype()!=null ?
                    unit.getStereotype().toString().toLowerCase() : resolveType(unit);

            Element el = getAdapter(name).toXML(document, unit);
            marshallMapping(unit, el);

            if(stack.isEmpty())
                throw new RuntimeException("Parse error");

            stack.peek().appendChild(el);
        }

        @Override
        public void endVisit(Container container) {
            if(stack.size()>1){
                Node el = stack.pop();
                stack.peek().appendChild(el);
            }
            else if(stack.size()==1)
            {
                Node root = stack.pop();

                Element dialogEl = DOMUtils.createElementNS(document, dialog.getId().getNamespaceURI(), "dialog");
                dialogEl.setAttribute("id", dialog.getId().getLocalPart());
                dialogEl.appendChild(root);
                document.appendChild(dialogEl);

            }
            else
            {
                throw new RuntimeException("Parse error");
            }
        }

        private void marshallMapping(InteractionUnit unit, Element target)
        {
            if(unit.hasMapping(MappingType.DMR))
            {
                DMRMapping mapping = (DMRMapping) unit.getMapping(MappingType.DMR);
                Element el = getAdapter("dmr").toXML(document, mapping);
                target.appendChild(el);
            }
        }
    }

    private static String resolveType(InteractionUnit interactionUnit) {
        String className = interactionUnit.getClass().getName().toLowerCase();
        return className.substring(className.lastIndexOf(".")+1, className.length());
    }


}
