package de.dtele.providers {
  
  import de.dtele.providers.events.ProviderEvent;
  
  import flash.errors.IllegalOperationError;
  import flash.events.EventDispatcher;
  import flash.utils.getQualifiedClassName;
  
  /**
   * Dispatched when a provider has been added
   * 
   * @eventType de.dtele.provider.events.ProviderEvent.ADDED
   */
  [Event("providerAdded", type="de.dtele.provider.events.ProviderEvent")]
  
  /**
   * Dispatched when a provider has been removed
   * 
   * @eventType de.dtele.provider.events.ProviderEvent.REMOVED
   */
  [Event("providerRemoved", type="de.dtele.provider.events.ProviderEvent")]
  
  /**
   * Manages providers for various services
   * 
   * @author Mathias Brodala
   */
  public final class ProviderManager extends EventDispatcher {
    
    /* Properties */
    /**
     * The singleton instance of the provider manager
     */
    private static var _instance:ProviderManager;
    public static function get instance():ProviderManager {
      
      if (_instance == null) {
        
        _instance = new ProviderManager();
      }
      
      return _instance;
    }
    
    /**
     * List of providers
     */
    private var providers:Object = {};
    
    /* Methods */
    public function ProviderManager() {
      
      if (_instance != null) {
        
        throw new IllegalOperationError("Cannot instantiate " + getQualifiedClassName(this) + ". " +
                                        "Use the singleton instance instead.");
      }
    }
    
    /**
     * Retrieves all providers for a service
     * 
     * @param serviceName The name of a service
     * @return A map of provider names and their providers
     */
    public function getProviders(serviceName:String):Object {
      
      var serviceProviders:Object = {};
      
      if (serviceName in this.providers) {
        
        var providers:Object = this.providers[serviceName];
        
        for (var providerName:String in providers) {
          
          // Only return the most recently added provider
          serviceProviders[providerName] = providers[providerName][0];
        }
      }
      
      return serviceProviders;
    }
    
    /**
     * Retrieves a single provider for a service
     * 
     * @param serviceName The name of a service
     * @param providerName The name of a provider for the service
     * @return The provider or null
     */
    public function getProvider(serviceName:String, providerName:String):Object {
      
      try {
        
        return this.providers[serviceName][providerName][0];
      } catch (e:Error) {}
      
      return null;
    }
    
    /**
     * Adds a new provider for a service
     * 
     * Existing providers with the same name for the same service will be
     * overriden and can be restored by removing the most recently added provider
     * 
     * @param serviceName The name of a service
     * @param providerName The name of the provider
     * @param provider The provider to add
     */
    public function addProvider(serviceName:String, providerName:String, provider:Object):void {
      
      // Create service container if necessary
      if (!(serviceName in this.providers)) {
        
        this.providers[serviceName] = {};
      }
      
      // Create provider list if necessary
      if (!(providerName in this.providers[serviceName])) {
        
        this.providers[serviceName][providerName] = [];
      }
      
      // Now add the provider
      providers[serviceName][providerName].unshift(provider);
      
      // Notify about the change
      instance.dispatchEvent(new ProviderEvent(ProviderEvent.ADDED, provider));
    }
    
    /**
     * Removes a provider from a service
     * 
     * @param serviceName The name of a service
     * @param providerName The name of the provider
     * @param provider The provider to remove
     */
    public function removeProvider(serviceName:String, providerName:String, provider:Object):void {
      
      // Do nothing if there is no such service or provider name
      if (!(serviceName in this.providers) ||
          !(providerName in this.providers[serviceName])) {
        
        return;
      }
      
      // Find the requested provider
      var i:int = this.providers[serviceName][providerName].indexOf(provider);
      
      if (i > -1) {
        
        // Remove the provider
        this.providers[serviceName][providerName].splice(i, 1);
        
        // Notify about the change
        instance.dispatchEvent(new ProviderEvent(ProviderEvent.REMOVED, provider));
        
        // Clean up if possible
        if (this.providers[serviceName][providerName].length == 0) {
          
          delete this.providers[serviceName][providerName];
        }
        
        // Workaround for AS3 not implementing ES5 Object.keys():
        // If there are still providers for a service, the loop will start
        // and we return out of this.removeProvider()
        for (var providerName:String in this.providers[serviceName]) {
          
          return;
        }
        
        // No more providers, drop container
        delete this.providers[serviceName];
      }
    }
  }
}