/*
 * Copyright 2015 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
*/

package org.kie.server.services.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;

import org.kie.internal.identity.IdentityProvider;
import org.kie.server.api.KieServerEnvironment;
import org.kie.server.api.model.KieContainerResource;
import org.kie.server.api.model.KieContainerStatus;
import org.kie.server.api.model.KieServerConfig;
import org.kie.server.services.api.ContainerLocator;
import org.kie.server.services.api.KieContainerInstance;
import org.kie.server.services.api.KieServerExtension;
import org.kie.server.services.api.KieServerRegistry;
import org.kie.server.services.impl.storage.KieServerState;
import org.kie.server.services.impl.storage.KieServerStateRepository;

public class KieServerRegistryImpl implements KieServerRegistry {

    private final ConcurrentMap<String, KieContainerInstanceImpl> containers = new ConcurrentHashMap<String, KieContainerInstanceImpl>();
    private final ConcurrentMap<String, List<KieContainerInstanceImpl>> containersByAlias = new ConcurrentHashMap<String, List<KieContainerInstanceImpl>>();
    private IdentityProvider identityProvider;
    private ConcurrentMap<String, KieServerExtension> serverExtensions = new ConcurrentHashMap<String, KieServerExtension>();

    private Set<String> controllers = new CopyOnWriteArraySet<String>();
    
    private Set<Class<?>> extraClasses = new HashSet<>();

    private KieServerStateRepository repository;

    private KieSessionLookupManager kieSessionLookupManager = new KieSessionLookupManager();

    @Override
    public KieContainerInstanceImpl registerContainer(String id, KieContainerInstanceImpl kieContainerInstance) {
        synchronized ( containers ) {
            KieContainerInstanceImpl kci = containers.putIfAbsent(id, kieContainerInstance);
            if( kci != null && kci.getStatus() == KieContainerStatus.FAILED ) {
                // if previous container failed, allow override
                containers.put(id, kieContainerInstance);

                registerWithAlias(kieContainerInstance);
                return null;
            }
            registerWithAlias(kieContainerInstance);
            return kci;
        }
    }

    @Override
    public KieContainerInstanceImpl unregisterContainer(String id) {
        KieContainerInstanceImpl containerInstance = containers.remove(id);

        removeFromAlias(containerInstance);
        return containerInstance;
    }

    @Override
    public KieContainerInstanceImpl getContainer(String id) {
        return containers.get(id);
    }

    @Override
    public KieContainerInstanceImpl getContainer(String alias, ContainerLocator locator) {
        KieContainerInstanceImpl containerInstance = getContainer(alias);

        if (containerInstance == null) {
            List<KieContainerInstanceImpl> aliasedContainers = containersByAlias.getOrDefault(alias, new ArrayList<KieContainerInstanceImpl>());
            if (aliasedContainers.isEmpty()) {
                // since there are no containers for given alias reject it
                throw new IllegalArgumentException("Container '" + alias + "' is not instantiated or cannot find container for alias '" + alias + "'");
            }
            String containerId = locator.locateContainer(alias, aliasedContainers);
            if (containerId == null) {
                throw new IllegalArgumentException("Cannot find container for alias '" + alias + "'");
            }
            
            aliasedContainers.stream()
            .filter(kci -> kci.getContainerId().equals(containerId))
            .findAny()
            .orElseThrow(() ->  new IllegalArgumentException("Container '" + containerId + "' is not associated with alias '" + alias + "'"));

            return getContainer(containerId);
        }
        return containerInstance;
    }

    @Override
    public String getContainerId(String alias, ContainerLocator locator) {

        KieContainerInstanceImpl kieContainerInstance = getContainer(alias, locator);
        if (kieContainerInstance == null) {
            return alias;
        }
        return kieContainerInstance.getContainerId();
    }

    @Override
    public List<KieContainerInstanceImpl> getContainers() {
        return new ArrayList<KieContainerInstanceImpl>(containers.values());
    }

    @Override
    public List<String> getContainerAliases() {
        return new ArrayList<String>(containersByAlias.keySet());
    }

    @Override
    public List<KieContainerInstanceImpl> getContainersForAlias(String alias) {
        return new ArrayList<KieContainerInstanceImpl>(containersByAlias.getOrDefault(alias, new ArrayList<KieContainerInstanceImpl>()));
    }

    @Override
    public void registerIdentityProvider(IdentityProvider identityProvider) {
        this.identityProvider = identityProvider;
    }

    @Override
    public IdentityProvider unregisterIdentityProvider() {
        IdentityProvider unregistered = this.identityProvider;

        this.identityProvider = null;
        return unregistered;
    }

    @Override
    public IdentityProvider getIdentityProvider() {
        return this.identityProvider;
    }

    @Override
    public void registerServerExtension(KieServerExtension kieServerExtension) {
        this.serverExtensions.putIfAbsent(kieServerExtension.getExtensionName(), kieServerExtension);
    }

    @Override
    public void unregisterServerExtension(KieServerExtension kieServerExtension) {
        this.serverExtensions.remove(kieServerExtension.getExtensionName());
    }

    @Override
    public List<KieServerExtension> getServerExtensions() {
        List<KieServerExtension> extensions = new ArrayList<KieServerExtension>(serverExtensions.values());
        Collections.sort(extensions, new Comparator<KieServerExtension>() {
            @Override
            public int compare(KieServerExtension e1, KieServerExtension e2) {
                return e1.getStartOrder().compareTo(e2.getStartOrder());
            }
        });
        return extensions;
    }

    @Override
    public KieServerExtension getServerExtension(String extensionName) {
        return serverExtensions.get(extensionName);
    }

    @Override
    public void registerController(String controllerUrl) {
        this.controllers.add(controllerUrl);
    }

    @Override
    public Set<String> getControllers() {

        return new HashSet<String>(controllers);
    }

    @Override
    public void registerStateRepository(KieServerStateRepository repository) {
        this.repository = repository;
    }

    @Override
    public KieServerStateRepository getStateRepository() {
        return this.repository;
    }

    @Override
    public KieSessionLookupManager getKieSessionLookupManager() {
        return kieSessionLookupManager;
    }

    @Override
    public KieServerConfig getConfig() {
        KieServerState currentState = repository.load(KieServerEnvironment.getServerId());
        return currentState.getConfiguration();
    }

    protected void registerWithAlias(KieContainerInstanceImpl kieContainerInstance) {
        KieContainerResource containerResource = kieContainerInstance.getResource();
        String alias = getContainerAlias(containerResource);

        List<KieContainerInstanceImpl> byAlias = containersByAlias.get(alias);
        if (byAlias == null) {
            byAlias = new ArrayList<>();
            containersByAlias.put(alias, byAlias);
        }
        byAlias.add(kieContainerInstance);
    }

    protected void removeFromAlias(KieContainerInstanceImpl kieContainerInstance) {
        if (kieContainerInstance == null) {
            return;
        }
        KieContainerResource containerResource = kieContainerInstance.getResource();
        String alias = getContainerAlias(containerResource);

        List<KieContainerInstanceImpl> byAlias = containersByAlias.get(alias);
        if (byAlias != null) {
            byAlias.remove(kieContainerInstance);
        }
    }

    protected String getContainerAlias(KieContainerResource containerResource) {
        String alias = containerResource.getContainerAlias();
        if (alias == null || alias.isEmpty()) {
            alias = containerResource.getReleaseId().getArtifactId();
        }

        return alias;
    }

	@Override
	public void addExtraClasses(Set<Class<?>> extraClasses) {
		this.extraClasses.addAll(extraClasses);
	}

	@Override
	public Set<Class<?>> getExtraClasses() {
		return extraClasses;
	}
}
