#include "pch.h"
#include "app.h"
#include "app_core/document_platform_io.h"
#include "app_core/document_sharing.h"
#include "platform_api/platform_services.h"
#include "platform_legacy/legacy_platform_services.h"
#include "renderer_gl/opengl_capabilities.h"

namespace {

[[nodiscard]] GLint rgba8_internal_format() noexcept
{
    return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
}

[[nodiscard]] bool should_dispatch_keyboard_visibility(bool visible) noexcept
{
    const auto action = pp::app::plan_virtual_keyboard(visible);
    return visible
        ? action == pp::app::VirtualKeyboardAction::show_keyboard
        : action == pp::app::VirtualKeyboardAction::hide_keyboard;
}

[[nodiscard]] bool should_dispatch_cursor_visibility(bool visible) noexcept
{
    const auto action = pp::app::plan_cursor_visibility(visible);
    return visible
        ? action == pp::app::CursorVisibilityAction::show_cursor
        : action == pp::app::CursorVisibilityAction::hide_cursor;
}

}

namespace {

[[nodiscard]] pp::platform::PlatformServices& active_platform_services()
{
    if (App::I)
    {
        if (auto* services = App::I->platform_services())
            return *services;
    }
    return pp::platform::legacy::platform_services();
}

}


void App::set_platform_services(pp::platform::PlatformServices* services) noexcept
{
    platform_services_ = services;
}

pp::platform::PlatformServices* App::platform_services() const noexcept
{
    return platform_services_;
}

pp::platform::PlatformStoragePaths App::prepare_storage_paths()
{
    return active_platform_services().prepare_storage_paths();
}

std::string App::clipboard_get_text()
{
    if (pp::app::plan_clipboard_read() != pp::app::ClipboardReadAction::read_text)
        return {};

    return active_platform_services().clipboard_text();
}

bool App::clipboard_set_text(const std::string& s)
{
    if (pp::app::plan_clipboard_write(s) != pp::app::ClipboardWriteAction::write_text)
        return false;

    return active_platform_services().set_clipboard_text(s);
}

void App::stacktrace()
{
    active_platform_services().log_stacktrace();
}

void App::crash_test()
{
    active_platform_services().trigger_crash_test();
}

void App::tick(float dt)
{
    if (auto* main = layout_designer[main_id])
        main->tick(dt);
    if (auto* main = layout[main_id])
        main->tick(dt);
}

void App::resize(float w, float h)
{
    LOG("App::resize %d %d", (int)w, (int)h);
    uirtt.create(static_cast<int>(w), static_cast<int>(h), -1, rgba8_internal_format(), true);
    redraw = true;
    width = w;
    height = h;
}

void App::show_cursor()
{
    if (!should_dispatch_cursor_visibility(true))
        return;

    active_platform_services().set_cursor_visible(true);
}

void App::hide_cursor()
{
    if (!should_dispatch_cursor_visibility(false))
        return;

    active_platform_services().set_cursor_visible(false);
}

void App::showKeyboard()
{
    LOG("show keyboard");
    redraw = true;
    if (!should_dispatch_keyboard_visibility(true))
        return;

    active_platform_services().set_virtual_keyboard_visible(true);
}

void App::hideKeyboard()
{
    LOG("hide keyboard");
    redraw = true;
    if (!should_dispatch_keyboard_visibility(false))
        return;

    active_platform_services().set_virtual_keyboard_visible(false);
}

void App::pick_image(std::function<void(std::string path)> callback)
{
    redraw = true;
    active_platform_services().pick_image(std::move(callback));
}

void App::pick_file(std::vector<std::string> types, std::function<void (std::string)> callback)
{
    redraw = true;
    active_platform_services().pick_file(std::move(types), std::move(callback));
}

#if __IOS__
void App::pick_file_save(const std::string& type, const std::string& default_name,
    std::function<void(std::string)> writer, std::function<void(const std::string& path, bool saved)> callback)
{
    redraw = true;
    std::string ext = "." + type;
    std::string path = tmp_path + "/" + default_name + ext;
    std::thread([=]{
        writer(path);
        save_prepared_file(path, default_name + ext, callback);
    }).detach();
}
#elif __WEB__
void App::pick_file_save(const std::string& type, const std::string& default_name,
    std::function<void(std::string)> writer, std::function<void(const std::string& path, bool saved)> callback)
{
    redraw = true;
    auto path = data_path + "/" + default_name + "." + type;
    LOG("App::pick_file_save %s", path.c_str());
    writer(path);
    save_prepared_file(path, default_name + "." + type, std::move(callback));
}
#else
void App::pick_file_save(std::vector<std::string> types, std::function<void(std::string)> callback)
{
    redraw = true;
    active_platform_services().pick_save_file(std::move(types), std::move(callback));
}
#endif

void App::pick_dir(std::function<void(std::string path)> callback)
{
    redraw = true;
    active_platform_services().pick_directory(std::move(callback));
}

void App::display_file(std::string path)
{
    if (pp::app::plan_display_file(path) == pp::app::DisplayFileAction::ignore_empty_path)
        return;

    active_platform_services().display_file(path);
}

void App::share_file(std::string path)
{
    const auto plan = pp::app::plan_document_share(path);
    if (plan == pp::app::DocumentShareAction::show_save_required_warning)
    {
        message_box("Sharing failed", "Please save the document before sharing it.");
        return;
    }
    active_platform_services().share_file(path);
}

void App::request_app_close()
{
    active_platform_services().request_app_close();
}

void App::attach_ui_thread()
{
    active_platform_services().attach_ui_thread();
}

void App::detach_ui_thread()
{
    active_platform_services().detach_ui_thread();
}

void App::acquire_render_context()
{
    active_platform_services().acquire_render_context();
}

void App::release_render_context()
{
    active_platform_services().release_render_context();
}

void App::present_render_context()
{
    active_platform_services().present_render_context();
}

void App::bind_default_render_target()
{
    active_platform_services().bind_default_render_target();
}

void App::bind_main_render_target()
{
    active_platform_services().bind_main_render_target();
}

void App::apply_render_platform_hints()
{
    active_platform_services().apply_render_platform_hints();
}

void App::install_render_debug_callback()
{
    active_platform_services().install_render_debug_callback();
}

void App::begin_render_capture_frame()
{
    active_platform_services().begin_render_capture_frame();
}

void App::end_render_capture_frame()
{
    active_platform_services().end_render_capture_frame();
}

bool App::platform_deletes_recorded_files_on_clear()
{
    return active_platform_services().deletes_recorded_files_on_clear();
}

void App::clear_platform_recorded_files(std::string path)
{
    active_platform_services().clear_recorded_files(path);
}

bool App::platform_enables_live_asset_reloading()
{
    return active_platform_services().enables_live_asset_reloading();
}

void App::update_platform_frame(float delta_time_seconds)
{
    active_platform_services().update_platform_frame(delta_time_seconds);
}

void App::report_rendered_frames(int frames)
{
    active_platform_services().report_rendered_frames(frames);
}

void App::save_prepared_file(
    std::string path,
    std::string suggested_name,
    std::function<void(const std::string& path, bool saved)> callback)
{
    active_platform_services().save_prepared_file(
        path,
        suggested_name,
        [callback = std::move(callback)](std::string saved_path, bool saved) {
            callback(saved_path, saved);
        });
}

bool App::mouse_down(int button, float x, float y, float pressure, kEventSource source, bool eraser)
{
    redraw = true;
    MouseEvent e;
    e.m_type = button ? kEventType::MouseDownR : kEventType::MouseDownL;
    e.m_pos = { x / zoom, y / zoom };
    e.m_pressure = pressure;
    e.m_source = source;
    e.m_eraser = eraser;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout_designer[main_id])
        return main->on_event(&e) == kEventResult::Consumed;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::mouse_move(float x, float y, float pressure, kEventSource source, bool eraser)
{
    cursor = { x / zoom, y / zoom };
    redraw = true;
    MouseEvent e;
    e.m_type = kEventType::MouseMove;
    e.m_pos = { x / zoom, y / zoom };
    e.m_pressure = pressure;
    e.m_source = source;
    e.m_eraser = eraser;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout_designer[main_id])
        return main->on_event(&e) == kEventResult::Consumed;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::mouse_up(int button, float x, float y, kEventSource source, bool eraser)
{
    redraw = true;
    MouseEvent e;
    e.m_type = button ? kEventType::MouseUpR : kEventType::MouseUpL;
    e.m_pos = { x / zoom, y / zoom };
    e.m_source = source;
    e.m_eraser = eraser;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout_designer[main_id])
        return main->on_event(&e) == kEventResult::Consumed;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::mouse_scroll(float x, float y, float delta)
{
    redraw = true;
    MouseEvent e;
    e.m_type = kEventType::MouseScroll;
    e.m_pos = { x / zoom, y / zoom };
    e.m_scroll_delta = delta;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout_designer[main_id])
        return main->on_event(&e) == kEventResult::Consumed;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::mouse_cancel(int button)
{
    redraw = true;
    MouseEvent e;
    e.m_type = kEventType::MouseCancel;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout_designer[main_id])
        return main->on_event(&e) == kEventResult::Consumed;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::gesture_start(const glm::vec2& p0, const glm::vec2& p1)
{
    redraw = true;
    GestureEvent e;
    glm::vec2 p = glm::lerp(p0, p1, 0.5f);
    e.m_type = kEventType::GestureStart;
    e.m_pos = p / glm::vec2(zoom);
    e.m_distance = glm::distance(p0, p1);
    gesture_p0 = p0;
    gesture_p1 = p1;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::gesture_move(const glm::vec2& p0, const glm::vec2& p1)
{
    redraw = true;
    GestureEvent e;
    glm::vec2 p = glm::lerp(p0, p1, 0.5f);
    e.m_type = kEventType::GestureMove;
    e.m_pos = p / glm::vec2(zoom);
    e.m_distance = glm::distance(p0, p1);
    e.m_distance_delta = e.m_distance - glm::distance(gesture_p0, gesture_p1);
    e.m_pos_delta = p - glm::lerp(gesture_p0, gesture_p1, 0.5f);
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::gesture_end()
{
    redraw = true;
    GestureEvent e;
    e.m_type = kEventType::GestureEnd;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::touch_tap(const glm::vec2& pos, int fingers, int tap_count)
{
    redraw = true;
    TouchEvent e;
    e.m_type = kEventType::TouchTap;
    e.m_finger_count = fingers;
    e.m_tap_count = tap_count;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::key_down(kKey key)
{
    if (key == kKey::KeySpacebar && vr_active)
        canvas->m_canvas->m_cam_rot = vr_rot;
    redraw = true;
    keys[(int)key] = true;
    KeyEvent e;
    e.m_type = kEventType::KeyDown;
    e.m_key = key;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::key_up(kKey key)
{
    redraw = true;
    keys[(int)key] = false;
    KeyEvent e;
    e.m_type = kEventType::KeyUp;
    e.m_key = key;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}
bool App::key_char(char key)
{
    redraw = true;
    KeyEvent e;
    e.m_type = kEventType::KeyChar;
    e.m_char = key;
    kEventResult ret = kEventResult::Available;
    if (auto* main = layout[main_id])
        ret = main->on_event(&e);
    return ret == kEventResult::Consumed;
}

void App::toggle_ui()
{
    auto m = layout[main_id]->m_children[1];
    ui_visible = !ui_visible;
    for (int i = 1; i < m->m_children.size(); i++)
        m->m_children[i]->m_display = ui_visible;
}

void App::set_stylus()
{
    has_stylus = true;
    if (canvas)
        canvas->m_canvas->m_touch_lock = true;
}
