/**
 *$Id: srvapi.js 85 2008-10-15 16:04:11Z aanpilogov $
 *
 * Работа с AJAX запросами и ответами в формате Pusk XML Response
 */
PuskFramework.srvapi = new function()
{
    var pf = PuskFramework;
    this.scriptDispatcher = {};
    this.scriptLoading = false;
    this.scriptStack = [];     // общий стек - для режима jsSequental
    this._jsSequental = {};

    /**
     * Оборачивает запрос для проксирования через прозрачный сервер
     * @param {Object} url URL
     * @return {String}
     */
    this.getProxy = function(url)
    {        
        if (!url) url = 'http://';
        if (!url.match(/^http(s|):\/\//)) return ('/proxy/http/' + url);        
        var newurl = url.replace(/^http(s|):\/\//, "http$1/");
        return ('/proxy/' + newurl);
    };

    /**
     * Осуществить запрос к серверу. Ответ обрабатывается по умолчанию внутренним обработчиком.
     *
     * @param {Object} url URL
     * @param {Object} after callback
     * @param {Object} onerr callback в случае ошибки
     * @param {Object} sync флаг синхронности запроса
     */
    this.invoke = function(url, after, onerr, sync)
    {
        url = url || '';
        return this._invoke(url, 'get', {}, after, onerr, sync);
    };

    /**
     * Осуществить запрос к серверу. Ответ обрабатывается по умолчанию внутренним обработчиком.
     *
     * @param {Object} url URL
     * @param {Object} after callback
     * @param {Object} onerr callback в случае ошибки
     * @param {Object} sync флаг синхронности запроса
     */
    this.pinvoke = function(url, params, after, onerr, sync)
    {
        url = url || '';
        return this._invoke(url, 'post', params, after, onerr, sync);
    };

    this._invoke = function(url, method, params, after, onerr, sync)
    {
        params = params || {};
        method = method || 'get';
        after = after || this.parseResponse;
        _url = url+((url.indexOf("?")>-1)?"&":"?")+'xml=1';

        debugNotice('<a href="'+_url+'" target="_blank">Запрос</a>');
        var reqStime = pf.$time();
        serverSetStatus('busy');
        return pf.ajax[method](
        {
            'url': _url,
            'async' : !sync,
            'parameters': params,
            'onSuccess': function (req)
            {
                try {
                    debugResult('Пришел ответ от сервера, задержка '+ pf.$time(reqStime) + 'мс.');
                    after(req, url);
                } catch(err)
                {
                    debugError('не получается выполнить коллбэк! '+err.name + ' : '+ err.message);
                    serverSetStatus('error');
                }
             },
             'onError':function(req)
             {
                debugError('Ошибка запроса!\nСтатус='+req.statusText);
                serverSetStatus('error');
                if (onerr) { onerr(req); }
             }
        });
    };

    /**
     * Осуществить запрос к серверу, передав в качестве объекта действия application
     *
     * @param {Object} props параметры запроса
     * @param {Object} after callback
     * @return {String}
     */
    this.appInvoke = function(props, after)
    {
        props = props || new Array();
        props['object'] = 'application';
        return pf.srvapi.invoke( '/server/?' + pf.hash.serialize(props), after || null);
    };

    /**
     * обработка xml-ответа сервера в формате pusk.ru
     *
     * @param {Object} xmlrequest объект запроса
     * @param {Object} url адрес запроса
     */
    this.parseResponse = function(xmlrequest, url)
    {        
        if (pf.$type(xmlrequest) != "object") throw pf.$exGen('pf.srvapi.parseResponse(): ' + 'typeof(xmlrequest) argument is ' + pf.$type(xmlrequest) + ', "object" expected');
        if (pf.$type(xmlrequest.responseXML) != "object") throw pf.$exGen('pf.srvapi.parseResponse(): ' + 'typeof(xmlrequest.responseXML) is ' + pf.$type(xmlrequest.responseXML) + ', "object" expected');
                
        xml = xmlrequest.responseXML;
        if (!xml || !xml.documentElement)
        {
            debugError('No data @ <a href="'+url+'&xml=1" target="_blank">response</a>');
            return false;
        }

        xml = xml.documentElement;
        var responseId = Math.rand(1, 1000000);

        pf.srvapi._parseResultBlock(xml, responseId);
        pf.srvapi._parseInlineStyle(xml);
        pf.srvapi._parseLinkedStyle(xml);
        var htmlData = pf.srvapi._parseHtml(xml);
        var xmlData  = pf.srvapi._parseXml(xml, xmlrequest);
        pf.srvapi.scriptDispatcher[responseId] = {'htmlData':htmlData, 'xmlData':xmlData};

        if (pf.srvapi._jsSequental[responseId])
        {
            pf.srvapi._parseScriptsSequental(xml, responseId);
        } else {
            pf.srvapi._parseScripts(xml, responseId);
        }

        debugResult('<a href="'+url+'&xml=1" target="_blank">Запрос</a> успешно обработан');
    };

    /**
     * Считываем код ответа и process instructions
     *
     * @private
     * @param {Object} xml XML документ с ответом
     * @param {Object} responseId идентификатор ответа
     */
    this._parseResultBlock = function(xml, responseId)
    {             
        var result = xml.getElementsByTagName('result');
        if (!result) { return false; }
        result = result[0];

        var code = parseInt(result.getAttribute('code'));
        serverSetStatus(code);

        pf.srvapi._jsSequental[responseId] = false;
        var pi, instructions = result.getElementsByTagName('pi');
        for (var i=0, len=instructions.length; i<len; i++)
        {
            pi = instructions[i];
            switch(pi.getAttribute('name'))
            {
                case 'js_sequental':
                {
                    pf.srvapi._jsSequental[responseId] = (pi.getAttribute('value') == 'true');
                }
            }
        }
    };

    /**
     * Инлайн-стили, обработка и подгрузка
     *
     * @private
     * @param {Object} xml XML ответ
     */
    this._parseInlineStyle = function(xml)
    {
        var item, items = xml.getElementsByTagName('style');
        if(!items) { return false; }

        for (var i=0, len=items.length; i < len; i++)
        {
            item = items[i];
            var style = pf.elem.getText(item);
            var instance = item.getAttribute('instance') || 'system';
            pf.cssManager.loadInlineStyle(style, instance, item.getAttribute('id'));
        }
        return len;
    };

   /**
    * Внешние стили - обработка и подгрузка
    *
    * @private
    * @param {Object} xml XML ответ
    */
    this._parseLinkedStyle = function(xml)
    {
        var items = xml.getElementsByTagName('stylesheet');
        if (!items) { return false; }

        for (var i=0, len=items.length; i<len; i++)
        {
            var linkHref = items[i].getAttribute('href');
            var instance = items[i].getAttribute('instance');
            pf.cssManager.loadStyle(linkHref, instance);
        }
        return len;
    };

    /**
     * Парсинг HTML блоков
     *
     * @private
     * @param {Object} xml XML ответ
     */
    this._parseHtml = function(xml)
    {
        var item, items = xml.getElementsByTagName('html');
        if (!items) { return {}; }

        var htmlData = {};
        var itemId, parsed, instance;

        for (var i=0, len=items.length; i<len; i++)
        {
            item = items[i];
            parsed = '';
            for (var j=0, len2=item.childNodes.length; j<len2; j++)
            {
                if (item.childNodes[j].data != '')
                {
                    parsed += item.childNodes[j].data;
                }
            };

            instance = item.getAttribute('instance');
            parsed = parsed.replace(/__VDINSTANCE__/g, instance);
            itemId = item.getAttribute('id') || 'TMPContainer'+Math.rand(1, 1000);
            htmlData[itemId] = parsed;
        };
        return htmlData;
    };

    /**
     * Парсер инкапсулированного XML
     *
     * @private
     * @param {Object} xml XML ответ
     * @param {Object} xhr Объект запроса
     * @return {Object}
     */
    this._parseXml = function(xml, xhr)
    {
        var item, items = xml.getElementsByTagName('xml');
        if (!items) { return {}; }

        var xmlData = {};
        var itemId, instance;
        var params = {'id':xhr.parameters._URID};

        for (var i=0, len=items.length; i<len; i++)
        {
            item = items[i];
            itemId = item.getAttribute('id');
            params.instance = item.getAttribute('instance') || 0;
            xmlData[itemId] = pf.xml.fragment2document(item, params);
        };
        return xmlData;
    };

    /**
     * Парсер подключенных скриптов
     *
     * @private
     * @param {Object} xml XML ответ
     * @param {Object} responseId Идентификатор ответа
     */
    this._parseScripts = function(xml, responseId)
    {
        var item, items = xml.selectNodes('/response/jscript | /response/execute');
        if (!items) { return false; }

        pf.srvapi.scriptDispatcher[responseId].stack = [];
        pf.srvapi.scriptDispatcher[responseId].len = 0;
        for (var i=0, len=items.length; i<len; i++)
        {
            item = items[i];
            switch(item.tagName)
            {
                case 'execute':
                {
                    var code    = item.text || item.textContent;
                    if (item.getAttribute('instant') == 'true')
                    {
                        _eval(code, responseId, html, xmlData);
                        break;
                    }

                    pf.srvapi.scriptDispatcher[responseId].stack.push(code);
                    break;
                }
                case 'jscript':
                {
                    var existing;
                    if (existing = _isExistingScript(item))
                    {
                        if (existing.getAttribute('loaded') != 'true' && existing.getAttribute('responseId') != responseId)
                        {
                            function _onScriptLoadExt(evt)
                            {
                                evt = evt || event;
                                var elem = evt.currentTarget || evt.srcElement;
                                if (evt.type == 'readystatechange' && elem.readyState && !(elem.readyState == 'complete' || elem.readyState == 'loaded')) { return } // 4IE
                                pf.srvapi.scriptDispatcher[responseId].len--;
                                _runExecutes(responseId);
                            }
                            pf.srvapi.scriptDispatcher[responseId].len++;
                            if (!pf.browsCap.isOpera) { pf.evt.add(existing, 'readystatechange', _onScriptLoadExt) }
                            pf.evt.add(existing, 'load',  _onScriptLoadExt);
                            pf.evt.add(existing, 'error', _onScriptLoadExt);
                        }
                        break;
                    }

                    var script = pf.srvapi._createScriptElement(item, responseId);

                    pf.srvapi.scriptLoading = true;
                    pf.srvapi.scriptDispatcher[responseId].len++;
                    var postfix = (window.Prj && window.Prj.version) ? '?'+window.Prj.version : '';
                    void(script.src = item.getAttribute('src') + postfix);
                    pf._scriptContainer.appendChild(script);

                    break;
                }
            }
        }

        _runExecutes(responseId);
    };

    /**
     * Обработчик скриптов (последовательный)
     *
     * @private
     * @param {Object} xml XML ответ
     * @param {Object} responseId идентификатор ответа
     */
    this._parseScriptsSequental = function(xml, responseId)
    {
        var item, items = xml.selectNodes('/response/jscript | /response/execute');
        if (!items) { return false; }

        var runStack = (pf.srvapi.scriptStack.length == 0); // нужно ли запускать стек
        for (var i=0, len=items.length; i<len; i++)
        {
            item = items[i];
            switch(item.tagName)
            {
                case 'execute':
                {
                    var code    = item.text || item.textContent;
                    if (item.getAttribute('instant') == 'true')
                    {
                        _eval(code, responseId);
                        break;
                    }

                    pf.srvapi.scriptStack.push(code);
                    break;
                }
                case 'jscript':
                {
//debugNotice('script объявлен '+item.getAttribute('src'));
                    if (_isExistingScript(item)) { break; }
                    var script = pf.srvapi._createScriptElement(item, responseId);

                    if (!_isStacked(script, responseId))
                    {
                        pf.srvapi.scriptStack.push(script);
//debugNotice('stacked '+script.getAttribute('wsrc'));
                    }
                    break;
                }
            }
        }

        if (runStack) { _fetchNextScript(responseId); }
    };

    /**
     * Созданеие скрипта для подгрузки файла
     *
     * @private
     * @param {Object} item свойства
     * @param {Object} responseId идентификатор ответа
     * @return {Element}
     */
    this._createScriptElement = function(item, responseId)
    {
        var scr = pf.$$$('script');
        scr.id   = item.getAttribute('id');
        scr.type = 'text/javascript';
        scr.responseId = responseId;
        scr.setAttribute('responseId', responseId);
        scr.setAttribute('loaded', false);
        var charset = item.getAttribute('charset');
        if (charset) { scr.charset = charset; }
        if (!pf.browsCap.isOpera) { pf.evt.add(scr, 'readystatechange', _onScriptLoad) }
        pf.evt.add(scr, 'load',  _onScriptLoad);
        pf.evt.add(scr, 'error', _onScriptLoad);
        var postfix = (window.Prj && window.Prj.version) ? '?'+window.Prj.version : '';
        scr.setAttribute('wsrc', item.getAttribute('src') + postfix);
        return scr;
    };

    /**
     * Обработчик события загрузки скрипта
     *
     * @private
     * @param {Object} evt событие
     */
    var _onScriptLoad = function(evt)
    {
        evt = evt || event;
        var elem = evt.currentTarget || evt.srcElement;
        if (evt.type == 'readystatechange' && elem.readyState && !(elem.readyState == 'complete' || elem.readyState == 'loaded')) { return } // 4IE

        var responseId = parseInt(elem.getAttribute('responseId'));
        elem.setAttribute('loaded', 'true');

        pf.srvapi.scriptLoading = false;
        if (pf.srvapi._jsSequental[responseId])
        {
            _fetchNextScript(responseId);
        } else {
            pf.srvapi.scriptDispatcher[responseId].len--;
            _runExecutes(responseId);
        }
    };

    /**
     * Выполнение куска кода
     *
     * @private
     * @param {Object} jsCode код
     * @param {Object} responseId идентификатор ответа
     */
    var _eval = function(jsCode, responseId)
    {
        var html =    pf.srvapi.scriptDispatcher[responseId].htmlData;
        var xmlData = pf.srvapi.scriptDispatcher[responseId].xmlData;
        try { eval(jsCode); } catch(err) {debugError('Inline script error '+err.name+ ' : '+err.message); }
    };

    /**
     * Проверка, существует ли скрипт в документе
     *
     * @private
     * @param {Object} scrElem элемент
     */
    var _isExistingScript = function(scrElem)
    {
        var scriptSrc = scrElem.getAttribute('src');
        var scriptId  = scrElem.getAttribute('id');

        var scriptObjects = pf._scriptContainer.getElementsByTagName('SCRIPT');
        for (var i=0, len=scriptObjects.length; i<len; i++)
        {
            var existing = scriptObjects[i];
            if (existing.src == scriptSrc || existing.id == scriptId)
            {
                return existing;
            }
        }
        return false;
    };

    /**
     * @private
     * @param {Object} scr
     * @param {Object} responseId
     */
    var _isStacked = function(scr, responseId)
    {
        var script;
        for(var i=0, len=pf.srvapi.scriptStack.length; i<len; i++)
        {
            script = pf.srvapi.scriptStack[i];
            if (!script || !script.tagName) { continue; }
            if (script.getAttribute('wsrc') == scr.getAttribute('wsrc') || script.id == scr.id) { return true; }
        }
        return false;
    };

    /**
     * Подгружает следующий скрипт в очереди
     *
     * @private
     * @param {Object} responseId идентификатор ответа
     */
    var _fetchNextScript = function(responseId)
    {
        if (pf.srvapi.scriptStack.length)
        {
            var script = pf.srvapi.scriptStack.shift();
            if (script.tagName)
            {
                pf.srvapi.scriptLoading = true;
                void(script.src = script.getAttribute('wsrc'));
                pf._scriptContainer.appendChild(script);
            } else {
                _eval(script, responseId);
                _fetchNextScript(responseId);
            }
        } else { // GC
            pf.srvapi.scriptDispatcher = {};
            pf.srvapi._jsSequental = {};
        }
    };

    /**
     * @private
     * @param {Object} responseId
     */
    var _runExecutes = function(responseId)
    {
        if (pf.srvapi.scriptDispatcher[responseId].len > 0) { return false; }

        for (var i=0, len=pf.srvapi.scriptDispatcher[responseId].stack.length; i<len; i++)
        {
            _eval(pf.srvapi.scriptDispatcher[responseId].stack[i], responseId);
        }

        delete pf.srvapi.scriptDispatcher[responseId];
        delete pf.srvapi._jsSequental[responseId];
    };
};

