#include "util.h"
#include <stdlib.h>
#include <strings.h>
#include <CoreFoundation/CoreFoundation.h>
#include <mach-o/dyld.h>
#include <Python.h>

#define EXPORT __attribute__((visibility("default")))

static const char *ERR_OOM = "Out of memory";

static int
report_error(const char *msg) {
    fprintf(stderr, "%s\n", msg);
    fflush(stderr);
    return -1;
}

static int
report_code(const char *preamble, const char* msg, int code) {
    fprintf(stderr, "%s: %s\n", preamble, msg);
    fflush(stderr);
    return code;
}

#define EXE "@executable_path/.."

static void
set_env_vars(const char **ENV_VARS, const char **ENV_VAR_VALS, const char* exe_path) {
    int i = 0;
    char buf[3*PATH_MAX];
    const char *env_var, *val;

    while(1) {
        env_var = ENV_VARS[i];
        if (env_var == NULL) break;
        val = ENV_VAR_VALS[i++];
        if (strstr(val, EXE) == val && strlen(val) >= strlen(EXE)+1) {
            strncpy(buf, exe_path, 3*PATH_MAX-150);
            strncpy(buf+strlen(exe_path), val+strlen(EXE), 150);
            setenv(env_var, buf, 1);
        } else
            setenv(env_var, val, 1);
    }
    return;
}

void initialize_interpreter(const char **ENV_VARS, const char **ENV_VAR_VALS,
        char *PROGRAM, const char *MODULE, const char *FUNCTION, const char *PYVER, int IS_GUI,
        const char* exe_path, const char *rpath, int argc, const char **argv) {
    PyObject *pargv, *v;
    int i;
    Py_OptimizeFlag = 2;
    Py_NoSiteFlag = 1;
    Py_DontWriteBytecodeFlag = 1;
    Py_IgnoreEnvironmentFlag = 1;
    Py_NoUserSiteDirectory = 1;
    Py_HashRandomizationFlag = 1;

    //Py_VerboseFlag = 1;
    //Py_DebugFlag = 1;
    
    Py_SetProgramName(PROGRAM);

    char pyhome[1000];
    snprintf(pyhome, 1000, "%s/Python", rpath);
    Py_SetPythonHome(pyhome);

    set_env_vars(ENV_VARS, ENV_VAR_VALS, exe_path);

    //printf("Path before Py_Initialize(): %s\r\n\n", Py_GetPath());
    Py_Initialize();

    char *dummy_argv[1] = {""};
    PySys_SetArgv(1, dummy_argv);
    //printf("Path after Py_Initialize(): %s\r\n\n", Py_GetPath());
    char path[3000];
    snprintf(path, 3000, "%s/lib/python%s:%s/lib/python%s/lib-dynload:%s/site-packages", pyhome, PYVER, pyhome, PYVER, pyhome);

    PySys_SetPath(path);
    //printf("Path set by me: %s\r\n\n", path);

    PySys_SetObject("calibre_basename", PyBytes_FromString(PROGRAM));
    PySys_SetObject("calibre_module", PyBytes_FromString(MODULE));
    PySys_SetObject("calibre_function", PyBytes_FromString(FUNCTION));
    PySys_SetObject("calibre_is_gui_app", ((IS_GUI) ? Py_True : Py_False));
    PySys_SetObject("resourcepath", PyBytes_FromString(rpath));
    snprintf(path, 3000, "%s/site-packages", pyhome);
    PySys_SetObject("site_packages", PyBytes_FromString(pyhome));


    pargv = PyList_New(argc);
    if (pargv == NULL) exit(report_error(ERR_OOM));
    for (i = 0; i < argc; i++) {
        v = PyBytes_FromString(argv[i]);
        if (v == NULL) exit(report_error(ERR_OOM));
        PyList_SetItem(pargv, i, v);
    }
    PySys_SetObject("argv", pargv);

}


int pyobject_to_int(PyObject *res) {
    int ret; PyObject *tmp;
    tmp = PyNumber_Int(res);
    if (tmp == NULL) ret = (PyObject_IsTrue(res)) ? 1 : 0;
    else ret = (int)PyInt_AS_LONG(tmp);

    return ret;
}

int handle_sysexit(PyObject *e) {
    PyObject *code;

    code = PyObject_GetAttrString(e, "code");
    if (!code) return 0;
    if (!PyInt_Check(code)) {
        PyObject_Print(code, stderr, Py_PRINT_RAW);
        fflush(stderr);
    }
    return pyobject_to_int(code);
}

int calibre_show_python_error(const char *preamble, int code) {
    PyObject *exc, *val, *tb, *str;
    int ret, issysexit = 0; char *i; 

    if (!PyErr_Occurred()) return code;
    issysexit = PyErr_ExceptionMatches(PyExc_SystemExit);

    PyErr_Fetch(&exc, &val, &tb);

    if (exc != NULL) {
        PyErr_NormalizeException(&exc, &val, &tb);

        if (issysexit) {
            return (val) ? handle_sysexit(val) : 0;
        }
        if (val != NULL) {
            str = PyObject_Unicode(val);
            if (str == NULL) {
                PyErr_Clear();
                str = PyObject_Str(val);
            }
            i = PyString_AsString(str);
            ret = report_code(preamble, (i==NULL)?ERR_OOM:i, code);
            if (tb != NULL) {
                PyErr_Restore(exc, val, tb);
                PyErr_Print();
            }
            return ret;
        }
    }
    return report_code(preamble, "", code);
}

EXPORT
int
run(const char **ENV_VARS, const char **ENV_VAR_VALS, char *PROGRAM,
        const char *MODULE, const char *FUNCTION, const char *PYVER,
        int IS_GUI, int argc, const char **argv, const char **envp) {
    char *pathPtr = NULL, *t = NULL;
    char buf[3*PATH_MAX];
    int ret = 0, i;
    PyObject *site, *mainf, *res;
    uint32_t buf_size = PATH_MAX+1;
    char *ebuf = calloc(buf_size, sizeof(char));

    ret = _NSGetExecutablePath(ebuf, &buf_size);
    if (ret == -1) {
        free(ebuf);
        ebuf = calloc(buf_size, sizeof(char));
        if (_NSGetExecutablePath(ebuf, &buf_size) != 0)
            return report_error("Failed to find real path of executable.");
    }
    pathPtr = realpath(ebuf, buf);
    if (pathPtr == NULL) {
        return report_error(strerror(errno));
    }
    for (i = 0; i < 3; i++) {
        t = rindex(pathPtr, '/');
        if (t == NULL) return report_error("Failed to determine bundle path.");
        *t = '\0';
    }
    if (strstr(pathPtr, "/calibre.app/Contents/") != NULL) {
        // We are one of the duplicate executables created to workaround codesign's limitations
        for (i = 0; i < 2; i++) {
            t = rindex(pathPtr, '/');
            if (t == NULL) return report_error("Failed to resolve bundle path in dummy executable");
            *t = '\0';
        }
    }

    char rpath[PATH_MAX+1], exe_path[PATH_MAX+1];
    snprintf(exe_path, PATH_MAX+1, "%s/Contents", pathPtr);
    snprintf(rpath, PATH_MAX+1, "%s/Resources", exe_path);
    initialize_interpreter(ENV_VARS, ENV_VAR_VALS, PROGRAM, MODULE, FUNCTION, PYVER, IS_GUI,
            exe_path, rpath, argc, argv);

    site = PyImport_ImportModule("site");

    if (site == NULL)
        ret = calibre_show_python_error("Failed to import site module",  -1);
    else {
        Py_XINCREF(site);

        mainf = PyObject_GetAttrString(site, "main");
        if (mainf == NULL || !PyCallable_Check(mainf)) 
            ret = calibre_show_python_error("site module has no main function", -1);
        else {
            Py_XINCREF(mainf);
            res = PyObject_CallObject(mainf, NULL);

            if (res == NULL) 
                ret = calibre_show_python_error("Python function terminated unexpectedly", -1);
            else {
            }
        }
    }
    PyErr_Clear();
    Py_Finalize();

    //printf("11111 Returning: %d\r\n", ret);
    return ret;
}



