// Copyright (c) 2014 Dropbox, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <dlfcn.h>
#include <stdarg.h>
#include <string.h>

#include "Python.h"

#include "codegen/compvars.h"
#include "core/threading.h"
#include "core/types.h"
#include "runtime/types.h"

namespace pyston {

static BoxedModule* test_module = NULL;

BoxedClass* capifunc_cls;
class BoxedCApiFunction : public Box {
private:
    const char* name;
    PyCFunction func;

public:
    BoxedCApiFunction(const char* name, PyCFunction func) : Box(capifunc_cls), name(name), func(func) {}

    static BoxedString* __repr__(BoxedCApiFunction* self) {
        assert(self->cls == capifunc_cls);
        return boxStrConstant(self->name);
    }

    static Box* __call__(BoxedCApiFunction* self, BoxedTuple* varargs) {
        assert(self->cls == capifunc_cls);
        assert(varargs->cls == tuple_cls);

        threading::GLPromoteRegion _gil_lock;

        Box* rtn = (Box*)self->func(test_module, varargs);
        assert(rtn);
        return rtn;
    }
};

extern "C" void* Py_InitModule4(const char* arg0, PyMethodDef* arg1, const char* arg2, PyObject* arg3, int arg4) {
    test_module = createModule("test", "../test/test_extension/test.so");

    while (arg1->ml_name) {
        if (VERBOSITY())
            printf("Loading method %s\n", arg1->ml_name);
        assert(arg1->ml_flags == METH_VARARGS);

        // test_module->giveAttr(arg1->ml_name, boxInt(1));
        test_module->giveAttr(arg1->ml_name, new BoxedCApiFunction(arg1->ml_name, arg1->ml_meth));

        arg1++;
    }
    return test_module;
}

extern "C" void* Py_BuildValue(const char* arg0) {
    assert(*arg0 == '\0');
    return None;
}

extern "C" bool PyArg_ParseTuple(void* tuple, const char* fmt, ...) {
    if (strcmp("", fmt) == 0)
        return true;

    assert(strcmp("O", fmt) == 0);

    BoxedTuple* varargs = (BoxedTuple*)tuple;
    assert(varargs->elts.size() == 1);

    va_list ap;
    va_start(ap, fmt);

    Box** arg0 = va_arg(ap, Box**);
    *arg0 = varargs->elts[0];

    va_end(ap);

    return true;
}

BoxedModule* getTestModule() {
    if (test_module)
        return test_module;

    void* handle = dlopen("../test/test_extension/test.so", RTLD_NOW);
    if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        exit(1);
    }
    assert(handle);

    void (*init)() = (void (*)())dlsym(handle, "inittest");

    char* error;
    if ((error = dlerror()) != NULL) {
        fprintf(stderr, "%s\n", error);
        exit(1);
    }

    // dlclose(handle);

    assert(init);
    (*init)();
    assert(test_module);
    return test_module;
}

void setupCAPI() {
    capifunc_cls = new BoxedClass(object_cls, NULL, 0, sizeof(BoxedCApiFunction), false);
    capifunc_cls->giveAttr("__name__", boxStrConstant("capifunc"));

    capifunc_cls->giveAttr("__repr__",
                           new BoxedFunction(boxRTFunction((void*)BoxedCApiFunction::__repr__, UNKNOWN, 1)));
    capifunc_cls->giveAttr("__str__", capifunc_cls->getattr("__repr__"));

    capifunc_cls->giveAttr(
        "__call__", new BoxedFunction(boxRTFunction((void*)BoxedCApiFunction::__call__, UNKNOWN, 1, 0, true, false)));

    capifunc_cls->freeze();
}

void teardownCAPI() {
}
}