// -*- mode: c++; c-file-style: "linux"; c-basic-offset: 2; indent-tabs-mode:
// nil -*-
//
//  Copyright (C) 2004-2015 Andrej Vodopivec <andrej.vodopivec@gmail.com>
//            (C) 2011-2011 cw.ahbong <cwahbong@users.sourceforge.net>
//            (C) 2012 Doug Ilijev <doug.ilijev@gmail.com>
//            (C) 2014-2024 Gunter Königsmann <wxMaxima@physikbuch.de>
//
//  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, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
//
//  SPDX-License-Identifier: GPL-2.0+

/*! \file
  This file defines the class GreekSidebar

  GreekSidebar is a on-screen keyboard that allows non-greek users to type
  greek chars.
*/
#include "wxMaximaFrame.h"
#include "Dirstructure.h"
#include <string>
#include <memory>
#include "ButtonWrapSizer.h"

#include "GreekSidebar.h"

GreekSidebar::GreekSidebar(wxWindow *parent,
                                    Configuration *configuration,
                                    wxWindow *worksheet, int ID)
  : wxScrolled<wxPanel>(parent, ID), m_configuration(configuration),
    m_lowercaseSizer(new Buttonwrapsizer(wxHORIZONTAL)),
    m_uppercaseSizer(new Buttonwrapsizer(wxHORIZONTAL)),
    m_worksheet(worksheet)
{
  wxBoxSizer *vbox = new wxBoxSizer(wxVERTICAL);
  m_lowerCasePanel = new wxPanel(this);
  m_upperCasePanel = new wxPanel(this);
  ShowScrollbars(wxSHOW_SB_NEVER, wxSHOW_SB_DEFAULT);
  EnableScrolling(false, true);
  SetScrollRate(5, 5);
  UpdateSymbols();
  m_lowerCasePanel->SetSizer(m_lowercaseSizer);
  m_upperCasePanel->SetSizer(m_uppercaseSizer);

  Bind(wxEVT_SIZE, &GreekSidebar::OnSize, this);
  Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
  m_lowerCasePanel->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
  m_upperCasePanel->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
  Bind(wxEVT_RIGHT_DOWN, &GreekSidebar::OnMouseRightDown, this);
  GetTargetWindow()->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
  vbox->Add(m_lowerCasePanel, wxSizerFlags().Expand());
  vbox->Add(m_upperCasePanel, wxSizerFlags().Expand());
  SetSizer(vbox);
  FitInside();
  SetMinSize(wxSize(GetContentScaleFactor() * 50, GetMinSize().y));
}

void GreekSidebar::UpdateVirtualSize() {
  const int width = GetClientSize().x;
  if (width <= 0)
    return;

  // How tall each panel's letters are once wrapped at the client width. The vbox
  // otherwise splits the client height between the two panels and each wrap sizer
  // squishes its letters into that single-row height instead of wrapping into the
  // rows it needs -- exactly why the sidebars "stopped breaking into lines" (one
  // row of letters, the rest clipped) on wxWidgets 3.3.
  const int lowerHeight = m_lowercaseSizer->HeightForWidth(width);
  const int upperHeight = m_uppercaseSizer->HeightForWidth(width);

  // Only re-layout when the wrapped geometry really changed: SetSize calls
  // made while GTK is allocating sizes (we are called from OnSize) are
  // deferred by wxWidgets and replayed on the next frame, where they trigger
  // a new allocation - so laying out unconditionally here re-layouts (and
  // repaints) the sidebar on every frame for as long as events keep coming.
  const wxSize virtualSize(width, lowerHeight + upperHeight);
  if (virtualSize == m_lastVirtualSize &&
      m_lowerCasePanel->GetMinSize().y == lowerHeight &&
      m_upperCasePanel->GetMinSize().y == upperHeight)
    return;
  m_lastVirtualSize = virtualSize;

  // Pin the panels to those wrapped heights so the vbox stacks every row (rather
  // than squishing them), and give the wxScrolled a virtual height tall enough
  // for all of them -- so the vertical scrollbar can reach the rows below the fold
  // while the width stays at the client width (never a horizontal scrollbar).
  m_lowerCasePanel->SetMinSize(wxSize(-1, lowerHeight));
  m_upperCasePanel->SetMinSize(wxSize(-1, upperHeight));
  Layout();
  SetVirtualSize(virtualSize);
}

void GreekSidebar::OnSize(wxSizeEvent &event) {
  UpdateVirtualSize();
  event.Skip();
}

void GreekSidebar::OnMenu(wxCommandEvent &event) {
  std::unordered_map<int, std::function<void()>> m{
    {EventIDs::menu_showLatinGreekLookalikes, [&](){
      bool showLookalikes = !m_configuration->GreekSidebar_ShowLatinLookalikes();
      m_configuration->GreekSidebar_ShowLatinLookalikes(showLookalikes);
      UpdateSymbols();
      Refresh();
      Layout();
    }},
    {EventIDs::menu_showGreekMu, [&](){
      bool show_mu = !m_configuration->GreekSidebar_Show_mu();
      m_configuration->GreekSidebar_Show_mu(show_mu);
      UpdateSymbols();
      Refresh();
      Layout();
    }
    }};
  auto varFunc = m.find(event.GetId());
  if(varFunc == m.end())
    {
      wxASSERT(false);
    }
  else
    varFunc->second();
  Layout();
}

void GreekSidebar::UpdateSymbols() {
  //  wxWindowUpdateLocker drawBlocker(this);
  enum class Cond { None, Show_mu, ShowLatinLookalikes };
  struct EnabledDefinition : CharButton::Definition {
    Cond condition;
    EnabledDefinition(wchar_t sym, const wxString &descr,
                      Cond cond = Cond::None)
      : CharButton::Definition{sym, descr}, condition(cond) {}
    explicit EnabledDefinition(wchar_t sym)
      : EnabledDefinition(sym, wxm::emptyString) {}
  };

  static const EnabledDefinition lowerCaseDefs[] = {
    {L'\u03B1', _("alpha")},
    {L'\u03B2', _("beta")},
    {L'\u03B3', _("gamma")},
    {L'\u03B4', _("delta")},
    {L'\u03B5', _("epsilon")},
    {L'\u03B6', _("zeta")},
    {L'\u03B7', _("eta")},
    {L'\u03B8', _("theta")},
    {L'\u03B9', _("iota")},
    {L'\u03BA', _("kappa")},
    {L'\u03BB', _("lambda")},
    {L'\u03BC', _("mu"), Cond::Show_mu},
    {L'\u03BD', _("nu")},
    {L'\u03BE', _("xi")},
    {L'\u03BF', _("omicron"), Cond::ShowLatinLookalikes},
    {L'\u03C0', _("pi")},
    {L'\u03C1', _("rho")},
    {L'\u03C3', _("sigma")},
    {L'\u03C4', _("tau")},
    {L'\u03C5', _("upsilon")},
    {L'\u03C6', _("phi")},
    {L'\u03C7', _("chi")},
    {L'\u03C8', _("psi")},
    {L'\u03C9', _("omega")},
  };

  static const EnabledDefinition upperCaseDefs[] = {
    {L'\u0391', ("Alpha"), Cond::ShowLatinLookalikes},
    {L'\u0392', _("Beta"), Cond::ShowLatinLookalikes},
    {L'\u0393', _("Gamma")},
    {L'\u0394', _("Delta")},
    {L'\u0395', _("Epsilon"), Cond::ShowLatinLookalikes},
    {L'\u0396', _("Zeta"), Cond::ShowLatinLookalikes},
    {L'\u0397', _("Eta"), Cond::ShowLatinLookalikes},
    {L'\u0398', _("Theta")},
    {L'\u0399', _("Iota"), Cond::ShowLatinLookalikes},
    {L'\u039A', _("Kappa"), Cond::ShowLatinLookalikes},
    {L'\u039B', _("Lambda")},
    {L'\u039C', _("Mu"), Cond::ShowLatinLookalikes},
    {L'\u039D', _("Nu"), Cond::ShowLatinLookalikes},
    {L'\u039E', _("Xi")},
    {L'\u039F', _("Omicron"), Cond::ShowLatinLookalikes},
    {L'\u03A0', _("Pi")},
    {L'\u03A1', _("Rho"), Cond::ShowLatinLookalikes},
    {L'\u03A3', _("Sigma")},
    {L'\u03A4', _("Tau"), Cond::ShowLatinLookalikes},
    {L'\u03A5', _("Upsilon"), Cond::ShowLatinLookalikes},
    {L'\u03A6', _("Phi")},
    {L'\u03A7', _("Chi"), Cond::ShowLatinLookalikes},
    {L'\u03A8', _("Psi")},
    {L'\u03A9', _("Omega")},
  };

  bool const Show_mu = m_configuration->GreekSidebar_Show_mu();
  bool const ShowLatinLookalikes = m_configuration->GreekSidebar_ShowLatinLookalikes();

  if(!m_lowercaseSizer->GetChildren().IsEmpty())
  m_lowercaseSizer->Clear(true);
  for (auto &def : lowerCaseDefs)
    if (def.condition == Cond::None ||
        (def.condition == Cond::Show_mu && Show_mu) ||
        (def.condition == Cond::ShowLatinLookalikes && ShowLatinLookalikes))
      {
        CharButton *button = new CharButton(m_lowerCasePanel, m_worksheet, m_configuration, def);
        m_lowercaseSizer->Add(button, wxSizerFlags().Expand());
        button->Bind(wxEVT_RIGHT_DOWN, &GreekSidebar::OnMouseRightDown, this);
        button->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
        button->GetTextObject()->Bind(wxEVT_RIGHT_DOWN, &GreekSidebar::OnMouseRightDown, this);
        button->GetTextObject()->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
      }

  if(!m_uppercaseSizer->GetChildren().IsEmpty())
    m_uppercaseSizer->Clear(true);
  for (auto &def : upperCaseDefs)
    if (def.condition == Cond::None ||
        (def.condition == Cond::Show_mu && Show_mu) ||
        (def.condition == Cond::ShowLatinLookalikes && ShowLatinLookalikes))
      {
        CharButton *button = new CharButton(m_upperCasePanel, m_worksheet, m_configuration, def);
        m_uppercaseSizer->Add(button, wxSizerFlags().Expand());
        button->Bind(wxEVT_RIGHT_DOWN, &GreekSidebar::OnMouseRightDown, this);
        button->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
        button->GetTextObject()->Bind(wxEVT_RIGHT_DOWN, &GreekSidebar::OnMouseRightDown, this);
        button->GetTextObject()->Bind(wxEVT_MENU, &GreekSidebar::OnMenu, this);
      }
}

void GreekSidebar::OnMouseRightDown(wxMouseEvent &WXUNUSED(event)) {
  std::unique_ptr<wxMenu> popupMenu(new wxMenu());
  popupMenu->AppendCheckItem(EventIDs::menu_showLatinGreekLookalikes,
                             _(wxS("Show greek \u21D4 latin lookalikes")));
  popupMenu->Check(EventIDs::menu_showLatinGreekLookalikes,
                   m_configuration->GreekSidebar_ShowLatinLookalikes());
  popupMenu->AppendCheckItem(EventIDs::menu_showGreekMu,
                             _(wxS("Show lookalike for unit prefix µ")));
  popupMenu->Check(EventIDs::menu_showGreekMu, m_configuration->GreekSidebar_Show_mu());
  PopupMenu(&*popupMenu);
}
