/**
 * @depend ../../sinon.js
 * @depend event.js
 */
/*jslint eqeqeq: false, onevar: false*/
/*global sinon, module, require, XDomainRequest*/
/**
 * Fake XDomainRequest object
 */
"use strict";

if (typeof sinon == "undefined") {
    this.sinon = {};
}
sinon.xdr = { XDomainRequest: this.XDomainRequest };

// wrapper for global
(function (global) {
    var xdr = sinon.xdr;
    xdr.GlobalXDomainRequest = global.XDomainRequest;
    xdr.supportsXDR = typeof xdr.GlobalXDomainRequest != "undefined";
    xdr.workingXDR = xdr.supportsXDR ? xdr.GlobalXDomainRequest :  false;

    function FakeXDomainRequest() {
        this.readyState = FakeXDomainRequest.UNSENT;
        this.requestBody = null;
        this.requestHeaders = {};
        this.status = 0;
        this.timeout = null;

        if (typeof FakeXDomainRequest.onCreate == "function") {
            FakeXDomainRequest.onCreate(this);
        }
    }

    function verifyState(xdr) {
        if (xdr.readyState !== FakeXDomainRequest.OPENED) {
            throw new Error("INVALID_STATE_ERR");
        }

        if (xdr.sendFlag) {
            throw new Error("INVALID_STATE_ERR");
        }
    }

    function verifyRequestSent(xdr) {
        if (xdr.readyState == FakeXDomainRequest.UNSENT) {
            throw new Error("Request not sent");
        }
        if (xdr.readyState == FakeXDomainRequest.DONE) {
            throw new Error("Request done");
        }
    }

    function verifyResponseBodyType(body) {
        if (typeof body != "string") {
            var error = new Error("Attempted to respond to fake XDomainRequest with " +
                                  body + ", which is not a string.");
            error.name = "InvalidBodyException";
            throw error;
        }
    }

    sinon.extend(FakeXDomainRequest.prototype, sinon.EventTarget, {
        open: function open(method, url) {
            this.method = method;
            this.url = url;

            this.responseText = null;
            this.sendFlag = false;

            this.readyStateChange(FakeXDomainRequest.OPENED);
        },

        readyStateChange: function readyStateChange(state) {
            this.readyState = state;
            var eventName = '';
            switch (this.readyState) {
            case FakeXDomainRequest.UNSENT:
                break;
            case FakeXDomainRequest.OPENED:
                break;
            case FakeXDomainRequest.LOADING:
                if (this.sendFlag){
                    //raise the progress event
                    eventName = 'onprogress';
                }
                break;
            case FakeXDomainRequest.DONE:
                if (this.isTimeout){
                    eventName = 'ontimeout'
                }
                else if (this.errorFlag || (this.status < 200 || this.status > 299)) {
                    eventName = 'onerror';
                }
                else {
                    eventName = 'onload'
                }
                break;
            }

            // raising event (if defined)
            if (eventName) {
                if (typeof this[eventName] == "function") {
                    try {
                        this[eventName]();
                    } catch (e) {
                        sinon.logError("Fake XHR " + eventName + " handler", e);
                    }
                }
            }
        },

        send: function send(data) {
            verifyState(this);

            if (!/^(get|head)$/i.test(this.method)) {
                this.requestBody = data;
            }
            this.requestHeaders["Content-Type"] = "text/plain;charset=utf-8";

            this.errorFlag = false;
            this.sendFlag = true;
            this.readyStateChange(FakeXDomainRequest.OPENED);

            if (typeof this.onSend == "function") {
                this.onSend(this);
            }
        },

        abort: function abort() {
            this.aborted = true;
            this.responseText = null;
            this.errorFlag = true;

            if (this.readyState > sinon.FakeXDomainRequest.UNSENT && this.sendFlag) {
                this.readyStateChange(sinon.FakeXDomainRequest.DONE);
                this.sendFlag = false;
            }
        },

        setResponseBody: function setResponseBody(body) {
            verifyRequestSent(this);
            verifyResponseBodyType(body);

            var chunkSize = this.chunkSize || 10;
            var index = 0;
            this.responseText = "";

            do {
                this.readyStateChange(FakeXDomainRequest.LOADING);
                this.responseText += body.substring(index, index + chunkSize);
                index += chunkSize;
            } while (index < body.length);

            this.readyStateChange(FakeXDomainRequest.DONE);
        },

        respond: function respond(status, contentType, body) {
            // content-type ignored, since XDomainRequest does not carry this
            // we keep the same syntax for respond(...) as for FakeXMLHttpRequest to ease
            // test integration across browsers
            this.status = typeof status == "number" ? status : 200;
            this.setResponseBody(body || "");
        },

        simulatetimeout: function(){
            this.status = 0;
            this.isTimeout = true;
            // Access to this should actually throw an error
            this.responseText = undefined;
            this.readyStateChange(FakeXDomainRequest.DONE);
        }
    });

    sinon.extend(FakeXDomainRequest, {
        UNSENT: 0,
        OPENED: 1,
        LOADING: 3,
        DONE: 4
    });

    sinon.useFakeXDomainRequest = function () {
        sinon.FakeXDomainRequest.restore = function restore(keepOnCreate) {
            if (xdr.supportsXDR) {
                global.XDomainRequest = xdr.GlobalXDomainRequest;
            }

            delete sinon.FakeXDomainRequest.restore;

            if (keepOnCreate !== true) {
                delete sinon.FakeXDomainRequest.onCreate;
            }
        };
        if (xdr.supportsXDR) {
            global.XDomainRequest = sinon.FakeXDomainRequest;
        }
        return sinon.FakeXDomainRequest;
    };

    sinon.FakeXDomainRequest = FakeXDomainRequest;
})(this);

if (typeof module == "object" && typeof require == "function") {
    module.exports = sinon;
}
