#ifndef __leds_H__
#define __leds_H__

#include "Arduino.h"
#include <Adafruit_NeoPixel.h>

class leds
{
    const uint8_t FADESTEPS = 40;
    const unsigned long FADE_WAIT_MS = 10; 
    const uint8_t MAX_BRIGHTNESS = 255;
    const uint8_t MIN_BRIGHTNESS = 10;
    const unsigned long WAIT_RAINBOWCYCLE_MS = 50;

    uint8_t _local_brightness = 255;
    float _R = 0;
    typedef struct
    {
      uint16_t lednr;
      uint32_t color;
    } _pixel;
    unsigned int _pixel_nr = 0;
    unsigned int _pixel_last, _pixel_first;
    _pixel *_pixels; 
    uint32_t * _state_flag;
    uint32_t _state_mask;
    int _fadestep = 0;
    unsigned long _last_faded_ms = 0;
    bool _fading_done = false;
    uint16_t _prev_state = 0;
    Adafruit_NeoPixel * _stripe;

    uint8_t _s = 0;
    uint16_t _h = 0;

    float _calc_R();
    void _setLeds();
    void _setLeds(uint32_t color);
    void _resetLeds(uint32_t color);
    bool _fade(bool fade_in);
    bool _fade_off();
    bool _fade_on();

    uint16_t _rainbow_pixel, _rainbow_color;
    unsigned long _last_rainbowcycle = 0;
    uint32_t _wheel(byte pos);

  public:
    
    enum led_update {UP_RAINBOW = 1, UP_FADE = 2};

    leds(Adafruit_NeoPixel * stripe, uint32_t * state_flag, uint16_t state_mask, uint16_t H, uint8_t S, unsigned int pixel_first, unsigned int pixel_last );
    ~leds();
    bool update();
    bool update(led_update type);
    void clear();
    bool colorWipe( uint32_t color );
    void add_button_id(uint16_t mask);
    bool do_fade();
    bool fade_off();
    bool fade_on();
    void setBrigthness(uint8_t brigthness);
    void setHS(uint16_t H, uint8_t S);
    void setColor(uint32_t color); 
    void rainbowCycle();
    bool do_rainbowCycle();
};

leds::leds(Adafruit_NeoPixel * stripe, uint32_t * state_flag, uint16_t state_mask, uint16_t H, uint8_t S, unsigned int pixel_first, unsigned int pixel_last )
{
    _state_flag = state_flag;
    _state_mask = state_mask;
    _h = H;
    _s = S;
    _pixel_last = pixel_last;
    _pixel_first = pixel_first;
    _pixel_nr = 1 + abs(pixel_last - pixel_first);
    _pixels = (_pixel *) malloc(sizeof(_pixel) * _pixel_nr);
    size_t index = 0;
    for(size_t nr=pixel_first; nr<=pixel_last;nr++)
    {
      _pixels[index].lednr = nr;
      index++;
    }
    _stripe = stripe;
    _R = _calc_R();
    _setLeds(0);    
    _rainbow_pixel = 0;
    _rainbow_color = 0;
    _prev_state = *_state_flag;
    _fading_done = true;
}

leds::~leds()
{
    if(NULL != _pixels)
        free(_pixels);
}

void leds::_setLeds()
{
    for(unsigned int nr = 0; nr < _pixel_nr; nr++)
    {
        _stripe->setPixelColor(_pixels[nr].lednr, _pixels[nr].color);
    }
}


void leds::_setLeds(uint32_t color)
{
    for(unsigned int nr = 0; nr < _pixel_nr; nr++)
    {
      //Serial.printf("setting led %d with nr %d in stripe to color %d\n", nr, _pixels[nr].lednr, color);
      _stripe->setPixelColor(_pixels[nr].lednr, color);
      _pixels[nr].color = color;
    }
}

void leds::clear()
{
    _setLeds(0);
}

void leds::add_button_id(uint16_t id)
{
    _state_mask = _state_mask | id;
}


bool leds::update()
{
  return(update(UP_FADE));
}

bool leds::update(led_update type)
{
  if(type == UP_RAINBOW )
  {
    return(do_rainbowCycle());
  }

  if(type == UP_FADE)
  {
    return(do_fade());
  }
  return(false);
}

bool leds::do_rainbowCycle()
{
    bool updated = false;
    uint16_t curr_state = _state_mask & *_state_flag;
    if(curr_state == 0)
    {
        clear();
    }
    else
    {
        rainbowCycle();
        updated = true;
    }
    return(updated);
}


bool leds::do_fade()
{
    bool updated = false;
    uint16_t curr_state = _state_mask & *_state_flag;

    if(_prev_state != curr_state)
    {
        //Serial.printf("Current state is 0x%08x\n", curr_state);
        _prev_state = curr_state;
        _fading_done = false;
    }

    if(_fading_done == false)
    {
        updated = true;
        if(0 == curr_state)
        {
            _fade_off();
            //Serial.printf("Fading off.\n");
        }
        else
        {
            _fade_on();
            //Serial.printf("Fading ON\n");
        }
    }
    return updated;
}

bool leds::fade_on()
{
  return _fade_on();
}

bool leds::fade_off()
{
  return  _fade_off();
}


float leds::_calc_R(void)
{
  return (FADESTEPS * log10(2))/(log10(_local_brightness));
}

void leds::setBrigthness(uint8_t brigthness)
{
  if(brigthness > MAX_BRIGHTNESS)
    _local_brightness = MAX_BRIGHTNESS;
  else
  { 
    if(brigthness < MIN_BRIGHTNESS)
      _local_brightness = MIN_BRIGHTNESS;
    else
      _local_brightness = brigthness;
  }
  _R = _calc_R();
  uint8_t fade = pow (2, (_fadestep / _R)) - 1;
  _setLeds(_stripe->ColorHSV(_h, _s, fade));
}

void leds::setHS(uint16_t H, uint8_t S)
{
  _h = H;
  _s = S;
  uint8_t fade = pow (2, (_fadestep / _R)) - 1;
  _setLeds(_stripe->ColorHSV(_h, _s, fade));
}

void leds::setColor(uint32_t color)
{
  _setLeds(color);
}

bool leds::_fade(bool fade_in)
{
  unsigned long curr_millis = millis();

  if( _last_faded_ms > (curr_millis - FADE_WAIT_MS))
    return(false);
  
  _last_faded_ms = curr_millis;
  if(_fadestep != -1)
  {
    uint8_t fade = pow (2, (_fadestep / _R)) - 1;
    _setLeds(_stripe->ColorHSV(_h, _s, fade));
  }

  if(true == fade_in)
  {
    if(_fadestep == -1)
      _fadestep = 0;
    else
    {  
      if(_fadestep<FADESTEPS) 
        _fadestep++ ;
      else
      {
        _fadestep = -1;
        _fading_done = true;
        return(true);
      }
    }
  }
  else
  {
    if(_fadestep == -1)
      _fadestep = FADESTEPS;
    else
    {
      if(_fadestep>0) 
        _fadestep--;
      else
      {
        _fadestep = -1;
        _fading_done = true;
        return(true);
      }
    }
  }
  return(false);
}

bool leds::_fade_on()
{
    return _fade(true);
}

bool leds::_fade_off()
{
    return _fade(false);
}


void leds::rainbowCycle() 
{
    if((millis() - _last_rainbowcycle) > WAIT_RAINBOWCYCLE_MS)
    {
        //Serial.printf("Rainbow! Next color\n");
        _last_rainbowcycle = millis();
        _rainbow_color++;
        if(_rainbow_color >= 256*1)
            _rainbow_color = 0;
        for(size_t nr = 0; nr< _pixel_nr; ++nr) {
            _pixels[nr].color = _wheel(((_rainbow_pixel * 256 / _pixel_nr) + _rainbow_color) & 255);
        }
        _setLeds();
    }
}

// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t leds::_wheel(byte pos) {
  pos = 255 - pos;
  if(pos < 85) {
    return _stripe->Color(255 - pos * 3, 0, pos * 3);
  }
  if(pos < 170) {
    pos -= 85;
    return _stripe->Color(0, pos * 3, 255 - pos * 3);
  }
  pos -= 170;
  return _stripe->Color(pos * 3, 255 - pos * 3, 0);
}

#endif


