#include "pch.h"
#include "app.h"
#include "app_core/app_dialog.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 "legacy_document_canvas_services.h"
#include "legacy_app_dialog_services.h"
#include "legacy_brush_package_export_services.h"
#include "legacy_document_export_services.h"
#include "legacy_document_layer_services.h"
#include "legacy_document_session_services.h"
#include "legacy_preference_storage.h"
#include "legacy_ui_overlay_services.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>

#include <utility>

#ifdef __QUEST__
#include "oculus_vr.h"
#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:
    {
        const auto plan = pp::app::plan_document_export_license_disabled_dialog();
        app.message_box(plan.title, plan.message, plan.show_cancel);
        return false;
    }
    case pp::app::DocumentExportStartDecision::unavailable_no_canvas:
        return false;
    }

    return false;
}

void start_document_export_collection(
    App& app,
    pp::app::DocumentExportCollectionKind kind)
{
    const auto plan = pp::app::plan_document_export_collection_target(
        kind,
        app.uses_work_directory_document_export_collections());
    const auto success_kind = pp::app::document_export_collection_success_kind(kind);

    if (plan.destination == pp::app::DocumentExportCollectionDestination::work_directory_collection) {
        const auto target = pp::app::make_document_export_collection_target(
            app.work_path,
            app.doc_name,
            plan.suffix);
        if (!target) {
            const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message);
            app.message_box(dialog.title, dialog.message, dialog.show_cancel);
            return;
        }

        const auto status = pp::panopainter::execute_legacy_document_export_collection(
            app,
            plan.kind,
            target.value());
        if (!status.ok())
            LOG(
                "%s: %s",
                pp::app::document_export_execution_log_message(
                    kind == pp::app::DocumentExportCollectionKind::layers
                        ? pp::app::DocumentExportExecutionKind::layers_collection
                        : pp::app::DocumentExportExecutionKind::animation_frames_collection),
                status.message);
        return;
    }

    app.pick_dir([
        &app,
        kind = plan.kind,
        success_kind
    ](std::string path) {
        const auto target = pp::app::make_document_export_stem_target(path, app.doc_name);
        if (!target) {
            const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message);
            app.message_box(dialog.title, dialog.message, dialog.show_cancel);
            return;
        }

        const auto status = pp::panopainter::execute_legacy_document_export_stem(
            app,
            kind,
            target.value());
        if (!status.ok())
            LOG(
                "%s: %s",
                pp::app::document_export_execution_log_message(
                    kind == pp::app::DocumentExportCollectionKind::layers
                        ? pp::app::DocumentExportExecutionKind::layers_stem
                        : pp::app::DocumentExportExecutionKind::animation_frames_stem),
                status.message);
    });
}

}

std::shared_ptr<NodeProgressBar> App::show_progress(const std::string& title, int total /*= 0*/)
{
    const auto plan = pp::app::plan_app_progress_dialog(title, total);
    const auto dialogs = pp::panopainter::make_legacy_app_dialog_factory(*this);
    return pp::panopainter::legacy_progress_dialog_node(dialogs->show_progress_dialog(plan));
}

std::shared_ptr<NodeMessageBox> App::message_box(const std::string &title, const std::string& text, bool cancel_button)
{
    const auto plan = pp::app::plan_app_message_dialog(title, text, cancel_button);
    const auto dialogs = pp::panopainter::make_legacy_app_dialog_factory(*this);
    return pp::panopainter::legacy_message_dialog_node(dialogs->show_message_dialog(plan));
}

std::shared_ptr<NodeInputBox> App::input_box(const std::string& title, 
    const std::string& field_name, const std::string& ok_caption /*= "Ok"*/)
{
    const auto plan_result = pp::app::plan_app_input_dialog(title, field_name, ok_caption);
    if (!plan_result) {
        LOG("input dialog skipped: %s", plan_result.status().message);
        return nullptr;
    }
    const auto dialogs = pp::panopainter::make_legacy_app_dialog_factory(*this);
    return pp::panopainter::legacy_input_dialog_node(dialogs->show_input_dialog(plan_result.value()));
}

void App::dialog_usermanual()
{
    (void)pp::panopainter::add_legacy_overlay_node<NodeUserManual>(*this);
}

void App::dialog_changelog()
{
    (void)pp::panopainter::add_legacy_overlay_node<NodeChangelog>(*this);
}

void App::dialog_about()
{
    (void)pp::panopainter::add_legacy_overlay_node<NodeAbout>(*this);
}

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);
    const auto status = pp::panopainter::execute_legacy_document_workflow_decision(
        *this,
        decision,
        std::move(action));
    if (!status.ok())
        LOG("Document workflow action failed: %s", status.message);
}

void App::dialog_newdoc()
{
    auto show_dialog = [this] {
        auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogNewDoc>(*this);
        dialog->input->set_text("name");

        (void)pp::panopainter::attach_legacy_overlay_node(*this, 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;
            }

            const auto status = pp::panopainter::execute_legacy_new_document_plan(*this, plan.value(), dialog);
            if (!status.ok())
                LOG("New document action failed: %s", status.message);

        };
        dialog->btn_cancel->on_click = [this, dialog](Node*)
        {
            pp::panopainter::close_legacy_dialog_and_hide_keyboard(*this, *dialog);
        };
    };

    continue_document_workflow_after_optional_save(show_dialog);
}

// DEPRECATED
void App::dialog_open()
{
    auto show_dialog = [this] {
        // load thumbnail test
        auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogOpen>(*this);

        (void)pp::panopainter::attach_legacy_overlay_node(*this, 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->search_paths = document_browse_roots();
        pp::panopainter::initialize_legacy_overlay_node(*this, *dialog);

        (void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);

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

    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;
    }

    const auto status = pp::panopainter::execute_legacy_document_version_save(*this, target.value());
    if (!status.ok())
        LOG("Document version save action failed: %s", status.message);
}

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);
    const auto status = pp::panopainter::execute_legacy_document_save_decision(*this, decision);
    if (!status.ok())
        LOG("Document save action failed: %s", status.message);
}

void App::dialog_save()
{
    if (!check_license())
    {
        message_box("License", "This function is disabled in demo mode.");
        return;
    }
    
    if (canvas)
    {
        auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogSave>(*this);
        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;
            }

            const auto status = pp::panopainter::execute_legacy_document_file_save_plan(*this, plan.value(), dialog);
            if (!status.ok())
                LOG("Document file save action failed: %s", status.message);
        };
        dialog->btn_cancel->on_click = [this, dialog](Node*)
        {
            pp::panopainter::close_legacy_dialog_and_hide_keyboard(*this, *dialog);
        };

        (void)pp::panopainter::attach_legacy_overlay_node(*this, 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) {
        const auto dialog = pp::app::plan_document_export_failure_dialog(
            pp::app::DocumentExportSuccessKind::equirectangular,
            target.status().message);
        message_box(dialog.title, dialog.message, dialog.show_cancel);
        return;
    }

    const auto status = pp::panopainter::execute_legacy_document_export_file(*this, target.value());
    if (!status.ok())
        LOG(
            "%s: %s",
            pp::app::document_export_execution_log_message(
                pp::app::DocumentExportExecutionKind::equirectangular_file),
            status.message);
}

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

    start_document_export_collection(
        *this,
        pp::app::DocumentExportCollectionKind::layers);
}

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

    start_document_export_collection(
        *this,
        pp::app::DocumentExportCollectionKind::animation_frames);
}

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

    const auto status = pp::panopainter::execute_legacy_document_export_depth(*this, doc_name);
    if (!status.ok())
        LOG(
            "%s: %s",
            pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::depth),
            status.message);
}

void App::dialog_resize()
{
    auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogResize>(*this);

    (void)pp::panopainter::attach_legacy_overlay_node(*this, 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)
        {
            pp::panopainter::close_legacy_dialog_node(*dialog);
            return;
        }
        const auto status = pp::panopainter::execute_legacy_document_resize_plan(*this, plan.value());
        if (!status.ok())
            LOG("Document resize failed: %s", status.message);
        pp::panopainter::close_legacy_dialog_node(*dialog);
    };
}

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

    const auto status = pp::panopainter::execute_legacy_document_export_cube_faces(*this, doc_name);
    if (!status.ok())
        LOG(
            "%s: %s",
            pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::cube_faces),
            status.message);
}

void App::dialog_layer_rename()
{
    auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogLayerRename>(*this);
    dialog->input->set_text(layers->m_current_layer->m_label_text);

    App::I->showKeyboard();

    (void)pp::panopainter::attach_legacy_overlay_node(*this, 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;
        const auto status = pp::panopainter::execute_legacy_document_layer_rename_plan(*this, plan.value(), dialog);
        if (!status.ok())
            LOG("Layer rename failed: %s", status.message);
    };
    dialog->btn_cancel->on_click = [this, dialog](Node*)
    {
        pp::panopainter::close_legacy_dialog_and_hide_keyboard(*this, *dialog);
    };
}

void App::dialog_preset_download()
{

}

void App::dialog_ppbr_export()
{
    auto dialog = pp::panopainter::add_legacy_overlay_node<NodeDialogExportPPBR>(*this);
    dialog->btn_ok->on_click = [this, dialog] (Node*) {
        const auto request = pp::panopainter::make_legacy_brush_package_export_request(*dialog);

        if (uses_prepared_file_writes())
        {
            pick_file_save("ppbr", "exported-brushes",
                [this, dialog, request] (std::string path) {
                    const auto status = pp::panopainter::execute_legacy_brush_package_export(
                        *this,
                        *dialog,
                        request,
                        path,
                        pp::panopainter::LegacyBrushPackageExportMode::inline_export_only);
                    if (!status.ok())
                        LOG("PPBR export failed: %s", status.message);
                },
                [dialog] (const std::string& path, bool saved) {
                    (void)path;
                    pp::panopainter::complete_legacy_brush_package_export(*dialog, saved);
                }
            );
            return;
        }

        pick_file_save({ "ppbr" }, [this, dialog, request] (std::string path) {
            const auto status = pp::panopainter::execute_legacy_brush_package_export(
                *this,
                *dialog,
                request,
                path,
                pp::panopainter::LegacyBrushPackageExportMode::desktop_async_close_and_message);
            if (!status.ok())
                LOG("PPBR export failed: %s", status.message);
        });
    };
}

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

    if (uses_prepared_file_writes())
    {
        const auto target = pp::app::make_document_export_suggested_name(doc_name, "-timelapse");
        if (!target) {
            const auto dialog = pp::app::plan_document_export_failure_dialog(
                pp::app::DocumentExportSuccessKind::timelapse,
                target.status().message);
            message_box(dialog.title, dialog.message, dialog.show_cancel);
            return;
        }

        pick_file_save("mp4", target.value().name,
            [this](std::string path) {
                const auto status = pp::panopainter::execute_legacy_document_video_export(
                    *this,
                    pp::app::DocumentVideoExportKind::timelapse,
                    path,
                    false);
                if (!status.ok())
                    LOG(
                        "%s: %s",
                        pp::app::document_export_execution_log_message(
                            pp::app::DocumentExportExecutionKind::timelapse),
                        status.message);
            },
            [](const std::string& path, bool saved) {
                (void)path;
                (void)saved;
            }
        );
        return;
    }

    pick_file_save({ "mp4" }, [this](std::string path) {
        const auto status = pp::panopainter::execute_legacy_document_video_export(
            *this,
            pp::app::DocumentVideoExportKind::timelapse,
            path,
            true);
        if (!status.ok())
            LOG(
                "%s: %s",
                pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::timelapse),
                status.message);
    });
}

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

    if (uses_prepared_file_writes())
    {
        const auto target = pp::app::make_document_export_suggested_name(doc_name, "-animation");
        if (!target) {
            const auto dialog = pp::app::plan_document_export_failure_dialog(
                pp::app::DocumentExportSuccessKind::animation_mp4,
                target.status().message);
            message_box(dialog.title, dialog.message, dialog.show_cancel);
            return;
        }

        pick_file_save("mp4", target.value().name,
            [this](std::string path) {
                const auto status = pp::panopainter::execute_legacy_document_video_export(
                    *this,
                    pp::app::DocumentVideoExportKind::animation_mp4,
                    path,
                    false);
                if (!status.ok())
                    LOG(
                        "%s: %s",
                        pp::app::document_export_execution_log_message(
                            pp::app::DocumentExportExecutionKind::animation_mp4),
                        status.message);
            },
            [](const std::string& path, bool saved) {
                (void)path;
                (void)saved;
            }
        );
        return;
    }

    pick_file_save({ "mp4" }, [this](std::string path) {
        const auto status = pp::panopainter::execute_legacy_document_video_export(
            *this,
            pp::app::DocumentVideoExportKind::animation_mp4,
            path,
            true);
        if (!status.ok())
            LOG(
                "%s: %s",
                pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::animation_mp4),
                status.message);
    });
}

void App::dialog_whatsnew(bool force_show)
{
    auto whatsnew = std::make_shared<NodeRemotePage>();
    whatsnew->set_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 = pp::panopainter::legacy_whatsnew_id_or(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)
                    (void)pp::panopainter::attach_legacy_overlay_node(*this, whatsnew);
            }
        }
    });
    whatsnew->add_button("Reload", 120, [this, whatsnew](Node*) {
        whatsnew->reload();
    });
    whatsnew->add_button("Read Later", 120, [this, whatsnew](Node*) {
        pp::panopainter::clear_legacy_whatsnew_id();
        pp::panopainter::save_legacy_preferences();
        pp::panopainter::close_legacy_dialog_node(*whatsnew);
    });
    whatsnew->add_button("Close", 100, [this, whatsnew](Node*) {
        pp::panopainter::set_legacy_whatsnew_id(whatsnew->m_page_id);
        pp::panopainter::save_legacy_preferences();
        pp::panopainter::close_legacy_dialog_node(*whatsnew);
    });
    if (force_show)
        (void)pp::panopainter::attach_legacy_overlay_node(*this, whatsnew);
}

void App::dialog_shortcuts()
{
    (void)pp::panopainter::add_legacy_overlay_node<NodeShortcuts>(*this);
}
