#include <pch.h>
#include <stdio.h>
#include <emscripten.h>
#include <emscripten/bind.h>
#include <thread>
#include <chrono>
#include <app.h>
#include <platform_api/platform_services.h>
#include <platform_legacy/legacy_platform_state.h>
#include <platform_legacy/legacy_platform_services.h>
#include <fstream>
#include <keymap.h>

using namespace emscripten;

App app;
GLFWwindow* wnd;
float theta = 0;
glm::vec2 g_cursor_pos;
std::unique_ptr<pp::platform::PlatformServices> g_platform_services;
std::unique_ptr<pp::platform::WebPlatformServices> g_web_platform_services;

template<typename F>
class TaskCallback
{
    std::function<F> fn;
public:
    template<typename T> TaskCallback(T callback) : fn(callback) {}
    void call(const std::string& tmp_file_path)
    {
        fn(tmp_file_path);
    }
    void call(bool success)
    {
        fn(success);
    }
    TaskCallback()
    {
        printf("callback created\n");
    }
    ~TaskCallback()
    {
        printf("callback destroyed\n");
    }
};

void TaskCallback_open_call(uintptr_t tc, std::string tmp_file_path)
{
    auto x = reinterpret_cast<TaskCallback<void(std::string)>*>(tc);
    x->call(tmp_file_path);
}
void TaskCallback_open_delete(uintptr_t tc)
{
    auto x = reinterpret_cast<TaskCallback<void(std::string)>*>(tc);
    delete x;
}
void TaskCallback_save_call(uintptr_t tc, bool success)
{
    auto x = reinterpret_cast<TaskCallback<void(bool)>*>(tc);
    x->call(success);
}
void TaskCallback_save_delete(uintptr_t tc)
{
    auto x = reinterpret_cast<TaskCallback<void(bool)>*>(tc);
    delete x;
}

std::string UtilityGetString(uintptr_t s)
{
    return *reinterpret_cast<std::string*>(s);
}

kEventSource StringToType(const std::string& type)
{
    if (type == "pen")
        return kEventSource::Stylus;
    else if (type == "touch")
        return kEventSource::Touch;
    return kEventSource::Mouse;
}

int ConvertButtonType(int js_button)
{
    if (js_button == 0)
        return 0;
    else if (js_button == 2)
        return 1;
    return 0;
}

void CanvasOnPointerDown(std::string type, float x, float y, float f, int button)
{
    app.ui_task_async([=]{
        if (button == 0 || button == 2)
            app.mouse_down(ConvertButtonType(button), x, y, f, StringToType(type), false);
    });
}

void CanvasOnPointerUp(std::string type, float x, float y, int button)
{
    app.ui_task_async([=]{
        if (button == 0 || button == 2)
            app.mouse_up(ConvertButtonType(button), x, y, StringToType(type), false);
    });
}

void CanvasOnPointerMove(std::string type, float x, float y, float f)
{
    g_cursor_pos = { x, y };
    app.ui_task_async([=]{
        app.mouse_move(x, y, f, StringToType(type), false);
    });
}

void CanvasOnWheel(float y)
{
    app.ui_task_async([=]{
        app.mouse_scroll(g_cursor_pos.x, g_cursor_pos.y, y);
    });
}

void StartApp()
{
    App::I = &app;
    pp::platform::set_injected_web_platform_services(g_web_platform_services.get());
    app.set_platform_services(g_platform_services.get());
    app.initLog();
    app.create();
    app.width = 1024;
    app.height = 768;

    // app.render_thread_tick();
    // app.ui_thread_tick();
    app.ui_task_async([]{
        app.init();
    });
}

EMSCRIPTEN_BINDINGS(TaskCallback_bind) {
    class_<TaskCallback<void(std::string)>>("TaskCallbackOpen");
    class_<TaskCallback<void(bool)>>("TaskCallbackSave");
    function("TaskCallback_save_call", &TaskCallback_save_call);
    function("TaskCallback_save_delete", &TaskCallback_save_delete);
    function("TaskCallback_open_call", &TaskCallback_open_call);
    function("TaskCallback_open_delete", &TaskCallback_open_delete);
    function("UtilityGetString", &UtilityGetString);
    function("CanvasOnPointerDown", &CanvasOnPointerDown);
    function("CanvasOnPointerUp", &CanvasOnPointerUp);
    function("CanvasOnPointerMove", &CanvasOnPointerMove);
    function("CanvasOnWheel", &CanvasOnWheel);
    function("StartApp", &StartApp);
}

extern "C" {
extern void js_sync();
extern void js_pick_file(TaskCallback<void(std::string)>* tc);
extern void js_pick_file_save(std::string path, std::string name, TaskCallback<void(bool)>* tc);
}

void webgl_pick_file(std::function<void(std::string)> callback)
{
    js_pick_file(new TaskCallback<void(std::string)>([callback](std::string tmp_file_path){ 
        printf("callback called: %s\n", tmp_file_path.c_str());
        callback(tmp_file_path);
        js_sync();
    }));
}

void webgl_pick_file_save(const std::string& path, 
    const std::string& name, std::function<void(bool)> callback)
{
    js_pick_file_save(path, name, new TaskCallback<void(bool)>([callback](bool success){ 
        printf("save callback called: %s\n", success ? "ok" : "failed");
        callback(success);
        js_sync();
    }));
}

void webgl_sync()
{
    app.ui_task_async([]{
        js_sync();
    });
}

void main_loop()
{
    app.render_thread_tick();
    app.ui_thread_tick();
    
    // theta += 0.1f;
    // float r = sinf(theta);
    // glClearColor(r, 0.9f, 0.9f, 1.0f);
    // glClear(GL_COLOR_BUFFER_BIT);
    // app.tick(0);
    // app.update(0);
    // app.draw(0);
    // SDL_GL_SwapWindow(wnd);
}

int main()
{
    if (glfwInit() != GL_TRUE)
        printf("Failed to init GLFW");
    wnd = glfwCreateWindow(1024, 768, "PanoPainter", nullptr, nullptr);
    g_web_platform_services = pp::platform::legacy::create_legacy_web_platform_services();
    g_platform_services = pp::platform::legacy::create_platform_services({
        .glfw_shell = {
            .acquire_render_context = [wnd] { glfwMakeContextCurrent(wnd); },
            .present_render_context = [wnd] { glfwSwapBuffers(wnd); },
        },
        .web_platform_services = g_web_platform_services.get(),
    });
    glfwMakeContextCurrent(wnd);

/*
    glfwSetCursorPosCallback(wnd, [](GLFWwindow* wnd, double x, double y){
        g_cursor_pos = glm::vec2(x, y);
        app.ui_task_async([=]{
            app.mouse_move(x, y, 1.f, kEventSource::Mouse, false);
        });
    });
    glfwSetMouseButtonCallback(wnd, [](GLFWwindow* wnd, int button, int action, int mods){
        app.ui_task_async([=]{
            if (action == GLFW_PRESS)
                app.mouse_down(button, g_cursor_pos.x, g_cursor_pos.y, 1.f, kEventSource::Mouse, false);
            else if (action == GLFW_RELEASE)
                app.mouse_up(button, g_cursor_pos.x, g_cursor_pos.y, kEventSource::Mouse, false);
        });
    });
*/
    glfwSetKeyCallback(wnd, [](GLFWwindow* wnd, int key, int scancode, int action, int mods){
        app.ui_task_async([=]{
            if (action == GLFW_PRESS)
                app.key_down(convert_key(key));
            else if (action == GLFW_RELEASE)
                app.key_up(convert_key(key));
        });
    });
    glfwSetCharCallback(wnd, [](GLFWwindow* wnd, unsigned int codepoint){
        std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
        std::string u8str = converter.to_bytes(codepoint);
        if (!u8str.empty())
        {
            app.ui_task_async([c=u8str[0]]{
                app.key_char(c);
            });
        }
        LOG("codepoint: %du -> %s", codepoint, u8str.c_str());
    });
    glfwSetWindowSizeCallback(wnd, [](GLFWwindow* wnd, int width, int height){
        app.ui_task_async([=]{
            app.resize(width, height);
        });
    });
    glfwSetWindowCloseCallback(wnd, [](GLFWwindow* wnd){
        app.ui_task([wnd] {
            if (!app.request_close())
                glfwSetWindowShouldClose(wnd, GLFW_FALSE);
        });
    });
    glfwSetWindowRefreshCallback(wnd, [](GLFWwindow* wnd){
        app.ui_task_async([=]{
            app.redraw = true;
        }, true);
    });

    EM_ASM (
        Module.canvas.onpointermove = function(event) {
            f = event.pointerType == "pen" ? event.pressure : 1.0;
            Module.CanvasOnPointerMove(event.pointerType, event.layerX, event.layerY, f);
        };
        Module.canvas.onpointerdown = function(event) {
            f = event.pointerType == "pen" ? event.pressure : 1.0;
            Module.CanvasOnPointerDown(event.pointerType, event.layerX, event.layerY, f, event.button);
        };
        Module.canvas.onpointerup = function(event) {
            Module.CanvasOnPointerUp(event.pointerType, event.layerX, event.layerY, event.button);
        };
        Module.canvas.onwheel = function(event) {
            event.preventDefault();
            Module.CanvasOnWheel(-event.deltaY / 100.0);
        };
        Module.canvas.style.touchAction = 'none';
        Module.js_fs_synching = false;
        FS.mkdir('/PanoPainter');
        FS.mount(IDBFS, {}, '/PanoPainter');
        FS.syncfs(true, function (err) {
            console.log("syncFS result:");
            console.log(err);
            Module.StartApp();
            Module.setCanvasSize(window.innerWidth, window.innerHeight);
        });
    );

    printf("GL version: %s\n", glGetString(GL_VERSION));
    printf("GL vendor: %s\n", glGetString(GL_VENDOR));
    printf("GL renderer: %s\n", glGetString(GL_RENDERER));
    printf("GLSL version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));

    LOG("start threads");
    //app.render_thread_start();
    // app.ui_thread_start();

    emscripten_set_main_loop(main_loop, 0, true);
    printf("hello world 003\n");
    return 0;
}

