// I simulate lighting efects on parking sensor lamp
// It should be a led ring lamp in a garage changing
// color and flashing by distance.

//Project link in the description :)

// Direction  Color     State
// power-up   white     steady
//  >400      green     rotating slowly
//  200-400   green     rotating faster
//  100-200   yellow    --||--
//  50-100    red       fast rotation
//  50-20     red       flashing
//  <20       red/blue  flashing


// 3rd approach: rotating and changing palette
// it is too demanding on calculation resources
// When I saw it finished, I realised it would be easier with FadeToBlackBy()
// but it is more flexible.

#include <FastLED.h>

#define TRIG_PIN A3
#define ECHO_PIN A2

#define LED_PIN     8
#define NUM_LEDS    16
#define BRIGHTNESS  255
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

#define UPDATES_FREQ    100 // per second
#define MEASURE_FREQ      2 // per second

#define DISTANCE_MAX  400 // cm
#define DISTANCE_MIN  0 // cm


CRGBPalette16 currentPalette;
TBlendType    currentBlending;

CRGB rgb;

extern const TProgmemPalette16 mySemaphorePalette_p PROGMEM;

/***
*
 ***
    *
 ***/
void setup() {
  delay(2000);

  pinMode(TRIG_PIN, OUTPUT);
  pinMode(ECHO_PIN, INPUT);

  // Serial.begin(9600);

  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS); // .setCorrection( TypicalLEDStrip );
  FastLED.setBrightness( BRIGHTNESS );

  currentPalette = mySemaphorePalette_p;
  currentBlending = LINEARBLEND;

  // blank
  FillColorDelay( CRGB::Yellow, 1000);
  // Serial.println(" [BEGIN] ");

  // blank
  FillColorDelay( CRGB::Black, 1000);
}

/*
 *
 *
 *
 ****/
void loop() {
  static unsigned int distance = 10;
  static uint8_t colorIndex;
  static byte offset = 0;
  static unsigned int offset_delay = 100;         // rotation speed

  static unsigned long lastOffsetTime = 0;        // last rotation change
  static unsigned long lastMeasureTime = 0;       // last use of HC-SR04 sensor
  static unsigned long lastPrintTime = 0;         // last Serial.print time

  // distance is 200-1500
  // ROTATION SPEED should change 20-1 rotation per second
  offset_delay = map( distance, 20, 400, 20, 100);
  if ( millis() - lastOffsetTime >= offset_delay ) {
    offset = (offset < NUM_LEDS - 1 ? offset + 1 : 0); // increment position
    lastOffsetTime = millis();
  }

  // Measure two times per second
  if (millis() - lastMeasureTime >= 1000 / MEASURE_FREQ) {
    distance = get_distance();
    colorIndex = map(distance, DISTANCE_MIN, DISTANCE_MAX, 0, 240);
    // update palette
    newGradientPaletteByDistance(distance);
    lastMeasureTime = millis();
  }

  if ( distance > 30 ) {
    FillLEDsFromPalette( offset );
  } else if ( offset % 2 == 0 ) {
    FillColorDelay(CRGB::Blue,200);
    // fill_solid( leds, NUM_LEDS, CRGB::Blue);
  } else {
    FillColorDelay(CRGB::Red,200);
    // fill_solid( leds, NUM_LEDS, CRGB::Red);
  }

  // Print only for debugging
  // if (millis() - lastPrintTime >= 2000) {
  //   Serial.print(" [ distance: ");            Serial.print(distance, DEC);
  //   Serial.print("mm   o_delay: ");           Serial.print(offset_delay, DEC);
  //   Serial.print("   index: ");               Serial.print(colorIndex, DEC);
  //   Serial.println(" ] ");
  //   lastPrintTime = millis();
  // }

  FastLED.show();
  FastLED.delay(1000 / UPDATES_FREQ);
}


// Get color mapped by distance and create gradient palette based by this color
void newGradientPaletteByDistance(int distance) {
  uint8_t xyz[12];  // Needs to be 4 times however many colors are being used.
  // 3 colors = 12, 4 colors = 16, etc.
  CRGB rgb;

  rgb = ColorFromPalette( mySemaphorePalette_p, map(distance, DISTANCE_MIN, DISTANCE_MAX, 0, 240), BRIGHTNESS, currentBlending);

  // index
  xyz[0] = 0;     xyz[1] = rgb.r;   xyz[2] = rgb.g;   xyz[3] = rgb.b;  // anchor of first color - must be zero
  xyz[4] = 40;    xyz[5] = rgb.r;   xyz[6] = rgb.g;   xyz[7] = rgb.b;  
  xyz[8] = 255;   xyz[9] = 0;       xyz[10] = 0;      xyz[11] = 0;      // anchor of last color - must be 255

  currentPalette.loadDynamicGradientPalette(xyz);
}



// fill FastLED leds[] strip/ring with colors
void FillLEDsFromPalette( byte offset) {
  //fill_solid( leds, NUM_LEDS, 0);

  // One gradient
  for ( int i = 0; i < NUM_LEDS; i++) {
    leds[ i ] = ColorFromPalette( currentPalette, (i+offset) * 255 / (NUM_LEDS - 1), BRIGHTNESS, currentBlending);
  }
  // Two gradients
  // for ( int i = 0; i < NUM_LEDS/2; i++) {
  //   leds[ i ] = ColorFromPalette( currentPalette, (i+offset) * 255 / (NUM_LEDS/2 - 1), BRIGHTNESS, currentBlending);
  //   leds[ i+(NUM_LEDS/2) ] = leds[i];
  // }
}

// Fill all leds with one color and wait duration ms
void FillColorDelay ( CRGB color, unsigned long duration ) {
  fill_solid( leds, NUM_LEDS, color);
  FastLED.show();
  FastLED.delay(duration);
}


// This example shows how to set up a static color palette
// which is stored in PROGMEM (flash), which is almost always more
// plentiful than RAM.  A static PROGMEM palette like this
// takes up 64 bytes of flash.
const TProgmemPalette16 mySemaphorePalette_p PROGMEM = {
  CRGB::Magenta, CRGB::Red,     CRGB::Red,     CRGB::Red,
  CRGB::Orange,  CRGB::Orange,  CRGB::Orange,  CRGB::Orange,
  CRGB::Yellow,  CRGB::Yellow,  CRGB::Yellow,  CRGB::Green,
  CRGB::Green,   CRGB::Green,   CRGB::Green,   CRGB::White
};



// get distance number 0-400 cm
// use TRIG_PIN and ECHO_PIN of HC-SR04 sensor
int get_distance() {
  static int distance;
  uint16_t duration = 0;
  uint32_t interval = 0;

  digitalWrite(TRIG_PIN, LOW);
  delayMicroseconds(5);
  digitalWrite(TRIG_PIN, HIGH);
  delayMicroseconds(10);
  digitalWrite(TRIG_PIN, LOW);

  // Read time of the trig and echo pins
  duration = pulseIn(ECHO_PIN, HIGH);
  // Calculates the distance
  distance = (duration / 2) / 29;

  return distance; // centimeters
}