#include "pch.h"
#include "platform_apple/apple_platform_services.h"

#if defined(__OSX__) && defined(__OBJC__)
#import <AppKit/AppKit.h>
#endif

#include "app_core/document_platform_io.h"
#include "platform_api/network_tls_policy.h"
#include "platform_api/platform_policy.h"

#if defined(__APPLE__)
#include "legacy_ui_gl_dispatch.h"
#include "log.h"
#include "objc_utils.h"
#include "renderer_gl/opengl_capabilities.h"
#endif

#include <array>
#include <filesystem>
#include <memory>
#include <utility>

namespace pp::platform::apple {

namespace {

void log_apple_platform_message(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    vfprintf(stderr, format, args);
    fputc('\n', stderr);
    va_end(args);
}

[[nodiscard]] std::vector<std::string> apple_image_file_types()
{
    static constexpr std::array<std::string_view, 5> kFileTypes = {
        "png",
        "PNG",
        "jpg",
        "JPG",
        "jpeg",
    };

    return { kFileTypes.begin(), kFileTypes.end() };
}

void invoke_picked_path_if_selected(
    const std::string& path,
    const PickedPathCallback& callback)
{
    if (pp::app::plan_picked_path(path) == pp::app::PickedPathAction::invoke_callback)
        callback(path);
}

[[nodiscard]] AppleDocumentPlatformServices& active_document_platform_services() noexcept
{
#if defined(__APPLE__)
    return active_legacy_apple_document_platform_services();
#else
    static AppleDocumentPlatformServices services(PlatformFamily::macos, {});
    return services;
#endif
}

[[nodiscard]] PlatformFamily active_platform_family() noexcept
{
#if defined(__APPLE__)
    return legacy_apple_platform_family();
#else
    return PlatformFamily::macos;
#endif
}

}

AppleDocumentPlatformServices::AppleDocumentPlatformServices(
    PlatformFamily family,
    AppleDocumentPickerBridge bridge)
    : family_(family)
    , bridge_(std::move(bridge))
{
}

std::vector<std::string> AppleDocumentPlatformServices::document_browse_roots(
    std::string_view work_path,
    std::string_view data_path) const
{
    return platform_document_browse_roots(family_, work_path, data_path);
}

std::string AppleDocumentPlatformServices::clipboard_text() const
{
    if (bridge_.clipboard_text)
        return bridge_.clipboard_text();
    return {};
}

bool AppleDocumentPlatformServices::set_clipboard_text(std::string_view text) const
{
    if (bridge_.set_clipboard_text)
        return bridge_.set_clipboard_text(text);
    return false;
}

void AppleDocumentPlatformServices::set_virtual_keyboard_visible(bool visible) const
{
#if defined(__IOS__)
    if (bridge_.set_virtual_keyboard_visible)
        bridge_.set_virtual_keyboard_visible(visible);
#else
    (void)visible;
#endif
}

void AppleDocumentPlatformServices::pick_image(PickedPathCallback callback) const
{
    if (family_ == PlatformFamily::ios)
    {
        if (bridge_.pick_image)
            bridge_.pick_image(std::move(callback));
        return;
    }

    if (family_ == PlatformFamily::macos && bridge_.pick_file)
    {
        bridge_.pick_file(
            apple_image_file_types(),
            [callback = std::move(callback)](std::string path) {
                invoke_picked_path_if_selected(path, callback);
            });
    }
}

void AppleDocumentPlatformServices::pick_file(
    std::vector<std::string> file_types,
    PickedPathCallback callback) const
{
    if (!bridge_.pick_file)
        return;

    if (family_ == PlatformFamily::ios)
    {
        bridge_.pick_file(std::move(file_types), std::move(callback));
        return;
    }

    if (family_ == PlatformFamily::macos)
    {
        bridge_.pick_file(
            std::move(file_types),
            [callback = std::move(callback)](std::string path) {
                invoke_picked_path_if_selected(path, callback);
            });
    }
}

void AppleDocumentPlatformServices::pick_save_file(
    std::vector<std::string> file_types,
    PickedPathCallback callback) const
{
    if (family_ == PlatformFamily::macos && bridge_.pick_save_file)
    {
        bridge_.pick_save_file(
            std::move(file_types),
            [callback = std::move(callback)](std::string path) {
                invoke_picked_path_if_selected(path, callback);
            });
    }
}

void AppleDocumentPlatformServices::pick_directory(PickedPathCallback callback) const
{
    if (family_ == PlatformFamily::macos && bridge_.pick_directory)
    {
        bridge_.pick_directory([callback = std::move(callback)](std::string path) {
            invoke_picked_path_if_selected(path, callback);
        });
    }
}

bool AppleDocumentPlatformServices::supports_working_directory_picker() const
{
    return platform_supports_working_directory_picker(family_);
}

std::string AppleDocumentPlatformServices::format_working_directory_path(std::string_view path) const
{
    if (family_ == PlatformFamily::macos && bridge_.format_working_directory_path)
        return bridge_.format_working_directory_path(path);
    return std::string(path);
}

void AppleDocumentPlatformServices::display_file(std::string_view path) const
{
    const std::string value(path);
#if defined(__IOS__)
    if (bridge_.display_file)
        bridge_.display_file(value);
#elif defined(__OSX__)
    [[NSWorkspace sharedWorkspace] openFile:[NSString stringWithUTF8String:value.c_str()]];
#else
    (void)value;
#endif
}

void AppleDocumentPlatformServices::share_file(std::string_view path) const
{
    const std::string value(path);
#if defined(__IOS__) || defined(__OSX__)
    if (bridge_.share_file)
        bridge_.share_file(value);
#else
    (void)value;
#endif
}

void AppleDocumentPlatformServices::set_cursor_visible(bool visible) const
{
#if defined(__OSX__)
    if (bridge_.set_cursor_visible)
        bridge_.set_cursor_visible(visible);
#else
    (void)visible;
#endif
}

void AppleDocumentPlatformServices::trigger_crash_test() const
{
    if (bridge_.trigger_crash_test)
        bridge_.trigger_crash_test();
}

void AppleDocumentPlatformServices::request_app_close() const
{
    if (bridge_.request_app_close)
        bridge_.request_app_close();
}

void AppleDocumentPlatformServices::start_sonarpen() const
{
    if (bridge_.start_sonarpen)
        bridge_.start_sonarpen();
}

void AppleDocumentPlatformServices::acquire_render_context() const
{
    if (bridge_.acquire_render_context)
        bridge_.acquire_render_context();
}

void AppleDocumentPlatformServices::release_render_context() const
{
    if (bridge_.release_render_context)
        bridge_.release_render_context();
}

void AppleDocumentPlatformServices::present_render_context() const
{
    if (bridge_.present_render_context)
        bridge_.present_render_context();
}

void AppleDocumentPlatformServices::bind_main_render_target() const
{
    if (bridge_.bind_main_render_target)
        bridge_.bind_main_render_target();
}

void AppleDocumentPlatformServices::save_prepared_file(
    std::string_view path,
    std::string_view suggested_name,
    PreparedFileCallback callback) const
{
#if defined(__IOS__)
    if (bridge_.save_prepared_file)
    {
        bridge_.save_prepared_file(
            std::string(path),
            std::string(suggested_name),
            std::move(callback));
    }
#else
    (void)path;
    (void)suggested_name;
    (void)callback;
#endif
}

void AppleDocumentPlatformServices::save_ui_state() const
{
#if defined(__OSX__)
    if (bridge_.save_ui_state)
        bridge_.save_ui_state();
#endif
}

PlatformStoragePaths ApplePlatformServices::prepare_storage_paths()
{
#if defined(__APPLE__)
    return prepare_legacy_apple_storage_paths();
#else
    return {};
#endif
}

void ApplePlatformServices::log_stacktrace()
{
#if defined(__OSX__)
    NSString* callstack = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"];
    log_apple_platform_message(
        "callstack:\n%s",
        [callstack cStringUsingEncoding:NSUTF8StringEncoding]);
#endif
}

void ApplePlatformServices::trigger_crash_test()
{
    active_document_platform_services().trigger_crash_test();
}

std::string ApplePlatformServices::clipboard_text()
{
    return active_document_platform_services().clipboard_text();
}

bool ApplePlatformServices::set_clipboard_text(std::string_view text)
{
    return active_document_platform_services().set_clipboard_text(text);
}

void ApplePlatformServices::set_cursor_visible(bool visible)
{
    active_document_platform_services().set_cursor_visible(visible);
}

void ApplePlatformServices::set_virtual_keyboard_visible(bool visible)
{
    active_document_platform_services().set_virtual_keyboard_visible(visible);
}

void ApplePlatformServices::attach_ui_thread()
{
}

void ApplePlatformServices::detach_ui_thread()
{
}

void ApplePlatformServices::acquire_render_context()
{
    active_document_platform_services().acquire_render_context();
}

void ApplePlatformServices::release_render_context()
{
    active_document_platform_services().release_render_context();
}

void ApplePlatformServices::present_render_context()
{
    active_document_platform_services().present_render_context();
}

void ApplePlatformServices::bind_default_render_target()
{
#if defined(__APPLE__)
    pp::legacy::ui_gl::bind_opengl_framebuffer(
        pp::renderer::gl::framebuffer_target(),
        pp::renderer::gl::default_framebuffer_id());
#endif
}

void ApplePlatformServices::bind_main_render_target()
{
#if defined(__IOS__)
    active_document_platform_services().bind_main_render_target();
#else
    bind_default_render_target();
#endif
}

void ApplePlatformServices::apply_render_platform_hints()
{
#if defined(__OSX__)
    const auto status = pp::renderer::gl::apply_opengl_render_platform_hints(
        pp::renderer::gl::OpenGlRenderPlatformHintDispatch {
            .enable = pp::legacy::ui_gl::enable_opengl_state,
        });
    if (!status.ok())
        log_apple_platform_message(
            "OpenGL legacy render platform hints failed: %s",
            status.message);
#endif
}

void ApplePlatformServices::install_render_debug_callback()
{
}

void ApplePlatformServices::begin_render_capture_frame()
{
}

void ApplePlatformServices::end_render_capture_frame()
{
}

bool ApplePlatformServices::deletes_recorded_files_on_clear()
{
    return platform_deletes_recorded_files_on_clear(active_platform_family());
}

void ApplePlatformServices::clear_recorded_files(std::string_view recording_path)
{
#if defined(__APPLE__)
    std::error_code error;
    std::filesystem::remove_all(std::filesystem::path(recording_path), error);
#else
    (void)recording_path;
#endif
}

void ApplePlatformServices::publish_exported_image(std::string_view path)
{
    if (!platform_publishes_exported_images(active_platform_family()))
        return;
#if defined(__IOS__)
    save_image_library(std::string(path));
#else
    (void)path;
#endif
}

void ApplePlatformServices::flush_persistent_storage()
{
}

std::vector<std::string> ApplePlatformServices::document_browse_roots(
    std::string_view work_path,
    std::string_view data_path)
{
    return active_document_platform_services().document_browse_roots(work_path, data_path);
}

void ApplePlatformServices::save_ui_state()
{
    if (!platform_saves_native_ui_state(active_platform_family()))
        return;
    active_document_platform_services().save_ui_state();
}

bool ApplePlatformServices::enables_live_asset_reloading()
{
    return platform_enables_live_asset_reloading(active_platform_family());
}

void ApplePlatformServices::update_platform_frame(float delta_time_seconds)
{
    (void)delta_time_seconds;
}

void ApplePlatformServices::report_rendered_frames(int frames)
{
    (void)frames;
}

void ApplePlatformServices::pick_image(PickedPathCallback callback)
{
    active_document_platform_services().pick_image(std::move(callback));
}

void ApplePlatformServices::pick_file(std::vector<std::string> file_types, PickedPathCallback callback)
{
    active_document_platform_services().pick_file(std::move(file_types), std::move(callback));
}

void ApplePlatformServices::pick_save_file(std::vector<std::string> file_types, PickedPathCallback callback)
{
    active_document_platform_services().pick_save_file(std::move(file_types), std::move(callback));
}

void ApplePlatformServices::pick_directory(PickedPathCallback callback)
{
    active_document_platform_services().pick_directory(std::move(callback));
}

bool ApplePlatformServices::supports_working_directory_picker()
{
    return active_document_platform_services().supports_working_directory_picker();
}

std::string ApplePlatformServices::format_working_directory_path(std::string_view path)
{
    return active_document_platform_services().format_working_directory_path(path);
}

bool ApplePlatformServices::uses_prepared_file_writes()
{
    return platform_uses_prepared_file_writes(active_platform_family());
}

bool ApplePlatformServices::uses_work_directory_document_export_collections()
{
    return platform_uses_work_directory_document_export_collections(active_platform_family());
}

bool ApplePlatformServices::disables_network_tls_verification()
{
    return default_disables_network_tls_verification();
}

bool ApplePlatformServices::uses_ppbr_export_data_directory_override()
{
    return platform_uses_ppbr_export_data_directory_override(active_platform_family());
}

bool ApplePlatformServices::supports_sonarpen()
{
    return platform_supports_sonarpen(active_platform_family());
}

void ApplePlatformServices::start_sonarpen()
{
    active_document_platform_services().start_sonarpen();
}

int ApplePlatformServices::default_canvas_resolution()
{
    return platform_default_canvas_resolution(active_platform_family());
}

bool ApplePlatformServices::draws_canvas_tip_for_pointer(
    bool is_mouse,
    bool is_stylus,
    bool is_left_button_release)
{
    return platform_draws_canvas_tip_for_pointer(
        active_platform_family(),
        is_mouse,
        is_stylus,
        is_left_button_release);
}

float ApplePlatformServices::adjust_canvas_input_pressure(float pressure)
{
    return pressure;
}

PreparedFileTarget ApplePlatformServices::prepare_writable_file(
    std::string_view type,
    std::string_view default_name,
    std::string_view data_path,
    std::string_view temporary_path)
{
    return plan_platform_writable_file(
        active_platform_family(),
        type,
        default_name,
        data_path,
        temporary_path);
}

void ApplePlatformServices::display_file(std::string_view path)
{
    active_document_platform_services().display_file(path);
}

void ApplePlatformServices::share_file(std::string_view path)
{
    active_document_platform_services().share_file(path);
}

void ApplePlatformServices::request_app_close()
{
    active_document_platform_services().request_app_close();
}

bool ApplePlatformServices::start_vr_mode()
{
    return false;
}

void ApplePlatformServices::stop_vr_mode()
{
}

void ApplePlatformServices::save_prepared_file(
    std::string_view path,
    std::string_view suggested_name,
    PreparedFileCallback callback)
{
    active_document_platform_services().save_prepared_file(
        path,
        suggested_name,
        std::move(callback));
}

std::unique_ptr<PlatformServices> create_apple_platform_services()
{
    return std::make_unique<ApplePlatformServices>();
}

}
