/*
 * Copyright (c) 2022, Sam Atkins <atkinssj@serenityos.org>
 *
 * SPDX-License-Identifier: BSD-2-Clause
 */

#include "CharacterMapWidget.h"
#include "CharacterSearchWidget.h"
#include <AK/StringUtils.h>
#include <Applications/CharacterMap/CharacterMapWindowGML.h>
#include <LibConfig/Client.h>
#include <LibDesktop/Launcher.h>
#include <LibGUI/Action.h>
#include <LibGUI/Application.h>
#include <LibGUI/Clipboard.h>
#include <LibGUI/FontPicker.h>
#include <LibGUI/Icon.h>
#include <LibGUI/InputBox.h>
#include <LibGUI/Label.h>
#include <LibGUI/Menu.h>
#include <LibGUI/TextBox.h>
#include <LibGUI/Toolbar.h>
#include <LibUnicode/CharacterTypes.h>

CharacterMapWidget::CharacterMapWidget()
{
    load_from_gml(character_map_window_gml);

    m_toolbar = find_descendant_of_type_named<GUI::Toolbar>("toolbar");
    m_font_name_label = find_descendant_of_type_named<GUI::Label>("font_name");
    m_glyph_map = find_descendant_of_type_named<GUI::GlyphMapWidget>("glyph_map");
    m_output_box = find_descendant_of_type_named<GUI::TextBox>("output_box");
    m_copy_output_button = find_descendant_of_type_named<GUI::Button>("copy_output_button");
    m_statusbar = find_descendant_of_type_named<GUI::Statusbar>("statusbar");

    m_choose_font_action = GUI::Action::create("Choose Font...", Gfx::Bitmap::try_load_from_file("/res/icons/16x16/app-font-editor.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
        auto font_picker = GUI::FontPicker::construct(window(), &font(), false);
        if (font_picker->exec() == GUI::Dialog::ExecOK) {
            auto& font = *font_picker->font();
            Config::write_string("CharacterMap", "History", "Font", font.qualified_name());
            set_font(font);
        }
    });

    m_copy_selection_action = GUI::CommonActions::make_copy_action([&](GUI::Action&) {
        auto selection = m_glyph_map->selection();
        StringBuilder builder;
        for (auto code_point = selection.start(); code_point < selection.start() + selection.size(); ++code_point) {
            if (!m_glyph_map->font().contains_glyph(code_point))
                continue;
            builder.append_code_point(code_point);
        }
        GUI::Clipboard::the().set_plain_text(builder.to_string());
    });
    m_copy_selection_action->set_status_tip("Copy the highlighted characters to the clipboard");

    m_previous_glyph_action = GUI::Action::create("Previous character", { Mod_Alt, Key_Left }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-back.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
        m_glyph_map->select_previous_existing_glyph();
    });
    m_previous_glyph_action->set_status_tip("Seek the previous visible glyph");

    m_next_glyph_action = GUI::Action::create("&Next Glyph", { Mod_Alt, Key_Right }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-forward.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
        m_glyph_map->select_next_existing_glyph();
    });
    m_next_glyph_action->set_status_tip("Seek the next visible glyph");

    m_go_to_glyph_action = GUI::Action::create("Go to glyph...", { Mod_Ctrl, Key_G }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/go-to.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
        String input;
        if (GUI::InputBox::show(window(), input, "Hexadecimal:", "Go to glyph") == GUI::InputBox::ExecOK && !input.is_empty()) {
            auto maybe_code_point = AK::StringUtils::convert_to_uint_from_hex(input);
            if (!maybe_code_point.has_value())
                return;
            auto code_point = clamp(maybe_code_point.value(), 0x0000, 0x10FFFF);
            m_glyph_map->set_focus(true);
            m_glyph_map->set_active_glyph(code_point);
            m_glyph_map->scroll_to_glyph(code_point);
        }
    });
    m_go_to_glyph_action->set_status_tip("Go to the specified code point");

    m_find_glyphs_action = GUI::Action::create("&Find glyphs...", { Mod_Ctrl, Key_F }, Gfx::Bitmap::try_load_from_file("/res/icons/16x16/find.png").release_value_but_fixme_should_propagate_errors(), [&](auto&) {
        if (m_find_window.is_null()) {
            m_find_window = GUI::Window::construct(window());
            auto& search_widget = m_find_window->set_main_widget<CharacterSearchWidget>();
            search_widget.on_character_selected = [&](auto code_point) {
                m_glyph_map->set_active_glyph(code_point);
                m_glyph_map->scroll_to_glyph(code_point);
            };
            m_find_window->set_icon(GUI::Icon::try_create_default_icon("find").value().bitmap_for_size(16));
            m_find_window->set_title("Find a character");
            m_find_window->resize(300, 400);
        }
        m_find_window->show();
        m_find_window->move_to_front();
        m_find_window->find_descendant_of_type_named<GUI::TextBox>("search_input")->set_focus(true);
    });

    m_toolbar->add_action(*m_choose_font_action);
    m_toolbar->add_separator();
    m_toolbar->add_action(*m_copy_selection_action);
    m_toolbar->add_separator();
    m_toolbar->add_action(*m_previous_glyph_action);
    m_toolbar->add_action(*m_next_glyph_action);
    m_toolbar->add_action(*m_go_to_glyph_action);
    m_toolbar->add_action(*m_find_glyphs_action);

    m_glyph_map->on_active_glyph_changed = [&](int) {
        update_statusbar();
    };

    m_glyph_map->on_glyph_double_clicked = [&](int code_point) {
        StringBuilder builder;
        builder.append(m_output_box->text());
        builder.append_code_point(code_point);
        m_output_box->set_text(builder.string_view());
    };

    m_copy_output_button->on_click = [&](auto) {
        GUI::Clipboard::the().set_plain_text(m_output_box->text());
    };

    did_change_font();
    update_statusbar();
}

CharacterMapWidget::~CharacterMapWidget()
{
}

void CharacterMapWidget::initialize_menubar(GUI::Window& window)
{
    auto& file_menu = window.add_menu("&File");
    file_menu.add_action(GUI::CommonActions::make_quit_action([](GUI::Action&) {
        GUI::Application::the()->quit();
    }));

    auto& help_menu = window.add_menu("&Help");
    help_menu.add_action(GUI::CommonActions::make_help_action([&](auto&) {
        Desktop::Launcher::open(URL::create_with_file_protocol("/usr/share/man/man1/CharacterMap.md"), "/bin/Help");
    }));
    help_menu.add_action(GUI::CommonActions::make_about_action("Character Map", GUI::Icon::default_icon("app-character-map"), &window));
}

void CharacterMapWidget::did_change_font()
{
    m_glyph_map->set_font(font());
    m_font_name_label->set_text(font().human_readable_name());
    m_output_box->set_font(font());
}

void CharacterMapWidget::update_statusbar()
{
    auto code_point = m_glyph_map->active_glyph();
    StringBuilder builder;
    builder.appendff("U+{:04X}", code_point);
    if (auto display_name = Unicode::code_point_display_name(code_point); display_name.has_value())
        builder.appendff(" - {}", display_name.value());
    m_statusbar->set_text(builder.to_string());
}
