/**
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2010 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.gallery3.rest {
	import com.adobe.serialization.json.JSON;
	
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.SecurityErrorEvent;
	import flash.net.Socket;
	
	import mx.messaging.messages.HTTPRequestMessage;
	import mx.rpc.AsyncToken;
	import mx.rpc.events.FaultEvent;
	import mx.rpc.events.ResultEvent;
	import mx.utils.StringUtil;
		
	public class Rest {
		//internal var _service:HTTPService;
		internal var _resultHandler:Function;
		internal var _faultHandler: Function;
		
		private var _socket:Socket;
		private var _protocol:String;
		private var _host:String;
		private var _method:String;
		private var _port:int;
		private var _uri:String;
		
		private var _response:String;
		private var _statusCode:int;
		private var _statusText:String;
		private var _body:String;
		private var _data:String;
		
		public var headers:Object = new Object;
		
		public function Rest() {
			_socket = new Socket();
			_socket.addEventListener(Event.CONNECT, function(event:Event):void {
				_sendHeaders();
				if (_method != "GET") {
					_socket.writeUTFBytes(_data);
				}
			});
			_socket.addEventListener(IOErrorEvent.IO_ERROR, function(event:IOErrorEvent): void {
				trace(event);
			});
			_socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, function(event:SecurityErrorEvent): void {
				trace(event);
			});
			_socket.addEventListener(Event.CLOSE, function(event:Event):void {
				var body:String = _parseResponse();
				if (_statusCode == 200) {
					var data:Object = JSON.decode(body);
					_resultHandler(_statusCode, data);
				} else if (_faultHandler != null) {
					trace("need a fault handler call");
				} else {
					trace(_response);
				}
			});
			_socket.addEventListener(ProgressEvent.SOCKET_DATA, function(event:ProgressEvent):void {
				_response += _socket.readUTFBytes(event.bytesLoaded);
			});
		}
		
		public static function factory(): Rest {
			return new Rest();
		}

		public function url(url:String): Rest  {
			var parts:Array = url.split(/^(http(s)?):\/\/([^\/]+?)(:(\d+))?(\/(.*))?$/);
			_protocol = parts[1];
			_host = parts[3]
			_port = _port = parts[5];
			if (parts[5] == undefined) {
				_port = _protocol == "http" ? 80 : 443;
			}
			_uri = parts[6] == undefined ? "" : parts[6];
			return this;
		}
		
		public function method(method:String): Rest  {
			_method = (method == "get" || method == "GET") ? HTTPRequestMessage.GET_METHOD : HTTPRequestMessage.POST_METHOD;
			headers["X-Gallery-Request-Method"] = method;
			return this;
		}
		
		public function token(token:String): Rest {
			headers["X-Gallery-Request-Key"] = token;
			return this;
		}
		
		public function resultHandler(resultHandler:Function): Rest {
			_resultHandler = resultHandler;
			return this;
		}

		public function faultHandler(faultHandler:Function): Rest {
			_faultHandler = faultHandler;
			return this;
		}
		
		public function load(params:Object=null): AsyncToken {
			if (_method != "GET") {
				_serializeData(params);
				headers["Content-Length"] = _data.length;
			} else {
				_serializeURL(params);
			}
			_response = "";
			_socket.connect(_host, _port);
			return null;
		}
		
		private function _sendHeaders(): void {
			var uri:String = _method != "get" ? _uri + _data : _uri;

			_socket.writeUTFBytes(_method + " " + uri + " HTTP/1.1\r\n");
			_socket.writeUTFBytes("Host: " + _host + "\r\n");
			for (var header:String in headers) {
				_socket.writeUTFBytes(header + ": " + headers[header] + "\r\n");
				trace(header + ": " + headers[header]);
			}
			_socket.writeUTFBytes("\r\n");
			_socket.flush();
		}

		private function _serializeData(params:Object): void {
			_data = "";
			for (var i:String in params) {
				if (params[i] is Array) {
					for each (var element:String in params[i]) {
						_data += "&" + i + "[]=" + element;
					}
				} else {
					_data += "&" + i + "=" + params[i];
				}
			}
		}

    	private function _serializeURL(obj:Object): void {
			_data = "";
    		if (_method == "GET" && obj != null) {
				for (var i:String in obj) {
					if (obj[i] is Array) {
						for each (var element:String in obj[i]) {
							_data += _data.length > 0 ? "&" : "?";
							_data += i + "[]=" + element;
						}
					} else {
						_data += _data.length > 0 ? "&" : "?";
						_data += i + "=" + obj[i];
					}
				}
    		}
    	}

		private function _parseResponse(): String {
			headers = new Object();
			var parts:Array;
			var body:String;
			while (1) {
				var eol:int = _response.indexOf("\r\n");
				var header:String = _response.substr(0, eol);
				_response = _response.substr(eol + 2);
				if (header == "") {
					break;
				}
				if (header.indexOf("HTTP/1.") != -1) {
					parts = header.split(" ");
					_statusCode = parseInt(parts[1]);
					_statusText = parts[2];
				} else {
					parts = header.split(":");
					headers[StringUtil.trim(parts[0])] = StringUtil.trim(parts[1]);
				}
			}
			
			if (headers["Transfer-Encoding"] != undefined) {
				body = "";
				while(1) {
					eol = _response.indexOf("\r\n");
					var chunkHeader:String = _response.substr(0, eol);
					var length:int = int("0x" + chunkHeader.split(" ")[0]);
					if (length == 0) {
						break;
					}
					_response = _response.substr(eol + 2);
					body += _response.substr(0, length);
					_response = _response.substr(length + 2);
				}
				
			} else {
				body = _response;
			}
			return body;
		}
		
		private function _onResult(event:ResultEvent): void {
			if (event.statusCode == 200) {
				_resultHandler(event.statusCode, event.result);
			} else if (_faultHandler != null) {
				trace("need a fault handler call");
			}
		}

		private function _onFault(event:FaultEvent): void {
			if (_faultHandler != null) {
				_faultHandler(event);
			}
		}
	}
}