#include "Atm_comparator.hpp"

Atm_comparator& Atm_comparator::begin( int attached_pin, int samplerate /* = 50 */ ) {
  // clang-format off
  const static state_t state_table[] PROGMEM = {
    /*              ON_ENTER    ON_LOOP  ON_EXIT  EVT_TRIGGER EVT_TIMER   ELSE */
    /* IDLE   */          -1,        -1,      -1,           -1,   SAMPLE,    -1,
    /* SAMPLE */  ENT_SAMPLE,        -1,      -1,         SEND,       -1,  IDLE,
    /* SEND   */    ENT_SEND,        -1,      -1,           -1,       -1,  IDLE,
  };
  // clang-format on
  Machine::begin( state_table, ELSE );
  pin = attached_pin;
  timer.set( samplerate );
  bitmap_sample = 0;
  bitmap_previous = 0;
  skip_mode = 0;
  return *this;
}

int Atm_comparator::event( int id ) {
  switch ( id ) {
    case EVT_TRIGGER:
      if ( bitmap_diff ) {
        return 1;
      }
      return 0;
    case EVT_TIMER:
      return timer.expired( this );
  }
  return 0;
}

void Atm_comparator::action( int id ) {
  switch ( id ) {
    case ENT_SAMPLE:
      v_previous = v_sample;
      bitmap_previous = bitmap_sample;
      v_sample = sample();
      bitmap( v_sample );
      return;
    case ENT_SEND:
      int final_step = -1;
      if ( v_sample >= v_previous ) {
        for ( uint16_t i = 0; i < p_threshold_size; i++ ) {
          if ( ( bitmap_diff >> i ) & 1 ) {
            if ( skip_mode == 0 ) {
              onup.push( i, 1 );
            } else {
              final_step = i;
            }
          }
        }
      } else {
        for ( int i = p_threshold_size; i >= 0; i-- ) {
          if ( ( bitmap_diff >> i ) & 1 ) {
            if ( skip_mode == 0 ) {
              ondown.push( i, 0 );
            } else {
              final_step = i;
            }
          }
        }
      }
      if ( final_step > -1 ) {
        if ( v_sample >= v_previous ) {
          onup.push( final_step, 0 );
        } else {
          ondown.push( final_step, 0 );
        }
      }
      return;
  }
}

Atm_comparator& Atm_comparator::onChange( atm_cb_push_t callback, int idx /* = 0 */ ) {
  onup.set( callback, idx );
  ondown.set( callback, idx );
  return *this;
}

Atm_comparator& Atm_comparator::onChange( Machine& machine, int event /* = 0 */ ) {
  onup.set( &machine, event );
  ondown.set( &machine, event );
  return *this;
}

Atm_comparator& Atm_comparator::onChange( bool status, atm_cb_push_t callback, int idx /* = 0 */ ) {
  if ( status ) {
    onup.set( callback, idx );
  } else {
    ondown.set( callback, idx );
  }
  return *this;
}

Atm_comparator& Atm_comparator::onChange( bool status, Machine& machine, int event /* = 0 */ ) {
  if ( status ) {
    onup.set( &machine, event );
  } else {
    ondown.set( &machine, event );
  }
  return *this;
}

Atm_comparator& Atm_comparator::skip() {
  skip_mode = 1;
  return *this;
}

int Atm_comparator::read_sample() {
  return analogRead( pin );
}

int Atm_comparator::avg() {
  uint16_t v = read_sample();
  avg_buf_total = avg_buf_total + v - avg_buf[avg_buf_head];
  avg_buf[avg_buf_head] = v;
  if ( avg_buf_head + 1 >= avg_buf_size ) {
    avg_buf_head = 0;
  } else {
    avg_buf_head++;
  }
  return avg_buf_total / avg_buf_size;
}

int Atm_comparator::sample() {
  return avg_buf_size > 0 ? avg() : read_sample();
}

int Atm_comparator::state( void ) {
  return v_sample;
}

Atm_comparator& Atm_comparator::threshold( uint16_t* v, uint16_t size, bool catchUp /* = false */ ) {
  p_threshold = v;
  p_threshold_size = size / sizeof( uint16_t );
  if ( !catchUp ) {
    v_sample = sample();
    bitmap( v_sample );
    v_previous = v_sample;
    bitmap_previous = bitmap_sample;
  }
  return *this;
}

Atm_comparator& Atm_comparator::average( uint16_t* v, uint16_t size ) {
  avg_buf = v;
  avg_buf_size = size / sizeof( uint16_t );
  avg_buf_head = 0;
  avg_buf_total = 0;
  for ( uint16_t i = 0; i < avg_buf_size; i++ ) {
    avg_buf[i] = read_sample();
    avg_buf_total += avg_buf[i];
  }
  return *this;
}

Atm_comparator& Atm_comparator::bitmap( uint16_t v ) {
  bitmap_sample = 0;
  for ( uint8_t i = 0; i < p_threshold_size; i++ ) {
    if ( v >= p_threshold[i] ) bitmap_sample |= ( 1 << i );
  }
  bitmap_diff = bitmap_sample ^ bitmap_previous;
  return *this;
}

Atm_comparator& Atm_comparator::trace( Stream& stream ) {
  setTrace( &stream, atm_serial_debug::trace,
            "EVT_TRIGGER\0EVT_TIMER\0ELSE\0"
            "IDLE\0SAMPLE\0SEND" );
  return *this;
}
