/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.controller.operations.global;

import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ABSOLUTE_ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FILTERED_ATTRIBUTES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.FILTERED_CHILDREN_TYPES;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.RELATIVE_ADDRESS;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.UNREADABLE_CHILDREN;

import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;

/**
 * Collection point for information about data filtered from a :read-resource[-description] call.
 *
 * @author Brian Stansberry (c) 2013 Red Hat Inc.
 */
class FilteredData {

    private final int baseAddressLength;
    private Map<PathAddress, ResourceData> map;

    FilteredData(PathAddress baseAddress) {
        this.baseAddressLength = baseAddress.size();
    }

    void addReadRestrictedAttribute(PathAddress fullAddress, String attribute) {
        ResourceData rd = getResourceData(fullAddress);
        if (rd.attributes == null) {
            rd.attributes = new HashSet<String>();
        }
        rd.attributes.add(attribute);
    }

    void addReadRestrictedResource(PathAddress fullAddress) {
        assert fullAddress.size() > 0 : "cannot filter root resource";
        ResourceData rd = getResourceData(fullAddress.subAddress(0, fullAddress.size() - 1));
        if (rd.children == null) {
            rd.children = new HashSet<PathElement>();
        }
        rd.children.add(fullAddress.getLastElement());

    }

    void addAccessRestrictedResource(PathAddress fullAddress) {
        assert fullAddress.size() > 0 : "cannot filter root resource";
        ResourceData rd = getResourceData(fullAddress.subAddress(0, fullAddress.size() - 1));
        if (rd.childTypes == null) {
            rd.childTypes = new HashSet<PathElement>();
        }
        rd.childTypes.add(fullAddress.getLastElement());

    }

    boolean hasFilteredData() {
        return map != null;
    }

    boolean isFilteredResource(PathAddress parent, PathElement child) {
        boolean result = false;
        ResourceData rd = map == null ? null : map.get(parent);
        if (rd != null) {
            result = (rd.children != null && rd.children.contains(child))
                       || (rd.childTypes != null && rd.childTypes.contains(child));
        }
        return result;
    }

    boolean isAddressFiltered(PathAddress parent, PathElement child) {
        boolean result = false;
        ResourceData rd = map == null ? null : map.get(parent);
        if (rd != null) {
            result = (rd.childTypes != null && rd.childTypes.contains(child));
        }
        return result;
    }

    /** Report on the filtered data in DMR . */
    ModelNode toModelNode() {
        ModelNode result = null;
        if (map != null) {
            result = new ModelNode();
            for (Map.Entry<PathAddress, ResourceData> entry : map.entrySet()) {
                ModelNode item = new ModelNode();
                PathAddress pa = entry.getKey();
                item.get(ABSOLUTE_ADDRESS).set(pa.toModelNode());
                ResourceData rd = entry.getValue();
                item.get(RELATIVE_ADDRESS).set(pa.subAddress(baseAddressLength).toModelNode());
                ModelNode attrs = new ModelNode().setEmptyList();
                if (rd.attributes != null) {
                    for (String attr : rd.attributes) {
                        attrs.add(attr);
                    }
                }
                if (attrs.asInt() > 0) {
                    item.get(FILTERED_ATTRIBUTES).set(attrs);
                }
                ModelNode children = new ModelNode().setEmptyList();
                if (rd.children != null) {
                    for (PathElement pe : rd.children) {
                        children.add(new Property(pe.getKey(), new ModelNode(pe.getValue())));
                    }
                }
                if (children.asInt() > 0) {
                    item.get(UNREADABLE_CHILDREN).set(children);
                }
                ModelNode childTypes = new ModelNode().setEmptyList();
                if (rd.childTypes != null) {
                    Set<String> added = new HashSet<String>();
                    for (PathElement pe : rd.childTypes) {
                        if (added.add(pe.getKey())) {
                            childTypes.add(pe.getKey());
                        }
                    }
                }
                if (childTypes.asInt() > 0) {
                    item.get(FILTERED_CHILDREN_TYPES).set(childTypes);
                }
                result.add(item);
            }
        }
        return result;
    }

    /**
     * Take data exported via {@link #toModelNode()} and store it locally. This is so filtering
     * information from a remote process can be incorporated with local data.
     *
     * @param modelNode a defined node of type LIST
     * @param addressPrefix address that should be added as a prefix to absolute addresses in {@code modelNode}
     */
    void populate(ModelNode modelNode, PathAddress addressPrefix) {
        for (ModelNode item : modelNode.asList()) {
            PathAddress absAddr = addressPrefix.append(PathAddress.pathAddress(item.get(ABSOLUTE_ADDRESS)));
            if (item.hasDefined(FILTERED_ATTRIBUTES)) {
                for (ModelNode node : item.get(FILTERED_ATTRIBUTES).asList()) {
                    addReadRestrictedAttribute(absAddr, node.asString());
                }
            }
            if (item.hasDefined(UNREADABLE_CHILDREN)) {
                for (Property prop : item.get(UNREADABLE_CHILDREN).asPropertyList()) {
                    PathElement pe = PathElement.pathElement(prop.getName(), prop.getValue().asString());
                    addReadRestrictedResource(PathAddress.pathAddress(absAddr, pe));
                }
            }
            if (item.hasDefined(FILTERED_CHILDREN_TYPES)) {
                for (ModelNode type : item.get(FILTERED_CHILDREN_TYPES).asList()) {
                    PathElement pe = PathElement.pathElement(type.asString());
                    addAccessRestrictedResource(PathAddress.pathAddress(absAddr, pe));
                }
            }
        }
    }

    private ResourceData getResourceData(PathAddress fullAddress) {
        if (map == null) {
            map = new LinkedHashMap<PathAddress, ResourceData>();
        }
        ResourceData result = map.get(fullAddress);
        if (result == null) {
            result = new ResourceData();
            map.put(fullAddress, result);
        }
        return result;
    }

    private static class ResourceData {
        private Set<String> attributes;
        private Set<PathElement> children;
        private Set<PathElement> childTypes;
    }
}
