/*
 Rocrail - Model Railroad Software
 Copyright (c) 2002-2019 Robert Jan Versluis, Rocrail.net
 All rights reserved.
*/
#include <FastLED.h>
#include <RASCII.h>

#include "LEDii_dfs.h"
#include "ledii.h"
#include "cv.h"

#define DATA_PIN 3
#define NUM_LEDS 64
CRGB leds[NUM_LEDS];

static struct _MODCNF modcnf;

RASCII Rascii(false);

/*
 * Setup RASCII and FastLED objects.
 */
void setup() {
  CV::getMOD(&modcnf);
  if( modcnf.magic != MAGIC ) {
    CV::reset( sizeof(struct _MODCNF) + NUM_LEDS * sizeof(struct _LEDCNF) );
    modcnf.magic = MAGIC;
    CV::setMOD(&modcnf);
  }
  
  Rascii.begin(9600);
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);
  FastLED.clear();

  //doLED(0, 20, 0xFF, 0xFF, 0x00, 0x01);

}

/*
 * Main loop.
 */
static byte cmd[32];
void loop() {
  memset(cmd, 0, sizeof(cmd));
  if( Rascii.read(cmd) ) {
    evaluateCmd(cmd);
  }
  doBri();
  delay(50); // A delay of less then 25 does hickup FastLED...
}


void doBri() {
  for( byte i = 0; i < NUM_LEDS; i++ ) {
    struct _LEDCNF ledcnf;
    CV::getLED(i, &ledcnf);
    
    if( (ledcnf.bricur != ledcnf.brinew) || (ledcnf.flag == FLAG_UPDATE) || (ledcnf.opt & OPT_BLINK && ledcnf.bri > 0) ) {
      ledcnf.flag = FLAG_NONE;
      
      int delay = (ledcnf.opt & OPT_DELAY);
      if( delay == 0 )
        delay = 1;

      int bricur = ledcnf.bricur;
      int brinew = ledcnf.brinew;
      // dim down
      if( bricur > brinew ) {
        bricur = bricur - delay;
        if( bricur < brinew )
          bricur = brinew;
      }

      // dim up
      else if( brinew > bricur ) {
        bricur = bricur + delay;
        if( bricur > brinew )
          bricur = brinew;
      }

      // check blink
      if( ledcnf.opt & OPT_BLINK ) {
        if( bricur == brinew ) {
          if( brinew > 0 ) 
            ledcnf.brinew = 0;
          else 
            ledcnf.brinew = ledcnf.bri;
        }
      }

      ledcnf.bricur = (byte)bricur;
      leds[i]  = ledcnf.rgb;
      leds[i] %= ledcnf.bricur;

      CV::setLED(i, &ledcnf);


#ifdef DEBUG
      //sprintf(dStr, "doBri %d bri:%d->%d rgb:%06lX opt:%02X delay=%d flag=%02X", i, ledcnf.bricur, ledcnf.brinew, ledcnf.rgb, ledcnf.opt, delay, ledcnf.flag );
      //Rascii.write(dStr);
#endif

    }
  }
  FastLED.show(); 
}

/*
 * Set LED RGB and brightness.
 */
void doLED(byte addr, byte bri, byte r, byte g, byte b, byte opt) {
  if( addr > NUM_LEDS ) {
    sprintf(dStr, "LED %d >= %d", addr, NUM_LEDS);
    Rascii.write(dStr);
    return;    
  }
  
  struct _LEDCNF ledcnf;
  CV::getLED(addr, &ledcnf);
  
  ledcnf.rgb = r;
  ledcnf.rgb <<= 16;
  ledcnf.rgb += (g << 8) + b;
  
  ledcnf.bri    = bri;
  ledcnf.opt    = opt;
  ledcnf.flag   = FLAG_UPDATE;
  ledcnf.brinew = bri;

  if( (opt & OPT_DELAY) == 0 ) {
    ledcnf.bricur = bri;
  }

  CV::setLED(addr, &ledcnf);
 
#ifdef DEBUG
  sprintf(dStr, "doLED %d bri:%d->%d rgb:%06lX opt:%02X flag:%02X", addr, ledcnf.bricur, ledcnf.brinew, ledcnf.rgb, ledcnf.opt, ledcnf.flag);
  Rascii.write(dStr);
#endif
}

/*
 * Evaluate a host command.
 */
void evaluateCmd(byte* cmd) {
  if( cmd[0] == CMD_INFO ) {
    Rascii.write(VERSION);
  }
  else if( cmd[0] == CMD_DOUT ) {
    byte bri = cmd[3] > 0 ? 255:0;
    if( cmd[3] > 1 )
      bri = cmd[3];
    doLED(cmd[2]-1, bri, 0xFF, 0xFF, 0xFF, 0x02);
  }
  else if( cmd[0] == CMD_AOUT ) {
    if( cmd[1] > 0 && cmd[2] > cmd[1] ) {
      // LED range:
      for( byte i = cmd[1]-1; i <= cmd[2]-1; i++ )
        doLED(i, cmd[3], cmd[4], cmd[5], cmd[6], cmd[7]);
    }
    else {
      // Single LED:
      doLED(cmd[2]-1, cmd[3], cmd[4], cmd[5], cmd[6], cmd[7]);
    }
  }
  else {
#ifdef DEBUG
    sprintf(dStr, "cmd=%02X", cmd[0]);
    Rascii.write(dStr);
#endif
  }
}
