#include "file_browser.h"
#include <sstream>
#include <iomanip>
#include <cctype>
#include <stdio.h>
#include <log.hpp>
#include <web_functions.hpp>
#ifdef __ANDROID__
#include <jni.h>
jclass MainActivity;
jobject mainActivity;
jmethodID GetUserDir_Method;
jmethodID OpenFilePicker_Method;
jmethodID GetPickedFile_Method;
jmethodID ClearSelected_Method;
jmethodID IsLoading_Method;
JNIEnv *env;
bool IsLoading() {
    return env->CallStaticBooleanMethod(MainActivity, IsLoading_Method, 0);
}
const char *GetUserDir() {
    jstring str = (jstring)env->CallStaticObjectMethod(MainActivity, GetUserDir_Method, 0);;
    jboolean tmp = true;
    return env->GetStringUTFChars(str, &tmp);
}
void OpenFilePicker(const char *pwd) {
        if (pwd == nullptr) {
            jstring string = env->NewStringUTF(pwd);
            env->CallStaticVoidMethod(MainActivity, OpenFilePicker_Method, string);
        } else {
            env->CallStaticVoidMethod(MainActivity, OpenFilePicker_Method, nullptr);
        }
}
const char *GetPickedFile() {
    jstring str = (jstring)env->CallStaticObjectMethod(MainActivity, GetPickedFile_Method, 0);
    jboolean tmp = true;
    return env->GetStringUTFChars(str, &tmp);
}
void ClearSelected() {
    env->CallStaticVoidMethod(MainActivity, ClearSelected_Method, 0);
}
#endif
FileBrowser::FileBrowser(bool save, ImGuiFileBrowserFlags extra_fallback_flags) {
    #ifdef PORTALS
    main_context = g_main_context_default();
    main_loop = g_main_loop_new(main_context, true);
    portal = xdp_portal_new();
    type = g_variant_type_new("a(sa(us))");
    inner_filter_type = g_variant_type_new("a(us)");
    #endif
    this->save = save;
    this->flags = (save ? ImGuiFileBrowserFlags_CreateNewDir|ImGuiFileBrowserFlags_EnterNewFilename : 0) | extra_fallback_flags;
    fallback = ImGui::FileBrowser(this->flags);

}
void FileBrowser::SetTypeFilters(string name, vector<string> filters) {
    filter_name = name;
    this->filters = filters;
    #ifdef __EMSCRIPTEN__
    std::string filterStr;
    for (auto filter : filters) {
        filterStr += ",";
        filterStr += filter;
    }
    filterStr = filterStr.substr(1);
    set_filter(filterStr.c_str());
    #endif
    #ifdef PORTALS
    if (variant != NULL) {
        g_variant_unref(variant);
    }
    GVariantBuilder builder;
    GVariantBuilder inner_filter_builder;
    g_variant_builder_init(&builder, type);
    g_variant_builder_init(&inner_filter_builder, inner_filter_type);
    GVariant *name_variant = g_variant_new_string((const gchar*)name.c_str());
    for (auto filter : filters) {
        GVariant *id = g_variant_new_uint32(0);
        GVariant *filter_variant = g_variant_new_string((const gchar*)(string("*") + filter).c_str());
        GVariant **filter_children = new GVariant*[2] {id, filter_variant};
        GVariant *filter_tuple = g_variant_new_tuple(filter_children, 2);
        g_variant_builder_add_value(&inner_filter_builder, filter_tuple);
    }
    GVariant *inner_filters = g_variant_builder_end(&inner_filter_builder);
    GVariant **outer_filter_children = new GVariant*[2] {name_variant, inner_filters};
    GVariant *outer_filter_tuple = g_variant_new_tuple(outer_filter_children, 2);
    g_variant_builder_add_value(&builder, outer_filter_tuple);
    variant = g_variant_builder_end(&builder);
    g_variant_ref(variant);
    #endif
    fallback.SetTypeFilters(filters);
}
void FileBrowser::SetPwd(path path) {
    pwd = path;
    #if !(defined(PORTALS) || defined(__ANDROID__))
    fallback.SetPwd(path);
    #endif
}
bool FileBrowser::HasSelected() {
    #ifdef PORTALS
    return selected.has_value();
    #elif defined(__ANDROID__)
    return strlen(GetPickedFile()) > 0;
    #elif defined(__EMSCRIPTEN__)
    return file_picker_confirmed();
    #else
    return fallback.HasSelected();
    #endif
}
path FileBrowser::GetSelected() {
    #ifdef PORTALS
    return selected.value_or(path());
    #elif defined(__ANDROID__)
    const char *file = GetPickedFile();
    if (strlen(file) > 0) {
        return std::string(file);
    } else {
        return {};
    }
    #elif defined(__EMSCRIPTEN__)
    if (HasSelected()) {
        const char *c_file = get_first_file();
        std::string name = c_file;
        free((void*)c_file);
        return name;
    } else {
        return {};
    }
    #else
    return fallback.GetSelected();
    #endif
}
void FileBrowser::SetWindowSize(int w, int h) {
    window_size = ImVec2(w, h);
    fallback.SetWindowSize(w, h);
}
void FileBrowser::SetWindowPos(int x, int y) {
    window_pos = ImVec2(x, y);
    fallback.SetWindowPos(x, y);
}
void FileBrowser::Open() {
    #ifdef PORTALS
    open = true;
    if (save) {
        xdp_portal_save_file(portal, NULL, title.c_str(), NULL, pwd.c_str(), NULL, variant, NULL, NULL, XDP_SAVE_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserSaveCallback, this);
    } else {
        xdp_portal_open_file(portal, NULL, title.c_str(), variant, NULL, NULL, XDP_OPEN_FILE_FLAG_NONE, NULL, &FileBrowser::FileBrowserOpenCallback, this);
    }
    #elif defined(__ANDROID__)
    ClearSelected();
    open = true;
    ::OpenFilePicker(nullptr);
    #elif defined(__EMSCRIPTEN__)
    open_filepicker();
    #else
    fallback.Open();
    #endif
}
#ifdef PORTALS
path url_decode(string str) {
    static const string file_proto = "file://";
    if (str.starts_with(file_proto)) {
        str = str.substr(file_proto.length());
    }
    string ret;
    int len = str.length();
    for (int i = 0; i < len; i++) {
        if (str[i] != '%') {
            // Don't decode '+' as a space as libportal doesn't encode space as '+', and encodes '+' in a file path verbatim
            ret += str[i];
        } else {
            unsigned int ch_int;
            sscanf(str.substr(i + 1, 2).c_str(), "%x", &ch_int);
            ret += static_cast<char>(ch_int);
            i = i + 2;
        }
    }
    return ret;
}
void FileBrowser::FileBrowserOpenCallback(GObject *src, GAsyncResult *res, gpointer data) {
    (void)src;
    FileBrowser *self = (FileBrowser*)data;
    GVariant *variant = xdp_portal_open_file_finish(self->portal, res, NULL);
    if (variant == NULL) {
        printf("Cancelled.\n");
        return;
    }
    GVariant *uris = g_variant_lookup_value(variant, "uris", G_VARIANT_TYPE_STRING_ARRAY);
    GVariant *first_uri = g_variant_get_child_value(uris, 0);
    gsize length;
    const gchar* c_str_without_terminater = g_variant_get_string(first_uri, &length);
    vector<char> c_str_vec;
    c_str_vec.reserve(length + 1);
    for (gsize i = 0; i < length; i++) {
        c_str_vec.push_back(c_str_without_terminater[i]);
    }
    c_str_vec.push_back('\0');
    const char *c_str = c_str_vec.data();
    printf("Selected file %s\n", c_str);
    path path = url_decode(c_str);
    self->open = false;
    self->selected.emplace(path);
}

void FileBrowser::FileBrowserSaveCallback(GObject *src, GAsyncResult *res, gpointer data) {
    (void)src;
    FileBrowser *self = (FileBrowser*)data;
    GVariant *variant = xdp_portal_save_file_finish(self->portal, res, NULL);
    if (variant == NULL) {
        printf("Cancelled.\n");
        return;
    }
    GVariant *uris = g_variant_lookup_value(variant, "uris", G_VARIANT_TYPE_STRING_ARRAY);
    GVariant *first_uri = g_variant_get_child_value(uris, 0);
    gsize length;
    const gchar* c_str_without_terminater = g_variant_get_string(first_uri, &length);
    vector<char> c_str_vec;
    c_str_vec.reserve(length + 1);
    for (gsize i = 0; i < length; i++) {
        c_str_vec.push_back(c_str_without_terminater[i]);
    }
    c_str_vec.push_back('\0');
    const char *c_str = c_str_vec.data();
    printf("Selected file %s\n", c_str);
    path path = url_decode(c_str);
    self->open = false;
    self->selected.emplace(path);
}
#endif

void FileBrowser::Display() {
    #ifdef PORTALS
    g_main_context_iteration(main_context, false);
    #elif defined(__ANDROID__)
    if (HasSelected()) {
        open = false;
    }
    if (IsLoading()) {
        ImVec2 pos(0, 0);
        ImVec2 size = ImGui::GetMainViewport()->Size;
        ImGui::SetNextWindowPos(pos);
        ImGui::SetNextWindowSize(size);
        ImGui::Begin("Loading...", NULL, ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoTitleBar);
        ImVec2 textSize = ImGui::CalcTextSize("Loading...");
        ImVec2 textPos = size;
        textPos.x -= textSize.x;
        textPos.y -= textSize.y;
        textPos.x /= 2;
        textPos.y /= 2;
        ImGui::SetCursorPos(textPos);
        ImGui::TextUnformatted("Loading...");
        ImGui::End();

    }
    #elif defined(__EMSCRIPTEN__)
    if (file_picker_visible() || file_picker_loading()) {
        if((flags & ImGuiFileBrowserFlags_NoModal))
        {
            if (window_pos.has_value())
                ImGui::SetNextWindowPos(
                        window_pos.value());
            ImGui::SetNextWindowSize(
                window_size);
        }
        else
        {
            if (window_pos.has_value())
                ImGui::SetNextWindowPos(
                        window_pos.value());
            ImGui::SetNextWindowSize(
                window_size);
        }
        if(flags & ImGuiFileBrowserFlags_NoModal)
        {
            if(!ImGui::BeginPopup(title.c_str(),
                (flags & ImGuiFileBrowserFlags_NoMove ? ImGuiWindowFlags_NoMove : 0) |
                (flags & ImGuiFileBrowserFlags_NoResize ? ImGuiWindowFlags_NoResize : 0)))
            {
                return;
            }
        }
        else if(!ImGui::BeginPopupModal(title.c_str(), nullptr,
                                (flags & ImGuiFileBrowserFlags_NoMove ? ImGuiWindowFlags_NoMove : 0) |
                                (flags & ImGuiFileBrowserFlags_NoResize ? ImGuiWindowFlags_NoResize : 0) |
                                (flags & ImGuiFileBrowserFlags_NoTitleBar ? ImGuiWindowFlags_NoTitleBar : 0)))
        {
            return;
        }
        if (file_picker_loading()) {
            ImGui::Text("Loading file(s)...");
        } else {
            ImGui::Text("Please select a file...");
        }
        ImGui::EndPopup();
    }
    #else
    fallback.Display();
    #endif
}

void FileBrowser::ClearSelected() {
    selected = optional<path>();
    #ifdef __EMSCRIPTEN__
    clear_file_selection();
    #endif
    #ifdef __ANDROID__
    ::ClearSelected();
    #endif
    #ifndef PORTALS
    fallback.ClearSelected();
    #endif
}
void FileBrowser::SetTitle(string title) {
    this->title = title;
    #ifndef PORTALS
    fallback.SetTitle(title);
    #endif
}
bool FileBrowser::IsOpened() {
    #ifdef PORTALS
    return open;
    #elif defined(__ANDROID__)
    return open;
    #elif defined(__EMSCRIPTEN__)
    return !file_picker_closed() || file_picker_confirmed();
    #else
    return fallback.IsOpened();
    #endif
}
FileBrowser::~FileBrowser() {
    #ifdef PORTALS
    if (variant != NULL) {
        g_variant_unref(variant);
    }
    if (type != NULL) {
        g_variant_type_free(type);
    }
    if (inner_filter_type != NULL) {
        g_variant_type_free(inner_filter_type);
    }
    g_main_loop_quit(main_loop);
    g_main_loop_unref(main_loop);
    g_object_unref(portal);
    #endif
}