/***************************************************************************
 *   Copyright (C) 2025  Alec Leamas                                       *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, see <https://www.gnu.org/licenses/>. *
 **************************************************************************/

/**
 * \file
 * Implement user_colors_dlg.h
 */

#include <functional>
#include <iostream>

#include <wx/button.h>
#include <wx/clrpicker.h>
#include <wx/frame.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/statline.h>
#include <wx/stattext.h>

#include "model/config_vars.h"
#include "model/gui_events.h"
#include "model/navmsg_filter.h"

#include "user_colors_dlg.h"
#include "tty_scroll.h"

static constexpr const char* const kDialogName = "DataMonitorColors";

const static NavmsgStatus kOkStatus =
    NavmsgStatus(NavmsgStatus::Direction::kHandled);
const static NavmsgStatus kNotOkStatus =
    NavmsgStatus(NavmsgStatus::State::kMalformed);
const static NavmsgStatus kNoOutputStatus =
    NavmsgStatus(NavmsgStatus::Accepted::kFilteredNoOutput);
const static NavmsgStatus kDroppedStatus =
    NavmsgStatus(NavmsgStatus::Accepted::kFilteredDropped);
const static NavmsgStatus kOutputStatus =
    NavmsgStatus(NavmsgStatus::Direction::kOutput);
const static NavmsgStatus kInputStatus =
    NavmsgStatus(NavmsgStatus::Direction::kInput);

/** Dialog top frame */
class UserColoursDlg : public wxFrame {
public:
  explicit UserColoursDlg(wxWindow* parent)
      : wxFrame(parent, wxID_ANY, _("Data Monitor: colours setup")),
        m_top_panel(nullptr) {
    wxWindow::SetName(kDialogName);
    auto on_apply = [&] { m_top_panel->Apply(); };
    auto on_ok = [&] {
      m_top_panel->Apply();
      Destroy();
    };
    auto on_cancel = [&] {
      m_top_panel->Cancel();
      Destroy();
    };
    auto vbox = new wxBoxSizer(wxVERTICAL);
    auto buttons = new ButtonSizer(this, on_ok, on_apply, on_cancel);
    m_top_panel = new TopPanel(this);
    vbox->Add(m_top_panel, wxSizerFlags(1).Border().Expand());
    vbox->Add(new wxStaticLine(this, wxID_ANY), wxSizerFlags(0).Expand());
    vbox->Add(buttons, wxSizerFlags(0).Expand().Border());
    SetSizer(vbox);
    wxWindow::Fit();
  }

private:
  using ButtonHandler = std::function<void()>;

  /** State used to support Cancel i.e. restore values to initial state. */
  class StoredConfig {
  public:
    StoredConfig() { Load(); }

    void Load() {
      dm_dropped = g_dm_dropped;
      dm_filtered = g_dm_filtered;
      dm_input = g_dm_input;
      dm_not_ok = g_dm_not_ok;
      dm_ok = g_dm_ok;
      dm_output = g_dm_output;
    }

    void Unload() {
      g_dm_dropped = WorkValue(dm_dropped, kDroppedStatus);
      g_dm_filtered = WorkValue(dm_filtered, kNoOutputStatus);
      g_dm_input = WorkValue(dm_input, kInputStatus);
      g_dm_not_ok = WorkValue(dm_not_ok, kNotOkStatus);
      g_dm_ok = WorkValue(dm_ok, kOkStatus);
      g_dm_output = WorkValue(dm_output, kOutputStatus);
    }

  private:
    unsigned dm_dropped;
    unsigned dm_filtered;
    unsigned dm_input;
    unsigned dm_not_ok;
    unsigned dm_ok;
    unsigned dm_output;

    UserColorsByState default_colors;

    unsigned WorkValue(unsigned stored_value, NavmsgStatus ns) {
      if (stored_value == kUndefinedColor) return default_colors(ns).GetRGB();
      return stored_value;
    }
  };

  /** The six color pickers and a "Restore Defaults" button in a grid.  */
  class TopPanel : public wxPanel {
  public:
    explicit TopPanel(wxWindow* parent) : wxPanel(parent) {
      auto grid = new wxGridSizer(2);

      m_msg_ok_pick = AddGridRow(grid, _("Input - OK"), kOkStatus);
      m_msg_not_ok_pick = AddGridRow(grid, _("Errors"), kNotOkStatus);
      m_msg_filtered_pick =
          AddGridRow(grid, _("Filtered, no output"), kNoOutputStatus);
      m_msg_dropped_pick =
          AddGridRow(grid, _("Filtered, dropped"), kDroppedStatus);
      m_msg_output_pick = AddGridRow(grid, _("Output"), kOutputStatus);
      m_msg_input_pick = AddGridRow(grid, _("Input event"), kInputStatus);

      auto reset_btn = new wxButton(this, wxID_UNDO, _("Restore defaults"));
      reset_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
                      [&](wxCommandEvent&) { Reset(); });
      grid->Add(reset_btn);

      SetSizer(grid);
    }

    /** Update globals from GUI state. */
    void Apply() const {
      g_dm_ok = m_msg_ok_pick->GetRgb();
      g_dm_not_ok = m_msg_not_ok_pick->GetRgb();
      g_dm_filtered = m_msg_filtered_pick->GetRgb();
      g_dm_dropped = m_msg_dropped_pick->GetRgb();
      g_dm_output = m_msg_output_pick->GetRgb();
      g_dm_input = m_msg_input_pick->GetRgb();
      GuiEvents::GetInstance().on_dm_colors_change.Notify();
    }

    /** Update GUI state from globals. */
    void Cancel() {
      m_stored_config.Unload();
      m_msg_ok_pick->SetColor(g_dm_ok);
      m_msg_not_ok_pick->SetColor(g_dm_not_ok);
      m_msg_filtered_pick->SetColor(g_dm_filtered);
      m_msg_dropped_pick->SetColor(g_dm_dropped);
      m_msg_output_pick->SetColor(g_dm_output);
      m_msg_input_pick->SetColor(g_dm_input);
    }

    /** Reset GUI state to initial, hardcoded values */
    void Reset() {
      m_msg_ok_pick->SetColour(m_std_colors(kOkStatus));
      m_msg_not_ok_pick->SetColour(m_std_colors(kNotOkStatus));
      m_msg_filtered_pick->SetColour(m_std_colors(kNoOutputStatus));
      m_msg_dropped_pick->SetColour(m_std_colors(kDroppedStatus));
      m_msg_output_pick->SetColour(m_std_colors(kOutputStatus));
      m_msg_input_pick->SetColour(m_std_colors(kInputStatus));
      m_stored_config.Load();
    }

  private:
    /** Convenience constructor and accessors */
    class ColorPicker : public wxColourPickerCtrl {
    public:
      ColorPicker(wxWindow* parent, const wxColour& c)
          : wxColourPickerCtrl(parent, wxID_ANY, c) {}

      void SetColor(unsigned rgb) { SetColour(wxColour(rgb)); }

      [[nodiscard]] unsigned GetRgb() const {
        return static_cast<unsigned>(GetColour().GetRGB());
      }
    };

    ColorPicker* m_msg_ok_pick;
    ColorPicker* m_msg_not_ok_pick;
    ColorPicker* m_msg_filtered_pick;
    ColorPicker* m_msg_dropped_pick;
    ColorPicker* m_msg_output_pick;
    ColorPicker* m_msg_input_pick;

    UserColorsByState m_colors;
    StdColorsByState m_std_colors;
    StoredConfig m_stored_config;

    ColorPicker* AddGridRow(wxGridSizer* grid, const char* label,
                            NavmsgStatus ns) {
      grid->Add(new wxStaticText(this, wxID_ANY, label));
      auto picker = new ColorPicker(this, m_colors(ns));
      grid->Add(picker);
      return picker;
    }
  };

  /** The three Apply, Cancel and OK buttons. */
  class ButtonSizer : public wxStdDialogButtonSizer {
  public:
    ButtonSizer(wxWindow* parent, ButtonHandler on_ok, ButtonHandler on_apply,
                ButtonHandler on_cancel)
        : wxStdDialogButtonSizer(),
          m_on_ok(std::move(on_ok)),
          m_on_apply(std::move(on_apply)),
          m_on_cancel(std::move(on_cancel)) {
      auto ok_btn = new wxButton(parent, wxID_OK);
      ok_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
                   [&](wxCommandEvent& ev) { m_on_ok(); });
      AddButton(ok_btn);
      auto cancel_btn = new wxButton(parent, wxID_CANCEL);
      cancel_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
                       [&](wxCommandEvent& ev) { m_on_cancel(); });
      AddButton(cancel_btn);
      auto apply_btn = new wxButton(parent, wxID_APPLY);
      apply_btn->Bind(wxEVT_COMMAND_BUTTON_CLICKED,
                      [&](wxCommandEvent& ev) { m_on_apply(); });
      AddButton(apply_btn);
      Realize();
    }

  protected:
    ButtonHandler m_on_ok;
    ButtonHandler m_on_apply;
    ButtonHandler m_on_cancel;
  };

private:
  TopPanel* m_top_panel;
};

wxFrame* UserColorsDlg(wxWindow* parent) {
  auto current_dlg = wxWindow::FindWindowByName(kDialogName);
  if (current_dlg) {
    current_dlg->Raise();
    return nullptr;
  } else {
    auto dialog = new UserColoursDlg(parent);
    dialog->Show();
    return dialog;
  }
}
