#include "buttons.h"
#include <avr/interrupt.h>
#include <util/atomic.h>
#include <util/delay.h>


ButtonState buttonUp;
ButtonState buttonDown;
ButtonState buttonOption;


// Since it's a pull-up, the value is low when the button is pressed.
// This macro just helps make the code more readable.
#define isPressed(state) (!state)

#define hasFlag(flags, value) ((flags & value) != 0)
#define setFlag(flags, value) flags |= (value)
#define clearFlag(flags, value) flags &= ~(value)


uint8_t inactivityTicks = 0;


void buttons_init()
{
  // Input with internal pull-up resistor
  DDRB &= ~BUTTON_ALL;
  PORTB |= BUTTON_ALL;

  buttons_reset();

  // Mode 2: CTC
  TCCR0A = _BV(WGM01);

  // Set prescaler to divide by 1024
  TCCR0B = _BV(CS02) | _BV(CS00);

  // 10ms
  OCR0A = XTAL / 1024.0 * 10e-3 - 1;

  // Enable timer interrupt
  TIMSK = _BV(OCIE0A);
  sei();
}


void button_init(ButtonState* state, uint8_t readState)
{
  (*state).flags = readState ? BSFLastReported : 0;
  (*state).debounceTicks = 0;
  (*state).repeatTicks = 0;
}


void buttons_reset()
{
  uint8_t pinState = PINB;

  button_init(&buttonUp, hasFlag(pinState, _BV(BUTTON_UP)));
  button_init(&buttonDown, hasFlag(pinState, _BV(BUTTON_DOWN)));
  button_init(&buttonOption, hasFlag(pinState, _BV(BUTTON_OPTION)));
}



uint8_t buttons_active()
{
  return inactivityTicks <= DEBOUNCE_TIME;
}



uint8_t button_check(ButtonState* state, uint8_t readState)
{
  #define stateRef (*state)

  uint8_t lastState = hasFlag(stateRef.flags, BSFLastReported);
  uint8_t newState = lastState;


  // Debounce
  if (readState != lastState)
  {
    stateRef.debounceTicks++;
    if (stateRef.debounceTicks >= DEBOUNCE_TIME)
    {
      // Pressed / released
      newState = readState;

      if (newState)
        setFlag(stateRef.flags, BSFLastReported);
      else
        clearFlag(stateRef.flags, BSFLastReported);
    }
  }
  else
  {
    // State reverted, reset debounce timer
    stateRef.debounceTicks = 0;
  }


  // Check new state
  if (isPressed(newState))
  {
    if (!isPressed(lastState))
    {
      // Pressed
      clearFlag(stateRef.flags, BSFRepeated | BSFReleased);
      setFlag(stateRef.flags, BSFPressed | BSFRepeatEnabled);
      stateRef.repeatTicks = REPEAT_START;
    }
    else if (hasFlag(stateRef.flags, BSFRepeatEnabled))
    {
      // Held pressed
      stateRef.repeatTicks--;

      if (stateRef.repeatTicks == 0)
      {
        stateRef.repeatTicks = REPEAT_NEXT;
        setFlag(stateRef.flags, BSFRepeated);
      }
    }
  }
  else if (isPressed(lastState))
  {
    // Only trigger Released if Pressed is not yet removed by polling
    if (hasFlag(stateRef.flags, BSFPressed))
      setFlag(stateRef.flags, BSFReleased);
  }

  return isPressed(newState) || (stateRef.debounceTicks > 0);
  #undef stateRef
}



// every 10ms
ISR(TIMER0_COMPA_vect)
{
  uint8_t pinState = PINB;
  uint8_t buttonActive;

  buttonActive = button_check(&buttonUp, hasFlag(pinState, _BV(BUTTON_UP)));
  buttonActive = button_check(&buttonDown, hasFlag(pinState, _BV(BUTTON_DOWN))) || buttonActive;
  buttonActive = button_check(&buttonOption, hasFlag(pinState, _BV(BUTTON_OPTION))) || buttonActive;

  if (buttonActive)
    inactivityTicks = 0;
  else if (inactivityTicks <= DEBOUNCE_TIME)
    inactivityTicks++;
}



inline uint8_t readAndClearFlag(ButtonState* state, uint8_t flag)
{
  uint8_t result;

  ATOMIC_BLOCK(ATOMIC_FORCEON)
  {
    result = hasFlag((*state).flags, flag);
    (*state).flags &= ~(flag);
  }

  return result;
}


uint8_t button_is_pressed(ButtonState* state)
{
  return readAndClearFlag(state, BSFPressed);
}

uint8_t button_is_released(ButtonState* state)
{
  return readAndClearFlag(state, BSFReleased);
}

uint8_t button_is_repeated(ButtonState* state)
{
  return readAndClearFlag(state, BSFRepeated);
}


uint8_t button_is_pressed_or_repeated(ButtonState* state)
{
  return readAndClearFlag(state, BSFPressed | BSFRepeated);
}


uint8_t button_is_pressed_short(ButtonState* state)
{
  uint8_t result;

  ATOMIC_BLOCK(ATOMIC_FORCEON)
  {
    result = hasFlag((*state).flags, BSFPressed) &&
             hasFlag((*state).flags, BSFReleased) &&
             (!hasFlag((*state).flags, BSFRepeated));

    if (result)
      (*state).flags &= ~(BSFPressed | BSFReleased);
  }

  return result;
}

uint8_t button_is_pressed_long(ButtonState* state)
{
  uint8_t result;

  ATOMIC_BLOCK(ATOMIC_FORCEON)
  {
    result = hasFlag((*state).flags, BSFPressed) &&
             hasFlag((*state).flags, BSFRepeated);

    // Remove RepeatEnabled as well to prevent further repeat events
    if (result)
      (*state).flags &= ~(BSFPressed | BSFRepeated | BSFRepeatEnabled);
  }

  return result;
}