#include "keyboardLayer.h"

#include <ranges>

using namespace std;
using namespace quokka_gfx;
//--------------------------------------------------------------
/* Constructor */
KeyboardLayer::KeyboardLayer(MainWindow &owner, const Size &size)
  : Layer(owner)
{
  // Initialize the piano keys
  initKeys(size);
}
//--------------------------------------------------------------
/* Render the visual piano */
void KeyboardLayer::render() const
{
  const auto renderer = GetRenderer();     // Get the renderer instance from the main window

  drawKeys(renderer);     // Draw the white keys first

  drawKeySymbols(renderer);     // Draw a symbol for the Middle C key (MIDI note 60)
}
//--------------------------------------------------------------
/* Draw the piano keys */
void KeyboardLayer::drawKeys(const Renderer *renderer) const
{
  for (const auto &key : m_keys | views::values)
  {
    if (key.color == KeyColor::White)
    {
      // Draw the white key (light gray if pressed, white otherwise)
      (void)renderer->setDrawColor(key.isPressed ? Color(200, 200, 200) : Color::White);
      (void)renderer->fillRect(key.pos, key.size);

      // Draw a thin border around the white key (dark gray)
      (void)renderer->setDrawColor(Color(50, 50, 50));
      (void)renderer->drawRect(key.pos, key.size);
    }
  }

  // Draw the black keys on top of the white keys
  for (const auto &key : m_keys | views::values)
  {
    if (key.color == KeyColor::Black)
    {
      // Draw the black key (dark gray if pressed, black otherwise)
      (void)renderer->setDrawColor(key.isPressed ? Color(50, 50, 50) : Color::Black);
      (void)renderer->fillRect(key.pos, key.size);

      // Draw a thin border around the black key (light gray)
      (void)renderer->setDrawColor(Color(200, 200, 200));
      (void)renderer->drawRect(key.pos, key.size);
    }
  }
}
//--------------------------------------------------------------
/* Draw symbols on the piano keys (e.g., Middle C marker, ...) */
void KeyboardLayer::drawKeySymbols(const Renderer *renderer) const
{
  // Draw a symbol for the Middle C key (MIDI note 60)
  constexpr float markerSize = 12.0f;

  auto &key = m_keys.at(60);     // Middle C key (MIDI note 60)

  // X and Y coordinates of the marker
  const float markerX = key.pos.x + (key.size.w - markerSize) / 2.0f;
  const float markerY = key.pos.y + key.size.h - markerSize - 25.0f;

  // Draw a marker above the Middle C key
  (void)renderer->setDrawColor(quokka_gfx::Color(230, 50, 50));
  (void)renderer->fillRect({ .x = markerX, .y = markerY }, { .w = markerSize, .h = markerSize });
}
//--------------------------------------------------------------
/* Initialize the piano keys */
void KeyboardLayer::initKeys(const Size &size)
{
  constexpr float WhiteKeyHeight = 200.0f;                                          // Height of the white keys
  constexpr float BlackKeyHeight = 120.0f;                                          // Height of the black keys
  const float     keyYPos        = static_cast<float>(size.h) - WhiteKeyHeight;     // Y position of the keys (bottom of the window)

  // Helper lambda function to determine if a MIDI note corresponds to a black key
  const auto isBlack = [](const int midiNote) -> bool
  {
    // Define the pattern of black keys in an octave (C = Do, D = Re, E = Mi, F = Fa, G = Sol, A = La, B = Si)
    // In an octave (0 to 11), the black keys are at indices: 1 (C#), 3 (D#), 6 (F#), 8 (G#), 10 (A#)
    const int noteInOctave = midiNote % 12;
    return (noteInOctave == 1 ||
            noteInOctave == 3 ||
            noteInOctave == 6 ||
            noteInOctave == 8 ||
            noteInOctave == 10);
  };

  // Count the number of white keys in the piano range (MIDI notes 21 to 108)
  int whiteKeyCount = 0;
  for (int midi = MIN_MIDI_NOTE; midi <= MAX_MIDI_NOTE; ++midi)
  {
    if (!isBlack(midi))
      whiteKeyCount++;
  }

  // Calculate the w of each white and black key based on the total number of white keys
  const float windowWidth   = static_cast<float>(size.w);
  const float whiteKeyWidth = windowWidth / static_cast<float>(whiteKeyCount);
  const float blackKeyWidth = whiteKeyWidth * 0.6f;     // Black keys are typically narrower than white keys

  // Initialize the white piano keys
  constexpr float startX   = 0.0f;
  float           currentX = startX;
  for (int midi = MIN_MIDI_NOTE; midi <= MAX_MIDI_NOTE; ++midi)
  {
    if (!isBlack(midi))
    {
      m_keys[midi] = { .midiNote  = midi,
                       .color     = KeyColor::White,
                       .pos       = { .x = currentX, .y = keyYPos },
                       .size      = { .w = whiteKeyWidth, .h = WhiteKeyHeight },
                       .isPressed = false };
      currentX += whiteKeyWidth;
    }
  }

  // Initialize the black piano keys
  currentX = startX;
  for (int midi = MIN_MIDI_NOTE; midi <= MAX_MIDI_NOTE; ++midi)
  {
    if (isBlack(midi))
    {
      // Position the black key between the two adjacent white keys
      const float blackKeyX = currentX - (blackKeyWidth / 2.0f);
      m_keys[midi]          = { .midiNote  = midi,
                                .color     = KeyColor::Black,
                                .pos       = { .x = blackKeyX, .y = keyYPos },
                                .size      = { .w = blackKeyWidth, .h = BlackKeyHeight },
                                .isPressed = false };
    }
    else
    {
      currentX += whiteKeyWidth;
    }
  }
}
//--------------------------------------------------------------
/* Update the active notes based on the midi notes (0-127) */
void KeyboardLayer::updateActiveNotes(const std::span<const int> notes)
{
  // Reset all keys to not pressed
  for (auto &key : m_keys | views::values)
    key.isPressed = false;

  // Set the keys corresponding to the active notes to pressed
  for (const auto midiNote : notes)
  {
    if (m_keys.contains(midiNote))
      m_keys[midiNote].isPressed = true;
  }
}
//--------------------------------------------------------------
