#include <Arduino.h>
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <math.h>

#if !defined(__AVR_ATmega328P__) || F_CPU != 16000000UL
#error "This sketch requires a classic Nano ATmega328P at 16 MHz."
#endif

const byte LED_PIN = 9;  // D9 -> 470 ohm -> BC337 base.
const byte POT_PIN = A0;
const uint16_t PULSE_US = 150;
const uint16_t TIMER_PRESCALER = 64;
const uint32_t TIMER_HZ = F_CPU / TIMER_PRESCALER;
const uint16_t PULSE_TICKS =
    (PULSE_US * (uint64_t)TIMER_HZ + 500000UL) / 1000000UL;

static_assert(PULSE_TICKS >= 2, "Pulse must span at least two timer ticks.");
static_assert(PULSE_TICKS < TIMER_HZ / 200UL,
              "Pulse must be shorter than the fastest period.");

float filteredPot = 0.0f;
float currentHz = 0.0f;
bool blinking = false;
bool timerRunning = false;
uint16_t previousTop = 0;
unsigned long lastRead = 0;
unsigned long lastPrint = 0;

void steadyLight() {
  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    TIMSK1 = 0;
    TCCR1B = 0;
    TCCR1A = 0;
    TIFR1 = _BV(TOV1) | _BV(OCF1B) | _BV(OCF1A);
    PORTB |= _BV(PB1);
  }
  timerRunning = false;
  previousTop = 0;
  currentHz = 0.0f;
}

// Timer1 schedules the edges; D9 is switched by software in these ISRs.
// Other interrupts can delay an edge, so pulse width is approximate.
ISR(TIMER1_OVF_vect) {
  PORTB |= _BV(PB1);
}

ISR(TIMER1_COMPB_vect) {
  PORTB &= ~_BV(PB1);
}

float setStrobe(float hz) {
  hz = constrain(hz, 5.0f, 200.0f);
  const uint32_t periodTicks = (uint32_t)(TIMER_HZ / hz + 0.5f);
  const uint16_t top = (uint16_t)(periodTicks - 1UL);
  const float actualHz = TIMER_HZ / (float)periodTicks;

  if (timerRunning && top == previousTop) {
    return actualHz;
  }

  ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
    if (!timerRunning) {
      TIMSK1 = 0;
      TCCR1B = 0;
      TCCR1A = 0;
      TCNT1 = 0;
      OCR1A = top;

      // Mode 15 sets TOV1 at TOP, one tick before BOTTOM.
      // 150 us rounds to 38 ticks (152 us); compare at 37.
      OCR1B = PULSE_TICKS - 1;
      TCCR1A = _BV(WGM11) | _BV(WGM10);
      TIFR1 = _BV(TOV1) | _BV(OCF1B) | _BV(OCF1A);
      PORTB &= ~_BV(PB1);
      TIMSK1 = _BV(TOIE1) | _BV(OCIE1B);
      TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS11) | _BV(CS10);
    } else {
      // OCR1A is double buffered; TOP changes at BOTTOM.
      OCR1A = top;
    }
  }
  timerRunning = true;
  previousTop = top;
  return actualHz;
}

void setup() {
  pinMode(LED_PIN, OUTPUT);
  Serial.begin(115200);
  filteredPot = analogRead(POT_PIN);
  steadyLight();
}

void loop() {
  const unsigned long now = millis();

  if (now - lastRead >= 20UL) {
    lastRead = now;
    filteredPot += 0.12f * (analogRead(POT_PIN) - filteredPot);

    if (!blinking && filteredPot >= 60.0f) {
      blinking = true;
    } else if (blinking && filteredPot <= 40.0f) {
      blinking = false;
      steadyLight();
    }

    if (blinking) {
      float position = (filteredPot - 60.0f) / (1023.0f - 60.0f);
      position = constrain(position, 0.0f, 1.0f);
      currentHz = setStrobe(5.0f * powf(40.0f, position));
    }
  }

  if (now - lastPrint >= 500UL) {
    lastPrint = now;
    if (blinking) {
      Serial.print(F("f="));
      Serial.print(currentHz, 1);
      Serial.println(F(" Hz"));
    } else {
      Serial.println(F("LED: ON"));
    }
  }
}
