/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package org.jboss.as.cli.gui;

import java.util.ArrayList;
import java.util.List;
import javax.swing.tree.DefaultMutableTreeNode;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;
import org.jboss.dmr.Property;

/**
 * A node in the management tree.  Non-leaves are addressable entities in a DMR command.  Leaves are attributes.
 *
 * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc.
 */
public class ManagementModelNode extends DefaultMutableTreeNode {

    private CliGuiContext cliGuiCtx;
    private CommandExecutor executor;
    private boolean isLeaf = false;
    private boolean isGeneric = false;

    /**
     * Constructor for root node only.
     */
    public ManagementModelNode(CliGuiContext cliGuiCtx) {
        this.cliGuiCtx = cliGuiCtx;
        this.executor = cliGuiCtx.getExecutor();
        this.isLeaf = false;
        setUserObject(new UserObject());
    }

    private ManagementModelNode(CliGuiContext cliGuiCtx, UserObject userObject) {
        this.cliGuiCtx = cliGuiCtx;
        this.executor = cliGuiCtx.getExecutor();
        this.isLeaf = userObject.isLeaf;
        this.isGeneric = userObject.isGeneric;
        if (isGeneric) setAllowsChildren(false);
        setUserObject(userObject);
    }

    /**
     * Clone as a root node
     * @return The cloned node.
     */
    @Override
    public ManagementModelNode clone() {
        UserObject toBeCloned = (UserObject)getUserObject();
        UserObject clonedUsrObj = new UserObject(toBeCloned, addressPath(), true);
        return new ManagementModelNode(cliGuiCtx, clonedUsrObj);
    }

    /**
     * Refresh children using read-resource operation.
     */
    public void explore() {
        if (isLeaf) return;
        if (isGeneric) return;
        removeAllChildren();

        try {
            String addressPath = addressPath();
            ModelNode resourceDesc = executor.doCommand(addressPath + ":read-resource-description");
            resourceDesc = resourceDesc.get("result");
            ModelNode response = executor.doCommand(addressPath + ":read-resource(include-runtime=true,include-defaults=true)");
            ModelNode result = response.get("result");
            if (!result.isDefined()) return;

            List<String> childrenTypes = getChildrenTypes(addressPath);
            for (ModelNode node : result.asList()) {
                Property prop = node.asProperty();
                if (childrenTypes.contains(prop.getName())) { // resource node
                    if (hasGenericOperations(addressPath, prop.getName())) {
                        add(new ManagementModelNode(cliGuiCtx, new UserObject(node, prop.getName())));
                    }
                    if (prop.getValue().isDefined()) {
                        for (ModelNode innerNode : prop.getValue().asList()) {
                            UserObject usrObj = new UserObject(innerNode, prop.getName(), innerNode.asProperty().getName());
                            add(new ManagementModelNode(cliGuiCtx, usrObj));
                        }
                    }
                } else { // attribute node
                    UserObject usrObj = new UserObject(node, resourceDesc, prop.getName(), prop.getValue().asString());
                    add(new ManagementModelNode(cliGuiCtx, usrObj));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private boolean hasGenericOperations(String addressPath, String resourceName) throws Exception {
        ModelNode response = executor.doCommand(addressPath + resourceName + "=*/:read-operation-names");
        if (response.get("outcome").asString().equals("failed")) return false;

        for (ModelNode node : response.get("result").asList()) {
            if (node.asString().equals("add")) return true;
        }

        return false;
    }

    private List<String> getChildrenTypes(String addressPath) throws Exception {
        List<String> childrenTypes = new ArrayList<String>();
        ModelNode readChildrenTypes = executor.doCommand(addressPath + ":read-children-types");
        for (ModelNode type : readChildrenTypes.get("result").asList()) {
            childrenTypes.add(type.asString());
        }
        return childrenTypes;
    }

    /**
     * Get the DMR path for this node.  For leaves, the DMR path is the path of its parent.
     * @return The DMR path for this node.
     */
    public String addressPath() {
        if (isLeaf) {
            ManagementModelNode parent = (ManagementModelNode)getParent();
            return parent.addressPath();
        }

        StringBuilder builder = new StringBuilder();
        for (Object pathElement : getUserObjectPath()) {
            UserObject userObj = (UserObject)pathElement;
            if (userObj.isRoot()) { // don't want to escape root
                builder.append(userObj.getName());
                continue;
            }

            builder.append(userObj.getName());
            builder.append("=");
            builder.append(userObj.getEscapedValue());
            builder.append("/");
        }

        return builder.toString();
    }

    @Override
    public boolean isLeaf() {
        return this.isLeaf;
    }

    public boolean isGeneric() {
        return this.isGeneric;
    }

    public static String escapeAddressElement(String element) {
        element = element.replace(":", "\\:");
        element = element.replace("/", "\\/");
        element = element.replace("=", "\\=");
        element = element.replace(" ", "\\ ");
        element = element.replace("$", "\\$");
        return element;
    }

    /**
     * Encapsulates name/value pair.  Also encapsulates escaping of the value.
     */
    public class UserObject {
        private ModelNode backingNode;
        private String name;
        private String value;
        private boolean isLeaf;
        private boolean isGeneric = false;
        private boolean isRoot = false;
        private String separator;
        private AttributeDescription attribDesc = null;

        /**
         * Constructor for the root node.
         */
        public UserObject() {
            this.backingNode = new ModelNode();
            this.name = "/";
            this.value = "";
            this.isLeaf = false;
            this.isRoot = true;
            this.separator = "";
        }

        /**
         * Constructor for cloning purposes.
         * @param usrObj The object to be cloned.
         */
        public UserObject(UserObject usrObj, String addressPath, boolean isRoot) {
            if (usrObj.backingNode != null) this.backingNode = usrObj.backingNode.clone();
            this.name = addressPath;
            this.value = usrObj.value;
            this.isLeaf = usrObj.isLeaf;
            this.isRoot = isRoot;
            this.isGeneric = usrObj.isGeneric;
            this.separator = usrObj.separator;
            if (usrObj.attribDesc != null) this.attribDesc = new AttributeDescription(usrObj.attribDesc);
        }

        /**
         * Constructor for generic folder where resource=*.
         *
         * @param name The name of the resource.
         */
        public UserObject(ModelNode backingNode, String name) {
            this.backingNode = backingNode;
            this.name = name;
            this.value = "*";
            this.isLeaf = false;
            this.isGeneric = true;
            this.separator = "=";
        }

        // resource node such as subsystem=weld
        public UserObject(ModelNode backingNode, String name, String value) {
            this.backingNode = backingNode;
            this.name = name;
            this.value = value;
            this.isLeaf = false;
            this.separator = "=";
        }

        // attribute
        public UserObject(ModelNode backingNode, ModelNode resourceDesc, String name, String value) {
            this.attribDesc = new AttributeDescription(resourceDesc.get("attributes", name));
            this.backingNode = backingNode;
            this.name = name;
            this.value = value;
            this.isLeaf = true;

            if (this.attribDesc.isGraphable()) {
                this.separator = " \u2245 ";
            } else {
                this.separator = " => ";
            }
        }

        public ModelNode getBackingNode() {
            return this.backingNode;
        }

        public AttributeDescription getAttributeDescription() {
            return this.attribDesc;
        }

        public String getName() {
            return this.name;
        }

        public String getValue() {
            return this.value;
        }

        public String getEscapedValue() {
            return ManagementModelNode.escapeAddressElement(this.value);
        }

        public boolean isRoot() {
            return this.isRoot;
        }

        public boolean isLeaf() {
            return this.isLeaf;
        }

        public boolean isGeneric() {
            return this.isGeneric;
        }

        @Override
        public String toString() {
            if (isRoot) {
                return this.name;
            } else {
                return this.name + this.separator + this.value;
            }
        }
    }

    class AttributeDescription {

        private ModelNode attributes;

        AttributeDescription(ModelNode attributes) {
            this.attributes = attributes;
        }

        // Used for cloning
        AttributeDescription(AttributeDescription attrDesc) {
            this.attributes = attrDesc.attributes.clone();
        }

        /**
         * Is this a runtime attribute?
         */
        public boolean isRuntime() {
            return attributes.get("storage").asString().equals("runtime");
        }

        public ModelType getType() {
            return attributes.get("type").asType();
        }

        public boolean isGraphable() {
            return isRuntime() && isNumeric();
        }

        public boolean isNumeric() {
            ModelType type = getType();
            return (type == ModelType.BIG_DECIMAL) ||
                   (type == ModelType.BIG_INTEGER) ||
                   (type == ModelType.DOUBLE) ||
                   (type == ModelType.INT) ||
                   (type == ModelType.LONG);
        }
    }

}