/*
 * LibrePCB - Professional EDA for everyone!
 * Copyright (C) 2013 LibrePCB Developers, see AUTHORS.md for contributors.
 * https://librepcb.org/
 *
 * 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 3 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 <http://www.gnu.org/licenses/>.
 */

/*******************************************************************************
 *  Includes
 ******************************************************************************/
#include "uihelpers.h"

#include "../editorcommandset.h"
#include "slinthelpers.h"

#include <QtCore>
#include <QtGui>

/*******************************************************************************
 *  Namespace
 ******************************************************************************/
namespace librepcb {
namespace editor {

/*******************************************************************************
 *  Non-Member Functions
 ******************************************************************************/

qint64 s2l(const ui::Int64& v) noexcept {
  return (static_cast<int64_t>(v.msb) << 32) | static_cast<uint32_t>(v.lsb);
}

ui::Int64 l2s(const Length& v) noexcept {
  return ui::Int64{
      static_cast<int>((v.toNm() >> 32) & 0xFFFFFFFF),
      static_cast<int>(v.toNm() & 0xFFFFFFFF),
  };
}

Length s2length(const ui::Int64& v) noexcept {
  return Length(s2l(v));
}

std::optional<UnsignedLength> s2ulength(const ui::Int64& v) noexcept {
  const Length l = s2length(v);
  return (l >= 0) ? std::make_optional(UnsignedLength(l)) : std::nullopt;
}

std::optional<PositiveLength> s2plength(const ui::Int64& v) noexcept {
  const Length l = s2length(v);
  return (l > 0) ? std::make_optional(PositiveLength(l)) : std::nullopt;
}

int l2s(const Angle& v) noexcept {
  return v.toMicroDeg();
}

Angle s2angle(int v) noexcept {
  return Angle(v);
}

int l2s(const Ratio& v) noexcept {
  return v.toPpm();
}

Ratio s2ratio(int v) noexcept {
  return Ratio(v);
}

ui::GridStyle l2s(Theme::GridStyle v) noexcept {
  switch (v) {
    case Theme::GridStyle::Lines:
      return ui::GridStyle::Lines;
    case Theme::GridStyle::Dots:
      return ui::GridStyle::Dots;
    case Theme::GridStyle::None:
      return ui::GridStyle::None;
    default:
      qCritical() << "Unhandled value in GridStyle conversion.";
      return ui::GridStyle::None;
  }
}

Theme::GridStyle s2l(ui::GridStyle v) noexcept {
  switch (v) {
    case ui::GridStyle::Lines:
      return Theme::GridStyle::Lines;
    case ui::GridStyle::Dots:
      return Theme::GridStyle::Dots;
    case ui::GridStyle::None:
      return Theme::GridStyle::None;
    default:
      qCritical() << "Unhandled value in GridStyle conversion.";
      return Theme::GridStyle::None;
  }
}

ui::LengthUnit l2s(const LengthUnit& v) noexcept {
  if (v == LengthUnit::millimeters()) {
    return ui::LengthUnit::Millimeters;
  } else if (v == LengthUnit::micrometers()) {
    return ui::LengthUnit::Micrometers;
  } else if (v == LengthUnit::nanometers()) {
    return ui::LengthUnit::Nanometers;
  } else if (v == LengthUnit::inches()) {
    return ui::LengthUnit::Inches;
  } else if (v == LengthUnit::mils()) {
    return ui::LengthUnit::Mils;
  } else {
    qCritical() << "Unhandled value in LengthUnit conversion.";
    return ui::LengthUnit::Millimeters;
  }
}

LengthUnit s2l(ui::LengthUnit v) noexcept {
  switch (v) {
    case ui::LengthUnit::Millimeters:
      return LengthUnit::millimeters();
    case ui::LengthUnit::Micrometers:
      return LengthUnit::micrometers();
    case ui::LengthUnit::Nanometers:
      return LengthUnit::nanometers();
    case ui::LengthUnit::Inches:
      return LengthUnit::inches();
    case ui::LengthUnit::Mils:
      return LengthUnit::mils();
    default:
      qCritical() << "Unhandled value in LengthUnit conversion.";
      return LengthUnit::millimeters();
  }
}

slint::cbindgen_private::TextHorizontalAlignment l2s(const HAlign& v) noexcept {
  if (v == HAlign::left()) {
    return slint::cbindgen_private::TextHorizontalAlignment::Left;
  } else if (v == HAlign::right()) {
    return slint::cbindgen_private::TextHorizontalAlignment::Right;
  } else {
    return slint::cbindgen_private::TextHorizontalAlignment::Center;
  }
}

HAlign s2l(slint::cbindgen_private::TextHorizontalAlignment v) noexcept {
  switch (v) {
    case slint::cbindgen_private::TextHorizontalAlignment::Left:
      return HAlign::left();
    case slint::cbindgen_private::TextHorizontalAlignment::Right:
      return HAlign::right();
    default:
      return HAlign::center();
  }
}

slint::cbindgen_private::TextVerticalAlignment l2s(const VAlign& v) noexcept {
  if (v == VAlign::top()) {
    return slint::cbindgen_private::TextVerticalAlignment::Top;
  } else if (v == VAlign::bottom()) {
    return slint::cbindgen_private::TextVerticalAlignment::Bottom;
  } else {
    return slint::cbindgen_private::TextVerticalAlignment::Center;
  }
}

VAlign s2l(slint::cbindgen_private::TextVerticalAlignment v) noexcept {
  switch (v) {
    case slint::cbindgen_private::TextVerticalAlignment::Top:
      return VAlign::top();
    case slint::cbindgen_private::TextVerticalAlignment::Bottom:
      return VAlign::bottom();
    default:
      return VAlign::center();
  }
}

ui::NotificationType l2s(RuleCheckMessage::Severity v) noexcept {
  switch (v) {
    case RuleCheckMessage::Severity::Hint:
      return ui::NotificationType::Info;
    case RuleCheckMessage::Severity::Warning:
      return ui::NotificationType::Warning;
    case RuleCheckMessage::Severity::Error:
      return ui::NotificationType::Critical;
    default:
      qCritical()
          << "Unhandled value in RuleCheckMessage::Severity conversion.";
      return ui::NotificationType::Critical;
  }
}

static const std::vector<Package::AssemblyType>& getAssemblyTypes() noexcept {
  static const std::vector<Package::AssemblyType> list = {
      // ATTENTION: Keep in sync with constants.slint!
      Package::AssemblyType::Tht,  // clang-format break
      Package::AssemblyType::Smt,  // clang-format break
      Package::AssemblyType::Mixed,  // clang-format break
      Package::AssemblyType::Other,  // clang-format break
      Package::AssemblyType::None,  // clang-format break
      Package::AssemblyType::Auto,  // clang-format break
  };
  return list;
}

int l2s(Package::AssemblyType v) noexcept {
  const auto& list = getAssemblyTypes();
  const auto it = std::find(list.begin(), list.end(), v);
  if (it == list.end()) {
    qCritical() << "Unhandled value in Package::AssemblyType conversion.";
    return -1;
  }
  return it - list.begin();
}

std::optional<Package::AssemblyType> s2assemblyType(int v) noexcept {
  const auto& list = getAssemblyTypes();
  if ((v >= 0) && (v < static_cast<int>(list.size()))) {
    return list.at(v);
  } else {
    qCritical() << "Unhandled value in Package::AssemblyType conversion.";
    return std::nullopt;
  }
}

ui::ComponentPinoutDisplayMode l2s(const CmpSigPinDisplayType& v) noexcept {
  if (v == CmpSigPinDisplayType::none()) {
    return ui::ComponentPinoutDisplayMode::None;
  } else if (v == CmpSigPinDisplayType::pinName()) {
    return ui::ComponentPinoutDisplayMode::PinName;
  } else if (v == CmpSigPinDisplayType::componentSignal()) {
    return ui::ComponentPinoutDisplayMode::SignalName;
  } else if (v == CmpSigPinDisplayType::netSignal()) {
    return ui::ComponentPinoutDisplayMode::NetName;
  } else {
    qCritical() << "Unhandled value in CmpSigPinDisplayType conversion.";
    return ui::ComponentPinoutDisplayMode::SignalName;
  }
}

const CmpSigPinDisplayType& s2l(ui::ComponentPinoutDisplayMode v) noexcept {
  switch (v) {
    case ui::ComponentPinoutDisplayMode::None:
      return CmpSigPinDisplayType::none();
    case ui::ComponentPinoutDisplayMode::PinName:
      return CmpSigPinDisplayType::pinName();
    case ui::ComponentPinoutDisplayMode::SignalName:
      return CmpSigPinDisplayType::componentSignal();
    case ui::ComponentPinoutDisplayMode::NetName:
      return CmpSigPinDisplayType::netSignal();
    default:
      qCritical() << "Unhandled value in CmpSigPinDisplayType conversion.";
      return CmpSigPinDisplayType::componentSignal();
  }
}

ui::EditorCommand l2s(const EditorCommand& cmd, ui::EditorCommand in) noexcept {
  QString text = cmd.getDisplayText();
  if (cmd.getFlags().testFlag(EditorCommand::Flag::OpensPopup)) {
    text += "...";
  }
  in.text = q2s(text);
  in.status_tip = q2s(cmd.getDescription());
  const QKeySequence shortcut = cmd.getKeySequences().value(0);
  if (shortcut.count() == 1) {
    in.shortcut = q2s(shortcut.toString());
    in.modifiers = q2s(shortcut[0].keyboardModifiers());
    in.key = q2s(shortcut[0].key());
    if (in.modifiers.shift && (in.key.size() == 1) &&
        std::isalpha(in.key.data()[0])) {
      in.key = in.key.to_uppercase();
    }
  } else {
    // Multi-combination shortcuts are not supported yet.
    in.shortcut = slint::SharedString();
    in.modifiers = slint::private_api::KeyboardModifiers{};
    in.key = slint::SharedString();
  }
  return in;
}

static bool isKeySequence(const slint::private_api::KeyEvent& e,
                          const QKeySequence& seq) {
  // Compare the pressed key (e.g. 'r').
  if (e.text.to_lowercase() != q2s(seq[0].key())) {
    return false;
  }

  // Compare the modifiers. For special key characters, allow an additional
  // SHIFT modifier as this migh be required on some keyboard layouts. See
  // discussion in https://github.com/LibrePCB/LibrePCB/issues/1641.
  static QString specialChars = "+-*/.,:;_[]=@#%&()?!{}<>|";
  const slint::private_api::KeyboardModifiers seqMod =
      q2s(seq[0].keyboardModifiers());
  return (e.modifiers == seqMod) ||
      ((seqMod.shift == false) &&  //
       (e.modifiers.shift == true) &&  //
       (e.modifiers.alt == seqMod.alt) &&  //
       (e.modifiers.control == seqMod.control) &&  //
       (e.modifiers.meta == seqMod.meta) &&  //
       (specialChars.contains(s2q(e.text))));
}

bool isShortcut(const slint::private_api::KeyEvent& e,
                const ui::EditorCommand& cmd) noexcept {
  // On the first call, build a hash table of all commands for fast lookup.
  auto hashCmd = []() {
    QHash<slint::SharedString, const EditorCommand*> map;
    EditorCommandSet& cmd = EditorCommandSet::instance();
    foreach (const EditorCommandCategory* category, cmd.getCategories()) {
      foreach (const EditorCommand* command, cmd.getCommands(category)) {
        map.insert(q2s(command->getIdentifier()), command);
      }
    }
    return map;
  };
  static const QHash<slint::SharedString, const EditorCommand*> map = hashCmd();

  // Find the command with the corresponding keyboard shortcut.
  if (const EditorCommand* c = map.value(cmd.id)) {
    for (const QKeySequence& seq : c->getDefaultKeySequences()) {
      if (isKeySequence(e, seq)) {
        return true;
      }
    }
  }
  return false;
}

ui::FeatureState toFs(bool enabled) noexcept {
  return enabled ? ui::FeatureState::Enabled : ui::FeatureState::Disabled;
}

/*******************************************************************************
 *  End of File
 ******************************************************************************/

}  // namespace editor
}  // namespace librepcb
