//////////////////////////////////////////////////////////////////////
// JavaScript-PHP Pipe System - Client Module
// Need: JavaScript1.2 & DOM browser
// © 2007 Makarov I. A. (YasonDelAlt@list.ru)
//////////////////////////////////////////////////////////////////////

function jsppObject (objName){
    this._version       = '1.0b';   // Версия системы

    // URL PHP-шлюза для вызова PHP-функций
    // принимает параметры:
    //      func - имя функции,
    //      params[] - массив параметров,
    //      rand - случайное число (чтобы браузер не возвращал данные из кеша)
    this._phpGateUrl    = '/js/gate.php';

    this._debugMode     = false;    // Режим отладки: вкл/выкл
    this._debugLogWnd   = null;     // Лог для отладки

    this._initialized   = false;    // Флаг готовности системы
    this._queue = Array();          // Очередь запросов
    
    this.objName    = objName;          // Имя объекта JSPP
    this.workId     = 'jspp_'+objName;  // Рабочий идентификатор
    this.returnCode = 0;                // Код ответа
    this.transferFrame  = null;         // Рабочий фрейм
    // Состояние передачи:
    //  0 - очередь пуста, загрузка закончена
    //  1 - идет загрузка
    this.transferState  = 0;

    /*_*_*_*_*_*_*_*_*_*_*_*_*_*_* Ядро *_*_*_*_*_*_*_*_*_*_*_*_*_*_*/

    //----------------------------------------------------------------
    // Объект запроса
    //----------------------------------------------------------------
    this._requestObject = function (r, c)
    {
        var obj = new Object();
        obj.callback = c;
        obj.request = r;
        return obj;
    }
    
    //----------------------------------------------------------------
    // Для отладки
    //----------------------------------------------------------------
    this._debug = function (funcname, data, isError)
    {
        if (!this._debugMode) return;
        var out = this.getTime()+(isError?' [X]':' [!]')+' ['+this.objName+'.'+funcname+']: '+data+'\n\n';
        this._debugLogWnd.dbgFrm.dbgOut.value += out;
    }

    //----------------------------------------------------------------
    // Подготавливает строку к отправке GET-запросом
    //----------------------------------------------------------------
    this._strClean = function (str)
    {
        var repArr = [];
        repArr['%'] = '%25';
        repArr['"'] = '%22';
        repArr['#'] = '%23';
        repArr['='] = '%26';
        repArr["'"] = '%27';
        repArr['+'] = '%2B';
        repArr['&'] = '%3D';
        repArr['?'] = '%3F';
        var out = '';
        for (var i=0,l=str.length; i<l; i++)
        {
            if (undefined == repArr[str.charAt(i)]) out += str.charAt(i);
            else out += repArr[str.charAt(i)];
        }
        return out;
    }
    
    //----------------------------------------------------------------
    // Обработка ответов сервера
    //----------------------------------------------------------------
    this._callback = function ()
    {
        if (0 == this.transferState) {return false;}
        var obj = this._queue.shift(); // Удаляем запрос из очереди
        this.returnCode = this.transferFrame.returnCode;
        if (undefined == this.returnCode) { this.returnCode = -1; }
        switch (this.returnCode)
        {
            case 0:
                this._debug('_callback','Получены данные...\n'+this.transferFrame.data);
                try
                { // Вызов callback-функции запроса
                    obj.callback(this.transferFrame.data);
                    this._debug('_callback','Вызов callback-функции успешен. Все OK!');
                }
                catch (e) { this._debug('_callback','ошибка вызова callback-функции!\n'+obj.callback,1); }
                break;
            case 10:
                this._debug('_callback','не найдена функция на сервере!\n'+obj.request,1);
                break;
            default:
                this._debug('_callback','неизвестный код ответа!\nКод: '+this.returnCode+' Данные: '+this.transferFrame.data,1);
        }
        this._process();
    }
    
    //----------------------------------------------------------------
    // Обработка запросов к серверу
    //----------------------------------------------------------------
    this._process = function ()
    {
        if (!this._initialized)
        {
            this._debug('_process','система не инициализирована.\nДальнейшая работа не возможна!',1);
            return false;
        }
        if (0 < this._queue.length)
        {
            // Получение имени вызываемой функции
            var fName = this._queue[0].request;
            fName = fName.substring(0, fName.indexOf('('));
            
            // Получение строки параметров фукции
            var paramsStr = this._queue[0].request;
            paramsStr = paramsStr.substring(paramsStr.indexOf('(')+1, paramsStr.lastIndexOf(')'));
            
            /*--------------------------------------------------*/
            // Здесь происходит выделение отдельных параметров вызываемой функции
            var params = Array();   // Массив с выделенными параметами
            var tmpStr = '';
            var isLiteral1 = false; // Флаг строки в одинарных кавычках
            var isLiteral2 = false; // Флаг строки в двойных кавычках
            var isSpecChar = false; // Флаг специалного символа (предворенного слешем)
            var bracketLev1 = 0;    // Уровень вложенности круглых скобок
            var bracketLev2 = 0;    // Уровень вложенности квадратных скобок
            var bracketLev3 = 0;    // Уровень вложенности фигурных скобок
            for (var i=0, l=paramsStr.length; i < l; i++)
            {
                var tmpChr = paramsStr.charAt(i);
                switch (tmpChr)
                {
                    case '(':
                        if (!(isLiteral1 || isLiteral2)) { bracketLev1++; }
                        tmpStr += tmpChr;
                        break;
                    case ')':
                        if (!(isLiteral1 || isLiteral2)) { bracketLev1--; }
                        tmpStr += tmpChr;
                        break;
                    case '[':
                        if (!(isLiteral1 || isLiteral2)) { bracketLev2++; }
                        tmpStr += tmpChr;
                        break;
                    case ']':
                        if (!(isLiteral1 || isLiteral2)) { bracketLev2--; }
                        tmpStr += tmpChr;
                        break;
                    case '{':
                        if (!(isLiteral1 || isLiteral2)) { bracketLev3++; }
                        tmpStr += tmpChr;
                        break;
                    case '}':
                        if (!(isLiteral1 || isLiteral2)) { bracketLev3--; }
                        tmpStr += tmpChr;
                        break;
                    case '\\':
                        isSpecChar = !isSpecChar;
                        tmpStr += tmpChr;
                        break;
                    case "'":
                        if (isSpecChar) { isSpecChar = false; }
                        else
                        {
                            if (!isLiteral2) { isLiteral1 = !isLiteral1; }
                        }
                        tmpStr += tmpChr;
                        break;
                    case '"':
                        if (isSpecChar) { isSpecChar = false; }
                        else
                        {
                            if (!isLiteral1) { isLiteral2 = !isLiteral2; }
                        }
                        tmpStr += tmpChr;
                        break;
                    case ',': // Если нашли запятую и она не лежит внутри кавычек или скобок
                        if ((!isLiteral1)&&(!isLiteral2)&&(0==bracketLev1)&&(0==bracketLev2)&&(0==bracketLev3))
                        {
                            params.push(tmpStr);// Значит это конец параметра
                            tmpStr = '';        // начинаем выделять следующий
                        }
                        else { tmpStr += tmpChr; } // Иначе запятая принадлежит параметру
                        break;
                    default:
                        tmpStr += tmpChr;
                }
            }
            if (tmpStr != '') params.push(tmpStr);
            // Закончили выделение параметров
            /*--------------------------------------------------*/
            
            // Пошла обработка параметров
            var callUrl = this._phpGateUrl + (-1==this._phpGateUrl.indexOf('?') ? '?' : '&');
            callUrl += 'func=' + fName;
            for (var i=0, l=params.length; i < l; i++) // Собирается GET-запрос для PHP-шлюза
            {
                try { callUrl += '&params[]=' + this._strClean(eval(params[i])); } // передаваемый параметр кодируется в HEX-кодировку
                catch (e)
                {
                    callUrl += '&params[]=';
                    this._debug('_process','ошибка обработки параметров!\n'+fName+'('+paramsStr+')',1);
                }
            }
            callUrl += '&rand=' + Math.round(1000000*Math.random());
            this._debug('_process','посылка запроса...\n'+callUrl);
            //window.frames[this.workId].location.href = callUrl;   // работает только в IE
            document.getElementById(this.workId).src = callUrl;     // работает и в IE и в Опере и в Лисе
            
            delete params;
            this.transferState = 1;
        }
        else
        {
            this.transferState = 0;
        }
        
        return true;
    }
    
    /* *************** Public *************** */
    //----------------------------------------------------------------
    // Инициализация объекта JSPP. Вызов в BODY
    //----------------------------------------------------------------
    this.init = function ()
    {
        // Создание фрейма для транзакций
        document.writeln('<IFRAME id="' + this.workId + '" name="' + this.workId +
            '" onLoad="' + this.objName + '._callback();" style="visibility:hidden; display:none"></IFRAME>');
        this.transferFrame = window.frames[this.workId]; // потребуется для получения данных из ответа
        this._initialized = true; // Выставляем флаг готовности
    }
    
    //----------------------------------------------------------------
    // Создание запроса к серверу
    // Принимает:
    //  request - строка вида 'func_name([param, [param2, [...]]])'
    //  callback - callback-функция принимающая один параметр - возвращаемое значение
    //----------------------------------------------------------------
    this.request = function (request, callback)
    {
        // Создание объекта запроса
        var obj = new this._requestObject(request, callback);
        // Добавление его в очередь
        this._queue.push(obj);
        // Если очередь была пуста, то вызываем обработчик запросов
        if (0 == this.transferState) { this._process(); }
    }
    
    //----------------------------------------------------------------
    // Сокращение для request()
    //----------------------------------------------------------------
    this.$ = this.request;
    
    //----------------------------------------------------------------
    // Включение/выключение режима отладки
    // Принимает: true - включение, false - выключение
    //----------------------------------------------------------------
    this.debugMode = function (mode)
    {
        if (mode)
        {
            this._debugLogWnd = window.open('', this.workId+'_debugLogWnd', 'height=450,width=600,resizable=no,titlebar=no');
            this._debugLogWnd.document.close();
            this._debugLogWnd.document.open();
            this._debugLogWnd.document.write('<FORM name=dbgFrm><TEXTAREA name=dbgOut cols=80 rows=25 style="font-size:8pt;" readonly></TEXTAREA><HR><INPUT type=reset value=Очистить></FORM>');
            this._debugLogWnd.dbgFrm.dbgOut.value = 'Режим отладки включен...\n\n';
        }
        else
        {
            if (null != this._debugLogWnd) this._debugLogWnd.close();
        }
        this._debugMode = mode;
    }
    
    //----------------------------------------------------------------
    // Возвращает текущее время в формате чч:мм:сс
    //----------------------------------------------------------------
    this.getTime = function ()
    {
        var t = new Date();
        var out = '';
        var h = t.getHours(), m = t.getMinutes(), s = t.getSeconds();
        if (h < 10) out+='0'+h.toString();
        else  out+=h.toString();
        out+=':';
        if (m < 10) out+='0'+m.toString();
        else  out+=m.toString();
        out+=':';
        if (s < 10) out+='0'+s.toString();
        else  out+=s.toString();
        delete t;
        return out;
    }
    
    /*_*_*_*_*_*_*_*_*_* Вспомогательные функции *_*_*_*_*_*_*_*_*_*/
    
    //----------------------------------------------------------------
    // Создает callback-функцию для вставки/добавления полученного значения в нужний тег
    // Принимает: id тега-цели
    //----------------------------------------------------------------
    this.cb_toTag = function (id, add)
    {
        var f = null;
        try{eval('f = function(d){document.getElementById(\''+id+'\').innerHTML '+(add?'+=':'=')+' d;}');}
        catch(e){this._debug('cb_toTag','ошибка создания функции!\n Параметр: '+id,1);}
        return f;
    }
    
    //----------------------------------------------------------------
    // Создает callback-функцию для вставки/добавления полученного значения в value нужного элемента
    // Принимает: name тега-цели (Пример: 'myform.myinput')
    //----------------------------------------------------------------
    this.cb_toVal = function (name, add)
    {
        var f = null;
        try{eval('f = function(d){document.'+name+'.value '+(add?'+=':'=')+' d;}');}
        catch(e){this._debug('cb_toVal','ошибка создания функции!\nПараметр: '+name,1);}
        return f;
    }
    
    //----------------------------------------------------------------
    // Создает callback-функцию для вставки/добавления полученного значения в value нужного элемента
    // Принимает: name тега-цели (Пример: 'myform.myinput')
    //----------------------------------------------------------------
    this.cb_echo = function ()
    {
        var f = null;
        try{eval('f = function(d){alert(d)}');}
        catch(e){this._debug('cb_echo','ошибка создания функции!',1);}
        return f;
    }
    
    // Регистрация глобального объекта
    window[this.objName] = this;
}
//////////////////////////////////////////////////////////////////////
// © 2007 Makarov I. A. (YasonDelAlt@list.ru)
//////////////////////////////////////////////////////////////////////