/*************************************************************************
 * libjson-rpc-cpp
 *************************************************************************
 * @file    jsclientstubgenerator.cpp
 * @date    10/22/2014
 * @author  Peter Spiess-Knafl <peter.knafl@gmail.com>
 * @license See attached LICENSE.txt
 ************************************************************************/

#include "jsclientstubgenerator.h"
#include <algorithm>

using namespace jsonrpc;
using namespace std;

#define TEMPLATE_JS_PROLOG "function <class>(url) {\n\
    this.url = url;\n\
    var id = 1;\n\
    \n\
    function doJsonRpcRequest(method, params, methodCall, callback_success, callback_error) {\n\
        var request = {};\n\
        if (methodCall)\n\
            request.id = id++;\n\
        request.jsonrpc = \"2.0\";\n\
        request.method = method;\n\
        if (params !== null) {\n\
            request.params = params;\n\
        }\n\
        JSON.stringify(request);\n\
        \n\
        $.ajax({\n\
            type: \"POST\",\n\
            url: url,\n\
            data: JSON.stringify(request),\n\
            success: function (response) {\n\
                if (methodCall) {\n\
                    if (response.hasOwnProperty(\"result\") && response.hasOwnProperty(\"id\")) {\n\
                        callback_success(response.id, response.result);\n\
                    } else if (response.hasOwnProperty(\"error\")) {\n\
                        if (callback_error != null)\n\
                            callback_error(response.error.code,response.error.message);\n\
                    } else {\n\
                        if (callback_error != null)\n\
                            callback_error(-32001, \"Invalid Server response: \" + response);\n\
                    }\n\
                }\n\
            },\n\
            error: function () {\n\
                if (methodCall)\n\
                    callback_error(-32002, \"AJAX Error\");\n\
            },\n\
            dataType: \"json\"\n\
        });\n\
        return id-1;\n\
    }\n\
    this.doRPC = function(method, params, methodCall, callback_success, callback_error) {\n\
        return doJsonRpcRequest(method, params, methodCall, callback_success, callback_error);\n\
    }\n\
}\n"

#define TEMPLATE_JS_METHOD "<class>.prototype.<procedure> = function(<params>callbackSuccess, callbackError) {"
#define TEMPLATE_JS_PARAM_NAMED "var params = {<params>};"
#define TEMPLATE_JS_PARAM_POSITIONAL "var params = [<params>];"
#define TEMPLATE_JS_PARAM_EMPTY "var params = null;"
#define TEMPLATE_JS_CALL_METHOD "return this.doRPC(\"<procedure>\", params, true, callbackSuccess, callbackError);"
#define TEMPLATE_JS_CALL_NOTIFICATION "this.doRPC(\"<procedure>\", params, false, callbackSuccess, callbackError);"



JSClientStubGenerator::JSClientStubGenerator(const string &stubname, std::vector<Procedure> &procedures, ostream &outputstream) :
    StubGenerator(stubname, procedures, outputstream)
{
}

JSClientStubGenerator::JSClientStubGenerator(const string &stubname, std::vector<Procedure> &procedures, const string &filename) :
    StubGenerator(stubname, procedures, filename)
{
}

string JSClientStubGenerator::class2Filename(const string &classname)
{
    string result = classname;
    std::transform(result.begin(), result.end(), result.begin(), ::tolower);
    return result + ".js";
}

void JSClientStubGenerator::generateStub()
{
    this->writeLine("/**");
    this->writeLine(" * This file is generated by jsonrpcstub, DO NOT CHANGE IT MANUALLY!");
    this->writeLine(" */");
    this->write(replaceAll(TEMPLATE_JS_PROLOG, "<class>", stubname));
    this->writeNewLine();

    for (unsigned int i=0; i < procedures.size(); i++)
    {
        this->generateMethod(procedures[i]);
    }

}

void JSClientStubGenerator::generateMethod(Procedure &proc)
{
    string method = TEMPLATE_JS_METHOD;
    replaceAll2(method, "<class>", stubname);
    replaceAll2(method, "<procedure>", noramlizeJsLiteral(proc.GetProcedureName()));

    stringstream param_string;
    stringstream params_assignment;

    parameterNameList_t list = proc.GetParameters();
    for (parameterNameList_t::iterator it = list.begin(); it != list.end();)
    {
        param_string << it->first;

        if (proc.GetParameterDeclarationType() == PARAMS_BY_NAME)
            params_assignment << it->first << " : " << it->first;
        else
            params_assignment << it->first;

        if (++it != list.end())
        {
                params_assignment << ", ";
        }
        param_string << ", ";
    }

    replaceAll2(method, "<params>", param_string.str());

    this->writeLine(method);
    this->increaseIndentation();

    string params;

    if (proc.GetParameters().size() > 0)
    {
        if (proc.GetParameterDeclarationType() == PARAMS_BY_NAME)
            params = TEMPLATE_JS_PARAM_NAMED;
        else
            params = TEMPLATE_JS_PARAM_POSITIONAL;

        replaceAll2(params, "<params>", params_assignment.str());
        this->writeLine(params);
    }
    else
    {
        this->writeLine(TEMPLATE_JS_PARAM_EMPTY);
    }


    if (proc.GetProcedureType() == RPC_METHOD)
        method = TEMPLATE_JS_CALL_METHOD;
    else
        method = TEMPLATE_JS_CALL_NOTIFICATION;

    replaceAll2(method, "<procedure>", proc.GetProcedureName());

    this->writeLine(method);

    this->decreaseIndentation();
    this->writeLine("};");

}

string JSClientStubGenerator::noramlizeJsLiteral(const string &literal)
{
    string result = literal;
    for(unsigned int i=0; i < literal.length(); i++)
    {
        if (!((literal[i] >= 'a' && literal[i] <= 'z') || (literal[i] >= 'A' && literal[i] <= 'Z') || (literal[i] >= '0' && literal[i] <= '9') || literal[i] == '_'))
        {
            result[i] = '_';
        }
    }
    return result;
}
