#pragma once

#include "log.h"
#include "shader.h"
#include "shape.h"
#include "texture.h"
#include "font.h"
#include "rtt.h"
#include "image.h"
#include "mp4enc.h"
#include "node_message_box.h"
#include "node_settings.h"
#include "node_popup_menu.h"
#include "node_panel_brush.h"
#include "node_panel_layer.h"
#include "node_panel_color.h"
#include "node_panel_stroke.h"
#include "node_scroll.h"
#include "node_canvas.h"
#include "node_dialog_layer_rename.h"
#include "node_progress_bar.h"
#include "node_panel_quick.h"
#include "node_input_box.h"
#include "node_panel_animation.h"
#include "layout.h"
#include "app_core/document_session.h"
#include "app_core/app_thread.h"
#include "app_runtime.h"

namespace pp::platform {
class PlatformServices;
struct PlatformStoragePaths;
}

class NodePanelGrid;

#if defined(__OBJC__) && defined(__IOS__)
@class GameViewController;
@class AppDelegate;
#endif

#if defined(__OBJC__) && defined(__OSX__)
@class View;
@class AppOSX;
#endif

#ifdef __ANDROID__
struct android_app;
struct engine;
#endif

#if __LINUX__ || __WEB__
struct GLFWwindow;
#endif
struct VRController
{
    enum class kButton : uint8_t
    {
        Pad,
        Trigger,
        Menu,
        Grip,
        A,
        COUNT,
    };
    enum class kAction
    {
        Press,
        Release,
    };
    std::array<bool, (int)kButton::COUNT> m_buttons;
    std::array<bool, (int)kButton::COUNT> m_analog_buttons;
    glm::mat4 m_mat;
    bool m_valid = false;
    glm::vec3 get_pos() const { return m_mat[3]; }
    glm::vec3 get_pos_n() const { return glm::normalize(xyz(m_mat[3])); }
    virtual float get_trigger_value() const { return 1.f; }
};

struct VrSessionSnapshot
{
    bool has_vr = false;
    bool vr_active = false;
    std::array<VRController, 2> vr_controllers{};
    glm::mat4 vr_head{1.0f};
};

class App
{
public:
    static App* I;
    std::string data_path{ "." };
    std::string work_path{ "." };
    std::string rec_path{ "." };
    std::string tmp_path{ "." };
    std::jthread rec_thread;
    bool rec_running = false;
    int rec_count = 0;
    std::mutex rec_mutex;
    std::condition_variable rec_cv;
    std::deque<std::unique_ptr<PBO>> rec_frames;

    RTT uirtt;
    Sampler sampler;
    Sampler sampler_stencil;
    Sampler sampler_linear;
    Plane m_face_plane;
    Sphere sphere;
    LayoutManager layout;
    LayoutManager layout_designer;
    NodeMessageBox* msgbox;
    NodeSettings* settings;
    NodeBorder* sidebar = nullptr;
    std::shared_ptr<NodePanelLayer> layers;
    std::shared_ptr<NodePanelColor> color;
    std::shared_ptr<NodePanelStroke> stroke;
    std::shared_ptr<NodePanelGrid> grid;
    std::shared_ptr<NodePanelBrushPreset> presets;
    std::shared_ptr<NodePanelAnimation> animation;
    NodePanelQuick* quick;
    std::map<kCanvasMode, NodePanelQuick::MiniState> quick_mode_state;
    Node* floatings_container;
    std::shared_ptr<NodePanelColor> floating_color;
    std::shared_ptr<NodeColorPicker> floating_picker;
    std::shared_ptr<NodePanelBrushPreset> floating_presets;
    std::shared_ptr<NodePanelLayer> floating_layers;
    NodeCanvas* canvas;
    const uint16_t main_id = const_hash("main");
    const std::array<int, 6> res_map{ 512, 1024, 1536, 2048, 4096, 8192 };
    const std::array<std::string, 6> res_map_str{ "2K", "4K", "6K", "8K", "16K", "32K" };
    std::string doc_name = "no-name";
    std::string doc_path;
    std::string doc_dir;
    std::string doc_filename;
    bool has_stylus = false;
    bool vr_controllers_enabled = true;
    float off_x = 0;
    float off_y = 0;
    float width;
    float height;
    bool keys[256]{false};
    bool redraw = true;
    bool animate = false;
    bool ui_visible = true;
    bool ui_rtl = false;
    bool vr_only = false;
    float vr_pressure = 1.f;
    glm::mat4 vr_rot{ 0 };
    glm::mat4 vr_uirot{ 0 };
    glm::vec2 cursor{ 0, 0 };
    glm::vec2 gesture_p0;
    glm::vec2 gesture_p1;
    float display_density = 1.f;
    float zoom = 1.f;
    int idle_ms = 100;
    pp::platform::PlatformServices* platform_services_ = nullptr;
    
    std::string clipboard_get_text();
    bool clipboard_set_text(const std::string& s);
    void pick_image(std::function<void(std::string path)> callback);
    void pick_file(std::vector<std::string> types, std::function<void(std::string path)> callback);
    void pick_file_save(const std::string& type, const std::string& default_name,
        std::function<void(std::string path)> writer, std::function<void(const std::string& path, bool saved)> callback);
    void pick_file_save(std::vector<std::string> types, std::function<void(std::string path)> callback);
    [[nodiscard]] bool supports_working_directory_picker() const;
    [[nodiscard]] std::string format_working_directory_path(std::string_view path) const;
    [[nodiscard]] bool uses_prepared_file_writes() const;
    [[nodiscard]] bool uses_work_directory_document_export_collections() const;
    [[nodiscard]] bool disables_network_tls_verification() const;
    [[nodiscard]] bool uses_ppbr_export_data_directory_override() const;
    [[nodiscard]] bool platform_supports_sonarpen() const;
    void start_platform_sonarpen();
    [[nodiscard]] int default_canvas_resolution() const;
    [[nodiscard]] bool draws_canvas_tip_for_input(kEventSource source, kEventType type) const;
    [[nodiscard]] float adjust_canvas_input_pressure(float pressure) const;
    void pick_dir(std::function<void(std::string path)> callback);
    void display_file(std::string path);
    void share_file(std::string path);
    void request_app_close();
    [[nodiscard]] bool start_platform_vr_mode();
    void stop_platform_vr_mode();
    void attach_ui_thread();
    void detach_ui_thread();
    void acquire_render_context();
    void release_render_context();
    void present_render_context();
    void bind_default_render_target();
    void bind_main_render_target();
    void apply_render_platform_hints();
    void install_render_debug_callback();
    void begin_render_capture_frame();
    void end_render_capture_frame();
    [[nodiscard]] bool platform_deletes_recorded_files_on_clear();
    void clear_platform_recorded_files(std::string path);
    void publish_exported_image(std::string path);
    void flush_platform_storage();
    [[nodiscard]] std::vector<std::string> document_browse_roots() const;
    void save_platform_ui_state();
    [[nodiscard]] bool platform_enables_live_asset_reloading();
    void update_platform_frame(float delta_time_seconds);
    void report_rendered_frames(int frames);
    [[nodiscard]] VrSessionSnapshot vr_session_snapshot() const;
    void save_prepared_file(
        std::string path,
        std::string suggested_name,
        std::function<void(const std::string& path, bool saved)> callback);
    void set_platform_services(pp::platform::PlatformServices* services) noexcept;
    [[nodiscard]] pp::platform::PlatformServices* platform_services() const noexcept;
    [[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths();
    void showKeyboard();
    void hideKeyboard();
    void initLog();
    void init();
    void initShaders();
    void initAssets();
    void initLayout();
    void open_document(std::string path);
    void create();
    bool request_close();
    void terminate();
    void clear();
    void tick(float dt);
    void draw(float dt);
    void update(float dt);
    bool update_ui_observer(Node* n);
    bool vr_start();
    void vr_stop();
    void vr_update(float dt);
    void vr_draw(const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose);
    void vr_analog(const VRController& c, VRController::kButton b, VRController::kAction a, glm::vec2 force);
    void vr_digital(const VRController& c, VRController::kButton b, VRController::kAction a, glm::vec2 axis);
    void vr_draw_ui();
    void async_start();
    void async_redraw();
    void async_swap();
    void async_end();
    void resize(float w, float h);
    void show_cursor();
    void hide_cursor();
    bool mouse_down(int button, float x, float y, float pressure, kEventSource source, bool eraser);
    bool mouse_move(float x, float y, float pressure, kEventSource source, bool eraser);
    bool mouse_up(int button, float x, float y, kEventSource source, bool eraser);
    bool mouse_scroll(float x, float y, float delta);
    bool mouse_cancel(int button);
    bool gesture_start(const glm::vec2& p0, const glm::vec2& p1);
    bool gesture_move(const glm::vec2& p0, const glm::vec2& p1);
    bool gesture_end();
    bool touch_tap(const glm::vec2& pos, int fingers, int tap_count);
    bool key_down(kKey key);
    bool key_up(kKey key);
    bool key_char(char key);
    void toggle_ui();
    void set_stylus();

    void renderdoc_frame_start();
    void renderdoc_frame_end();

    void rec_clear();
    void rec_loop();
    void rec_start();
    void rec_stop();
    void rec_export(std::string path);
    
    void init_toolbar_main();
    void init_toolbar_draw();
    void init_sidebar();
    void init_menu_file();
    void init_menu_edit();
    void init_menu_layer();
    void init_menu_tools();
    void init_menu_about();
    void dialog_usermanual();
    void dialog_changelog();
    void dialog_about();
    void save_document(pp::app::DocumentSaveIntent intent);
    void continue_document_workflow_after_optional_save(std::function<void()> action);
    void dialog_newdoc();
    void dialog_save();
    void dialog_save_ver();
    void dialog_open();
    void dialog_browse();
    void dialog_export(std::string ext);
    void dialog_export_layers();
    void dialog_export_anim_frames();
    void dialog_export_depth();
    void dialog_export_cube_faces();
    void dialog_layer_rename();
    void dialog_resize();
    void dialog_preset_download();
    void dialog_ppbr_export();
    void dialog_export_mp4();
    void dialog_timelapse_export();
    void dialog_whatsnew(bool force_show);
    void dialog_shortcuts();

    void cloud_upload();
    void cloud_upload_all();
    void cloud_browse();
    bool check_license();
    
    std::shared_ptr<NodeProgressBar> show_progress(const std::string& title, int total = 0);
    std::shared_ptr<NodeMessageBox> message_box(const std::string& title, 
        const std::string& text, bool cancel_button = false);
    std::shared_ptr<NodeInputBox> input_box(const std::string& title, 
        const std::string& field_name, const std::string& ok_caption = "Ok");

    void brush_update(bool update_color, bool update_brush);
    void title_update();
    void update_memory_usage(size_t bytes);
    void update_rec_frames();
    int res_from_index(int i);
    int res_to_index(int res);
    std::string res_to_string(int res);
    void crash_test();
    void stacktrace();

    void ui_save();
    void ui_restore();
    void set_ui_rtl(bool rtl);
    bool get_ui_rtl() const;
    void set_ui_scale(float scale);

    void cmd_convert(std::string pano_path, std::string out_path);

    AppRuntime& runtime() noexcept { return runtime_; }
    const AppRuntime& runtime() const noexcept { return runtime_; }

    //////////////////////////////////////////////////////////////////////////
    // RENDER THREAD
    //////////////////////////////////////////////////////////////////////////

    void render_thread_tick();
    void render_thread_main();
    void render_thread_start();
    void render_thread_stop();

    bool is_render_thread()
    {
        return runtime_.is_render_thread();
    }

    template<typename T>
    std::future<void> render_task_async(T task, bool unique = false)
    {
        return runtime_.render_task_async(std::move(task), unique);
    }

    template<typename T>
    void render_task(T task)
    {
        runtime_.render_task(std::move(task));
    }

    void render_sync()
    {
        runtime_.render_sync();
    }

    //////////////////////////////////////////////////////////////////////////
    // UI THREAD
    //////////////////////////////////////////////////////////////////////////

    void ui_thread_tick();
    void ui_thread_main();
    void ui_thread_start();
    void ui_thread_stop();

    bool is_ui_thread()
    {
        return runtime_.is_ui_thread();
    }

    template<typename T>
    std::future<void> ui_task_async(T task, bool unique = false)
    {
        return runtime_.ui_task_async(std::move(task), unique);
    }

    template<typename T>
    void ui_task(T task)
    {
        runtime_.ui_task(std::move(task));
        if (runtime_.request_redraw())
            redraw = true;
        runtime_.clear_request_redraw();
    }

    void ui_sync()
    {
        runtime_.ui_sync();
    }

private:
    AppRuntime runtime_;
};
