package de.dtele.net.protocol {
  
  import co.uk.mikestead.net.URLFileVariable;
  import co.uk.mikestead.net.URLRequestBuilder;
  
  import com.adobe.serialization.json.JSON;
  import com.adobe.serialization.json.JSONParseError;
  import com.hurlant.util.Base64;
  
  import de.dtele.data.MimeTypes;
  import de.dtele.messages.MessageManager;
  import de.dtele.net.MediaRequest;
  import de.dtele.net.MediaRequestError;
  import de.dtele.net.MediaResponse;
  import de.dtele.net.events.ProtocolHandlerErrorEvent;
  import de.dtele.net.events.ProtocolHandlerEvent;
  
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IOErrorEvent;
  import flash.events.ProgressEvent;
  import flash.events.SecurityErrorEvent;
  import flash.net.FileReference;
  import flash.net.URLLoader;
  import flash.net.URLRequest;
  import flash.net.URLRequestMethod;
  import flash.net.URLVariables;
  
  /**
   * Dispatched when the connection to a source has been established
   * 
   * @eventType de.dtele.net.events.ProtocolHandlerEvent.CONNECTED
   */
  [Event("connected", type="de.dtele.net.events.ProtocolHandlerEvent")]
  /**
   * Dispatched when the connection to a source has been closed
   * 
   * @eventType de.dtele.net.events.ProtocolHandlerEvent.DISCONNECTED
   */
  [Event("disconnected", type="de.dtele.net.events.ProtocolHandlerEvent")]
  /**
   * Dispatched when a message from the source was received
   * 
   * @eventType de.dtele.net.events.ProtocolHandlerEvent.RESPONSE
   */
  [Event("response", type="de.dtele.net.events.ProtocolHandlerEvent")]
  /**
   * Dispatched when an error has occurred
   * 
   * @eventType de.dtele.net.events.ProtocolHandlerErrorEvent.ERROR
   */
  [Event("error", type="de.dtele.net.events.ProtocolHandlerErrorEvent")]
  
  /**
   * Implements the HTTP protocol for MediaRequest
   * 
   * @see http://blog.mikestead.me/upload-multiple-files-with-a-single-request-in-flash/
   * @see co.uk.mikestead.net.URLRequestBuilder
   * 
   * @author Mathias Brodala
   */
  public class HTTPProtocolHandler extends EventDispatcher implements IProtocolHandler {
    
    /* Properties */
    /**
     * The URL request object for a given URL
     */
    private var sourceURL:String;
    /**
     * List of managed requests
     */
    private var loaders:Vector.<URLLoader> = new Vector.<URLLoader>();
    
    /* Methods */
    /**
     * Connects this handler to a source
     * 
     * @param url The url of a source
     */
    public function connect(url:String):void {
      
      this.sourceURL = url;
      
      // Immediately signifies successfully opened connections
      this.dispatchEvent(new ProtocolHandlerEvent(ProtocolHandlerEvent.CONNECTED));
    }
    
    /**
     * Disconnects this handler from a source
     */
    public function disconnect():void {
      
      this.loaders.forEach(function(loader:URLLoader):void {
        
        loader.close();
      });
      
      loaders.length = 0;
      
      this.dispatchEvent(new ProtocolHandlerEvent(ProtocolHandlerEvent.DISCONNECTED));
    }
    
    /**
     * Performs a request to a source
     * @see http://blog.mikestead.me/upload-multiple-files-with-a-single-request-in-flash/
     * 
     * XXX: Workaround due to restrictions wrt uploading file data
     * @see http://www.adobe.com/devnet/flashplayer/articles/fplayer10_uia_requirements.html
     * @see http://jessewarden.com/2008/10/flash-player-10-surprise-error-2176.html
     * 
     * @param request The request to submit
     */
    public function send(request:MediaRequest):void {
      
      var handler:HTTPProtocolHandler = this;
      var loader:URLLoader = new URLLoader();
      var data:URLVariables = new URLVariables();
      var fileOperations:Array = [];
      
      data["mediaRequest[version]"] = request.version;
      data["mediaRequest[type]"] = request.type;
      
      if (request.credentials) {
        
        data["mediaRequest[credentials]"] = JSON.encode(request.credentials);
      }
      
      // Add resources and files
      switch (request.type) {
        
        case MediaRequest.ADD:
        case MediaRequest.REMOVE:
          
          if (request.resources) {
            
            data["mediaRequest[resources]"] = JSON.encode(request.resources);
          }
          // Intentionally not breaking here
          
        case MediaRequest.ADD:
          
          if (request.files) {
            
            // Prepare file loading
            request.files.forEach(function(file:FileReference, ...a):void {
              
              fileOperations.push(file);
            });
          }
          break;
      }
      
      // Prepare event listeners
      loader.addEventListener(Event.COMPLETE, function(e:Event):void {
        
        var loader:URLLoader = e.target as URLLoader;
        
        loaders.splice(loaders.indexOf(loader), 1);
        
        var response:MediaResponse;
        
        try {
          
          trace("[HTTPProtocolHandler] Got response with data: " + loader.data);
          response = new MediaResponse(JSON.decode(loader.data));
        } catch (e:JSONParseError) {
          
          handler.dispatchEvent(new ProtocolHandlerErrorEvent(
            ProtocolHandlerErrorEvent.ERROR,
            new MediaRequestError(MediaRequestError.MALFORMED_RESPONSE),
            request
          ));
          return;
        }

        handler.dispatchEvent(new ProtocolHandlerEvent(ProtocolHandlerEvent.RESPONSE, request, response));
      });
      loader.addEventListener(ProgressEvent.PROGRESS, function(e:ProgressEvent):void {
        
        request.bytesLoaded = e.bytesLoaded;
        request.bytesTotal = e.bytesTotal;
      });
      loader.addEventListener(IOErrorEvent.IO_ERROR, function(e:IOErrorEvent):void {
        
        loaders.splice(loaders.indexOf(e.target as URLLoader), 1);
        
        handler.dispatchEvent(new ProtocolHandlerErrorEvent(
          ProtocolHandlerErrorEvent.ERROR,
          new MediaRequestError(MediaRequestError.CONNECTION_FAILED, e.text),
          request
        ));
      });
      loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(e:SecurityErrorEvent):void {
        
        loaders.splice(loaders.indexOf(e.target as URLLoader), 1);
        
        handler.dispatchEvent(new ProtocolHandlerErrorEvent(
          ProtocolHandlerErrorEvent.ERROR,
          new MediaRequestError(MediaRequestError.CONNECTION_FAILED, e.text),
          request
        ));
      });
      
      // Allow for cancelling the request
      request.addEventListener(Event.CANCEL, function(e:Event):void {

        fileOperations.forEach(function(file:FileReference, ...a):void {
          
          file.cancel();
        });
        
        // Swallow error if no stream has been opened yet
        try { loader.close(); } catch (e:Error) {}
      });
      
      // Load file data if possible and submit the request
      if (fileOperations.length > 0) {
        
        // XXX: Store files as encoded POST data
        data["mediaRequest[files]"] = [];
        
        fileOperations.forEach(function(file:FileReference, ...a):void {
          
          file.addEventListener(Event.COMPLETE, function(e:Event):void {
            
            var file:FileReference = e.target as FileReference;
            
            // Remove this file operation from the queue
            fileOperations.splice(fileOperations.indexOf(file), 1);
            
            // XXX: Get proper MIME type
            var type:String = file.type;
            var dotPosition:int = type.indexOf(".");
            
            if (dotPosition > -1) {
              
              type = MimeTypes.getMimeType(type.substr(dotPosition + 1));
            }
            // XXX: Store file data as base64 encoded data
            data["mediaRequest[files]"].push({
              name: file.name,
              type: type,
              size: file.size,
              data: Base64.encodeByteArray(file.data)
            });
            
            if (fileOperations.length == 0) {
              
              // XXX: JSON encode file data
              data["mediaRequest[files]"] = JSON.encode(data["mediaRequest[files]"]);
            
              // XXX: Submit regular URL request
              var urlRequest:URLRequest = new URLRequest(handler.sourceURL);
              urlRequest.method = URLRequestMethod.POST;
              urlRequest.data = data;
              trace("[HTTPProtocolHandler] Sending request with data: " + urlRequest.data);
              
              loader.load(urlRequest);
              loaders.push(loader);
            }
            
            /*
            // Store result in the request data
            data["mediaRequest[files][" + file.name + "]"] = new URLFileVariable(file.data, file.name);
            
            // Send the request if this was the last file operation
            if (fileOperations.length == 0) {
              
              var urlRequest:URLRequest = new URLRequestBuilder(data).build();
              urlRequest.url = handler.sourceURL;
              urlRequest.method = URLRequestMethod.POST;
              trace("[HTTPProtocolHandler] Sending request with data: " + urlRequest.data);
              
              try {
                
                loader.load(urlRequest);
                loaders.push(loader);
              } catch (e:SecurityError) {
                
                handler.dispatchEvent(new ProtocolHandlerErrorEvent(
                  ProtocolHandlerErrorEvent.ERROR,
                  new MediaRequestError(MediaRequestError.UNKNOWN, e.message),
                  request
                ));
              }
            }
            */
          });
          
          file.addEventListener(ProgressEvent.PROGRESS, function(e:ProgressEvent):void {
            
            request.bytesLoaded = e.bytesLoaded;
            request.bytesTotal = e.bytesTotal;
          });
          
          file.addEventListener(IOErrorEvent.IO_ERROR, function(e:Event):void {
            
            var file:FileReference = e.target as FileReference;
            
            // Remove this file operation from the queue
            fileOperations.splice(fileOperations.indexOf(file), 1);
            
            MessageManager.instance.addWarning(
              "Lesevorgang fehlgeschlagen",
              "Das Lesen der Datei " + file.name + " ist fehlgeschlagen."
            );
          });
          
          // Initiate loading
          file.load();
        });
      } else { // Directly send the request
        
        var urlRequest:URLRequest = new URLRequest(handler.sourceURL);
        urlRequest.method = URLRequestMethod.POST;
        urlRequest.data = data;
        trace("[HTTPProtocolHandler] Sending request with data: " + urlRequest.data);
        
        loader.load(urlRequest);
        loaders.push(loader);
      }
    }
  }
}