//ARDUINO UNO - PRIMARY LIMITATION: 32K bytes of Flash program memory, 2k bytes of SRAM and 1K bytes of EEPROM!
#include <Adafruit_NeoPixel.h>  //Library for LED matrix 
#include <LiquidCrystal_I2C.h>  //Library for I2C LCD screen

//LED Matrix Characteristics
#define LED_Matrix_PIN  3
#define LED_COUNT 484 //Number of LEDs on strip (= 22*22)
#define matrixSide 22 //Width and height of LED matrix for YX coordinates

Adafruit_NeoPixel LED_strip = Adafruit_NeoPixel(LED_COUNT, LED_Matrix_PIN, NEO_GRB + NEO_KHZ800); //Strip properties

//LDR Characteristics
#define LDR_PIN A0
//int8_t RL10 = 50;  //LDR resistance at 10 lux in kOhm
//float GAMMA = 0.7;  //Slope of log(R)/log(lux) curve

//LED Characteristics
#define LED_PIN 11

//LCD Screen Charachteristics
#define LCD_PIN_1 A4
#define LCD_PIN_2 A5

LiquidCrystal_I2C LCD_screen(0x27, 16, 2);  //Screen properties

//Range of optimal lux conditions (dynamic range: no fixed values/saturation)
int8_t lux_floor = 1;
int lux_ceil = 10000;

//Range of stimulation voltages (in volt)
float stimV_floor = 0.3;
int8_t stimV_ceil = 3;

//Interval borders for "binning" of stimulation voltages
float stimV_steps[5] = {0.30082, 0.30909, 0.34075, 0.50495, 1.3757}; //IGNORE UPPER AND LOWER BOUNDARIES (0V and 3V)

//Light conditions for different lux values to print on LCD screen
char *light_condition[9] = {"FULL MOON", "DEEP TWILIGHT", "TWILIGHT", "MONITOR LIGHT", "STAIRWAYS", "OFFICE", "OVERCAST DAY", "FULL DAYLIGHT", "DIRECT SUN"};

//Ring coordinates for each square (ONLY PASS LEFT UPPER CORNER OF RINGS IN YX COORDINATES!)
//Ring order from Q1 to Q6: Central to peripheral
//Square order in arrays: Upper left, Lower left, Upper right, Lower right

//Centers (Stimulation electrodes/"Ring 1") 
int8_t Q1y[] = {5, 16, 5, 16, 1};  //Y coordinates for all four squares plus ring width/height on last position
int8_t Q1x[] = {5, 5, 16, 16};     //X coordinates for all four squares (nothing else!)
//Ring 2
int8_t Q2y[] = {4, 15, 4, 15, 3};
int8_t Q2x[] = {4, 4, 15, 15};
//Ring 3
int8_t Q3y[] = {3, 14, 3, 14, 5};
int8_t Q3x[] = {3, 3, 14, 14};
//Ring 4
int8_t Q4y[] = {2, 13, 2, 13, 7};
int8_t Q4x[] = {2, 2, 13, 13};
//Ring 5
int8_t Q5y[] = {1, 1, 12, 12, 9};
int8_t Q5x[] = {1, 12, 1, 12};
//Ring 6
int8_t Q6y[] = {0, 11, 0, 11, 11};
int8_t Q6x[] = {0, 0, 11, 11};

//Pre-defined possible colors of rings (in descending order of intensity)
uint8_t color_peach[3] = {255, 100, 100}; //Highest intensity
uint8_t color_red[3] = {255, 0, 0};
uint8_t color_orange[3] = {255, 100, 0};
uint8_t color_yellow[3] = {255, 247, 0};
uint8_t color_turquoise[3] = {0, 213, 255};
uint8_t color_blue[3] = {0, 0, 255};  //Lowest intensity

void setup()  //Runs once upon start of the simulation
{
  //Optional definitions (analog pins) - Not required for functional code
  pinMode(LCD_PIN_1, OUTPUT);
  pinMode(LCD_PIN_2, OUTPUT);
  pinMode(LDR_PIN, INPUT);

  //Technically necessary definitions (digital pins) - Not required in simulation
  pinMode(LED_Matrix_PIN, OUTPUT);
  pinMode(LED_PIN, OUTPUT);

  //LED_strip.begin(); //Start LED strip
  //LED_strip.show(); //Set strip to black before displaying anything

  LCD_screen.begin(16, 2); //Initialize LCD screen

  //Serial.begin(9600); //May prevent initialization of LED strip! ONLY USE FOR DEBUGGING!!!
}

void loop() //Runs repeatedly as long as the simulation is running (16 MHz intrinsic clock frequency)
{
  //Check analog value of LDR output
  int LDR_OUTPUT = analogRead(A0); //Ranging from 1015 to 8 (NOT ENTIRE RANGE 0-1023!)

  //Indicate light condition with LED
  uint8_t LED_brightness = map(LDR_OUTPUT, 8, 1015, 255, 0);  //Ranging from 255 to 0
  analogWrite(LED_PIN, LED_brightness);

  //Compute lux value from LDR output
  float voltage = LDR_OUTPUT / 1024. * 5;
  float resistance = 2000 * voltage / (1 - voltage / 5);
  float lux = pow(50000 * pow(10, 0.7) / resistance, (1 / 0.7));
  
  //Cap lux value if it is too low (< 1 lux) or too high (> 10000 lux)
  float lux_capped = lux;
  if (lux < lux_floor)
  {
    lux_capped = lux_floor;
  } else if (lux > lux_ceil)
  {
    lux_capped = lux_ceil;
  }

  //Transform capped lux into stimulation voltages between 0.3V and 3V (can't use map() because it returns int)
  float stimVoltage = (lux_capped - lux_floor)*(stimV_ceil - stimV_floor)/(lux_ceil - lux_floor) + stimV_floor;

  //Set entire LED matrix to lowest intensity (blue)
  RingCol(&Q1y[0], &Q1x[0], color_blue);
  RingCol(&Q2y[0], &Q2x[0], color_blue);
  RingCol(&Q3y[0], &Q3x[0], color_blue);
  RingCol(&Q4y[0], &Q4x[0], color_blue);
  RingCol(&Q5y[0], &Q5x[0], color_blue);
  RingCol(&Q6y[0], &Q6x[0], color_blue);

  //Set ring colors of squares according to current stimulation voltage
  if ((stimVoltage > stimV_steps[0]) && (stimVoltage <= stimV_steps[1]))
  {
    RingCol(&Q1y[0], &Q1x[0], color_turquoise); //Color of centers
  }
  else if ((stimVoltage > stimV_steps[1]) && (stimVoltage <= stimV_steps[2]))
  {
    RingCol(&Q1y[0], &Q1x[0], color_yellow); //Color of centers
    RingCol(&Q2y[0], &Q2x[0], color_turquoise); //Color of ring 2
  }
  else if ((stimVoltage > stimV_steps[2]) && (stimVoltage <= stimV_steps[3]))
  {
    RingCol(&Q1y[0], &Q1x[0], color_orange); //Color of centers
    RingCol(&Q2y[0], &Q2x[0], color_yellow); //Color of ring 2
    RingCol(&Q3y[0], &Q3x[0], color_turquoise); //Color of ring 3
  }
  else if ((stimVoltage > stimV_steps[3]) && (stimVoltage < stimV_steps[4]))
  {
    RingCol(&Q1y[0], &Q1x[0], color_red); //Color of centers
    RingCol(&Q2y[0], &Q2x[0], color_orange); //Color of ring 2
    RingCol(&Q3y[0], &Q3x[0], color_yellow); //Color of ring 3
    RingCol(&Q4y[0], &Q4x[0], color_turquoise); //Color of ring 4
  }
  else if (stimVoltage >= stimV_steps[4])
  {
    RingCol(&Q1y[0], &Q1x[0], color_peach); //Color of centers
    RingCol(&Q2y[0], &Q2x[0], color_red); //Color of ring 2
    RingCol(&Q3y[0], &Q3x[0], color_orange); //Color of ring 3
    RingCol(&Q4y[0], &Q4x[0], color_yellow); //Color of ring 4
    RingCol(&Q5y[0], &Q5x[0], color_turquoise); //Color of ring 5
  }

  //Add noise
  for (int pos = 0; pos < 484; pos++)
  {
    uint8_t rand_val = random(29);

    if (rand_val == 0)
    {
      uint32_t noise_color = LED_strip.Color(color_yellow[0], color_yellow[1], color_yellow[2]);
      LED_strip.setPixelColor(pos, noise_color);
    }
    else if (rand_val == 1)
    {
     uint32_t noise_color = LED_strip.Color(color_turquoise[0], color_turquoise[1], color_turquoise[2]);
      LED_strip.setPixelColor(pos, noise_color);
    }
    else if (rand_val == 2)
    {
      uint32_t noise_color = LED_strip.Color(color_orange[0], color_orange[1], color_orange[2]);
      LED_strip.setPixelColor(pos, noise_color);
    }
  }
  LED_strip.show();

  /*//Print lux on LCD screen
  LCD_screen.setCursor(0, 0);
  LCD_screen.print("LUX = ");
  LCD_screen.print(lux);
  LCD_screen.print("     ");*/ //Overwrite max. possible number of unused digits in this line (DON'T CHANGE! MAY CAUSE SCREEN FLICKER!)

  //Evaluate light condition
  bool light_state[8] = {};
  light_state[0] = (lux < 0.2);
  light_state[1] = ((lux >= 0.2) && (lux < 1));
  light_state[2] = ((lux >= 1) && (lux < 10));
  light_state[3] = ((lux >= 10) && (lux < 50));
  light_state[4] = ((lux >= 50) && (lux < 100));
  light_state[5] = ((lux >= 100) && (lux < 400));
  light_state[6] = ((lux >= 400) && (lux < 1000));
  light_state[7] = ((lux >= 1000) && (lux <= 10000));

  //Print light condition on LCD screen
  LCD_screen.setCursor(0, 0);
  if (light_state[0])
  { 
    LCD_screen.print(light_condition[0]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else if (light_state[1])
  {
    LCD_screen.print(light_condition[1]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else if (light_state[2])
  {
    LCD_screen.print(light_condition[2]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else if (light_state[3])
  {
    LCD_screen.print(light_condition[3]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  } 
  else if (light_state[4])
  { 
    LCD_screen.print(light_condition[4]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else if (light_state[5])
  { 
    LCD_screen.print(light_condition[5]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else if (light_state[6])
  { 
    LCD_screen.print(light_condition[6]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else if (light_state[7])
  { 
    LCD_screen.print(light_condition[7]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("        ");
  }
  else
  {
    LCD_screen.print(light_condition[8]);
    LCD_screen.print("       ");
    LCD_screen.setCursor(0, 1);
    LCD_screen.print("DANGER!!");
  }
}


//ADDITIONALLY REQUIRED FUNCTIONS

//Set ring color for all four squares (without displaying them immediately)
void RingCol(int8_t *ringY, int8_t *ringX, uint8_t coloration[])
{
  int upperPixel;
  int lowerPixel;
  int leftPixel;
  int rightPixel;

  uint32_t color = LED_strip.Color(coloration[0], coloration[1], coloration[2]); //Define ring color   

  for (int8_t qp = 0; qp < 4; qp++)  //Cycle through squares in same order as in arrays
  {
    for (int8_t rr = 0; rr < ringY[4]; rr++) //Set color of single pixels
    {
      //Top row from left to right
      upperPixel = YX(ringY[qp], ringX[qp] + rr);
      LED_strip.setPixelColor(upperPixel, color);

      //Bottom row from left to right
      lowerPixel = YX(ringY[qp] + ringY[4] - 1, ringX[qp] + rr);
      LED_strip.setPixelColor(lowerPixel, color);

      //Left column from top to bottom
      leftPixel = YX(ringY[qp] + rr, ringX[qp]);
      LED_strip.setPixelColor(leftPixel, color);

      //Right column from top to bottom
      rightPixel = YX(ringY[qp] + rr, ringX[qp] + ringY[4] - 1);
      LED_strip.setPixelColor(rightPixel, color);
    }
  }
}

//Transform YX coordinates in LED coordinates on strip
uint16_t YX( uint8_t y, uint8_t x)
{
  int i; 

  if (y & 0x01)
  {
    uint8_t reverseX = (matrixSide - 1) - x;  //Odd rows run backwards
    i = (y * matrixSide) + reverseX;
  } else
  {
    i = (y * matrixSide) + x; //Even rows run forwards
  }
  return i; //Single LED coordinate on strip
}