#include "pch.h"
#include "app.h"
#include "action.h"
#include "app_core/document_layer.h"
#include "app_core/document_resize.h"
#include "app_core/document_export.h"
#include "app_core/document_session.h"
#include "settings.h"
#include "node_dialog_open.h"
#include "node_dialog_browse.h"
#include "node_dialog_resize.h"
#include "node_dialog_cloud.h"
#include "node_about.h"
#include "node_changelog.h"
#include "node_usermanual.h"
#include "node_dialog_export_ppbr.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"

#include <codec_api.h>
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>

#ifdef __QUEST__
#include "oculus_vr.h"
#elif __WEB__
void webgl_pick_file(std::function<void(std::string)> callback);
void webgl_sync();
#endif

namespace {

[[nodiscard]] bool can_start_document_export(App& app, bool requires_license)
{
    const auto decision = pp::app::plan_document_export_start(
        requires_license,
        !requires_license || app.check_license(),
        app.canvas != nullptr);

    switch (decision) {
    case pp::app::DocumentExportStartDecision::start_now:
        return true;
    case pp::app::DocumentExportStartDecision::show_license_disabled:
        app.message_box("License", "This function is disabled in demo mode.");
        return false;
    case pp::app::DocumentExportStartDecision::unavailable_no_canvas:
        return false;
    }

    return false;
}

}

std::shared_ptr<NodeProgressBar> App::show_progress(const std::string& title, int total /*= 0*/)
{
    auto pb = std::make_shared<NodeProgressBar>();
    pb->set_manager(&layout);
    pb->init();
    pb->create();
    pb->loaded();
    pb->m_progress->SetWidthP(0);
    pb->m_title->set_text(title.c_str());
    pb->m_total = total;
    pb->m_count = 0;
    layout[main_id]->add_child(pb);
    return pb;
}

std::shared_ptr<NodeMessageBox> App::message_box(const std::string &title, const std::string& text, bool cancel_button)
{
    auto m = std::make_shared<NodeMessageBox>();
    m->set_manager(&layout);
    m->init();
    m->create();
    m->loaded();
    m->m_title->set_text(title.c_str());
    m->m_message->set_text(text.c_str());
    m->btn_ok->m_text->set_text("Ok");
    if (!cancel_button)
        m->btn_cancel->destroy();
    layout[main_id]->add_child(m);
    return m;
}

std::shared_ptr<NodeInputBox> App::input_box(const std::string& title, 
    const std::string& field_name, const std::string& ok_caption /*= "Ok"*/)
{
    auto m = std::make_shared<NodeInputBox>();
    m->set_manager(&layout);
    m->init();
    m->create();
    m->loaded();
    m->m_title->set_text(title.c_str());
    m->m_field_name->set_text(field_name.c_str());
    m->btn_ok->m_text->set_text(ok_caption.c_str());
    layout[main_id]->add_child(m);
    return m;
}

void App::dialog_usermanual()
{
    auto dialog = std::make_shared<NodeUserManual>();
    dialog->set_manager(&layout);
    dialog->init();
    dialog->create();
    dialog->loaded();

    layout[main_id]->add_child(dialog);
}

void App::dialog_changelog()
{
    auto dialog = std::make_shared<NodeChangelog>();
    dialog->set_manager(&layout);
    dialog->init();
    dialog->create();
    dialog->loaded();

    layout[main_id]->add_child(dialog);
}

void App::dialog_about()
{
    auto dialog = std::make_shared<NodeAbout>();
    dialog->set_manager(&layout);
    dialog->init();
    dialog->create();
    dialog->loaded();

    layout[main_id]->add_child(dialog);
}

void App::continue_document_workflow_after_optional_save(std::function<void()> action)
{
    const bool has_canvas = canvas != nullptr;
    const bool has_unsaved_changes = has_canvas && Canvas::I->m_unsaved;
    const auto decision = pp::app::plan_document_workflow(has_canvas, has_unsaved_changes);
    switch (decision) {
    case pp::app::DocumentWorkflowDecision::unavailable:
        return;
    case pp::app::DocumentWorkflowDecision::continue_now:
        action();
        return;
    case pp::app::DocumentWorkflowDecision::prompt_save_before_continue:
        break;
    }

    auto m = layout[main_id]->add_child<NodeMessageBox>();
    m->m_title->set_text("Unsaved document");
    m->m_message->set_text("Would you like to save this document before closing?");
    m->btn_ok->m_text->set_text("Yes");
    m->btn_cancel->m_text->set_text("No");
    m->btn_ok->on_click = [this, m, action](Node*) {
        Canvas::I->project_save([this, m, action](bool success) {
            if (success)
                action();
            else
                message_box("Saving Error", "There was a problem saving the document");
        });
        m->destroy();
    };
    m->btn_cancel->on_click = [m, action](Node*) {
        action();
        m->destroy();
    };
}

void App::dialog_newdoc()
{
    auto show_dialog = [this] {
        auto dialog = std::make_shared<NodeDialogNewDoc>();
        dialog->set_manager(&layout);
        dialog->init();
        dialog->create();
        dialog->loaded();
        dialog->input->set_text("name");

        layout[main_id]->add_child(dialog);

        App::I->showKeyboard();

        dialog->btn_ok->on_click = [this, dialog](Node*)
        {
            std::string name = dialog->input->m_text;
            const auto plan = pp::app::plan_new_document(
                work_path,
                name,
                dialog->m_resolution->m_current_index,
                [](const std::string& path) {
                    return Asset::exist(path);
                });
            if (!plan)
            {
                const bool missing_name =
                    plan.status().code == pp::foundation::StatusCode::invalid_argument;
                message_box(
                    "Warning",
                    missing_name ? "You need to specify a name to file." : plan.status().message);
                return;
            }

            auto action = [this, dialog, plan = plan.value()] {
                doc_name = plan.target.name;
                doc_path = plan.target.path;
                doc_filename = plan.target.name + ".ppi";
                doc_dir = plan.target.directory;

                layers->clear();
                canvas->m_canvas->m_layers.clear();
                canvas->m_canvas->resize(plan.resolution, plan.resolution);
                canvas->reset_camera();
                ActionManager::clear();

                layers->add_layer("Default", false, true);

                canvas->m_canvas->m_unsaved = true;
                canvas->m_canvas->m_newdoc = false;
                title_update();

                dialog->destroy();
                App::I->hideKeyboard();
            };

            if (plan.value().write_decision == pp::app::DocumentFileWriteDecision::prompt_overwrite)
            {
                // ask confirm is file already exist
                auto msgbox = new NodeMessageBox();
                msgbox->set_manager(&layout);
                msgbox->init();
                msgbox->m_title->set_text("Warning");
                msgbox->m_message->set_text("A document with this name already exists, continue?");
                msgbox->btn_ok->on_click = [this, msgbox, action](Node*) {
                    action();
                    msgbox->destroy();
                };
                layout[main_id]->add_child(msgbox);
            }
            else
            {
                action();
            }

        };
        dialog->btn_cancel->on_click = [this, dialog](Node*)
        {
            dialog->destroy();
            App::I->hideKeyboard();
        };
    };

    continue_document_workflow_after_optional_save(show_dialog);
}

// DEPRECATED
void App::dialog_open()
{
    auto show_dialog = [this] {
        // load thumbnail test
        auto dialog = std::make_shared<NodeDialogOpen>();
        dialog->set_manager(&layout);
        dialog->init();
        dialog->create();
        dialog->loaded();

        layout[main_id]->add_child(dialog);

        dialog->btn_ok->on_click = [this, dialog](Node*)
        {
//            canvas->reset_camera();
//            layers->clear();
//            doc_name = dialog->selected_name;
//            canvas->m_canvas->project_open(dialog->selected_path, [this](bool success) {
//                // on complete
//                async_start();
//                title_update();
//                for (auto& i : canvas->m_canvas->m_order)
//                    layers->add_layer(canvas->m_canvas->m_layers[i]->m_name.c_str());
//                async_end();
//            });
//            dialog->destroy();
//            ActionManager::clear();
        };
    };

    continue_document_workflow_after_optional_save(show_dialog);
}

void App::dialog_browse()
{
    auto show_dialog = [this] {
        // load thumbnail test
        auto dialog = std::make_shared<NodeDialogBrowse>();
        dialog->set_manager(&layout);
#ifdef __IOS__
        dialog->search_paths = {work_path, data_path + "/Inbox"};
#else
        dialog->search_paths = {work_path};
#endif
        dialog->init();
        dialog->create();
        dialog->loaded();

        layout[main_id]->add_child(dialog);

        dialog->btn_ok->on_click = [this, dialog](Node*)
        {
            if (dialog->is_selected())
            {
                open_document(dialog->selected_path);
                dialog->destroy();
            }
        };
    };

    continue_document_workflow_after_optional_save(show_dialog);
}

void App::dialog_save_ver()
{
    if (!check_license())
    {
        message_box("License", "This function is disabled in demo mode.");
        return;
    }

    const auto target = pp::app::find_next_document_version_target(
        doc_dir,
        doc_name,
        [](const std::string& path) {
            return Asset::exist(path);
        });
    if (!target) {
        message_box("Saving Error", target.status().message);
        return;
    }

    doc_name = target.value().name;
    doc_path = target.value().path;
    canvas->m_canvas->m_unsaved = true;
    title_update();
    canvas->m_canvas->project_save(doc_path);
}

void App::save_document(pp::app::DocumentSaveIntent intent)
{
    const auto decision = pp::app::plan_document_save(
        Canvas::I->m_newdoc,
        Canvas::I->m_unsaved,
        intent);
    switch (decision) {
    case pp::app::DocumentSaveDecision::show_save_dialog:
        dialog_save();
        break;
    case pp::app::DocumentSaveDecision::save_existing:
        Canvas::I->project_save();
        break;
    case pp::app::DocumentSaveDecision::save_version:
        dialog_save_ver();
        break;
    case pp::app::DocumentSaveDecision::no_op:
        break;
    }
}

void App::dialog_save()
{
    if (!check_license())
    {
        message_box("License", "This function is disabled in demo mode.");
        return;
    }
    
    if (canvas)
    {
        auto dialog = std::make_shared<NodeDialogSave>();
        dialog->set_manager(&layout);
        dialog->init();
        dialog->create();
        dialog->loaded();
        dialog->input->set_text(doc_name);

        App::I->showKeyboard();

        dialog->btn_ok->on_click = [this, dialog](Node*)
        {
            std::string name = dialog->input->m_text;
            const auto plan = pp::app::plan_document_file_save(
                work_path,
                name,
                [](const std::string& path) {
                    return Asset::exist(path);
                });
            if (!plan)
            {
                message_box("Warning", "You need to specify a name to file.");
                return;
            }

            auto action = [this, dialog, plan = plan.value()] {
                canvas->m_canvas->project_save(plan.target.path);
                doc_name = plan.target.name;
                doc_path = plan.target.path;
                doc_dir = plan.target.directory;
                title_update();
                dialog->destroy();
                App::I->hideKeyboard();
            };

            if (plan.value().write_decision == pp::app::DocumentFileWriteDecision::prompt_overwrite)
            {
                // ask confirm is file already exist
                auto msgbox = new NodeMessageBox();
                msgbox->set_manager(&layout);
                msgbox->init();
                msgbox->m_title->set_text("Warning");
                msgbox->m_message->set_text(("Are you sure you want to overwrite " + plan.value().target.name + "?").c_str());
                msgbox->btn_ok->on_click = [this, msgbox, action](Node*) {
                    action();
                    msgbox->destroy();
                };
                layout[main_id]->add_child(msgbox);
            }
            else
            {
                action();
            }
        };
        dialog->btn_cancel->on_click = [this, dialog](Node*)
        {
            dialog->destroy();
            App::I->hideKeyboard();
        };

        layout[main_id]->add_child(dialog);
    }
}

void App::dialog_export(std::string ext)
{
    if (!can_start_document_export(*this, true))
        return;

    // TODO: use picker
    const auto target = pp::app::make_document_export_file_target(work_path, doc_name, ext);
    if (!target) {
        message_box("Export Equirectangular", target.status().message);
        return;
    }

    canvas->m_canvas->export_equirectangular(target.value().path, [this, target = target.value()]{
#if defined(__IOS__)
        message_box("Export Equirectangular", "Image exported to Photos");
#elif defined(__OSX__)
        message_box("Export Equirectangular", "Image exported to Pictures/PanoPainter folder");
#elif defined(_WIN32)
        message_box("Export Equirectangular", "Image exported to " + work_path);
#elif defined(__QUEST__)
        //auto result = ovr_Media_ShareToFacebook("Sharing from PanoPainter on Oculus Quest", path.c_str(), ovrMediaContentType_Photo);
#elif __WEB__
        ui_task([=]{
            save_prepared_file(target.path, target.suggested_name, [](const std::string&, bool) { });
        });
#endif
    });
}

void App::dialog_export_layers()
{
    if (!can_start_document_export(*this, true))
        return;

#if defined(__IOS__)
    const auto target = pp::app::make_document_export_collection_target(work_path, doc_name, "_layers");
    if (!target) {
        message_box("Export Layers", target.status().message);
        return;
    }

    if (Asset::create_dir(target.value().directory))
    {
        canvas->m_canvas->export_layers(target.value().stem_path, [this] {
            message_box("Export Layers", "Image layers exported to Files/PanoPainter");
        });
    }
#else
    pick_dir([this](std::string path) {
        const auto target = pp::app::make_document_export_stem_target(path, doc_name);
        if (!target) {
            message_box("Export Layers", target.status().message);
            return;
        }

        canvas->m_canvas->export_layers(target.value().stem_path, [this, target = target.value()] {
            message_box("Export Layers", "Layers exported to: " + target.stem_path);
        });
    });
#endif
}

void App::dialog_export_anim_frames()
{
    if (!can_start_document_export(*this, true))
        return;

#if defined(__IOS__)
    const auto target = pp::app::make_document_export_collection_target(work_path, doc_name, "_frames");
    if (!target) {
        message_box("Export Layers", target.status().message);
        return;
    }

    if (Asset::create_dir(target.value().directory))
    {
        canvas->m_canvas->export_anim_frames(target.value().stem_path, [this] {
            message_box("Export Layers", "Image layers exported to Files/PanoPainter");
        });
    }
#else
    pick_dir([this](std::string path) {
        const auto target = pp::app::make_document_export_stem_target(path, doc_name);
        if (!target) {
            message_box("Export Layers", target.status().message);
            return;
        }

        canvas->m_canvas->export_anim_frames(target.value().stem_path, [this, target = target.value()] {
            message_box("Export Layers", "Layers exported to: " + target.stem_path);
        });
    });
#endif
}

void App::dialog_export_depth()
{
    if (!can_start_document_export(*this, true))
        return;

    // TODO: use picker
    canvas->m_canvas->export_depth(doc_name, [this] {
#if defined(__IOS__)
        message_box("Export 3D View + Depth", "Image and depth exported to Files/PanoPainter");
#elif defined(__OSX__)
        message_box("Export 3D View + Depth", "Image and depth exported to Pictures/PanoPainter folder");
#elif defined(_WIN32)
        message_box("Export 3D View + Depth", "Image and depth exported to " + work_path);
#endif
    });
}

void App::dialog_resize()
{
    class LegacyDocumentResizeServices final : public pp::app::DocumentResizeServices {
    public:
        explicit LegacyDocumentResizeServices(App& app) noexcept
            : app_(app)
        {
        }

        void resize_document(int width, int height) override
        {
            if (app_.canvas)
                app_.canvas->m_canvas->resize(width, height);
        }

        void update_title() override
        {
            app_.title_update();
        }

        void clear_history() override
        {
            ActionManager::clear();
        }

    private:
        App& app_;
    };

    auto dialog = std::make_shared<NodeDialogResize>();
    dialog->set_manager(&layout);
    dialog->init();
    dialog->create();
    dialog->loaded();

    layout[main_id]->add_child(dialog);

    dialog->btn_ok->on_click = [this,dialog](Node*)
    {
        const auto plan = pp::app::plan_document_resize(
            dialog->combo ? dialog->combo->m_current_index : 0);
        if (!plan)
        {
            dialog->destroy();
            return;
        }
        LegacyDocumentResizeServices services(*this);
        const auto status = pp::app::execute_document_resize_plan(plan.value(), services);
        if (!status.ok())
            LOG("Document resize failed: %s", status.message);
        dialog->destroy();
    };
}

void App::dialog_export_cube_faces()
{
    if (!can_start_document_export(*this, false))
        return;

    canvas->m_canvas->export_cube_faces(doc_name, [this] {
#if defined(__IOS__)
        message_box("Export Cube Faces", "Image and depth exported to Files/PanoPainter");
#elif defined(__OSX__)
        message_box("Export Cube Faces", "Image and depth exported to Pictures/PanoPainter folder");
#elif defined(_WIN32)
        message_box("Export Cube Faces", "Image and depth exported to " + work_path);
#endif
    });
}

void App::dialog_layer_rename()
{
    auto dialog = std::make_shared<NodeDialogLayerRename>();
    dialog->set_manager(&layout);
    dialog->init();
    dialog->create();
    dialog->loaded();
    dialog->input->set_text(layers->m_current_layer->m_label_text);

    App::I->showKeyboard();

    layout[main_id]->add_child(dialog);

    dialog->btn_ok->on_click = [this,dialog](Node*)
    {
        const auto old_name = layers->m_current_layer->m_label_text;
        const auto plan = pp::app::plan_document_layer_rename(old_name, dialog->get_name());
        if (!plan)
            return;
        if (plan.value().action == pp::app::DocumentLayerRenameAction::no_op_same_name)
        {
            dialog->destroy();
            App::I->hideKeyboard();
            return;
        }

        struct ActionLayerRename : public Action
        {
            std::string m_old_name;
            std::string m_new_name;
            bool m_unsaved;
            Layer* m_layer;
            std::shared_ptr<NodeLayer> m_layer_node;
            ActionLayerRename(std::string old_name, std::string new_name, std::shared_ptr<NodeLayer> layer_node, Layer* layer) :
                m_old_name(old_name), m_new_name(new_name), m_layer_node(layer_node), m_layer(layer) { }
            virtual void run() override { }
            virtual size_t memory() override { return 0; }
            virtual void undo() override
            {
                m_layer_node->set_name(m_old_name.c_str());
                m_layer->m_name = m_old_name;
            }
            virtual Action* get_redo() override
            {
                return new ActionLayerRename(m_new_name, m_old_name, m_layer_node, m_layer);
            }
        };
        auto layer_node = std::static_pointer_cast<NodeLayer>(layers->m_current_layer->shared_from_this());
        auto* layer = canvas->m_canvas->m_layers[canvas->m_canvas->m_current_layer_idx].get();
        ActionManager::add(new ActionLayerRename(
            plan.value().old_name,
            plan.value().new_name,
            layer_node,
            layer));
        layer_node->set_name(plan.value().new_name.c_str());
        layer->m_name = plan.value().new_name;
        dialog->destroy();
        App::I->hideKeyboard();
    };
    dialog->btn_cancel->on_click = [this, dialog](Node*)
    {
        dialog->destroy();
        App::I->hideKeyboard();
    };
}

void App::dialog_preset_download()
{

}

void App::dialog_ppbr_export()
{
    auto root = layout[main_id];
    auto dialog = root->add_child_ref<NodeDialogExportPPBR>();
    dialog->btn_ok->on_click = [this, dialog] (Node*) {
        NodePanelBrushPreset::PPBRInfo info;
        info.author = dialog->txt_author->m_text;
        info.url = dialog->txt_url->m_text;
        info.email = dialog->txt_email->m_text;
        info.descr = dialog->txt_descr->m_text;
        info.header_image = dialog->m_header_image;
        info.dest_path = dialog->m_dest_path;
        if (dialog->export_check)
            info.export_data = dialog->export_check->checked;
#if __IOS__ || __WEB__
        App::I->pick_file_save("ppbr", "exported-brushes",
            [this, dialog, info] (std::string path) {
                presets->export_ppbr(path, info);
            },
            [dialog] (const std::string& path, bool saved) {
                if (saved)
                    dialog->destroy();
            }
        );
#else
        App::I->pick_file_save({ "ppbr" }, [this, dialog, info] (std::string path) {
            std::thread([this, path, dialog, info] {
                BT_SetTerminate();
                presets->export_ppbr(path, info);
                dialog->destroy();
                App::I->message_box("Export PPBR", "Brushes exported to:\n" + path);
            }).detach();
        });
#endif
    };
}

void App::dialog_timelapse_export()
{
    if (!can_start_document_export(*this, false))
        return;

#if __IOS__ || __WEB__
    const auto target = pp::app::make_document_export_suggested_name(doc_name, "-timelapse");
    if (!target) {
        message_box("Export Timelapse", target.status().message);
        return;
    }

    pick_file_save("mp4", target.value().name,
        [this](std::string path) {
            rec_export(path);
        },
        [this](const std::string& path, bool saved) {
            message_box("Export Timelapse", "Timelapse exported successfully.");
        }
    );
#else
    pick_file_save({ "mp4" }, [this](std::string path) {
        std::thread([this, path] {
            BT_SetTerminate();
            rec_export(path);
            message_box("Export Timelapse", "Timelapse exported to: " + path);
        }).detach();
    });
#endif
}

void App::dialog_export_mp4()
{
    if (!can_start_document_export(*this, false))
        return;

#if __IOS__ || __WEB__
    const auto target = pp::app::make_document_export_suggested_name(doc_name, "-animation");
    if (!target) {
        message_box("Export Animation", target.status().message);
        return;
    }

    pick_file_save("mp4", target.value().name,
        [this](std::string path) {
            export_anim_mp4(path);
        },
        [this](const std::string& path, bool saved) {
            message_box("Export Animation", "Animation exported successfully.");
        }
    );
#else
    pick_file_save({ "mp4" }, [this](std::string path) {
        Canvas::I->export_anim_mp4(path, [this, path] {
            message_box("Export Animation", "Animation exported to: " + path);
        });
    });
#endif
}

void App::dialog_whatsnew(bool force_show)
{
    auto whatsnew = std::make_shared<NodeRemotePage>();
    whatsnew->m_manager = &layout;
    whatsnew->init();
    std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build);
    whatsnew->load_url(url, [this, whatsnew, force_show](bool success) {
        if (success)
        {
            int last_id = Settings::value_or<Serializer::Integer>("whatsnew-id", 0);
            if (force_show || (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id))
            {
                whatsnew->set_title(fmt::format("What's new in version {}", g_version_number));
                if (!force_show)
                    layout[main_id]->add_child(whatsnew);
            }
        }
    });
    whatsnew->add_button("Reload", 120, [this, whatsnew](Node*) {
        whatsnew->reload();
    });
    whatsnew->add_button("Read Later", 120, [this, whatsnew](Node*) {
        Settings::unset("whatsnew-id");
        Settings::save();
        whatsnew->destroy();
    });
    whatsnew->add_button("Close", 100, [this, whatsnew](Node*) {
        Settings::set<Serializer::Integer>("whatsnew-id", whatsnew->m_page_id);
        Settings::save();
        whatsnew->destroy();
    });
    if (force_show)
        layout[main_id]->add_child(whatsnew);
}

void App::dialog_shortcuts()
{
    layout[main_id]->add_child<NodeShortcuts>();
}
