/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, 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.registry;

import static org.jboss.as.controller.ControllerMessages.MESSAGES;

import java.util.Collections;
import java.util.HashSet;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

import org.jboss.as.controller.PathAddress;
import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ProxyController;
import org.jboss.as.controller.ResourceDefinition;
import org.jboss.as.controller.access.management.AccessConstraintUtilizationRegistry;
import org.jboss.as.controller.descriptions.DescriptionProvider;

/**
 * A registry of values within a specific key type.
 */
final class NodeSubregistry {

    private static void checkPermission() {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(ImmutableManagementResourceRegistration.ACCESS_PERMISSION);
        }
    }

    private static final String WILDCARD_VALUE = PathElement.WILDCARD_VALUE;

    private final String keyName;
    private final ConcreteResourceRegistration parent;
    private final AccessConstraintUtilizationRegistry constraintUtilizationRegistry;
    @SuppressWarnings( { "unused" })
    private volatile Map<String, AbstractResourceRegistration> childRegistries;

    private static final AtomicMapFieldUpdater<NodeSubregistry, String, AbstractResourceRegistration> childRegistriesUpdater = AtomicMapFieldUpdater.newMapUpdater(AtomicReferenceFieldUpdater.newUpdater(NodeSubregistry.class, Map.class, "childRegistries"));

    NodeSubregistry(final String keyName, final ConcreteResourceRegistration parent, AccessConstraintUtilizationRegistry constraintUtilizationRegistry) {
        this.keyName = keyName;
        this.parent = parent;
        this.constraintUtilizationRegistry = constraintUtilizationRegistry;
        childRegistriesUpdater.clear(this);
    }

    AbstractResourceRegistration getParent() {
        return parent;
    }

    Set<String> getChildNames(){
        final Map<String, AbstractResourceRegistration> snapshot = this.childRegistries;
        if (snapshot == null) {
            return Collections.emptySet();
        }
        return new HashSet<String>(snapshot.keySet());
    }

    ManagementResourceRegistration register(final String elementValue, final ResourceDefinition provider, boolean runtimeOnly) {
        final AbstractResourceRegistration newRegistry = new ConcreteResourceRegistration(elementValue, this, provider, constraintUtilizationRegistry, runtimeOnly);
        final AbstractResourceRegistration existingRegistry = childRegistriesUpdater.putIfAbsent(this, elementValue, newRegistry);
        if (existingRegistry != null) {
            throw MESSAGES.nodeAlreadyRegistered(getLocationString(), elementValue);
        }
        return newRegistry;
    }

    ProxyControllerRegistration registerProxyController(final String elementValue, final ProxyController proxyController) {
        final ProxyControllerRegistration newRegistry = new ProxyControllerRegistration(elementValue, this, proxyController);
        final AbstractResourceRegistration appearingRegistry = childRegistriesUpdater.putIfAbsent(this, elementValue, newRegistry);
        if (appearingRegistry != null) {
            throw MESSAGES.nodeAlreadyRegistered(getLocationString(), elementValue);
        }
        //register(elementValue, newRegistry);
        return newRegistry;
    }

    void unregisterProxyController(final String elementValue) {
        checkPermission();
        childRegistriesUpdater.remove(this, elementValue);
    }

    public AliasResourceRegistration registerAlias(final String elementValue, AliasEntry aliasEntry, AbstractResourceRegistration target) {
        final AliasResourceRegistration newRegistry = new AliasResourceRegistration(elementValue, this, aliasEntry, target);
        final AbstractResourceRegistration existingRegistry = childRegistriesUpdater.putIfAbsent(this, elementValue, newRegistry);
        if (existingRegistry != null) {
            throw MESSAGES.nodeAlreadyRegistered(getLocationString(), elementValue);
        }
        return newRegistry;
    }

    public void unregisterAlias(final String elementValue) {
        checkPermission();
        childRegistriesUpdater.remove(this, elementValue);
    }


    void unregisterSubModel(final String elementValue) {
        checkPermission();
        childRegistriesUpdater.remove(this, elementValue);
    }

    OperationEntry getOperationEntry(final ListIterator<PathElement> iterator, final String child, final String operationName, OperationEntry inherited) {

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        // First search the non-wildcard child; if not found, search the wildcard child
        OperationEntry result = null;

        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getOperationEntry(searchControl.getIterator(), operationName, inherited);
        }

        if (result == null && searchControl.getWildCardRegistry() != null) {
            result = searchControl.getWildCardRegistry().getOperationEntry(searchControl.getIterator(), operationName, inherited);
        }

        return result;
    }

    void getHandlers(final ListIterator<PathElement> iterator, final String child, final Map<String, OperationEntry> providers, final boolean inherited) {

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        // First search the wildcard child, then if there is a non-wildcard child search it
        // Non-wildcard goes second so its description overwrites in case of duplicates

        if (searchControl.getWildCardRegistry() != null) {
            searchControl.getWildCardRegistry().getOperationDescriptions(searchControl.getIterator(), providers, inherited);
        }

        if (searchControl.getSpecifiedRegistry() != null) {
            searchControl.getSpecifiedRegistry().getOperationDescriptions(searchControl.getIterator(), providers, inherited);
        }
    }

    String getLocationString() {
        return parent.getLocationString() + "(" + keyName + " => ";
    }

    DescriptionProvider getModelDescription(final ListIterator<PathElement> iterator, final String child) {

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        // First search the non-wildcard child; if not found, search the wildcard child
        DescriptionProvider result = null;

        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getModelDescription(searchControl.getIterator());
        }

        if (result == null && searchControl.getWildCardRegistry() != null) {
            result = searchControl.getWildCardRegistry().getModelDescription(searchControl.getIterator());
        }

        return result;
    }

    Set<String> getChildNames(final ListIterator<PathElement> iterator, final String child){

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        Set<String> result = null;
        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getChildNames(searchControl.getIterator());
        }

        if (searchControl.getWildCardRegistry() != null) {
            final Set<String> wildCardChildren = searchControl.getWildCardRegistry().getChildNames(searchControl.getIterator());
            if (result == null) {
                result = wildCardChildren;
            } else if (wildCardChildren != null) {
                // Merge
                result = new HashSet<String>(result);
                result.addAll(wildCardChildren);
            }
        }
        return result;
    }

    Set<String> getAttributeNames(final ListIterator<PathElement> iterator, final String child){

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        Set<String> result = null;
        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getAttributeNames(searchControl.getIterator());
        }

        if (searchControl.getWildCardRegistry() != null) {
            final Set<String> wildCardChildren = searchControl.getWildCardRegistry().getAttributeNames(searchControl.getIterator());
            if (result == null) {
                result = wildCardChildren;
            } else if (wildCardChildren != null) {
                // Merge
                result = new HashSet<String>(result);
                result.addAll(wildCardChildren);
            }
        }
        return result;
    }

    AttributeAccess getAttributeAccess(final ListIterator<PathElement> iterator, final String child, final String attributeName) {

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        // First search the non-wildcard child; if not found, search the wildcard child
        AttributeAccess result = null;

        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getAttributeAccess(searchControl.getIterator(), attributeName);
        }

        if (result == null && searchControl.getWildCardRegistry() != null) {
            result = searchControl.getWildCardRegistry().getAttributeAccess(searchControl.getIterator(), attributeName);
        }

        return result;
    }


    Set<PathElement> getChildAddresses(final ListIterator<PathElement> iterator, final String child){

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        Set<PathElement> result = null;
        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getChildAddresses(searchControl.getIterator());
        }

        if (searchControl.getWildCardRegistry() != null) {
            final Set<PathElement> wildCardChildren = searchControl.getWildCardRegistry().getChildAddresses(searchControl.getIterator());
            if (result == null) {
                result = wildCardChildren;
            } else if (wildCardChildren != null) {
                // Merge
                result = new HashSet<PathElement>(result);
                result.addAll(wildCardChildren);
            }
        }
        return result;
    }

    ProxyController getProxyController(final ListIterator<PathElement> iterator, final String child) {

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        // First search the non-wildcard child; if not found, search the wildcard child
        ProxyController result = null;

        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getProxyController(searchControl.getIterator());
        }

        if (result == null && searchControl.getWildCardRegistry() != null) {
            result = searchControl.getWildCardRegistry().getProxyController(searchControl.getIterator());
        }

        return result;
    }

    AbstractResourceRegistration getResourceRegistration(final ListIterator<PathElement> iterator, final String child) {

        final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

        // First search the non-wildcard child; if not found, search the wildcard child
        AbstractResourceRegistration result = null;

        if (searchControl.getSpecifiedRegistry() != null) {
            result = searchControl.getSpecifiedRegistry().getResourceRegistration(searchControl.getIterator());
        }

        if (result == null && searchControl.getWildCardRegistry() != null) {
            result = searchControl.getWildCardRegistry().getResourceRegistration(searchControl.getIterator());
        }

        return result;
    }

    void getProxyControllers(final ListIterator<PathElement> iterator, final String child, Set<ProxyController> controllers) {
        if (child != null) {
            final RegistrySearchControl searchControl = new RegistrySearchControl(iterator, child);

            // First search the wildcard child, then if there is a non-wildcard child search it

            if (searchControl.getWildCardRegistry() != null) {
                searchControl.getWildCardRegistry().getProxyControllers(searchControl.getIterator(), controllers);
            }

            if (searchControl.getSpecifiedRegistry() != null) {
                searchControl.getSpecifiedRegistry().getProxyControllers(searchControl.getIterator(), controllers);
            }
        } else {
            final Map<String, AbstractResourceRegistration> snapshot = childRegistriesUpdater.get(NodeSubregistry.this);
            for (AbstractResourceRegistration childRegistry : snapshot.values()) {
                childRegistry.getProxyControllers(iterator, controllers);
            }
        }
    }

    PathAddress getPathAddress(String valueString) {
        return parent.getPathAddress().append(PathElement.pathElement(keyName, valueString));
    }

    /**
     * Encapsulates data and behavior to help with searches in both a specified child and in the wildcard child if
     * it exists and is different from the specified child
     */
    private class RegistrySearchControl {
        private final AbstractResourceRegistration specifiedRegistry;
        private final AbstractResourceRegistration wildCardRegistry;
        private final ListIterator<PathElement> iterator;
        private final int restoreIndex;
        private boolean backupRequired;

        private RegistrySearchControl(final ListIterator<PathElement> iterator, final String childName) {
            final Map<String, AbstractResourceRegistration> snapshot = childRegistriesUpdater.get(NodeSubregistry.this);
            this.specifiedRegistry = snapshot.get(childName);
            this.wildCardRegistry = WILDCARD_VALUE.equals(childName) ? null : snapshot.get(WILDCARD_VALUE);
            this.iterator = iterator;
            this.restoreIndex = (specifiedRegistry != null && wildCardRegistry != null) ? iterator.nextIndex() : -1;
        }

        private AbstractResourceRegistration getSpecifiedRegistry() {
            return specifiedRegistry;
        }

        private AbstractResourceRegistration getWildCardRegistry() {
            return wildCardRegistry;
        }

        private ListIterator<PathElement> getIterator() {
            if (backupRequired) {
                if (restoreIndex == -1) {
                    // Coding mistake; someone wants to search twice for no reason, since we only have a single registration
                    throw new IllegalStateException("Multiple iterator requests are not supported since both " +
                            "named and wildcard entries were not present");
                }
                // Back the iterator to the restore index
                while (iterator.nextIndex() > restoreIndex) {
                    iterator.previous();
                }
            }
            backupRequired = true;
            return iterator;
        }
    }

    String getKeyName() {
        return keyName;
    }
}
