var generic = generic || {};

/**
 * This singleton class provides an interface to the Perl Gem JSON-RPC methods via AJAX.
 * @memberOf generic
 */
generic.jsonrpc = ( function() {
    var jsonRpcObj = {

	    id: 0, 
	    url: generic.env.domain + "/rpc/jsonrpc.tmpl",
	    errorCodes: {
	        101: "The data type of this method is not supported.",
	        102: "The data type of the request parameters is not supported.",
	        103: "Your request did not return any results.",
	        104: "Response is not in the expected format."
	    },

		/**
		 * This is the method to use when calling a JSON-RPC method.
		 * @example
		generic.jsonrpc.fetch({
		    method: 'rpc.form',
			"params": [
				{
					"_SUBMIT": "address",
					"COUNTRY_ID": "46",
					"ADDRESS_ID": "9342012",
					"LAST_NAME": "Don",
					"FIRST_NAME": "William"
				}
			]
		    onSuccess: function(jsonRpcResponse) {
		        var responseData = jsonRpcResponse.getData();
				console.dir(responseData);
		    },
		    onFailure: function (jsonRpcResponse) {
		        var errorObjectsArray = jsonRpcResponse.getMessages();
		        var errListNode = addressForm.select("ul.error_messages")[0];
		        generic.showErrors(errorObjectsArray, errListNode, addressForm);
		    }
		}); // end jsonRpcWrapper.fetch
		
         * @param {Array} *Optional* args.params an Array of hashes of method parameters.
         * @param {string|Node} args.method *Optional* name of JSON-RPC method to call. Default value is 'rpc.form'
         * @param {function} args.onSuccess *Optional* callback function. It is invoked with a JsonRpcResponse object as a parameter if the AJAX call returns with HTTP status of 200 AND without a JSON-RPC error.
         * @param {function} args.onFailure *Optional* callback function. It is invoked with a JsonRpcResponse object if the AJAX call returns with HTTP status other than 200. It is also invoked if a 200 response contains a JSON-RPC error.
         * @param {function} args.onBoth *Optional* callback function. If provided, it will override any other callback function passed as a parameter and it will be invoked by any JSON-RPC response.
		 * @memberOf generic.jsonrpc
		 */
	    fetch: function(/* Object*/args) {
	        var self = this;
	        this.id++;

	        var options = {method:'post'};

	        if (args.onBoth) {
	            options.onSuccess = args.onBoth;
	            options.onFailure = args.onBoth;
	        } else {
	            options.onSuccess = args.onSuccess || function (response) {
	                console.log('JSON-RPC success');
	                console.log(Object.toJSON(response.getValue()));
	            };
	            options.onFailure = args.onFailure || function (response) {
	                console.log('JSON-RPC failure');
	                console.log(Object.toJSON(response.getMessages()));
	            };
	        }

	        options.onSuccess = options.onSuccess.wrap(
	            function(proceed, response) {
	                if (!response||!response.responseText) { // empty response
	                    errorHandler(this.createErrorResponse(103));
	                    return;
	                }
	                // Analytics general event for RPC..
	                generic.events.fire({event:'RPC:RESULT',msg:response});

	                var responseArray = response.responseText.evalJSON(true);

	                if (Object.isArray(responseArray)) {
	                    var resultObj = responseArray[0];
	                    if (resultObj) {
	                        var jsonRpcResponse = generic.jsonRpcResponse(resultObj);
	                        if (resultObj.error) { // server returns an error
	                            errorHandler(jsonRpcResponse);
	                        } else if (resultObj.result) { // successful response in expected format
	                            //console.log("generic.jsonrpc.onSuccess");
	                            proceed(jsonRpcResponse);
	                        }
	                    } else { // top-level response array is empty
	                        errorHandler(self.createErrorResponse(103));
	                    }
	                } else { // response is not in expected format (array) 
	                    errorHandler(self.createErrorResponse(104));
	                }
	            });

	        options.onFailure = options.onFailure.wrap( function(proceed, response) {
                var resp = response;
                //server returned failure, i.e. onFailure was not triggered by this class
                if (typeof response.responseText != "undefined") {
                    //console.log("generic.jsonRPC onFailure: server error");
                    try { //server returns an error in json
                        var responseArray = response.responseText.evalJSON(true);
                        var resultObj = responseArray[0];
                        resp = generic.jsonRpcResponse(resultObj);
                    } catch(e) { //server response is not json
                        //console.log("generic.jsonRPC onFailure: server error, result is not json");
                        resp = self.createErrorResponse(response.status,response.responseText);
                    }
                }

                proceed(resp);
            });

	        var errorHandler = options.onFailure;
	        var method = args.method || 'rpc.form';
	        var params = args.params || [];

	        var postObj = {
	            method: method,
	            id: self.id
	        };

	        // make sure a method was passed
	        if ( !Object.isString(method) || method.length <= 0 ) {
	            errorHandler(self.createErrorResponse(101));
	            return null;
	        }

	        //make sure that the params type is an obj
	        if (typeof params === 'string') {
	            postObj.params = params.evalJSON();
	        } else if (typeof params === 'object') {
	            postObj.params = params;
	        } else {
	            errorHandler(self.createErrorResponse(102));
	            return null;
	        }

	        var postString = "[" + Object.toJSON(postObj) + "]";

	        options.parameters = $H({JSONRPC:[postString]}).toQueryString();
	        var url = this.url + '?dbgmethod=' + method;
	        new Ajax.Request( url, options );
	        return this.id;
	    },

	    createErrorResponse: function(errorCode, errorMsg) {
	        errorMsg = errorMsg || this.errorCodes[errorCode];
	        var errorObj = new generic.jsonRpcResponse({
	            "error" : {
	                "code": errorCode,
	                "data": {
	                "messages" : [{
	                    "text" : errorMsg,
	                    "display_locations" : [],
	                    "severity" : "MESSAGE",
	                    "tags" : [],
	                    "key" : ""
	                }]
	                }
	            },
	            "id" : this.id
	        });
	        return errorObj;
	    }
	};
    return jsonRpcObj;
} )(); 


 /**
 * A JsonRpcResponse object is instantiated and returned to the onSuccess and onError
 * callback functions that are passed to the fetch() method. It exposes the contents
 * of the response through its getData, getError, and getMessages methods.
 */
generic.jsonRpcResponse = function (resultObj) {
	var jsonRpcResponseObj = {};
    var rawResponse = resultObj; // raw response data is kept in a private variable
    
    var CartItem = function(itemData) {
        this.product = { 
            sku: {}
        };
        var prodRegEx = /^prod\.(.+)$/;
        var skuRegEx = /sku\.(.+)$/;
        var prodObj = { sku: {} };
        for (var prop in itemData) {
            var newPropName = null;
            var prodResult = prop.match(prodRegEx);
            if (prodResult && prodResult[1]) {
                newPropName = prodResult[1];
                this.product[newPropName] = itemData[prop];
            }
            if (!newPropName) {
                var skuResult = prop.match(skuRegEx);
                if (skuResult && skuResult[1]) {
                    newPropName = skuResult[1];
                    this.product.sku[newPropName] = itemData[prop];
                }
            }
            if (!newPropName) {
                this[prop] = itemData[prop];
            }
        }
    }

	var CartResult = function(responseData) {
	    var data = responseData;
	    var cartItem = {
	        product: { 
	            sku: {}
	        }
	    };
	    var cartMethod;
	    var allItems = [];
		var cartItems = [];

	    if (data.ac_results &&
	            Object.isArray(data.ac_results) && 
	            data.ac_results[0]) {
	        var i;
			for ( i=0; i < data.ac_results.length; i++ ) {
		        if (data.ac_results[i].result &&
		                data.ac_results[i].result.CARTITEM) {
		            cartItem = new CartItem(data.ac_results[i].result.CARTITEM);
		        }
		        if (data.ac_results[i].action) {
		            cartMethod = data.ac_results[i].action;    
		        }
		        
		        cartItems.push(cartItem);
                overlay = data.ac_results[i].result.OVERLAY;// ? data.ac_results[i].result.OVERLAY : overlay;
		    }
	    }
	    
	    if (data.trans_data &&
	            data.trans_data.order &&
	            Object.isArray(data.trans_data.order.items) ) {
	        data.trans_data.order.items.each(function (itemData) {
	            var tempItem = new CartItem(itemData);
	            allItems.push(tempItem);
	        });
	    }
	    //------------------
	    // PUBLIC METHODS
	    //------------------
	    this.getAllItems = function() {
	        return allItems;
	    }
	    this.getItem = function() {
	        return cartItem;
	    };
		this.getItems = function() {
	        return cartItems;
	    };
	    this.getMethod = function() {
	        return cartMethod;
	    }
	};


    jsonRpcResponseObj.getId = function() {
        if (rawResponse) {
            return rawResponse.id;
        }
        return null;
    };
    jsonRpcResponseObj.getError = function() {
        if (rawResponse &&
            rawResponse.error) {
            return rawResponse.error;
        }
        return null;
    };
    jsonRpcResponseObj.getData = function() {
        if (rawResponse &&
            rawResponse.result &&
            rawResponse.result.data) {
            return rawResponse.result.data;
        }
        return null;
    };
    jsonRpcResponseObj.getValue = function() {
        if (rawResponse &&
            rawResponse.result &&
            typeof rawResponse.result.value != "undefined") {
            return rawResponse.result.value;
        }
        return null;
    };
    /**
     * This method returns the contents of the response's error property.
     * It first checks the result property, then checks the error property.
     */        
    jsonRpcResponseObj.getMessages = function() {
        if (rawResponse) {
            if (rawResponse.result &&
                rawResponse.result.data &&
                rawResponse.result.data.messages) {
                return rawResponse.result.data.messages;
            } else if (rawResponse.error &&
                       rawResponse.error.data &&
                       rawResponse.error.data.messages) {
                return rawResponse.error.data.messages;
            }
        }
        return null;
    };
    jsonRpcResponseObj.getCartResults = function() {
		var data = this.getData();
		if (!data) {
			return null;
		}
		var returnObj = new CartResult(data);
		return returnObj;	
    };

    return jsonRpcResponseObj;
};



