/* phprpc_client.js - JavaScript PHPRPC Client
 *
 * Copyright Ma Bingyao <andot@ujn.edu.cn>
 * Version: 2.1
 * LastModified: 2006-03-21
 * This library is free.  You can redistribute it and/or modify it.
 * http://www.coolcode.cn/?p=145
 */

/*
 * Interfaces:
 * phprpc_client.create('rpc');
 * rpc.use_service('http://domain.com/phprpc/server.php');
 * rpc.method_callback = function(result, args, output) {
 *     if (result instanceof phprpc_error) {
 *         alert(result.errstr);
 *     }
 *     else {
 *         alert(result);  // or do any other things.
 *     }
 * }
 * ....
 * if (rpc.ready) rpc.method();
 */

function phprpc_error(errno, errstr) {
    this.errno = errno;
    this.errstr = errstr;
}

function phprpc_client() {
    this.__php = new PHP_Serializer();
    this.__url = '';
    this.__encrypt = false;
    this.encrypt = 0;
    this.ready = false;
    this.use_service = function (url, encrypt) {
        if (encrypt) {
            this.__encrypt = true;
        }
        if (typeof(this.__name) == "undefined") {
            return false;
        }
        this.__url = url;
        this.ready = false;
        var id = this.__create_id();
        var callback;
        if (this.__encrypt) {
            callback = base64encode(utf16to8([this.__name, ".__switch_key('", id, "');"].join('')));
        }
        else {
            callback = base64encode(utf16to8([this.__name, ".__get_functions('", id, "');"].join('')));
        }
        this.__append_script(id, ['phprpc_encrypt=', this.__encrypt, '&phprpc_callback=', callback, '&phprpc_encode=false'].join(''));
        return true;
    }
    this.__switch_key = function (id) {
        if (typeof(phprpc_encrypt) == "undefined") {
            this.__encrypt = false;
            this.__get_functions(id);
        }
        else {
            this.__encrypt = this.__php.unserialize(phprpc_encrypt);
            this.__remove_script(id);
            this.__encrypt['p'] = dec2num(this.__encrypt['p']);
            this.__encrypt['g'] = dec2num(this.__encrypt['g']);
            this.__encrypt['y'] = dec2num(this.__encrypt['y']);
            this.__encrypt['x'] = rand(127, 1);
            var key = pow_mod(this.__encrypt['y'],
                              this.__encrypt['x'],
                              this.__encrypt['p']);
            key = num2str(key);
            for (var i = 0; i < 16 - key.length; i++) {
                key = '\0' + key;
            }
            this.__encrypt['k'] = key;
            var encrypt = pow_mod(this.__encrypt['g'],
                                  this.__encrypt['x'],
                                  this.__encrypt['p']);
            var callback = base64encode(utf16to8([this.__name, ".__get_functions('", id, "');"].join('')));
            this.__append_script(id, ['phprpc_encrypt=', num2dec(encrypt), '&phprpc_callback=', callback, '&phprpc_encode=false'].join(''));
        }
    }
    this.__get_functions = function (id) {
        var functions = this.__php.unserialize(phprpc_functions);
        var func = [];
        for (var i = 0; i < functions.length; i++) {
            func = [this.__name, ".", functions[i],
                    " = function () {\r\n    this.__call('",
                    functions[i],
                    "', this.__args_to_array(arguments));\r\n}\r\n",
                    this.__name, ".", functions[i],
                    ".ref = false;\r\n"].join('');
            eval(func);
        }
        this.__remove_script(id);
        this.ready = true;
        if (typeof(this.onready) == "function") {
            this.onready();
        }
    }
    this.__call = function (func, args) {
        var __args = this.__php.serialize(args);
        if ((this.__encrypt !== false) && (this.encrypt > 0)) {
            __args = xxtea_encrypt(__args, this.__encrypt['k']);
        }
        __args = base64encode(__args);
        var id = this.__create_id();
        var request = ['phprpc_func=', func,
                       '&phprpc_args=', __args,
                       '&phprpc_encode=false',
                       '&phprpc_encrypt=', this.encrypt];
        var callback = [this.__name, ".", func, "_callback"].join('');
        if (typeof(eval(callback)) == "function") {
            request[request.length] = '&phprpc_callback=';
            request[request.length] = base64encode(utf16to8([this.__name, ".__callback('", id, "', ", callback, ");"].join('')));
        }
        var ref = eval([this.__name, ".", func, ".ref"].join(''));
        if (!ref) {
            request[request.length] = '&phprpc_ref=false';
        }
        this.__append_script(id, request.join(''), args, ref);
    }
    this.__callback = function (id, callback) {
        phprpc_warning = null;
        if ((phprpc_errno != 1) && (phprpc_errno != 16) &&
            (phprpc_errno != 64) && (phprpc_errno != 256)) {
            var script = document.getElementById("script_" + id);
            if ((this.__encrypt !== false) && (script.encrypt > 0)) {
                if (script.encrypt > 1) {
                    phprpc_result = xxtea_decrypt(phprpc_result, this.__encrypt['k']);
                }
                if (script.ref) {
                    phprpc_args = xxtea_decrypt(phprpc_args, this.__encrypt['k']);
                }
            }
            phprpc_result = this.__php.unserialize(phprpc_result);
            if (script.ref) {
                phprpc_args = this.__php.unserialize(phprpc_args);
            }
            else {
                phprpc_args = script.args;
            }
            phprpc_warning = new phprpc_error(phprpc_errno, phprpc_errstr);
        }
        else {
            phprpc_result = new phprpc_error(phprpc_errno, phprpc_errstr);
            phprpc_args = null;
        }
        callback(phprpc_result, phprpc_args, phprpc_output, phprpc_warning);
        this.__remove_script(id);
    }
    this.__create_id = function () {
        return (new Date()).getTime().toString(36) + Math.floor(Math.random() * 100000000).toString(36);
    }
    this.__append_script = function (id, request, args, ref) {
        var script = document.createElement("script");
        script.id = "script_" + id;
        script.src = [this.__url, "?", request.replace(/\+/g, '%2B')].join('');
        script.defer = true;
        script.type = "text/javascript";
        script.args = args;
        script.ref = ref;
        script.encrypt = this.encrypt;
        var head = document.getElementsByTagName("head").item(0);
        head.appendChild(script);
    }
    this.__remove_script = function (id) {
        var script = document.getElementById("script_" + id);
        var head = document.getElementsByTagName("head").item(0);
        head.removeChild(script);
    }
    this.__args_to_array = function (args) {
        argArray = new Array();
        for (i = 0; i < args.length; i++) {
            argArray[i] = args[i];
        }
        return argArray;
    }
}

phprpc_client.create = function (name, encrypt) {
    eval([name, ' = new phprpc_client();', name, '.__name = "', name, '";'].join(''));
    if (encrypt) {
        encrypt = true;
        eval([name, '.__encrypt = ', encrypt, ';'].join(''));
    }
}