// Copyright (C) 2026 Allen Hill <allenofthehills@gmail.com>
// SPDX-License-Identifier: GPL-3.0-or-later

#include <avr/interrupt.h>
#include <avr/io.h>
#include <stdint.h>
#include <util/atomic.h>

#include "media_avr.h" // media_sync_during_mask (used by the bus guard)
#include "hal/media.h"

// F_CPU defined in timing_avr.h; the mic tick constants below are derived from
// it (this hardware generation's TCA0/PB1 button-press implementation).
#include "timing_avr.h"

// pending WO1 toggles (even); signed to avoid underflows from a stray OVF
static volatile int8_t mic_ntoggles = 0;

// Target pulse length is ~40-150ms, with interval between pulses of
// ~100-200ms
// TCA0 period (CMP0/TOP) in ticks at F_CPU with the CLKSEL=DIV1024 prescaler.
// A press phase is ~100 ms; the final LOW phase is stretched to
// mic_refractory_period (~333 ms) so consecutive presses stay distinct
static constexpr uint16_t mic_press_ticks = (uint16_t)((F_CPU / 1024UL) / 10UL);
static constexpr uint16_t mic_refractory_period =
    (uint16_t)((F_CPU / 1024UL) / 3UL);

// the longest AVCLAN frame duration is ~15ms
static constexpr uint16_t close_thresh =
    (uint16_t)(mic_press_ticks * 15UL / 100UL);

#ifndef NDEBUG
// Toggle PB1 and return its new level.
bool media_mic_toggle() {
  // Take manual control of PB1 (CMP1EN gives TCA0 control of WO1/PB1 level)
  TCA0.SINGLE.CTRLB &= ~TCA_SINGLE_CMP1EN_bm;
  VPORTB.OUT ^= PIN1_bm;
  return (VPORTB.OUT & PIN1_bm) != 0;
}

bool media_busy() { return mic_ntoggles != 0; }
#endif

// Begin a press waveform of `nphases` × 100 ms level segments.
// - ~Immediately toggles high, alternates each phase (1 = single HIGH press, 3
//   = skip H/L/H, etc).
// - Halting the timer freezes WO1 at its last level; must run even number of
// phases to ensure we return to low
static void mic_pulse(uint8_t nphases) {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    if (mic_ntoggles) // Skip if already pulsing
      return;

    // must be even to return to idle-low
    mic_ntoggles = (nphases & 0x01) ? nphases + 1 : nphases;
    TCA0.SINGLE.CTRLB |=
        TCA_SINGLE_CMP1EN_bm; // Reassert TCA control of WO1/PB1
    TCA0.SINGLE.CTRLC = 0;    // Reset WO1 level just in case
    TCA0.SINGLE.CNT = 0;
    TCA0.SINGLE.CMP0 =   // TOP
        mic_press_ticks; // always restore default ~100 ms period
    TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // clear any stale flag
    TCA0.SINGLE.INTCTRL |= TCA_SINGLE_OVF_bm;
    TCA0.SINGLE.CTRLA |= TCA_SINGLE_ENABLE_bm;
  }
}

// OVF ISR body function:
//  - counts phases
//  - stretches the final LOW phase as a refractory period, to keep separate
//    pulse trains distinct
//  - stops the timer after the last phase
static inline void mic_timer_isr_body(bool is_early) {
  if (--mic_ntoggles == 1) {
    // Stretch final phase to a ~333 ms idle-low so back-to-back presses stay
    // distinct
    // Invariant: mic_refractory_period > mic_press_ticks, so counter can never
    // silently wrap
    TCA0.SINGLE.CMP0 = mic_refractory_period;
  } else if (mic_ntoggles <= 0) {
    TCA0.SINGLE.CTRLA &= ~TCA_SINGLE_ENABLE_bm;
    TCA0.SINGLE.CTRLC = 0; // Timer must be disabled before (re)setting
                           // CTRLC/WO1 level (§20.5.3)
    mic_ntoggles = 0;      // clamp to avoid perma-lockout in mic_pulse
  }
  if (is_early)
    TCA0.SINGLE.CNT = 0;

  // OVF FLAG must be cleared via write. MUST BE PERFORMED LAST.
  // This function is called in two cases:
  //  - ISR, triggered by actual OVF: OVF FLAG is set and needs clearing
  //  - stopEvent, running ISR body ~early: OVF flag may be set while in
  //    ATOMIC_BLOCK (either in stopEvent, or earlier in this function body).
  //    Clear OVF FLAG as a precaution to prevent erroneous ISR runs
  TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm;
}

ISR(TCA0_OVF_vect) { mic_timer_isr_body(false); }

// Emulate a transport-control button press on the source device. Each action
// maps to a press-train of a given length on MIC_CONTROL.
void media_action(enum MediaAction action) {
  switch (action) {
    case Play:
    case Pause:
    case Play_Pause: mic_pulse(1); break;    // single press
    case Skip_Forward:
    case Track_Next: mic_pulse(3); break;  // double-press
    case Skip_Backward:
    case Track_Prev: mic_pulse(5); break; // triple-press
    case Repeat:
    case Repeat_Single:
    case Shuffle:
    case Volume_Up:
    case Volume_Down:
    default: break;
  }
}

// Pre-emptively "overflow" and run the OVF ISR body early if a press is in
// progress and likely to overflow within the masked window. This maintains:
//  - a rough absolute time for the pulse train
//  - peripheral WO1 toggles and mic_ntoggles kept in sync
//  - maximum frame duration is ~15ms, "early" OVF remains within acceptable
//    ranges for either high/low pulses
// Caller (phy_guard_enter) guarantees interrupts are disabled.
void media_sync_during_guard() {
  if (mic_ntoggles && TCA0.SINGLE.CNT >= (TCA0.SINGLE.CMP0 - close_thresh))
    mic_timer_isr_body(true);
}

void media_init() {
  // PB1 needs to be set as an output for TCA0 to set the level
  PORTB.DIRSET = PIN1_bm;

  // Experimentally, a press should be ~100ms; multiple presses can be separated
  // by the same ~100ms (but separate pulse trains need more separation to
  // remain distinct)
  TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1024_gc;

  // In frequency (FRQ) mode, channel N compare match triggers "UPDATE"
  // When CMPnEN is set, TCA0 has control of the output level for the channel's
  // pin, and UPDATE toggles the level
  // Channel 1 controls WO1, which is mapped to PB1
  TCA0.SINGLE.CTRLB = TCA_SINGLE_WGMODE_FRQ_gc | TCA_SINGLE_CMP1EN_bm;
  TCA0.SINGLE.CTRLC = 0; // Preset WO1 level low just to be sure
  mic_ntoggles = 0;

  // toggle WO1 ~immediately after each period start; should go low => high
  TCA0.SINGLE.CMP1 = 2;
  TCA0.SINGLE.INTFLAGS = TCA_SINGLE_OVF_bm; // Clear OVF flag just in case
  TCA0.SINGLE.INTCTRL = 0;
}
