#include "generate_java.h"
#include "Type.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

Type* SERVICE_CONTEXT_TYPE = new Type("android.content",
        "Context", Type::BUILT_IN, false, false, false);
Type* PRESENTER_BASE_TYPE = new Type("android.support.place.connector",
        "EventListener", Type::BUILT_IN, false, false, false);
Type* PRESENTER_LISTENER_BASE_TYPE = new Type("android.support.place.connector",
        "EventListener.Listener", Type::BUILT_IN, false, false, false);
Type* RPC_BROKER_TYPE = new Type("android.support.place.connector", "Broker",
        Type::BUILT_IN, false, false, false);
Type* RPC_CONTAINER_TYPE = new Type("com.android.athome.connector", "ConnectorContainer",
        Type::BUILT_IN, false, false, false);
Type* PLACE_INFO_TYPE = new Type("android.support.place.connector", "PlaceInfo",
        Type::BUILT_IN, false, false, false);
// TODO: Just use Endpoint, so this works for all endpoints.
Type* RPC_CONNECTOR_TYPE = new Type("android.support.place.connector", "Connector",
        Type::BUILT_IN, false, false, false);
Type* RPC_ENDPOINT_INFO_TYPE = new UserDataType("android.support.place.rpc",
        "EndpointInfo", true, __FILE__, __LINE__);
Type* RPC_RESULT_HANDLER_TYPE = new UserDataType("android.support.place.rpc", "RpcResultHandler",
        true, __FILE__, __LINE__);
Type* RPC_ERROR_LISTENER_TYPE = new Type("android.support.place.rpc", "RpcErrorHandler",
        Type::BUILT_IN, false, false, false);
Type* RPC_CONTEXT_TYPE = new UserDataType("android.support.place.rpc", "RpcContext", true,
        __FILE__, __LINE__);

static void generate_create_from_data(Type* t, StatementBlock* addTo, const string& key,
        Variable* v, Variable* data, Variable** cl);
static void generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from);
static void generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v,
        Variable* data);

static string
format_int(int n)
{
    char str[20];
    sprintf(str, "%d", n);
    return string(str);
}

static string
class_name_leaf(const string& str)
{
    string::size_type pos = str.rfind('.');
    if (pos == string::npos) {
        return str;
    } else {
        return string(str, pos+1);
    }
}

static string
results_class_name(const string& n)
{
    string str = n;
    str[0] = toupper(str[0]);
    str.insert(0, "On");
    return str;
}

static string
results_method_name(const string& n)
{
    string str = n;
    str[0] = toupper(str[0]);
    str.insert(0, "on");
    return str;
}

static string
push_method_name(const string& n)
{
    string str = n;
    str[0] = toupper(str[0]);
    str.insert(0, "push");
    return str;
}

// =================================================
class DispatcherClass : public Class
{
public:
    DispatcherClass(const interface_type* iface, Expression* target);
    virtual ~DispatcherClass();

    void AddMethod(const method_type* method);
    void DoneWithMethods();

    Method* processMethod;
    Variable* actionParam;
    Variable* requestParam;
    Variable* rpcContextParam;
    Variable* errorParam;
    Variable* requestData;
    Variable* resultData;
    IfStatement* dispatchIfStatement;
    Expression* targetExpression;

private:
    void generate_process();
};

DispatcherClass::DispatcherClass(const interface_type* iface, Expression* target)
    :Class(),
     dispatchIfStatement(NULL),
     targetExpression(target)
{
    generate_process();
}

DispatcherClass::~DispatcherClass()
{
}

void
DispatcherClass::generate_process()
{
    // byte[] process(String action, byte[] params, RpcContext context, RpcError status)
    this->processMethod = new Method;
        this->processMethod->modifiers = PUBLIC;
        this->processMethod->returnType = BYTE_TYPE;
        this->processMethod->returnTypeDimension = 1;
        this->processMethod->name = "process";
        this->processMethod->statements = new StatementBlock;

    this->actionParam = new Variable(STRING_TYPE, "action");
    this->processMethod->parameters.push_back(this->actionParam);

    this->requestParam = new Variable(BYTE_TYPE, "requestParam", 1);
    this->processMethod->parameters.push_back(this->requestParam);

    this->rpcContextParam = new Variable(RPC_CONTEXT_TYPE, "context", 0);
    this->processMethod->parameters.push_back(this->rpcContextParam);    

    this->errorParam = new Variable(RPC_ERROR_TYPE, "errorParam", 0);
    this->processMethod->parameters.push_back(this->errorParam);

    this->requestData = new Variable(RPC_DATA_TYPE, "request");
    this->processMethod->statements->Add(new VariableDeclaration(requestData,
                new NewExpression(RPC_DATA_TYPE, 1, this->requestParam)));

    this->resultData = new Variable(RPC_DATA_TYPE, "resultData");
    this->processMethod->statements->Add(new VariableDeclaration(this->resultData,
                NULL_VALUE));
}

void
DispatcherClass::AddMethod(const method_type* method)
{
    arg_type* arg;

    // The if/switch statement
    IfStatement* ifs = new IfStatement();
        ifs->expression = new MethodCall(new StringLiteralExpression(method->name.data), "equals",
                1, this->actionParam);
    StatementBlock* block = ifs->statements = new StatementBlock;
    if (this->dispatchIfStatement == NULL) {
        this->dispatchIfStatement = ifs;
        this->processMethod->statements->Add(dispatchIfStatement);
    } else {
        this->dispatchIfStatement->elseif = ifs;
        this->dispatchIfStatement = ifs;
    }
    
    // The call to decl (from above)
    MethodCall* realCall = new MethodCall(this->targetExpression, method->name.data);

    // args
    Variable* classLoader = NULL;
    VariableFactory stubArgs("_arg");
    arg = method->args;
    while (arg != NULL) {
        Type* t = NAMES.Search(arg->type.type.data);
        Variable* v = stubArgs.Get(t);
        v->dimension = arg->type.dimension;

        // Unmarshall the parameter
        block->Add(new VariableDeclaration(v));
        if (convert_direction(arg->direction.data) & IN_PARAMETER) {
            generate_create_from_data(t, block, arg->name.data, v,
                    this->requestData, &classLoader);
        } else {
            if (arg->type.dimension == 0) {
                block->Add(new Assignment(v, new NewExpression(v->type)));
            }
            else if (arg->type.dimension == 1) {
                generate_new_array(v->type, block, v, this->requestData);
            }
            else {
                fprintf(stderr, "aidl:internal error %s:%d\n", __FILE__,
                        __LINE__);
            }
        }

        // Add that parameter to the method call
        realCall->arguments.push_back(v);

        arg = arg->next;
    }

    // Add a final parameter: RpcContext. Contains data about
    // incoming request (e.g., certificate)
    realCall->arguments.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));

    Type* returnType = NAMES.Search(method->type.type.data);
    if (returnType == EVENT_FAKE_TYPE) {
        returnType = VOID_TYPE;
    }
    
    // the real call
    bool first = true;
    Variable* _result = NULL;
    if (returnType == VOID_TYPE) {
        block->Add(realCall);
    } else {
        _result = new Variable(returnType, "_result",
                                method->type.dimension);
        block->Add(new VariableDeclaration(_result, realCall));

        // need the result RpcData
        if (first) {
            block->Add(new Assignment(this->resultData,
                        new NewExpression(RPC_DATA_TYPE)));
            first = false;
        }

        // marshall the return value
        generate_write_to_data(returnType, block,
                new StringLiteralExpression("_result"), _result, this->resultData);
    }

    // out parameters
    int i = 0;
    arg = method->args;
    while (arg != NULL) {
        Type* t = NAMES.Search(arg->type.type.data);
        Variable* v = stubArgs.Get(i++);

        if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
            // need the result RpcData
            if (first) {
                block->Add(new Assignment(this->resultData, new NewExpression(RPC_DATA_TYPE)));
                first = false;
            }

            generate_write_to_data(t, block, new StringLiteralExpression(arg->name.data),
                    v, this->resultData);
        }

        arg = arg->next;
    }
}

void
DispatcherClass::DoneWithMethods()
{
    if (this->dispatchIfStatement == NULL) {
        return;
    }

    this->elements.push_back(this->processMethod);

    IfStatement* fallthrough = new IfStatement();
        fallthrough->statements = new StatementBlock;
        fallthrough->statements->Add(new ReturnStatement(
                    new MethodCall(SUPER_VALUE, "process", 4, 
                    this->actionParam, this->requestParam, 
                    this->rpcContextParam,
                    this->errorParam)));
    this->dispatchIfStatement->elseif = fallthrough;
    IfStatement* s = new IfStatement;
        s->statements = new StatementBlock;
    this->processMethod->statements->Add(s);
    s->expression = new Comparison(this->resultData, "!=", NULL_VALUE);
    s->statements->Add(new ReturnStatement(new MethodCall(this->resultData, "serialize")));
    s->elseif = new IfStatement;
    s = s->elseif;
    s->statements->Add(new ReturnStatement(NULL_VALUE));
}

// =================================================
class RpcProxyClass : public Class
{
public:
    RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType);
    virtual ~RpcProxyClass();

    Variable* endpoint;
    Variable* broker;

private:
    void generate_ctor();
    void generate_get_endpoint_info();
};

RpcProxyClass::RpcProxyClass(const interface_type* iface, InterfaceType* interfaceType)
    :Class()
{
    this->comment = gather_comments(iface->comments_token->extra);
    this->modifiers = PUBLIC;
    this->what = Class::CLASS;
    this->type = interfaceType;

    // broker
    this->broker = new Variable(RPC_BROKER_TYPE, "_broker");
    this->elements.push_back(new Field(PRIVATE, this->broker));
    // endpoint
    this->endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "_endpoint");
    this->elements.push_back(new Field(PRIVATE, this->endpoint));

    // methods
    generate_ctor();
    generate_get_endpoint_info();
}

RpcProxyClass::~RpcProxyClass()
{
}

void
RpcProxyClass::generate_ctor()
{
    Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
    Variable* endpoint = new Variable(RPC_ENDPOINT_INFO_TYPE, "endpoint");
    Method* ctor = new Method;
        ctor->modifiers = PUBLIC;
        ctor->name = class_name_leaf(this->type->Name());
        ctor->statements = new StatementBlock;
        ctor->parameters.push_back(broker);
        ctor->parameters.push_back(endpoint);
    this->elements.push_back(ctor);

    ctor->statements->Add(new Assignment(this->broker, broker));
    ctor->statements->Add(new Assignment(this->endpoint, endpoint));
}

void
RpcProxyClass::generate_get_endpoint_info()
{
    Method* get = new Method;
    get->modifiers = PUBLIC;
    get->returnType = RPC_ENDPOINT_INFO_TYPE;
    get->name = "getEndpointInfo";
    get->statements = new StatementBlock;
    this->elements.push_back(get);

    get->statements->Add(new ReturnStatement(this->endpoint));
}

// =================================================
class EventListenerClass : public DispatcherClass
{
public:
    EventListenerClass(const interface_type* iface, Type* listenerType);
    virtual ~EventListenerClass();

    Variable* _listener;

private:
    void generate_ctor();
};

Expression*
generate_get_listener_expression(Type* cast)
{
    return new Cast(cast, new MethodCall(THIS_VALUE, "getView"));
}

EventListenerClass::EventListenerClass(const interface_type* iface, Type* listenerType)
    :DispatcherClass(iface, new FieldVariable(THIS_VALUE, "_listener"))
{
    this->modifiers = PRIVATE;
    this->what = Class::CLASS;
    this->type = new Type(iface->package ? iface->package : "",
                        append(iface->name.data, ".Presenter"),
                        Type::GENERATED, false, false, false);
    this->extends = PRESENTER_BASE_TYPE;

    this->_listener = new Variable(listenerType, "_listener");
    this->elements.push_back(new Field(PRIVATE, this->_listener));

    // methods
    generate_ctor();
}

EventListenerClass::~EventListenerClass()
{
}

void
EventListenerClass::generate_ctor()
{
    Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
    Variable* listener = new Variable(this->_listener->type, "listener");
    Method* ctor = new Method;
        ctor->modifiers = PUBLIC;
        ctor->name = class_name_leaf(this->type->Name());
        ctor->statements = new StatementBlock;
        ctor->parameters.push_back(broker);
        ctor->parameters.push_back(listener);
    this->elements.push_back(ctor);

    ctor->statements->Add(new MethodCall("super", 2, broker, listener));
    ctor->statements->Add(new Assignment(this->_listener, listener));
}

// =================================================
class ListenerClass : public Class
{
public:
    ListenerClass(const interface_type* iface);
    virtual ~ListenerClass();

    bool needed;

private:
    void generate_ctor();
};

ListenerClass::ListenerClass(const interface_type* iface)
    :Class(),
     needed(false)
{
    this->comment = "/** Extend this to listen to the events from this class. */";
    this->modifiers = STATIC | PUBLIC ;
    this->what = Class::CLASS;
    this->type = new Type(iface->package ? iface->package : "",
                        append(iface->name.data, ".Listener"),
                        Type::GENERATED, false, false, false);
    this->extends = PRESENTER_LISTENER_BASE_TYPE;
}

ListenerClass::~ListenerClass()
{
}

// =================================================
class EndpointBaseClass : public DispatcherClass
{
public:
    EndpointBaseClass(const interface_type* iface);
    virtual ~EndpointBaseClass();

    bool needed;

private:
    void generate_ctor();
};

EndpointBaseClass::EndpointBaseClass(const interface_type* iface)
    :DispatcherClass(iface, THIS_VALUE),
     needed(false)
{
    this->comment = "/** Extend this to implement a link service. */";
    this->modifiers = STATIC | PUBLIC | ABSTRACT;
    this->what = Class::CLASS;
    this->type = new Type(iface->package ? iface->package : "",
                        append(iface->name.data, ".EndpointBase"),
                        Type::GENERATED, false, false, false);
    this->extends = RPC_CONNECTOR_TYPE;

    // methods
    generate_ctor();
}

EndpointBaseClass::~EndpointBaseClass()
{
}

void
EndpointBaseClass::generate_ctor()
{
    Variable* container = new Variable(RPC_CONTAINER_TYPE, "container");
    Variable* broker = new Variable(RPC_BROKER_TYPE, "broker");
	Variable* place = new Variable(PLACE_INFO_TYPE, "placeInfo");
    Method* ctor = new Method;
        ctor->modifiers = PUBLIC;
        ctor->name = class_name_leaf(this->type->Name());
        ctor->statements = new StatementBlock;
        ctor->parameters.push_back(container);
        ctor->parameters.push_back(broker);
        ctor->parameters.push_back(place);
    this->elements.push_back(ctor);

    ctor->statements->Add(new MethodCall("super", 3, container, broker, place));
}

// =================================================
class ResultDispatcherClass : public Class
{
public:
    ResultDispatcherClass();
    virtual ~ResultDispatcherClass();

    void AddMethod(int index, const string& name, Method** method, Variable** param);

    bool needed;
    Variable* methodId;
    Variable* callback;
    Method* onResultMethod;
    Variable* resultParam;
    SwitchStatement* methodSwitch;

private:
    void generate_ctor();
    void generate_onResult();
};

ResultDispatcherClass::ResultDispatcherClass()
    :Class(),
     needed(false)
{
    this->modifiers = PRIVATE | FINAL;
    this->what = Class::CLASS;
    this->type = new Type("_ResultDispatcher", Type::GENERATED, false, false, false);
    this->interfaces.push_back(RPC_RESULT_HANDLER_TYPE);

    // methodId
    this->methodId = new Variable(INT_TYPE, "methodId");
    this->elements.push_back(new Field(PRIVATE, this->methodId));
    this->callback = new Variable(OBJECT_TYPE, "callback");
    this->elements.push_back(new Field(PRIVATE, this->callback));

    // methods
    generate_ctor();
    generate_onResult();
}

ResultDispatcherClass::~ResultDispatcherClass()
{
}

void
ResultDispatcherClass::generate_ctor()
{
    Variable* methodIdParam = new Variable(INT_TYPE, "methId");
    Variable* callbackParam = new Variable(OBJECT_TYPE, "cbObj");
    Method* ctor = new Method;
        ctor->modifiers = PUBLIC;
        ctor->name = class_name_leaf(this->type->Name());
        ctor->statements = new StatementBlock;
        ctor->parameters.push_back(methodIdParam);
        ctor->parameters.push_back(callbackParam);
    this->elements.push_back(ctor);

    ctor->statements->Add(new Assignment(this->methodId, methodIdParam));
    ctor->statements->Add(new Assignment(this->callback, callbackParam));
}

void
ResultDispatcherClass::generate_onResult()
{
    this->onResultMethod = new Method;
        this->onResultMethod->modifiers = PUBLIC;
        this->onResultMethod->returnType = VOID_TYPE;
        this->onResultMethod->returnTypeDimension = 0;
        this->onResultMethod->name = "onResult";
        this->onResultMethod->statements = new StatementBlock;
    this->elements.push_back(this->onResultMethod);

    this->resultParam = new Variable(BYTE_TYPE, "result", 1);
    this->onResultMethod->parameters.push_back(this->resultParam);

    this->methodSwitch = new SwitchStatement(this->methodId);
    this->onResultMethod->statements->Add(this->methodSwitch);
}

void
ResultDispatcherClass::AddMethod(int index, const string& name, Method** method, Variable** param)
{
    Method* m = new Method;
        m->modifiers = PUBLIC;
        m->returnType = VOID_TYPE;
        m->returnTypeDimension = 0;
        m->name = name;
        m->statements = new StatementBlock;
    *param = new Variable(BYTE_TYPE, "result", 1);
    m->parameters.push_back(*param);
    this->elements.push_back(m);
    *method = m;

    Case* c = new Case(format_int(index));
    c->statements->Add(new MethodCall(new LiteralExpression("this"), name, 1, this->resultParam));
    c->statements->Add(new Break());

    this->methodSwitch->cases.push_back(c);
}

// =================================================
static void
generate_new_array(Type* t, StatementBlock* addTo, Variable* v, Variable* from)
{
    fprintf(stderr, "aidl: implement generate_new_array %s:%d\n", __FILE__, __LINE__);
    exit(1);
}

static void
generate_create_from_data(Type* t, StatementBlock* addTo, const string& key, Variable* v,
                            Variable* data, Variable** cl)
{
    Expression* k = new StringLiteralExpression(key);
    if (v->dimension == 0) {
        t->CreateFromRpcData(addTo, k, v, data, cl);
    }
    if (v->dimension == 1) {
        //t->ReadArrayFromRpcData(addTo, v, data, cl);
        fprintf(stderr, "aidl: implement generate_create_from_data for arrays%s:%d\n",
                __FILE__, __LINE__);
    }
}

static void
generate_write_to_data(Type* t, StatementBlock* addTo, Expression* k, Variable* v, Variable* data)
{
    if (v->dimension == 0) {
        t->WriteToRpcData(addTo, k, v, data, 0);
    }
    if (v->dimension == 1) {
        //t->WriteArrayToParcel(addTo, v, data);
        fprintf(stderr, "aidl: implement generate_write_to_data for arrays%s:%d\n",
                __FILE__, __LINE__);
    }
}

// =================================================
static Type*
generate_results_method(const method_type* method, RpcProxyClass* proxyClass)
{
    arg_type* arg;

    string resultsMethodName = results_method_name(method->name.data);
    Type* resultsInterfaceType = new Type(results_class_name(method->name.data),
            Type::GENERATED, false, false, false);

    if (!method->oneway) {
        Class* resultsClass = new Class;
            resultsClass->modifiers = STATIC | PUBLIC;
            resultsClass->what = Class::INTERFACE;
            resultsClass->type = resultsInterfaceType;

        Method* resultMethod = new Method;
            resultMethod->comment = gather_comments(method->comments_token->extra);
            resultMethod->modifiers = PUBLIC;
            resultMethod->returnType = VOID_TYPE;
            resultMethod->returnTypeDimension = 0;
            resultMethod->name = resultsMethodName;
        if (0 != strcmp("void", method->type.type.data)) {
            resultMethod->parameters.push_back(new Variable(NAMES.Search(method->type.type.data),
                        "_result", method->type.dimension));
        }
        arg = method->args;
        while (arg != NULL) {
            if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
                resultMethod->parameters.push_back(new Variable(
                                    NAMES.Search(arg->type.type.data), arg->name.data,
                                    arg->type.dimension));
            }
            arg = arg->next;
        }
        resultsClass->elements.push_back(resultMethod);

        if (resultMethod->parameters.size() > 0) {
            proxyClass->elements.push_back(resultsClass);
            return resultsInterfaceType;
        } 
    }
    //delete resultsInterfaceType;
    return NULL;
}

static void
generate_proxy_method(const method_type* method, RpcProxyClass* proxyClass,
        ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
{
    arg_type* arg;
    Method* proxyMethod = new Method;
        proxyMethod->comment = gather_comments(method->comments_token->extra);
        proxyMethod->modifiers = PUBLIC;
        proxyMethod->returnType = VOID_TYPE;
        proxyMethod->returnTypeDimension = 0;
        proxyMethod->name = method->name.data;
        proxyMethod->statements = new StatementBlock;
    proxyClass->elements.push_back(proxyMethod);

    // The local variables
    Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
    proxyMethod->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));

    // Add the arguments
    arg = method->args;
    while (arg != NULL) {
        if (convert_direction(arg->direction.data) & IN_PARAMETER) {
            // Function signature
            Type* t = NAMES.Search(arg->type.type.data);
            Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
            proxyMethod->parameters.push_back(v);

            // Input parameter marshalling
            generate_write_to_data(t, proxyMethod->statements,
                    new StringLiteralExpression(arg->name.data), v, _data);
        }
        arg = arg->next;
    }

    // If there is a results interface for this class
    Expression* resultParameter;
    if (resultsInterfaceType != NULL) {
        // Result interface parameter
        Variable* resultListener = new Variable(resultsInterfaceType, "_result");
        proxyMethod->parameters.push_back(resultListener);

        // Add the results dispatcher callback
        resultsDispatcherClass->needed = true;
        resultParameter = new NewExpression(resultsDispatcherClass->type, 2,
                new LiteralExpression(format_int(index)), resultListener);
    } else {
        resultParameter = NULL_VALUE;
    }

    // All proxy methods take an error parameter
    Variable* errorListener = new Variable(RPC_ERROR_LISTENER_TYPE, "_errors");
    proxyMethod->parameters.push_back(errorListener);

    // Call the broker
    proxyMethod->statements->Add(new MethodCall(new FieldVariable(THIS_VALUE, "_broker"),
                "sendRpc", 5,
                proxyClass->endpoint,
                new StringLiteralExpression(method->name.data),
                new MethodCall(_data, "serialize"),
                resultParameter,
                errorListener));
}

static void
generate_result_dispatcher_method(const method_type* method,
        ResultDispatcherClass* resultsDispatcherClass, Type* resultsInterfaceType, int index)
{
    arg_type* arg;
    Method* dispatchMethod;
    Variable* dispatchParam;
    resultsDispatcherClass->AddMethod(index, method->name.data, &dispatchMethod, &dispatchParam);

    Variable* classLoader = NULL;
    Variable* resultData = new Variable(RPC_DATA_TYPE, "resultData");
    dispatchMethod->statements->Add(new VariableDeclaration(resultData,
                new NewExpression(RPC_DATA_TYPE, 1, dispatchParam)));

    // The callback method itself
    MethodCall* realCall = new MethodCall(
            new Cast(resultsInterfaceType, new FieldVariable(THIS_VALUE, "callback")),
            results_method_name(method->name.data));

    // The return value
    {
        Type* t = NAMES.Search(method->type.type.data);
        if (t != VOID_TYPE) {
            Variable* rv = new Variable(t, "rv");
            dispatchMethod->statements->Add(new VariableDeclaration(rv));
            generate_create_from_data(t, dispatchMethod->statements, "_result", rv,
                    resultData, &classLoader);
            realCall->arguments.push_back(rv);
        }
    }

    VariableFactory stubArgs("arg");
    arg = method->args;
    while (arg != NULL) {
        if (convert_direction(arg->direction.data) & OUT_PARAMETER) {
            // Unmarshall the results
            Type* t = NAMES.Search(arg->type.type.data);
            Variable* v = stubArgs.Get(t);
            dispatchMethod->statements->Add(new VariableDeclaration(v));

            generate_create_from_data(t, dispatchMethod->statements, arg->name.data, v,
                    resultData, &classLoader);

            // Add the argument to the callback
            realCall->arguments.push_back(v);
        }
        arg = arg->next;
    }

    // Call the callback method
    IfStatement* ifst = new IfStatement;
        ifst->expression = new Comparison(new FieldVariable(THIS_VALUE, "callback"), "!=", NULL_VALUE);
    dispatchMethod->statements->Add(ifst);
    ifst->statements->Add(realCall);
}

static void
generate_regular_method(const method_type* method, RpcProxyClass* proxyClass,
        EndpointBaseClass* serviceBaseClass, ResultDispatcherClass* resultsDispatcherClass,
        int index)
{
    arg_type* arg;

    // == the callback interface for results ================================
    // the service base class
    Type* resultsInterfaceType = generate_results_method(method, proxyClass);
    
    // == the method in the proxy class =====================================
    generate_proxy_method(method, proxyClass, resultsDispatcherClass, resultsInterfaceType, index);

    // == the method in the result dispatcher class =========================
    if (resultsInterfaceType != NULL) {
        generate_result_dispatcher_method(method, resultsDispatcherClass, resultsInterfaceType,
                index);
    }

    // == The abstract method that the service developers implement ==========
    Method* decl = new Method;
        decl->comment = gather_comments(method->comments_token->extra);
        decl->modifiers = PUBLIC | ABSTRACT;
        decl->returnType = NAMES.Search(method->type.type.data);
        decl->returnTypeDimension = method->type.dimension;
        decl->name = method->name.data;
    arg = method->args;
    while (arg != NULL) {
        decl->parameters.push_back(new Variable(
                            NAMES.Search(arg->type.type.data), arg->name.data,
                            arg->type.dimension));
        arg = arg->next;
    }

    // Add the default RpcContext param to all methods
    decl->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
	
    serviceBaseClass->elements.push_back(decl);
    

    // == the dispatch method in the service base class ======================
    serviceBaseClass->AddMethod(method);
}

static void
generate_event_method(const method_type* method, RpcProxyClass* proxyClass,
        EndpointBaseClass* serviceBaseClass, ListenerClass* listenerClass,
        EventListenerClass* presenterClass, int index)
{
    arg_type* arg;
    listenerClass->needed = true;

    // == the push method in the service base class =========================
    Method* push = new Method;
        push->modifiers = PUBLIC;
        push->name = push_method_name(method->name.data);
        push->statements = new StatementBlock;
        push->returnType = VOID_TYPE;
    serviceBaseClass->elements.push_back(push);

    // The local variables
    Variable* _data = new Variable(RPC_DATA_TYPE, "_data");
    push->statements->Add(new VariableDeclaration(_data, new NewExpression(RPC_DATA_TYPE)));

    // Add the arguments
    arg = method->args;
    while (arg != NULL) {
        // Function signature
        Type* t = NAMES.Search(arg->type.type.data);
        Variable* v = new Variable(t, arg->name.data, arg->type.dimension);
        push->parameters.push_back(v);

        // Input parameter marshalling
        generate_write_to_data(t, push->statements,
                new StringLiteralExpression(arg->name.data), v, _data);

        arg = arg->next;
    }

    // Send the notifications
    push->statements->Add(new MethodCall("pushEvent", 2,
                new StringLiteralExpression(method->name.data),
                new MethodCall(_data, "serialize")));

    // == the event callback dispatcher method  ====================================
    presenterClass->AddMethod(method);

    // == the event method in the listener base class =====================
    Method* event = new Method;
        event->modifiers = PUBLIC;
        event->name = method->name.data;
        event->statements = new StatementBlock;
        event->returnType = VOID_TYPE;
    listenerClass->elements.push_back(event);
    arg = method->args;
    while (arg != NULL) {
        event->parameters.push_back(new Variable(
                            NAMES.Search(arg->type.type.data), arg->name.data,
                            arg->type.dimension));
        arg = arg->next;
    }

    // Add a final parameter: RpcContext. Contains data about
    // incoming request (e.g., certificate)
    event->parameters.push_back(new Variable(RPC_CONTEXT_TYPE, "context", 0));
}

static void
generate_listener_methods(RpcProxyClass* proxyClass, Type* presenterType, Type* listenerType)
{
    // AndroidAtHomePresenter _presenter;
    // void startListening(Listener listener) {
    //     stopListening();
    //     _presenter = new Presenter(_broker, listener);
    //     _presenter.startListening(_endpoint);
    // }
    // void stopListening() {
    //     if (_presenter != null) {
    //         _presenter.stopListening();
    //     }
    // }

    Variable* _presenter = new Variable(presenterType, "_presenter");
    proxyClass->elements.push_back(new Field(PRIVATE, _presenter));

    Variable* listener = new Variable(listenerType, "listener");

    Method* startListeningMethod = new Method;
        startListeningMethod->modifiers = PUBLIC;
        startListeningMethod->returnType = VOID_TYPE;
        startListeningMethod->name = "startListening";
        startListeningMethod->statements = new StatementBlock;
        startListeningMethod->parameters.push_back(listener);
    proxyClass->elements.push_back(startListeningMethod);

    startListeningMethod->statements->Add(new MethodCall(THIS_VALUE, "stopListening"));
    startListeningMethod->statements->Add(new Assignment(_presenter,
                new NewExpression(presenterType, 2, proxyClass->broker, listener)));
    startListeningMethod->statements->Add(new MethodCall(_presenter,
                "startListening", 1, proxyClass->endpoint));

    Method* stopListeningMethod = new Method;
        stopListeningMethod->modifiers = PUBLIC;
        stopListeningMethod->returnType = VOID_TYPE;
        stopListeningMethod->name = "stopListening";
        stopListeningMethod->statements = new StatementBlock;
    proxyClass->elements.push_back(stopListeningMethod);

    IfStatement* ifst = new IfStatement;
        ifst->expression = new Comparison(_presenter, "!=", NULL_VALUE);
    stopListeningMethod->statements->Add(ifst);

    ifst->statements->Add(new MethodCall(_presenter, "stopListening"));
    ifst->statements->Add(new Assignment(_presenter, NULL_VALUE));
}

Class*
generate_rpc_interface_class(const interface_type* iface)
{
    // the proxy class
    InterfaceType* interfaceType = static_cast<InterfaceType*>(
        NAMES.Find(iface->package, iface->name.data));
    RpcProxyClass* proxy = new RpcProxyClass(iface, interfaceType);

    // the listener class
    ListenerClass* listener = new ListenerClass(iface);

    // the presenter class
    EventListenerClass* presenter = new EventListenerClass(iface, listener->type);

    // the service base class
    EndpointBaseClass* base = new EndpointBaseClass(iface);
    proxy->elements.push_back(base);

    // the result dispatcher
    ResultDispatcherClass* results = new ResultDispatcherClass();

    // all the declared methods of the proxy
    int index = 0;
    interface_item_type* item = iface->interface_items;
    while (item != NULL) {
        if (item->item_type == METHOD_TYPE) {
            if (NAMES.Search(((method_type*)item)->type.type.data) == EVENT_FAKE_TYPE) {
                generate_event_method((method_type*)item, proxy, base, listener, presenter, index);
            } else {
                generate_regular_method((method_type*)item, proxy, base, results, index);
            }
        }
        item = item->next;
        index++;
    }
    presenter->DoneWithMethods();
    base->DoneWithMethods();

    // only add this if there are methods with results / out parameters
    if (results->needed) {
        proxy->elements.push_back(results);
    }
    if (listener->needed) {
        proxy->elements.push_back(listener);
        proxy->elements.push_back(presenter);
        generate_listener_methods(proxy, presenter->type, listener->type);
    }

    return proxy;
}
