/*
 * JBoss, Home of Professional Open Source
 * Copyright 2011 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @authors tag. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 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.jboss.arquillian.core.impl.loadable;

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

import org.jboss.arquillian.core.api.Injector;
import org.jboss.arquillian.core.spi.ServiceLoader;

/**
 * ServiceRegistry
 *
 * @author <a href="mailto:aslak@redhat.com">Aslak Knutsen</a>
 * @author Davide D'Alto
 * @version $Revision: $
 */
public class ServiceRegistry
{
   private final Injector injector;
   private final Map<Class<?>, Set<Class<?>>> registry;
   private final Map<Class<?>, Set<Class<?>>> vetoed;
   
   public ServiceRegistry(Injector injector, Map<Class<?>, Set<Class<?>>> vetoed)
   {
      this.registry = new HashMap<Class<?>, Set<Class<?>>>();
      this.vetoed = new HashMap<Class<?>, Set<Class<?>>>(vetoed);
      this.injector = injector;
   }
   
   public <T> void addService(Class<T> service, Class<? extends T> serviceImpl)
   {
      synchronized (registry)
      {
         if(isImplementationVetoed(service, serviceImpl))
            return;
         
         Set<Class<?>> registeredImpls = registry.get(service);
         if(registeredImpls == null)
         {
            registeredImpls = new HashSet<Class<?>>();
         }
         registeredImpls.add(serviceImpl);   
         registry.put(service, registeredImpls);
      }
   }

   public <T> void removeService(Class<T> service, Class<? extends T> serviceImpl)
   {
      synchronized (registry)
      {
         Set<Class<?>> registeredImpls = registry.get(service);
         if(registeredImpls == null)
            return;

         registeredImpls.remove(serviceImpl);   
      }
   }
   
   public <T> void overrideService(Class<T> service, Class<? extends T> oldServiceImpl, Class<? extends T> newServiceImpl)
   {
      synchronized (registry)
      {

         if(isImplementationVetoed(service, newServiceImpl))
            return;

         Set<Class<?>> vetoedImpls = vetoed.get(service);
         if(vetoedImpls == null)
         {
            vetoedImpls = new HashSet<Class<?>>();
            vetoed.put(service, vetoedImpls);
         }
         vetoedImpls.add(oldServiceImpl);
         
         removeService(service, oldServiceImpl);
         addService(service, newServiceImpl);
      }
   }
   
   public <T> Set<Class<? extends T>> getServiceImpls(Class<T> service)
   {
      Set<Class<?>> registeredImpls = registry.get(service);
      Set<Class<? extends T>> typedImpls = new HashSet<Class<? extends T>>();
      if(registeredImpls == null)
      {
         return typedImpls;
      }
      for(Class<?> registeredImpl : registeredImpls)
      {
         typedImpls.add(registeredImpl.asSubclass(service));
      }
      
      return typedImpls;
   }

   private <T> boolean isImplementationVetoed(Class<?> service, Class<? extends T> serviceImpl)
   {
      Set<Class<?>> vetoedImpls = vetoed.get(service);
      return vetoedImpls != null && vetoedImpls.contains(serviceImpl);
   }
   
   public void clear()
   {
      registry.clear();
   }
   
   public ServiceLoader getServiceLoader()
   {
      return new ServiceRegistryLoader(injector, this);
   }
}
