package be.iminds.ilabt.jfed.ui.javafx.log_gui;

import be.iminds.ilabt.jfed.util.JSonHelper;
import be.iminds.ilabt.jfed.util.XmlRpcPrintUtil;
import javafx.scene.Node;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.paint.Color;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.GlyphFont;
import org.controlsfx.glyphfont.GlyphFontRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.tree.TreeNode;
import java.util.*;

/**
 * XmlRpcPane
 */
public class XmlRpcPane extends TreeView<TreeNode> {
    private final static Logger LOG = LoggerFactory.getLogger(XmlRpcPane.class);

    private Object obj;
    private TreeNode rootTreeNode;

    public XmlRpcPane() {

    }


    public Object getObj() {
        return obj;
    }

    public void setObj(Object obj) {
        this.obj = obj;
        updateObj();
    }

    public Object getObjString() {
        return XmlRpcPrintUtil.xmlRpcObjectToString(obj);
    }

    public void setObjString(String xmlRpcString) {
        setObj(JSonHelper.jsonStringToXmlRpcLikeObject(xmlRpcString));
    }


    public void updateObj() {
        this.rootTreeNode = new TreeNode(null, this.obj);

        LinkedList<TreeNode> nodesToRecurse = new LinkedList<>();
        if (!rootTreeNode.isLeaf())
            nodesToRecurse.add(rootTreeNode);

        while (!nodesToRecurse.isEmpty()) {
            TreeNode next = nodesToRecurse.removeFirst();
            next.createChildren().stream().filter(child -> !child.isLeaf()).forEach(nodesToRecurse::addLast);
        }

        this.rootProperty().setValue(rootTreeNode.treeItem);

        expandOrCollapseBasedOnItemLength();
    }


    private static final GlyphFont fontAwesome = GlyphFontRegistry.font("FontAwesome");
    private static Node getIcon(Object o) {
        if (o == null) return fontAwesome.create(FontAwesome.Glyph.BOMB).color(Color.RED);
        if (o instanceof String) return fontAwesome.create(FontAwesome.Glyph.PENCIL).color(Color.GREEN);
        if (o instanceof Map) return fontAwesome.create(FontAwesome.Glyph.TH_LIST).color(Color.BLUE);
        if (o instanceof Map.Entry) return fontAwesome.create(FontAwesome.Glyph.TH_LIST).color(Color.BLUE);
        if (o instanceof List) return fontAwesome.create(FontAwesome.Glyph.LIST_ALT).color(Color.BLUE);
        if (o instanceof Integer) return fontAwesome.create(FontAwesome.Glyph.LIST_OL).color(Color.GREEN);
        if (o instanceof Double) return fontAwesome.create(FontAwesome.Glyph.LIST_OL).color(Color.GREEN);
        if (o instanceof Boolean) return fontAwesome.create(FontAwesome.Glyph.STOP).color(Color.GREEN);
        return fontAwesome.create(FontAwesome.Glyph.BOMB).color(Color.RED);
    }

    private static String objectToString(Object o) {
        if (o == null)
            return "empty";
        if (o instanceof Map.Entry) {
            Object key = ((Map.Entry)o).getKey();
            Object value = ((Map.Entry)o).getValue();
            if (value instanceof Map || value instanceof List)
                return objectToString(key);
            String valueAsString = objectToString(value);
            if (valueAsString.length() < 100)
                return objectToString(key) + " -> " + valueAsString;
            else
                return objectToString(key);
        }
        if (o instanceof String) {
            return "\""+ o +"\"";
        }
        if (o instanceof Map) {
            return "struct with "+(((Map)o).size())+" items";
        }
        if (o instanceof List) {
            return "list with "+(((List)o).size())+" items";
        }
        if (o instanceof Integer) {
            return o + "";
        }
        if (o instanceof Double) {
            return  o + "";
        }
        if (o instanceof Boolean) {
            return  ((Boolean) o) ? "true" : "false";
        }
        return "unknown! toString=\""+o+"\"";
    }

    private static class TreeNode {
        private final TreeItem treeItem;
        private final TreeNode parent;
        private final List<TreeNode> children = new ArrayList<>();
        private final Object o;
        private final String typeText;
        private final String text;
        private final boolean leaf;

        private TreeNode(TreeNode parent, Object o) {
            this.o = o;
            this.parent = parent;

            String t = null;
            String tt = null;
            Node icon = getIcon(o);
            boolean leaf = true;
            if (o == null) {
                t = "empty";
                tt = "empty";
            } else {
                if (o instanceof Map.Entry) {
                    tt = "entry";

                    Object key = ((Map.Entry)o).getKey();
                    Object value = ((Map.Entry)o).getValue();
                    if (value instanceof Map || value instanceof List) {
                        leaf = false;
                        t = objectToString(key);
                        icon = getIcon(key);
                    }
                    else {
                        String valueAsString = objectToString(value);
                        if (valueAsString.length() < 100) {
                            leaf = true;
                            t = objectToString(key) + " -> " + valueAsString;
                            icon = getIcon(value);
                        }
                        else {
                            leaf = false;
                            icon = getIcon(key);
                            t = objectToString(key);
                        }
                    }
                }
                if (o instanceof String) {
                    t = objectToString(o);
                    tt = "string";
                }
                if (o instanceof Map) {
                    t = objectToString(o);
                    tt = "struct";
                    leaf = false;
                }
                if (o instanceof List) {
                    t = objectToString(o);
                    tt = "list";
                    leaf = false;
                }
                if (o instanceof Integer) {
                    t = objectToString(o);
                    tt = "integer";
                }
                if (o instanceof Double) {
                    t = objectToString(o);
                    tt = "double";
                }
                if (o instanceof Boolean) {
                    t = objectToString(o);
                    tt = "boolean";
                }
            }
            if (t != null) {
                text = t;
                assert tt != null;
                typeText = tt;
            } else {
                LOG.warn("Unknown type in XmlRpc object: "+o.getClass().getName());
                text = "unknown! toString=\""+o+"\"";
                typeText = ""+o.getClass();
            }
            this.leaf = leaf;

            this.treeItem = new TreeItem<>(this, icon);
        }

        TreeNode createChild(Object o) {
            TreeNode child = new TreeNode(this, o);
            children.add(child);
            treeItem.getChildren().add(child.treeItem);
            return child;
        }
        List<TreeNode> createChildren() {
            assert !isLeaf();

            List<TreeNode> children = new ArrayList<>();
            if (o instanceof List) {
                List v = (List) o;
                for (Object oo : v)
                    children.add(createChild(oo));
            }
            if (o instanceof Map) {
                Map ht = (Map) o;
                for (Object oo : ht.entrySet())
                    children.add(createChild(oo));
            }
            if (o instanceof Map.Entry) {
                Map.Entry e = (Map.Entry) o;
                children.add(createChild(e.getValue()));
                treeItem.setExpanded(true);
            }
            return children;
        }

        public boolean isLeaf() {
            return leaf;
        }

        public String getTypeText() {
            return typeText;
        }

        public String getText() {
            return text;
        }

        @Override
        public String toString() {
            return text;
        }

        public boolean isLong() {
            if (leaf)
                return text.length() > 200;
            else {
                for (TreeNode child : children) {
                    if (child.isLeaf() && child.isLong())
                        return true;
                }
                return false;
            }
        }
    }

    public void expandAll() {
        Deque<TreeNode> nodesToCheck = new LinkedList<>();
        nodesToCheck.add(rootTreeNode);
        while (!nodesToCheck.isEmpty()) {
            TreeNode n = nodesToCheck.removeFirst();
            n.treeItem.setExpanded(true);
            for (TreeNode c : n.children)
                nodesToCheck.add(c);
        }
    }
    public void collapseAll() {
        Deque<TreeNode> nodesToCheck = new LinkedList<>();
        nodesToCheck.add(rootTreeNode);
        while (!nodesToCheck.isEmpty()) {
            TreeNode n = nodesToCheck.removeFirst();
            n.treeItem.setExpanded(false);
            for (TreeNode c : n.children)
                nodesToCheck.add(c);
        }

    }
    public void collapseOnlyLongItems() {
        Deque<TreeNode> nodesToCheck = new LinkedList<>();
        nodesToCheck.add(rootTreeNode);
        while (!nodesToCheck.isEmpty()) {
            TreeNode n = nodesToCheck.removeFirst();
            if (n.isLong())
                n.treeItem.setExpanded(false);
            for (TreeNode c : n.children)
                nodesToCheck.add(c);
        }

    }
    public void expandOnlyShortItems() {
        Deque<TreeNode> nodesToCheck = new LinkedList<>();
        nodesToCheck.add(rootTreeNode);
        while (!nodesToCheck.isEmpty()) {
            TreeNode n = nodesToCheck.removeFirst();
            if (!n.isLong())
                n.treeItem.setExpanded(true);
            for (TreeNode c : n.children)
                nodesToCheck.add(c);
        }
    }
    public void expandOrCollapseBasedOnItemLength() {
        Deque<TreeNode> nodesToCheck = new LinkedList<>();
        nodesToCheck.add(rootTreeNode);
        while (!nodesToCheck.isEmpty()) {
            TreeNode n = nodesToCheck.removeFirst();
            n.treeItem.setExpanded(!n.isLong());
            for (TreeNode c : n.children)
                nodesToCheck.add(c);
        }
    }
}
