/**
 * Constructor
 * @param {Function(ASXLoader, newState, oldState)} [stateChangeCallback=null] A state change callback
 * @param {Object} [owner=null] this reference
 */
function ASXLoader (stateChangeCallback, owner) {
    //Debug trace
    this.DEBUG = false;

    //Initialize data
    this._data = null;

    //XMLHttpRequest object
    this._ajax = null;

    //Component state
    this._loaderState = ASXLoader.prototype.STATE_UNLOADED;

    //State change callback
    this.onStateChange = stateChangeCallback;

    //Owner object
    this.owner = owner;
}

ASXLoader.prototype.STATE_UNDEFINED = "stateUndefined";
ASXLoader.prototype.STATE_UNLOADED = "stateUnloaded";
ASXLoader.prototype.STATE_ASX_LOADING = "asxLoading";
ASXLoader.prototype.STATE_ASX_LOADED = "asxLoaded";
ASXLoader.prototype.STATE_VIDEO_LOADING = "videoLoading";
ASXLoader.prototype.STATE_VIDEO_LOADED = "videoLoaded";
ASXLoader.prototype.STATE_ERROR = "error";

ASXLoader.prototype.URL_BASE_FILE  = 'http://nfw.content-video.ru/flv/file.aspx';
ASXLoader.prototype.URL_BASE_MEDIA = 'http://nfw.content-video.ru/flv/media.aspx';

/**
 * Media type - VOD, LIVE, DVR etc.
 */
ASXLoader.prototype.getMediaType = function() {
    return null != this._data ? this._data["contenttype"] : "";
}

/**
 * @private
 * Returns page referer if possible
 */
ASXLoader.prototype.addReferer = function(url) {
    var href = window.location.href.toString();
    if ("" == href) return url;

    url     = url
            + ("&" == url.charAt(url.length - 1) ? "" : (-1 == url.indexOf("?") ? "?" : "&"))
            + "referer"
            + "="
            + escape(href);

    return url;
}

/**
 * Media status according to Aurora
 */
ASXLoader.prototype.getMediaState = function() {
    return null != this._data ? this._data["status"] : "";
}

/**
 * Check if video is SmartTag
 */
ASXLoader.prototype.getSmartagId = function() {
    return null != this._data ? this._data["smartag_id"] : false;
}

/**
 * Poster image URL
 */
ASXLoader.prototype.getPoster = function() {
    return null != this._data ? this._data["image"] : "";
}

/**
 * Video file URL
 */
ASXLoader.prototype.getVideoUrl = function() {
    return null != this._data ? this._data["video"] : "";
}

/**
 * Loader state
 */
ASXLoader.prototype.getLoaderState = function() {
    return this._loaderState;
}


/**
 * Loads balancing script and retrieves video resource
 */
ASXLoader.prototype.getVideo = function() {
    var loaderState = this.getLoaderState()

    if (ASXLoader.prototype.STATE_VIDEO_LOADING == loaderState || ASXLoader.prototype.STATE_VIDEO_LOADED == loaderState) {
        //Already loaded
        return true;
    }

    if (ASXLoader.prototype.STATE_ASX_LOADED != loaderState) {
        //Loads from STATE_ASX_LOADED state only
        return false;
    }

    this.setState(ASXLoader.prototype.STATE_VIDEO_LOADING);

    this._ajax = this.createRequest(this._data['videoBalancing']);

    return true;
}

/**
 * Unloads data
 */
ASXLoader.prototype.unLoad = function() {
    this.setState(ASXLoader.prototype.STATE_UNLOADED);
    if (this._ajax) this._ajax.abort();
}

/**
 * @private
 * Transforms url and creates XMLHttpRequest
 */
ASXLoader.prototype.createRequest = function(url, replaceAurora) {
    //Replace other site referencies
   // url = replaceAurora ? this.replaceAurora(url) : url;
   url = this.replaceAurora(url);

    this.trace("Creating request for: " + url);

    var requestExp = /([^?]+)\??(.*)/;
    var request = requestExp.exec(url);
    if (null == request) return null;

    var request = makeRequest(request[1], request[2], this.onLoad, this.onError, false, false, null, this);

    return request;
}

/**
 * @private
 * XML load handler. Parses ASX
 */
ASXLoader.prototype.onLoad = function(httpRequest, dataPackage) {
    this.trace ("ASX loaded: ");
    this.trace (httpRequest);

    this._ajax = null;

    if (null == this._data) {
        this._data = new Object();
    }

    var rootNode = httpRequest.responseXML
                 ? (0 != httpRequest.responseXML.childNodes.length
                       ? httpRequest.responseXML.childNodes[0]
                       : null)
                 : null;

    //Check XML
    if (null == rootNode || 0 == rootNode.childNodes.length) {
        this.setState(ASXLoader.prototype.STATE_ERROR);
        return;
    }

    var aNode = rootNode.childNodes[0];
    var bNode;

    var entry;
    var entryParams;
    var paramName;
    for (; null != aNode; aNode = aNode.nextSibling) {
        if (1 != aNode.nodeType) continue;
        switch (aNode.nodeName) {
            case "config":
                this._data["contenttype"] = this.getAttribute(aNode, "contenttype", "vod");
                this._data["status"] = this.getAttribute(aNode, "status", "odddk");
                break;
            case "entry":
                if (0 == aNode.childNodes.length) continue;
                bNode = aNode.childNodes[0];

                //Create new entry
                entry = new Object();
                entryParams = new Object();

                for (; null != bNode; bNode = bNode.nextSibling) {
                    if (1 != bNode.nodeType) continue;
                    switch (bNode.nodeName) {
                        case "ref":
                            entry["ref"] = this.getAttribute(bNode, "href", "");
                            break;
                        case "param":
                            paramName = this.getAttribute(bNode, "name", "");
                            if ("" == paramName) continue;
                            entryParams[paramName] = this.getAttribute(bNode, "value", "");

                            if (paramName == 'smartag_id') {
                                // Store Smartag ID to _data
                                this._data["smartag_id"] = this.getAttribute(bNode, "value", "");
                            }
                            break;
                    }
                }

                if ("" == entry["ref"]) continue;

                switch (this.getLoaderState()) {
                    case ASXLoader.prototype.STATE_ASX_LOADING:
                        this._data["image"] = entryParams["image"];
                        this._data["videoBalancing"] = entry["ref"];
                        this._data["videoBalancingIsASX"] = "video/x-ms-asf" == entryParams["type"];
                        break;
                    case ASXLoader.prototype.STATE_VIDEO_LOADING:
                        if ("video/x-ms-asf" == entryParams["type"]) {
                            //Next balancing script
                            this.loadNextBalancer(entry["ref"]);
                            return;
                        }
                        this._data["video"] = entry["ref"];
                        break;
                }
                break;
        }
    }

    //Check data
    switch (this.getLoaderState()) {
        case ASXLoader.prototype.STATE_ASX_LOADING:
            if (this._data["videoBalancing"]) {
                this.setState(ASXLoader.prototype.STATE_ASX_LOADED);
                return;
            } else {
                this.setState(ASXLoader.prototype.STATE_ERROR);
                return;
            }
            break;
        case ASXLoader.prototype.STATE_VIDEO_LOADING:
            if (this._data["video"]) {
                this.setState(ASXLoader.prototype.STATE_VIDEO_LOADED);
                return;
            } else {
                this.setState(ASXLoader.prototype.STATE_ERROR);
                return;
            }
            break;
    }
}

/**
 * @private
 * Returns a value for attribute
 */
ASXLoader.prototype.getAttribute = function(node, attributeName, defaultValue) {
    var attributes = node.attributes;
    if (0 == attributes.length) return defaultValue;

    for (var i = 0; i < attributes.length; ++i) {
        var aNode = attributes[i];
        if (2 != aNode.nodeType) continue;
        if (attributeName != aNode.nodeName) continue;
        return aNode.nodeValue
    }

    return defaultValue;
}

/**
 * @private
 * Loads next balancing script in chain
 */
ASXLoader.prototype.loadNextBalancer =function (url) {
    this._ajax = this.createRequest(url);
}

/**
 * @private
 * Error handler
 */
ASXLoader.prototype.onError = function(httpRequest) {
    this.setState(ASXLoader.prototype.STATE_ERROR);
}


/**
 * @private
 * Replaces aurora servers with relative path to make AJAX sandbox valid
 */
ASXLoader.prototype.replaceAurora = function(url) {
    return url.replace(/^http:\/\/nfw\.content-video\.ru/i, "/aurora");
}

/**
 * @private
 * Console tracer
 */
ASXLoader.prototype.trace = function(string) {
    if (!this.DEBUG) return;
    if (!console) return;
    console.log(string);
}

/**
 * @private
 * Sets new states
 */
ASXLoader.prototype.setState = function (newState) {
    if (newState == this.getLoaderState()) return;

    var oldState = this.getLoaderState();

    switch (newState) {
        case ASXLoader.prototype.STATE_UNLOADED:
        case ASXLoader.prototype.STATE_ERROR:
            _data = null;
        case ASXLoader.prototype.STATE_ASX_LOADING:
        case ASXLoader.prototype.STATE_ASX_LOADED:
        case ASXLoader.prototype.STATE_VIDEO_LOADING:
        case ASXLoader.prototype.STATE_VIDEO_LOADED:
            this._loaderState = newState;
            break;
        default:
            //Invalid state
            return;
    }

    this.trace ("ASXLoader: setState: " + oldState + " -> " + newState);

    if (!this.onStateChange) return;
    this.onStateChange.call(this.owner, this, newState, oldState);
}

/**
 * Loads top-level ASX
 * Function adds refering page to request
 * @param {String} url - URL to Aurora ASX resource
 */
ASXLoader.prototype.loadASX = function(url) {
    //Unload first
    this.unLoad();

    //Check url
    if (!url) return false;

    url = this.addReferer(url);

    this._ajax = this.createRequest(url);

    if (null == this._ajax) return false;

    this.setState(ASXLoader.prototype.STATE_ASX_LOADING);

    return true;
}

/**
 * Browser-independent [A]JAX call
 *
 * @param {String} locationURL an URL to call, without parameters
 * @param {String} [parameters=null] a parameters list, in the form
 *        'param1=value1&param2=value2&param3=value3'
 * @param {Function(XHMLHTTPRequest, Object)} [onComplete=null] a function that
 *        will be called when the response (responseText or responseXML of
 *        XHMLHTTPRequest) will be received
 * @param {Function(XHMLHTTPRequest)} [onError=null] a function that
 *        will be called if status is not 200;
 * @param {Boolean} [doSynchronous=false] make a synchronous request (onComplete
 *        will /not/ be called)
 * @param {Boolean} [doPost=false] make a POST request instead of GET
 * @param {Object} [dataPackage=null] any object to transfer to the onComplete
 *        listener
 * @param {Object} [thisObject=null] an object that is considered as "this" for callback call
 * @return {XHMLHTTPRequest} request object, if no exceptions occured
 */
function makeRequest(locationURL, parameters, onComplete, onError, doSynchronous, doPost, dataPackage, thisObject) {

    var http_request = false;
    if (window.XMLHttpRequest) {
        // IE>7, Firefox, Chrome, Opera, Safari
        http_request = new XMLHttpRequest();
    } else {
        // IE6
        http_request = new ActiveXObject("Microsoft.XMLHTTP");
    }

    if (http_request.overrideMimeType) {
      http_request.overrideMimeType('text/xml');
    }

    if (!http_request) {
      if (console) console.error('Cannot create XMLHTTP instance');
      return false;
    }

    if (onComplete && !doSynchronous) {
        completeListener = function() {
            if (http_request.readyState == 4) {
                if (http_request.status == 200) {
                    onComplete.call(thisObject, http_request, dataPackage);
                } else if (onError) {
                    onError.call(thisObject, http_request);
                }
            }
        };
        http_request.onreadystatechange = completeListener;
    }

    if (doPost) {
        http_request.open('POST', locationURL, !doSynchronous);
        http_request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        http_request.setRequestHeader("Content-length", parameters.length);
        http_request.setRequestHeader("Connection", "close");
        http_request.send(parameters);
    } else {
        http_request.open('GET', locationURL + (parameters ? ("?" + parameters) : ""), !doSynchronous);
        http_request.send(null);
    }

    return http_request;
}
