#include "Atm_led.hpp"

Atm_led& Atm_led::begin( int attached_pin, bool activeLow ) {
  // clang-format off
  static const state_t state_table[] PROGMEM = {
    /*               ON_ENTER    ON_LOOP    ON_EXIT  EVT_ON_TIMER  EVT_OFF_TIMER EVT_WT_TIMER EVT_COUNTER  EVT_ON  EVT_OFF  EVT_BLINK  EVT_TOGGLE  EVT_TOGGLE_BLINK   ELSE */
    /* IDLE      */  ENT_INIT, ATM_SLEEP,        -1,           -1,            -1,          -1,         -1,  WT_ON,      -1,  WT_START,         ON,         WT_START,    -1, // LED off
    /* ON        */    ENT_ON, ATM_SLEEP,        -1,           -1,            -1,          -1,         -1,     -1,     OFF,  WT_START,        OFF,              OFF,    -1, // LED on
    /* START     */    ENT_ON,        -1,        -1,    BLINK_OFF,            -1,          -1,         -1,  WT_ON,     OFF,        -1,        OFF,              OFF,    -1, // Start blinking
    /* BLINK_OFF */   ENT_OFF,        -1,        -1,           -1,          LOOP,          -1,         -1,  WT_ON,     OFF,        -1,        OFF,              OFF,    -1,
    /* LOOP      */        -1,        -1,        -1,           -1,            -1,          -1,       DONE,  WT_ON,     OFF,        -1,        OFF,              OFF, START,    
    /* DONE      */        -1,        -1, EXT_CHAIN,           -1,           OFF,          -1,         -1,  WT_ON,     OFF,  WT_START,        OFF,              OFF,    -1, // Wait after last blink
    /* OFF       */   ENT_OFF,        -1,        -1,           -1,            -1,          -1,         -1,  WT_ON,     OFF,  WT_START,         -1,               -1,  IDLE, // All off -> IDLE
    /* WT_ON     */        -1,        -1,        -1,           -1,            -1,          ON,         -1,  WT_ON,     OFF,  WT_START,         -1,               -1,    -1, // LEAD for ON
    /* WT_START  */        -1,        -1,        -1,           -1,            -1,       START,         -1,  WT_ON,     OFF,  WT_START,         -1,               -1,    -1, // LEAD for BLINK
  }; 
  // clang-format on
  Machine::begin( state_table, ELSE );
  pin = attached_pin;
  this->activeLow = activeLow;
  level = 255;
  toLow = 0;
  toHigh = 255;
  wrap = false;
  pinMode( pin, OUTPUT );
  digitalWrite( pin, activeLow ? HIGH : LOW );
  on_timer.set( 500 );
  off_timer.set( 500 );
  pwm( 512, 1 );
  lead_timer.set( 0 );
  repeat_count = ATM_COUNTER_OFF;
  counter.set( repeat_count );
  while ( state() != 0 ) cycle();
  return *this;
}

Atm_led& Atm_led::pwm( uint16_t width, float freq ) {

    if ( freq > -1 ) {	
		this->freq = freq;
	} else {
		freq = this->freq;
	}
	this->width = width;
	float cycle_width = 1000 / freq;
	on_timer.set( cycle_width / 1024 * this->width );
	off_timer.set( cycle_width / 1024 * ( 1024 - this->width ) );
	return *this;
}

Atm_led& Atm_led::frequency( float freq ) {

	this->freq = freq;
	float cycle_width = 1000 / freq;
	on_timer.set( cycle_width / 1024 * this->width );
	off_timer.set( cycle_width / 1024 * ( 1024 - this->width ) );
	return *this;
}

int Atm_led::event( int id ) {
  switch ( id ) {
    case EVT_ON_TIMER:
      return on_timer.expired( this );
    case EVT_OFF_TIMER:
      return off_timer.expired( this );
    case EVT_WT_TIMER:
      return lead_timer.expired( this );
    case EVT_COUNTER:
      return counter.expired();
  }
  return 0;
}

void Atm_led::action( int id ) {
  switch ( id ) {
    case ENT_INIT:
      counter.set( repeat_count );
      return;
    case ENT_ON:
      if ( on_timer.value > 0 ) { // Never turn if on_timer is zero (duty cycle 0 must be dark)
        if ( activeLow ) {
          digitalWrite( pin, LOW );
        } else {
          if ( level == toHigh ) {
            digitalWrite( pin, HIGH );
          } else {
            analogWrite( pin, mapLevel( level ) );
          }
        }
      }
      return;
    case ENT_OFF:
      counter.decrement();
      if ( !activeLow ) {
        digitalWrite( pin, LOW );
      } else {
        if ( level == toHigh ) {
          digitalWrite( pin, HIGH );
        } else {
          analogWrite( pin, mapLevel( level ) );
        }
      }
      return;
    case EXT_CHAIN:
      onfinish.push( 0 );
      return;
  }
}

int Atm_led::mapLevel( int level ) {
  if ( levelMapSize ) {
    return levelMap[level];
  } else {
    return map( level, toLow, toHigh, 0, 255 );
  }
}

Atm_led& Atm_led::on( void ) {
  trigger( EVT_ON );
  return *this;
}

Atm_led& Atm_led::off( void ) {
  trigger( EVT_OFF );
  return *this;
}

Atm_led& Atm_led::toggle( void ) {
  trigger( EVT_TOGGLE );
  return *this;
}

Atm_led& Atm_led::toggleBlink( void ) {
  trigger( EVT_TOGGLE_BLINK );
  return *this;
}

Atm_led& Atm_led::start( void ) {
  trigger( EVT_BLINK );
  return *this;
}

Atm_led& Atm_led::onFinish( Machine& machine, int event /* = 0 */ ) {
  onfinish.set( &machine, event );
  return *this;
}

Atm_led& Atm_led::onFinish( atm_cb_push_t callback, int idx /* = 0 */ ) {
  onfinish.set( callback, idx );
  return *this;
}

Atm_led& Atm_led::blink( uint32_t duration, uint32_t pause_duration, uint16_t repeat_count /* = ATM_COUNTER_OFF */ ) {
  blink( duration );  // Time in which led is fully on
  pause( pause_duration );
  repeat( repeat_count );
  return *this;
}

Atm_led& Atm_led::blink( uint32_t duration ) {
  on_timer.set( duration );  // Time in which led is fully on
  return *this;
}

Atm_led& Atm_led::blink( void ) {
  trigger( EVT_BLINK );
  return *this;
}

Atm_led& Atm_led::range( int toLow, int toHigh, bool wrap /* = false */ ) {
  this->toLow = toLow; 
  this->toHigh = toHigh; 
  this->wrap = wrap;
  level = toHigh;
  return *this;
}

Atm_led& Atm_led::levels( unsigned char* map, int mapsize, bool wrap /* = false */ ) {
  this->levelMap = map;
  levelMapSize = mapsize;
  range( 0, mapsize - 1, wrap );
  return *this;
}

Atm_led& Atm_led::pause( uint32_t duration ) {  // Time in which led is fully off
  off_timer.set( duration ? duration : 1 );     // Make sure off_timer is never 0 (work around)
  return *this;
}

Atm_led& Atm_led::fade( int fade ) {
  return *this;
}  // Dummy for method compatibility with Atm_fade

Atm_led& Atm_led::lead( uint32_t ms ) {
  lead_timer.set( ms );
  return *this;
} 

Atm_led& Atm_led::repeat( uint16_t repeat ) {
  counter.set( repeat_count = repeat );
  return *this;
}

int Atm_led::brightness( int level /* = -1 */ ) {
  if ( level > -1 ) {
    this->level = level;
    if ( current == ON || current == START ) {
      analogWrite( pin, mapLevel( level ) );
    }
  }
  return this->level;
}

int Atm_led::brighten( int v ) {
  if ( abs( v ) == 1 ) {
    int br = (int)this->level + v;
    if ( br > toHigh ) 
      br = wrap ? toLow : toHigh;
    if ( br < toLow ) 
      br = wrap ? toHigh : toLow;
    brightness( br );
  }
  return this->level;
}

Atm_led& Atm_led::trigger( int event ) {
  if ( event > ELSE ) {
    brighten( event == EVT_BRUP ? 1 : -1 );
  } else {
    Machine::trigger( event );
  }
  return *this;
}

Atm_led& Atm_led::trace( Stream& stream ) {
  setTrace( &stream, atm_serial_debug::trace,
            "LED\0EVT_ON_TIMER\0EVT_OFF_TIMER\0EVT_WT_TIMER\0EVT_COUNTER\0EVT_ON\0EVT_OFF\0EVT_"
            "BLINK\0EVT_TOGGLE\0EVT_TOGGLE_BLINK\0ELSE\0"
            "IDLE\0ON\0START\0BLINK_OFF\0LOOP\0DONE\0OFF\0WT_ON\0WT_START" );
  return *this;
}
