#include "viewer.h"
#include "imgui/imgui_stdlib.h"
#include "common/map/map_drawer.h"
#include "common/projection/reprojector.h"
#include "logger.h"
#include "resources.h"
#include "core/style.h"
#include "main_ui.h"
#include "common/image/image_utils.h"
#include "common/widgets/switch.h"
#include "common/widgets/stepped_slider.h"
#include "imgui/pfd/pfd_utils.h"

#include "common/image/meta.h"
#include "common/projection/projs2/proj_json.h"
#include "common/widgets/spinner.h"

#include "settings.h"
#include "common/widgets/json_editor.h"

namespace satdump
{
    int osm_url_regex_len = 0;
    float general_progress = 0;
    float general_sum = 1;
    float *progress_pointer = nullptr;

    void ViewerApplication::drawProjectionPanel()
    {
        bool disable_buttons = projections_are_generating;
        bool disable_add_layer = is_opening_layer;
        if (ImGui::CollapsingHeader("Projection", ImGuiTreeNodeFlags_DefaultOpen))
        {
            ImGui::Text("Output image : ");
            ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.40f);
            ImGui::InputInt("##width", &projections_image_width, 0);
            ImGui::SameLine();
            ImGui::Text(u8"\uea76");
            ImGui::SameLine();
            ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.40f);
            ImGui::InputInt("##height", &projections_image_height, 0);

            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            ImGui::SetNextItemWidth(ImGui::GetWindowWidth() * 0.96f);
            ImGui::Combo("##targetproj", &projections_current_selected_proj, "Equirectangular\0"
                                                                             "UTM (Mercator)\0"
                                                                             "Stereo\0"
                                                                             "Satellite (TPERS)\0"
                         //  "Azimuthal Equidistant\0"
            );

            if (projections_current_selected_proj == 0)
            {
                if (!projection_auto_mode)
                {
                    ImGui::Text("Top Left Coordinates :");
                    ImGui::InputFloat("Lat##tl", &projections_equirectangular_tl_lat);
                    ImGui::InputFloat("Lon##tl", &projections_equirectangular_tl_lon);
                    ImGui::Spacing();
                    ImGui::Text("Bottom Right Coordinates :");
                    ImGui::InputFloat("Lat##br", &projections_equirectangular_br_lat);
                    ImGui::InputFloat("Lon##br", &projections_equirectangular_br_lon);
                }
            }
            else if (projections_current_selected_proj == 1)
            {
                ImGui::InputInt("UTM Zone###projutmzone", &projections_utm_zone);
                if (projections_utm_zone > 60)
                    projections_utm_zone = 60;
                if (projections_utm_zone < 1)
                    projections_utm_zone = 1;
                ImGui::Checkbox("South###projutmsouth", &projections_utm_south);
                ImGui::InputFloat("Northing (m)##utm", &projections_utm_offset_y);
                ImGui::Spacing();
                ImGui::InputFloat("Scale (m/px)##utm", &projections_utm_scale);
            }
            else if (projections_current_selected_proj == 2)
            {
                ImGui::Text("Center Coordinates :");
                ImGui::InputFloat("Lat##stereo", &projections_stereo_center_lat);
                ImGui::InputFloat("Lon##stereo", &projections_stereo_center_lon);
                ImGui::Spacing();
                ImGui::InputFloat("Scale (m/px)##stereo", &projections_stereo_scale);
            }
            else if (projections_current_selected_proj == 3)
            {
                ImGui::Text("Center Coordinates :");
                ImGui::InputFloat("Lat##tpers", &projections_tpers_lat);
                ImGui::InputFloat("Lon##tpers", &projections_tpers_lon);
                ImGui::Spacing();
                ImGui::InputFloat("Altitude (m)##tpers", &projections_tpers_alt);
                ImGui::InputFloat("Angle##tpers", &projections_tpers_ang);
                ImGui::InputFloat("Azimuth##tpers", &projections_tpers_azi);
                ImGui::Spacing();
                ImGui::InputFloat("Scale##tpers", &projections_tpers_scale);
            }
            /*else if (projections_current_selected_proj == 4)
            {
                ImGui::Text("Center Coordinates :");
                ImGui::InputFloat("Lat##eqaz", &projections_azeq_lat);
                ImGui::InputFloat("Lon##eqaz", &projections_azeq_lon);
            }*/

            if (projections_current_selected_proj == 0 || projections_current_selected_proj == 2)
            {
                ImGui::Checkbox("Auto Mode###pojautomode", &projection_auto_mode);
                ImGui::Checkbox("Auto Scale Mode##projautoscalemode", &projection_auto_scale_mode);
                if (projection_auto_scale_mode)
                {
                    ImGui::InputDouble("Scale X (m/px)##projscalexauto", &projection_autoscale_x);
                    ImGui::InputDouble("Scale Y (m/px)##projscalexauto", &projection_autoscale_y);
                }
            }

            ImGui::Spacing();
            ImGui::Separator();
            ImGui::Spacing();

            if (disable_buttons || projection_layers.size() == 0)
                style::beginDisabled();
            if (ImGui::Button("Generate Projection"))
            {
                auto fun = [this](int)
                {
                    logger->info("Update projection...");
                    try
                    {
                        generateProjectionImage();
                    }
                    catch (std::exception &e)
                    {
                        logger->error("Lego should be happy!\n%s", e.what());
                        projections_are_generating = false;
                    }
                    logger->info("Done");
                };
                ui_thread_pool.push(fun);
            }
            if (projections_are_generating)
            {
                ImGui::SameLine();
                widgets::Spinner("###spinner1", ImGui::GetItemRectSize().y / 2 - ImGui::GetStyle().FramePadding.y, 3 * ui_scale, ImGui::GetColorU32(ImGuiCol_Text));
            }
            if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
            {
                if (projections_are_generating)
                    ImGui::SetTooltip("Generating, please wait...");
                if (projection_layers.size() == 0)
                    ImGui::SetTooltip("No layers loaded!");
            }

            ImGui::Spacing();

            if (ImGui::Button("Save Projected Image"))
            {
                ui_thread_pool.push([this](int)
                                    {   projections_are_generating = true;
                        logger->info("Saving Projection...");
                        std::string default_path = config::main_cfg["satdump_directories"]["default_projection_output_directory"]["value"].get<std::string>();
                        std::string saved_at = save_image_dialog("projection", default_path, "Save Projection", &projected_image_result, &viewer_app->save_type);

                        if (saved_at == "")
                            logger->info("Save cancelled");
                        else
                            logger->info("Saved current projection at %s", saved_at.c_str());
                        projections_are_generating = false; });
            }

            if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
            {
                if (projections_are_generating)
                    ImGui::SetTooltip("Generating, please wait...");
                if (projection_layers.size() == 0)
                    ImGui::SetTooltip("No layers loaded!");
            }
            if (disable_buttons || projection_layers.size() == 0)
                style::endDisabled();
        }
        if (ImGui::CollapsingHeader("Layers"))
        {
            ImGui::BeginGroup();
            ImGui::Text("Mode :  ");
            ImGui::SameLine();
            ImGui::Text("Blend");
            ImGui::SameLine();

            ImGui::SetNextItemWidth(50);
            ToggleButton("##projtog", &projections_mode_radio);
            ImGui::SameLine();
            ImGui::Text("Overlay");
            ImGui::EndGroup();

            ImGui::Separator(); //////////////////////////////////////////////////////

            ImGui::Text("Layers :");

            if (disable_add_layer)
                style::beginDisabled();

            ImGui::SameLine();
            ImGuiStyle &imguistyle = ImGui::GetStyle();
            ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - ((projections_loading_new_layer ? 16.0 * ui_scale + imguistyle.ItemSpacing.x : 0) +
                                                                   ImGui::CalcTextSize("Add Layer").x + imguistyle.FramePadding.x * 2.0));

            if (projections_loading_new_layer)
            {
                widgets::Spinner("###spinner1", 8 * ui_scale, 3 * ui_scale, ImGui::GetColorU32(ImGuiCol_Text));
                ImGui::SameLine();
            }

            if (ImGui::Button("Add Layer##button"))
                ImGui::OpenPopup("Add Layer##popup", ImGuiPopupFlags_None);

            if (disable_add_layer)
                style::endDisabled();

            {
                if (ImGui::BeginPopupModal("Add Layer##popup", NULL, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_AlwaysAutoResize))
                {
                    ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
                    ImGui::RadioButton("Equirectangular", &selected_external_type, 0);
                    ImGui::RadioButton("Tile Map (OSM)", &selected_external_type, 1);
                    ImGui::RadioButton("GeoTIFF", &selected_external_type, 2);
                    ImGui::RadioButton("Other", &selected_external_type, 3);

                    if (selected_external_type == 0 ||
                        selected_external_type == 2 ||
                        selected_external_type == 3)
                    {
                        ImGui::InputText("Name", &projection_new_layer_name);
                        projection_new_layer_file.draw("Input Image");
                        if (selected_external_type == 3)
                            projection_new_layer_cfg.draw("Projection Config File");
                        ImGui::Checkbox("Normalize###normalizeinput", &projection_normalize_image);
                    }
                    else if (selected_external_type == 1)
                    {
                        ImGui::InputDouble("Lat1##osmlat1", &projection_osm_lat1);
                        ImGui::InputDouble("Lon1##osmlat1", &projection_osm_lon1);
                        ImGui::InputDouble("Lat2##osmlat1", &projection_osm_lat2);
                        ImGui::InputDouble("Lon2##osmlat1", &projection_osm_lon2);
                        ImGui::SliderInt("Zoom##osmsliderzoom", &projection_osm_zoom, 0, 6);
                        if (!urlgood)
                            ImGui::PushStyleColor(ImGuiCol_Text, style::theme.red.Value);

                        ImGui::InputText("Tile URL", &mapurl, ImGuiInputTextFlags_None);

                        if (!urlgood)
                        {
                            if (ImGui::IsItemHovered())
                                ImGui::SetTooltip("Invalid URL! must be https://server/{z}/{x}/{y}.ext");
                            ImGui::PopStyleColor();
                        }
                    }

                    if (ImGui::Button("Add layer"))
                    {
                        auto func = [this](int)
                        {
                            LayerLoadingConfig cfg;
                            if (selected_external_type == 0)
                                cfg.type = "equirectangular";
                            else if (selected_external_type == 1)
                                cfg.type = "tilemap";
                            else if (selected_external_type == 2)
                                cfg.type = "geotiff";
                            else if (selected_external_type == 3)
                                cfg.type = "other";

                            cfg.file = projection_new_layer_file.getPath();
                            cfg.normalize = projection_normalize_image;
                            cfg.projfile = projection_new_layer_cfg.getPath();

                            if (selected_external_type == 1) // TODO: Move to reprojector backend
                            {
                                try
                                {
                                    logger->info("Generating tile map");
                                    image::Image timemap = downloadTileMap(mapurl, projection_osm_lat1, projection_osm_lon1, projection_osm_lat2, projection_osm_lon2, projection_osm_zoom);

                                    projection_layers.push_front({"Tile Map", timemap});
                                }
                                catch (std::exception &e)
                                {
                                    logger->error("Could not load tile map! %s", e.what());
                                    projections_loading_new_layer = false;
                                }
                            }
                            else
                            {
                                try
                                {
                                    ProjectionLayer newlayer = satdump::loadExternalLayer(cfg);
                                    newlayer.name = projection_new_layer_name;
                                    projection_layers.push_front(newlayer);
                                }
                                catch (std::exception &e)
                                {
                                    logger->error(e.what());
                                }
                            }

                            projections_loading_new_layer = false;
                        };

                        ui_thread_pool.push(func);
                        projections_loading_new_layer = true;

                        ImGui::CloseCurrentPopup();
                    }

                    ImGui::SameLine();

                    if (ImGui::Button("Cancel"))
                        ImGui::CloseCurrentPopup();

                    ImGui::EndPopup();
                }
            }

            if (ImGui::BeginListBox("##projectionslistbox", ImVec2(ImGui::GetWindowWidth(), 300 * ui_scale)))
            {
                for (int i = 0; i < (int)projection_layers.size(); i++)
                {
                    ImGui::PushID(i);
                    ImGui::BeginGroup();
                    ProjectionLayer &layer = projection_layers[i];
                    std::string label;
                    label = layer.name;
                    ImGui::SetCursorPos(ImVec2(ImGui::GetCursorPos().x, ImGui::GetCursorPos().y + 4 * ui_scale));
                    ImGui::PushTextWrapPos(ImGui::GetContentRegionMax().x - 70 * ui_scale);
                    ImGui::Text("%s", label.c_str());
                    ImGui::PopTextWrapPos();

                    int active_layers = 0;
                    for (auto &lay : projection_layers)
                        if (lay.enabled)
                            active_layers++;

                    if (settings::advanced_mode)
                    {
                        ImGui::SameLine(ImGui::GetWindowWidth() - 100 * ui_scale);
                        ImGui::SetCursorPosY(ImGui::GetCursorPos().y - 2 * ui_scale);
                        ImGui::Checkbox(std::string("##enablelayersettings" + layer.name + std::to_string(i)).c_str(), &layer.allow_editor);
                    }

                    ImGui::SameLine(ImGui::GetWindowWidth() - 70 * ui_scale);
                    ImGui::SetCursorPosY(ImGui::GetCursorPos().y - 2 * ui_scale);
                    ImGui::Checkbox(std::string("##enablelayer" + layer.name + std::to_string(i)).c_str(), &layer.enabled);

                    if (active_layers < 1)
                        layer.enabled = true;

                    {
                        if (disable_buttons)
                            ImGui::BeginDisabled();
                        // Closing button
                        ImGui::SameLine();
                        ImGui::SetCursorPosY(ImGui::GetCursorPos().y - 2 * ui_scale);
                        ImGui::PushStyleColor(ImGuiCol_Text, style::theme.red.Value);
                        ImGui::PushStyleColor(ImGuiCol_Button, ImVec4());
                        ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0);
                        if (ImGui::Button(std::string(u8"\uf00d##layerdelete" + layer.name + std::to_string(i)).c_str()))
                        {
                            projection_layers.erase(projection_layers.begin() + i);
                            ImGui::PopStyleVar();
                            ImGui::PopStyleColor(2);
                            ImGui::EndGroup();
                            ImGui::PopID();
                            break;
                        }
                        if (projections_are_generating && ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
                            ImGui::SetTooltip("Wait for the processing to finish!");
                        ImGui::PopStyleVar();
                        ImGui::PopStyleColor(2);
                        if (disable_buttons)
                            ImGui::EndDisabled();
                    }

                    if (layer.enabled)
                    {
                        // ImGui::DragFloat(std::string("Opacity##opacitylayer" + layer.name + std::to_string(i)).c_str(), &layer.opacity, 1.0, 0, 100);
                        ImGui::Image((void *)(intptr_t)layer.getPreview(), {50 * ui_scale, 50 * ui_scale});
                        ImGui::SameLine();

                        ImGui::BeginGroup();
                        if (projections_mode_radio == 0)
                            style::beginDisabled();
                        FancySlider(std::string("##opacitylayer" + layer.name + std::to_string(i)).c_str(), "Opacity", &layer.opacity, ImGui::GetWindowWidth() - 76 * ui_scale);
                        if (projections_mode_radio == 0)
                        {
                            ImGui::SetItemTooltip("%s", "Opacity is for overlay mode only");
                            style::endDisabled();
                        }
                        ImGui::ProgressBar(layer.progress, ImVec2(ImGui::GetWindowWidth() - 76 * ui_scale, ImGui::GetFrameHeight()));

                        if (layer.allow_editor)
                        {
                            ImGui::Separator();
                            auto js = image::get_metadata_proj_cfg(layer.img);
                            widgets::JSONTableEditor(js, "proj_cfg");
                            image::set_metadata_proj_cfg(layer.img, js);
                            ImGui::Separator();
                        }

                        ImGui::EndGroup();
                    }
                    ImGui::EndGroup();

                    if (ImGui::BeginDragDropSource(ImGuiDragDropFlags_SourceAllowNullID))
                    {
                        // Set payload to carry the index of our item (could be anything)
                        ImGui::SetDragDropPayload("LAYER_PROJECTION", &i, sizeof(int));

                        ImGui::Text("%s", label.c_str());
                        ImGui::EndDragDropSource();
                    }
                    if (ImGui::BeginDragDropTarget())
                    {
                        if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("LAYER_PROJECTION"))
                        {
                            IM_ASSERT(payload->DataSize == sizeof(int));
                            int payload_n = *(const int *)payload->Data;
                            ProjectionLayer pr = projection_layers[payload_n];
                            projection_layers.erase(projection_layers.begin() + payload_n);
                            auto it = projection_layers.begin();
                            projection_layers.insert(it + i, pr);
                        }
                        ImGui::EndDragDropTarget();
                    }
                    ImGui::PopID();
                    ImGui::Separator();
                }
                ImGui::EndListBox();
            }
            if (!(disable_buttons || disable_add_layer))
                style::beginDisabled();

            ImGui::ProgressBar((general_progress + (progress_pointer == nullptr ? 0 : *(progress_pointer))) / general_sum);

            if (!(disable_buttons || disable_add_layer))
                style::endDisabled();
        }
        if (ImGui::CollapsingHeader("Overlay##viewerpojoverlay"))
        {
            projection_overlay_handler.drawUI();
        }
    }

    void ViewerApplication::generateProjectionImage()
    {
        projections_are_generating = true;
        general_progress = 0;
        general_sum = 0;

        nlohmann::json cfg;

        if (projections_current_selected_proj == 0)
        {
            cfg["type"] = "equirec";
            cfg["offset_x"] = projections_equirectangular_tl_lon;
            cfg["offset_y"] = projections_equirectangular_tl_lat;
            cfg["scalar_x"] = (projections_equirectangular_br_lon - projections_equirectangular_tl_lon) / double(projections_image_width);
            cfg["scalar_y"] = (projections_equirectangular_br_lat - projections_equirectangular_tl_lat) / double(projections_image_height);
        }
        else if (projections_current_selected_proj == 1)
        {
            cfg["type"] = "utm";
            cfg["scale"] = projections_utm_scale;
            cfg["zone"] = projections_utm_zone;
            cfg["south"] = projections_utm_south;
            cfg["offset_y"] = projections_utm_offset_y;
        }
        else if (projections_current_selected_proj == 2)
        {
            cfg["type"] = "stereo";
            cfg["center_lon"] = projections_stereo_center_lon;
            cfg["center_lat"] = projections_stereo_center_lat;
            cfg["scale"] = projections_stereo_scale;
            cfg["width"] = projections_image_width;
            cfg["height"] = projections_image_height;
        }
        else if (projections_current_selected_proj == 3)
        {
            cfg["type"] = "tpers";
            cfg["center_lon"] = projections_tpers_lon;
            cfg["center_lat"] = projections_tpers_lat;
            cfg["altitude"] = projections_tpers_alt;
            cfg["tilt"] = projections_tpers_ang;
            cfg["azimuth"] = projections_tpers_azi;
            cfg["scale"] = projections_tpers_scale;
            cfg["width"] = projections_image_width;
            cfg["height"] = projections_image_height;
        }
        /*else if (projections_current_selected_proj == 4)
        {
            cfg["type"] = "azeq";
            cfg["lon"] = projections_azeq_lon;
            cfg["lat"] = projections_azeq_lat;
        }*/

        // Automatic projection settings!
        if (projection_auto_scale_mode)
        {
            cfg["scale_x"] = projection_autoscale_x;
            cfg["scale_y"] = projection_autoscale_y;
        }
        else
        {
            cfg["width"] = projections_image_width;
            cfg["height"] = projections_image_height;
        }

        satdump::applyAutomaticProjectionSettings(projection_layers, projection_auto_mode, projection_auto_scale_mode, projections_image_width, projections_image_height, cfg);

        for (int i = projection_layers.size() - 1; i >= 0; i--)
        {
            ProjectionLayer &layer = projection_layers[i];
            if (!layer.enabled)
                continue;
            general_sum++;
        }
        general_sum += projection_overlay_handler.enabled();

        // Generate all layers
        std::vector<image::Image> layers_images =
            generateAllProjectionLayers(projection_layers, projections_image_width, projections_image_height, cfg, &general_progress);

        logger->info("Combining images...");
        if (projections_mode_radio == 0) // Blend
        {
            projected_image_result = image::blend_images(layers_images);
        }
        else if (projections_mode_radio == 1) // Overlay
        {
            projected_image_result = image::Image(16, layers_images[0].width(), layers_images[0].height(), layers_images[0].channels());
            for (int i = 0; i < (int)layers_images.size(); i++)
            {
                projected_image_result = image::merge_images_opacity(projected_image_result,
                                                                     layers_images[i],
                                                                     projection_layers[(projection_layers.size() - 1) - i].opacity / 100.0f);
            }
        }

        // Copy projection metadata
        if (image::has_metadata(layers_images[0]))
            image::set_metadata(projected_image_result, image::get_metadata(layers_images[0]));

        // Free up memory
        layers_images.clear();

        logger->info("Applying overlays...");

        // Setup projection to draw stuff on top
        auto proj_func = satdump::reprojection::setupProjectionFunction(projections_image_width, projections_image_height, cfg, {});

        // Draw map borders
        projection_overlay_handler.clear_cache();
        projection_overlay_handler.apply(projected_image_result, proj_func, &general_progress);

        // Update ImageView
        projection_image_widget.update(projected_image_result);
        general_sum = 1;
        general_progress = 1;
        progress_pointer = nullptr;
        projections_are_generating = false;
    }
}
