#include <wasm-rt.h>
#include <client.h>
#include <flyff.h>
#include <fugg.h>

#include <stdio.h>
#include <assert.h>

#include <emscripten.h>
#include <emscripten/html5.h>

#include <iostream>

extern void before_main_loop();
extern void after_main_loop();

/* import: 'a' 'qa' */
// "qa": "_emscripten_set_main_loop",
WASM_IMPORT_IMPL(env, _emscripten_set_main_loop) = [](u32 a, u32 fps, u32 simulate_infinite_loop) {
    static u32 original_main_loop = 0;

    auto main_loop_override = []() {
        // std::cout << "This is fugg main loop being called! Calling original loop... at " << reinterpret_cast<void*>(original_main_loop) << std::endl;
        before_main_loop();

        const auto& func = fugg::function_ref<em_callback_func>(original_main_loop);
        func();

        after_main_loop();
    };

    original_main_loop = a;

#if TRACE_CALLBACK_CALLS
    std::cout << "_emscripten_set_main_loop(" 
              << func << " (" << reinterpret_cast<void*>(a) << "), "
              << fps << ", "
              << simulate_infinite_loop << ")" << std::endl;
#endif


    emscripten_set_main_loop(main_loop_override, fps, simulate_infinite_loop);
};
/* import: 'a' 'pa' */
// "pa": "_emscripten_cancel_main_loop",
WASM_IMPORT_IMPL(env, _emscripten_cancel_main_loop) = []() {
    emscripten_cancel_main_loop();
};

/* import: 'a' 'ab' */
// "ab": "_emscripten_async_wget_data",
WASM_IMPORT_IMPL(env, _emscripten_async_wget_data) = [](u32 url_, u32 arg_, u32 onload_, u32 onerror_) {
    auto& neuz = flyff::Neuz::instance();
    // This function is only called by the flyff runtime, so we introduce 
    // stubs to refer the onload and onerror on to actual flyff functions.
    // See `onload_stub` and `onerror_stub`.

    struct wget_data {
        u32 onload;
        u32 onerror;
        u32 original_arg;
    };

    auto onload_stub = [](void* arg_, void *data_, int size) {
        const auto& interop_data = reinterpret_cast<wget_data*>(arg_);

        const auto& onload = fugg::function_ref<em_async_wget_onload_func>(interop_data->onload);
        // This pointer can stay unchanged, it came from inside and is going inside.
        const auto& arg = reinterpret_cast<void*>(interop_data->original_arg);
        const auto& data = fugg::RuntimePointer<void> { data_ };

    #if TRACE_EM_CALLS
        std::cout << "onload_stub: Calling function at " << reinterpret_cast<void*>(onload) << "(" 
                << arg << ", "
                << data << ", "   
                << size << ")" << std::endl;
    #endif

        onload(arg, data, size);

        delete interop_data;
    };

    auto onerror_stub = [](void* arg_) {
        const auto& interop_data = reinterpret_cast<wget_data*>(arg_);

        const auto& onerror = fugg::function_ref<em_arg_callback_func>(interop_data->onerror);
        const auto& arg = reinterpret_cast<void*>(interop_data->original_arg);

    #if TRACE_EM_CALLS
        std::cout << "onerror_stub: Calling function at " << reinterpret_cast<void*>(onerror) << "(" 
                << arg << ")" << std::endl;
    #endif

        onerror(arg);

        delete interop_data;
    };

    const auto& url = fugg::module_ref<const char>(url_);

    const auto data = new wget_data {
        .onload = onload_,
        .onerror = onerror_,
        .original_arg = arg_
    };

#if TRACE_EM_CALLS
    std::cout << "_emscripten_async_wget_data("
              << url << ", "
              << arg_ << ", "
              << reinterpret_cast<void*>(onload_) << ", "
              << reinterpret_cast<void*>(onerror_) << ")" << std::endl;
#endif

    // std::cout << "Client: Downloading " << static_cast<const char*>(url) << std::endl;

    // This function can allocate, which will call the Flyff runtime allocate.
    emscripten_async_wget_data(&url, data, onload_stub, onerror_stub);
};
/* import: 'a' 'fb' */
// "fb": "_emscripten_set_element_css_size",
WASM_IMPORT_IMPL(env, _emscripten_set_element_css_size) = [](u32 target_, f64 width, f64 height) {
    const auto& target = fugg::ModulePointer<const char>(target_);

#if TRACE_EM_CALLS
    std::cout << "_emscripten_set_element_css_size("
              << target << ", "
              << width << ", "
              << height << ")" << std::endl;
#endif

    return static_cast<u32>(
        emscripten_set_element_css_size(target, width, height)
    );
};
/* import: 'a' 'gb' */
// "gb": "_emscripten_set_canvas_element_size",
WASM_IMPORT_IMPL(env, _emscripten_set_canvas_element_size) = [](u32 target_, u32 width, u32 height) {
    const auto& target = fugg::ModulePointer<const char>(target_);

#if TRACE_EM_CALLS
    std::cout << "_emscripten_set_canvas_element_size("
              << target << ", "
              << width << ", "
              << height << ")" << std::endl;
#endif

    return static_cast<u32>(
        emscripten_set_canvas_element_size(target, width, height)
    );
};
/* import: 'a' 'ob' */
// "ob": "_emscripten_get_orientation_status",
WASM_IMPORT_IMPL(env, _emscripten_get_orientation_status) = [](u32 a) {
    return static_cast<u32>(
        emscripten_get_orientation_status(
            fugg::ModulePointer<EmscriptenOrientationChangeEvent>(a)
        )
    );
};
/* import: 'a' 'Ab' */
/* _fd_seek */
WASM_IMPORT_IMPL(env, _fd_seek) = [](u32 a, u32 b, u32 c, u32 d, u32 e) {
    // For some reason, it only returns 70.
    return static_cast<u32>(70);
};

WASM_IMPORT_IMPL(env, _abort) = [](void) {
    puts("abort() called!");
    assert(0);
};

// "la": "_emscripten_exit_fullscreen",
WASM_IMPORT_IMPL(env, _emscripten_exit_fullscreen) = []() {
    return static_cast<u32>(
        emscripten_exit_fullscreen()
    );
};
// "ma": "_emscripten_request_fullscreen",
WASM_IMPORT_IMPL(env, _emscripten_request_fullscreen) = [](u32 a, u32 b) {
    return static_cast<u32>(
        emscripten_request_fullscreen(
            reinterpret_cast<const char*>(a),
            b
        )
    );
};
/* import: 'a' 'oa' */
// "oa": "_emscripten_performance_now",
WASM_IMPORT_IMPL(env, _emscripten_performance_now) = []() {
    return emscripten_performance_now();
};
/* import: 'a' 'Za' */
/* __emscripten_date_now */
WASM_IMPORT_IMPL(env, __emscripten_date_now) = []() {
    return emscripten_get_now();
};
// "da": "_emscripten_vibrate",
WASM_IMPORT_IMPL(env, _emscripten_vibrate) = [](u32 a) {
    return static_cast<u32>(
        emscripten_vibrate(a)
    );  
};
/* import: 'a' 'Aa' */
/* _strftime */
WASM_IMPORT_IMPL(env, _strftime) = [](u32 a, u32 b, u32 c, u32 d) {
    return static_cast<u32>(
        strftime(
            fugg::ModulePointer<char>(a),
            b,
            fugg::ModulePointer<char>(c),
            fugg::ModulePointer<const tm>(d)
        )
    );
};
/* import: 'a' 'Oa' */
// "Oa": "_emscripten_webgl_destroy_context",
WASM_IMPORT_IMPL(env, _emscripten_webgl_destroy_context) = [](u32 a) {
    return static_cast<u32>(
        emscripten_webgl_destroy_context(a)
    );
};

/* import: 'a' 'Cb' */
/* _strftime_l */
WASM_IMPORT_IMPL(env, _strftime_l) = [](u32 a, u32 b, u32 c, u32 d, u32 e) {
    return static_cast<u32>(
        strftime_l(
            reinterpret_cast<char*>(a),
            b,
            reinterpret_cast<char*>(c),
            reinterpret_cast<const tm*>(d),
            reinterpret_cast<locale_t>(e)
        )
    );
};
/* import: 'a' 'Db' */
/* _environ_get */
WASM_IMPORT_IMPL(env, _environ_get) = [](u32 a, u32 b) {
    puts("_environ_get");
    // FIXME:
    return static_cast<u32>(0);
};
/* import: 'a' 'Eb' */
/* _environ_sizes_get */
WASM_IMPORT_IMPL(env, _environ_sizes_get) = [](u32, u32) {
    puts("_environ_sizes_get");
    // FIXME:
    return static_cast<u32>(0);
};
/* import: 'a' 'Fb' */
/* _fd_close */
WASM_IMPORT_IMPL(env, _fd_close) = [](u32 a) {
    // For some reason returns 52...
    return static_cast<u32>(52);
};
/* import: 'a' 'Gb' */
/* _fd_write */
WASM_IMPORT_IMPL(env, _fd_write) = [](u32 a, u32 b, u32 c, u32 d) {
    puts("fd_write");
    // FIXME:
    return static_cast<u32>(0);  
};

/* import: 'a' 'Hb' */
// "Hb": "_emscripten_memcpy_big",
WASM_IMPORT_IMPL(env, _emscripten_memcpy_big) = [](u32 dest_, u32 src_, u32 length) {
    // WASM runtime calls this, so no need to change the pointers since it's "internal".
    const auto& dest = fugg::ModulePointer<void>(dest_);
    const auto& src = fugg::ModulePointer<void>(src_);

#if TRACE_ALLOC
    std::cout << "_emscripten_memcpy_big(" 
              << dest << ", " 
              << src << ", "
              << length << ")" << std::endl;
#endif

    wasm_rt_memcpy(dest, src, length);
};
/* import: 'a' 'Ib' */
/* __emscripten_get_now_is_monotonic */
WASM_IMPORT_IMPL(env, __emscripten_get_now_is_monotonic) = []() {
    return static_cast<u32>(true);
};
/* import: 'a' 'Jb' */
/* __gmtime_js */
WASM_IMPORT_IMPL(env, __gmtime_js) = [](u32 a, u32 b) {
    // FIXME:
    auto& t = fugg::module_ref<const time_t>(a);
    const auto& info = gmtime(&t);

    auto& result = fugg::module_ref<tm>(b);
    result.tm_sec = info->tm_sec;
    result.tm_min = info->tm_min;
    result.tm_hour = info->tm_hour;
    result.tm_mday = info->tm_mday;
    result.tm_mon = info->tm_mon;
    result.tm_year = info->tm_year;
    result.tm_wday = info->tm_wday;
    result.tm_yday = info->tm_yday;
};
/* import: 'a' 'Kb' */
/* __localtime_js */
WASM_IMPORT_IMPL(env, __localtime_js) = [](u32 a, u32 b) {
    // FIXME:
    auto& t = fugg::module_ref<const time_t>(a);
    const auto& info = gmtime(&t);

    auto& result = fugg::module_ref<tm>(b);
    result.tm_sec = info->tm_sec;
    result.tm_min = info->tm_min;
    result.tm_hour = info->tm_hour;
    result.tm_mday = info->tm_mday;
    result.tm_mon = info->tm_mon;
    result.tm_year = info->tm_year;
    result.tm_wday = info->tm_wday;
    result.tm_yday = info->tm_yday;
    result.tm_isdst = info->tm_isdst;
    result.tm_gmtoff = info->tm_gmtoff;
};
/* import: 'a' 'Lb' */
/* __mktime_js */
WASM_IMPORT_IMPL(env, __mktime_js) = [](u32 a) {
    // FIXME:
    // puts("__mktime_js is not implemented yet.");
    auto& t = fugg::module_ref<tm>(a);

    auto result = mktime(&t);

    return static_cast<u32>(result);
};
/* import: 'a' 'Mb' */
/* __tzset_js */
WASM_IMPORT_IMPL(env, __tzset_js) = [](u32 a, u32 b, u32 c) {
    // FIXME:
    // Not sure if this is correct...
    tzset();    
};

/* import: 'a' 'Rb' */
// "Rb": "_emscripten_log",
WASM_IMPORT_IMPL(env, _emscripten_log) = [](u32 flags, u32 b, u32 c) {
    const auto& format = fugg::ModulePointer<const char>(b);

    emscripten_log(flags, format, c);
};
/* import: 'a' 'bc' */
// "bc": "_emscripten_get_fullscreen_status",
WASM_IMPORT_IMPL(env, _emscripten_get_fullscreen_status) = [](u32 a) {
    const auto& fullscreen_status = fugg::ModulePointer<EmscriptenFullscreenChangeEvent>(a);

#if TRACE_EM_CALLS
    std::cout << "_emscripten_get_fullscreen_status("
              << fullscreen_status << ")" << std::endl;
#endif

    return static_cast<u32>(
        emscripten_get_fullscreen_status(fullscreen_status)
    );
};
/* import: 'a' 'cc' */
// "cc": "_emscripten_force_exit",
WASM_IMPORT_IMPL(env, _emscripten_force_exit) = [](u32 a) {
    emscripten_force_exit(a);
};