package de.dtele.net {
  
  import de.dtele.net.events.ConnectionErrorEvent;
  import de.dtele.net.events.ConnectionEvent;
  import de.dtele.net.events.ProtocolHandlerErrorEvent;
  import de.dtele.net.events.ProtocolHandlerEvent;
  import de.dtele.net.protocol.IProtocolHandler;
  import de.dtele.providers.ProviderManager;
  
  import flash.events.EventDispatcher;
  
  import mx.utils.URLUtil;
  
  /**
   * Dispatched when the connection has been opened
   * 
   * @eventType de.dtele.net.events.ConnectionEvent.OPENED
   */
  [Event("opened", type="de.dtele.net.events.ConnectionEvent")]
  
  /**
   * Dispatched when the connection has been closed
   * 
   * @eventType de.dtele.net.events.ConnectionEvent.CLOSED
   */
  [Event("closed", type="de.dtele.net.events.ConnectionEvent")]
  
  /**
   * Dispatched when a response has been received
   * 
   * @eventType de.dtele.net.events.ConnectionEvent.RESPONSE
   */
  [Event("response", type="de.dtele.net.events.ConnectionEvent")]
  
  /**
   * Dispatched when an error has occurred
   * 
   * @eventType de.dtele.net.events.ConnectionEvent.ERROR
   */
  [Event("error", type="de.dtele.net.events.ConnectionEvent")]
  
  /**
   * Represents a connection to an URL, no matter the underlying implementation.
   * Handles MediaRequest requests to and responses from URLs.
   * 
   * @author Mathias Brodala
   */
  public class Connection extends EventDispatcher {
    
    /* Constants */
    public static const IDLE:String = "idle";
    public static const OPENING:String = "opening";
    public static const OPEN:String = "open";
    public static const CLOSING:String = "closing";
    public static const CLOSED:String = "CLOSED";
    
    /* Properties */
    private var _url:String;
    /**
     * The URL this connection points to
     */
    public function get url():String {
      return _url;
    }
    
    /**
     * The specific handler for this URL's protocol
     */
    private var handler:IProtocolHandler;
    
    private var _state:String = Connection.IDLE;
    /**
     * The state this connection is in
     */
    public function get state():String {
      return _state;
    }
    
    /* Methods */
    /**
     * Sets up the connection
     * 
     * @param url The url the connection shall point to
     */
    public function Connection(url:String) {
      
      this._url = url;
    }
    
    /**
     * Opens the connection to the URL
     */
    public function open():void {
      
      var protocol:String = URLUtil.getProtocol(this.url);
      var protocolHandler:Class = ProviderManager.instance.getProvider("protocolHandlers", protocol) as Class
                                  ||
                                  ProviderManager.instance.getProvider("protocolHandlers", "__unknown__") as Class;
      
      this.handler = new protocolHandler();
      
      this.handler.addEventListener(ProtocolHandlerEvent.CONNECTED, this.onHandlerConnected);
      this.handler.addEventListener(ProtocolHandlerEvent.DISCONNECTED, this.onHandlerDisconnected);
      this.handler.addEventListener(ProtocolHandlerEvent.RESPONSE, this.onHandlerResponse);
      this.handler.addEventListener(ProtocolHandlerErrorEvent.ERROR, this.onHandlerError);
      
      trace("[Connection] Connecting to " + this.url + " via " + handler);
      this.handler.connect(this.url);
    }
    
    /**
     * Sends requests to the URL
     * 
     * @param request The MediaRequest
     */
    public function send(request:MediaRequest):void {
      
      if (this.state == Connection.CLOSED) {
        
        this.dispatchEvent(new ConnectionEvent(ConnectionEvent.CLOSED));
        return;
      }
      
      if (this.state == Connection.CLOSING) {
        
        return;
      }
      
      if (this.handler != null) {
        
        this.handler.send(request);
      } else {
        
        this.dispatchEvent(new ConnectionEvent(ConnectionEvent.CLOSED));
      }
    }
    
    /**
     * Closes the connection to the URL,
     * no further requests will be possible
     */
    public function close():void {
      
      if (this.state == Connection.CLOSING) {
        
        return;
      }
      
      if (this.state == Connection.CLOSED) {
        
        this.dispatchEvent(new ConnectionEvent(ConnectionEvent.CLOSED));
        return;
      }
      
      this._state = Connection.CLOSING;
      
      handler.disconnect();
    }
    
    /**
     * Notifies about successfully opened connections
     */
    private function onHandlerConnected(e:ProtocolHandlerEvent):void {
      
      this._state = Connection.OPEN;
      this.dispatchEvent(new ConnectionEvent(ConnectionEvent.OPENED));
    }
    
    /**
     * Notifies about closed connections
     */
    private function onHandlerDisconnected(e:ProtocolHandlerEvent):void {
      
      this._state = Connection.CLOSED;
      this.dispatchEvent(new ConnectionEvent(ConnectionEvent.CLOSED));
    }
    
    /**
     * Notifies about the response from the source
     */
    private function onHandlerResponse(e:ProtocolHandlerEvent):void {
      
      if (e.request) {
        
        e.request.bytesLoaded = e.request.bytesTotal = 1;
      }
      
      if (e.response.version != MediaRequest.VERSION) {
        
        this.dispatchEvent(new ConnectionErrorEvent(
          ConnectionErrorEvent.ERROR,
          new MediaRequestError(MediaRequestError.UNKNOWN_PROTOCOL_VERSION),
          e.request
        ));
      }
      
      if (e.response.result == MediaResponse.ERROR) {
        
        this.dispatchEvent(new ConnectionErrorEvent(ConnectionErrorEvent.ERROR, e.response.error, e.request));
        return;
      }
      
      this.dispatchEvent(new ConnectionEvent(ConnectionEvent.RESPONSE, e.request, e.response));
    }
    
    /**
     * Notifies about errors of a request
     */
    private function onHandlerError(e:ProtocolHandlerErrorEvent):void {
      
      this.dispatchEvent(new ConnectionErrorEvent(ConnectionErrorEvent.ERROR, e.error, e.request));
    }
  }
}