#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "myGFX.h"

#include <FastLED.h>
#define NUM_LEDS           20
#define BRIGHTNESS         25
FASTLED_USING_NAMESPACE
CRGB leds[NUM_LEDS];

#define potiPin     36
#define ledPin      14
#define encA        34
#define encB        35
#define encBtn      33
/*
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_RESET     4 // Reset pin # (or -1 if sharing Arduino reset pin)
*/
Adafruit_SSD1306 display(128, 64, &Wire, 4);

#define MENU_TIME 0  
#define MENU_TEMP 1
#define MENU_STATE 2





struct scale{ uint8_t lineLen; float linePosDeg; char t_val[3]; int8_t xOff; int8_t yOff;};

const scale scaleVals[9] = {
{4, -120.0, "0",-12,3},
{3, -90.0 , "",   0, 0},
{4, -60.0 , "10",-13,-8},
{3, -30.0 , "",0,0},
{4,  0.0  , "20",-4,-12},
{3, 30.0  , "",0,0},
{4, 60.0  , "30",8,-8},
{3, 90.0  , "",0,0},
{4, 120.0 , "40",6,2}
};




  unsigned long time_stop;

  char time_m[4];
  char time_s[4];
  //overview grapics const
 // const int gauge_x = 36;
 // const int gauge_y = 37;
 // const int gauge_r = 26;
    const int gauge_x = 36;
  const int gauge_y = 40;
  const int gauge_r = 16;
  const int dot_r = 6;
  const float arcMin = -120.0;
  const float arcMax = 120.0;
  // val contraining const
  const float temp_min = 30;
  const float temp_max = 60;
  const uint16_t time_min = 60;
  const uint16_t time_max = 1500;


  uint16_t time_setP = 600;
  float temp_setP = 50.0;
  bool state = false;
  int8_t menuSelect = MENU_TEMP; // selected menu index MENU_TIME=0, MENU_TEMP=1 MENU_STATE=2


void setup() {
  Serial.begin(115200);
  FastLED.addLeds<WS2812B, ledPin, GRB>(leds, NUM_LEDS);
  FastLED.setBrightness(BRIGHTNESS);
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) { Serial.println(F("SSD1306 allocation failed")); for(;;);}

  pinMode(potiPin, INPUT);

  display.clearDisplay();
  drawOverview(); // draw all static elements (gauge, static icons etc...)

  // Overview initial values :
  drawTimer(time_setP);  // int, seconds
  drawTempSP(temp_setP); // float, temp setpoint --> TO DO: update PID set point
  drawState(state); // bool, plate on/off state

  drawTempRead(25.0); // update gauge

  display.display();


setMenuSelect(0); // also changes menuSelect (menuSelect in bounds 0 to 2)

drawState(false);

showWarn(true);display.display();
delay(3000);
showWarn(false);display.display();
}



void loop() {

uint16_t potiVal = chkPotiUpdate();
if(potiVal != -1){

  switch (menuSelect) {
    case MENU_TIME : drawTimer(poti2time(potiVal)); break;
    case MENU_TEMP : drawTimer(poti2tempSP(potiVal)); break; 
    case MENU_STATE: setState(potiVal); break; 
    default: break; 
  }
}
Serial.println(analogRead(potiPin));
}

// returns -1 if no significant update happend
// deadzone depends on last time & amount value changed
// run this at least every 100 ms
int16_t chkPotiUpdate(){
  static unsigned long potiSample; //sampling timer to compare value deviation
  static uint8_t deadzone = 15; //5 or 15
  static int16_t potiVal_last = analogRead(potiPin);
  static int16_t potiVal_last_s = potiVal_last;
  int16_t potiVal = analogRead(potiPin);
  static uint8_t count = 0;
  if(millis() > potiSample){ // 100ms min sample time
    potiSample = millis() + 100;
    if((potiVal < potiVal_last_s + 5 && potiVal > potiVal_last_s - 5) && deadzone == 5){count++;}
    else{count = 0;}
    potiVal_last_s = potiVal;
  }
  if(count > 10){deadzone = 15;} // raise deadzone to 15 after 10x 10ms within range of 5

  if(potiVal > potiVal_last + deadzone || potiVal < potiVal_last - deadzone){
    count = 0; deadzone = 5;
    potiVal_last = potiVal;
    return potiVal;
  }
  else{
    potiVal_last = potiVal;
    return -1; // no significant change
  }


}
// just as additional alias
void setMenuSelect(int8_t idx){drawSelFrame(idx);}

void drawSelFrame(int8_t idx){
  static int8_t idx_last = -1;
  if(idx_last == idx){return;}
  if(idx < 0){idx += 3; }
  menuSelect = (idx % 3); 
  drawSelFrame_color(idx_last,BLACK);
  drawSelFrame_color(idx,WHITE);
  display.display();
  idx_last = idx;
}
void drawSelFrame_color(uint8_t idx, uint16_t color){
 display.drawRoundRect(75, 1+idx*20, 52, 21, 3, color);
 
}

//---------------------------------------TIME--------------------------------------------

//returns timer val in seconds constrained by time_min & time_max
uint16_t poti2time(uint16_t potiVal){
  static uint16_t time_last = 0;
  uint16_t time_new;
  time_new = map(potiVal,12,1000, time_min / 5,time_max / 5) * 5 ;
  // poti bounds -> 12 to 1000
  if(time_new > time_max){return time_max;} // when poti val is > 1000
  else if(time_new < time_min){return time_min;}
  return time_new;
}


void drawTimer(uint16_t time_in){
  static uint16_t time_in_last = 0;
  if(time_in_last == time_in )return;
  printTimeColor(time_in_last,BLACK);
  printTimeColor(time_in,WHITE);
  time_in_last = time_in;
  display.display();
}
void printTimeColor(uint16_t time_in,uint16_t color){
if(time_in >= time_max){ display.drawBitmap(92, 9,  infinite_15x7 , 15, 7, color);}
else{
  display.setTextSize(1);   
  display.setTextColor(color); 
  display.setCursor(79, 5);   
  sprintf(time_m,"%02d", (int)(time_in / 60));
  sprintf(time_s,"%02d", (int)(time_in % 60));
  display.print(time_m);
  display.print(':');
  display.print(time_s);
  display.drawBitmap(82, 14,  mmss_21x5 , 21, 5, color);
  
  }
}
//----------------------------------------TEMP--------------------------------------------
float poti2tempSP(uint16_t potiVal){
  float tempSP_new;
  if(potiVal < 12){tempSP_new = temp_min;}
  else if (potiVal > 1012){tempSP_new = temp_max;}
  else {tempSP_new = ((int)map(potiVal,12,1012, (int)temp_min*10,(int)temp_max*10)/10.0) ;}
  return tempSP_new;
}


void drawTempSP(float tempSP_in){
  
  static float tempSP_last = 0;
  if(tempSP_in == tempSP_last)return;
    if(tempSP_last != 0 ) { printTempSP_color(tempSP_last,BLACK);}
  printTempSP_color(tempSP_in,WHITE);
  drawSetP(gauge_x,gauge_y,gauge_r,tempSP_in);
  tempSP_last = tempSP_in;
  display.display();
}
void printTempSP_color(float tempSP_in,uint16_t color){
  display.setTextSize(1);   
  display.setTextColor(color); 
  display.setCursor(82, 27);      
  char t_disp[4];
  dtostrf(tempSP_in,3,1,t_disp);
  display.print(t_disp);

}
//----------------------------------------ON/OFF-------------------------------------
void setState(uint16_t pVal){
  static int16_t pVal_last = -1;
  if(pVal_last == -1){pVal_last = pVal; return;}

}
void drawOverview(){
    display.clearDisplay();
    drawArc(gauge_x,gauge_y,gauge_r,arcMin,arcMax,true,2);
    for(int i = 0; i < (sizeof(scaleVals)/sizeof(scaleVals[0])); i++){
      drawScale(gauge_x,gauge_y,gauge_r,scaleVals[i]);
    }

    drawCenter(gauge_x,gauge_y,dot_r);

    display.drawBitmap(112, 4,  time_13x16 , 13, 16, SSD1306_WHITE);
    display.drawBitmap(112, 24, temperature_13x16 , 13, 16, SSD1306_WHITE);

    display.display();

}

float DEG2RAD(float deg) { return ((deg * 71.0) / 4068.0);}
char attrBuf[8];
const char * i2char(int ival){ itoa(ival,&attrBuf[strlen(attrBuf)],10);  return attrBuf;}
//----------------------------------- --------------------------------------------------------------//
//----------------------------------- static display functions -------------------------------------//
void drawArc(uint8_t xcenter, uint8_t ycenter, uint8_t r_arc, float degStart, float degEnd, bool clockwise, int width){

  degStart+= clockwise ? 90.0 : 270.0; // shift start from 3 o'clock to 12 o'clock 
  degEnd+=clockwise ? 90.0 : 270.0;// shift end like degStart
  float dir = clockwise ? -1 : 1 ;
  static int xlast = -1; 
  static int ylast = -1;
  for(int w = 0; w < width; w++){
    for (float i = DEG2RAD(degStart); i < DEG2RAD(degEnd) ; i = i + 0.01){
    
      int xnew = xcenter + ( cos(i) * (r_arc + w) * dir );
      int ynew = ycenter + ( sin(i) * (r_arc + w) * dir );
      if(xnew != xlast || ynew != ylast){ // skip when already drawn
        display.drawPixel(xnew,ynew,WHITE);
        Serial.print("{ ");Serial.print(xnew);Serial.print(" , ");Serial.print(xnew);Serial.println("}");
        delay(10);
        xlast = xnew;  ylast = ynew;
      }
    }
  }
}


void drawScale(uint8_t xcenter, uint8_t ycenter, uint8_t r_arc, scale thisVal){
  //uint8_t len, float degPos, String value, int8_t xoff, int8_t yoff
  float i = DEG2RAD(thisVal.linePosDeg+90);
  uint8_t x1 = xcenter -  cos(i) * r_arc ;
  uint8_t y1 = ycenter -  sin(i) * r_arc ;
  uint8_t x2 = xcenter -  cos(i) * (r_arc - thisVal.lineLen) ;
  uint8_t y2 = ycenter -  sin(i) * (r_arc - thisVal.lineLen) ;
  display.drawLine(x1, y1, x2, y2, SSD1306_WHITE);

  if(thisVal.t_val[0] != '\0'){  drawTinyNum(x2 + thisVal.xOff,  y2 + thisVal.yOff ,thisVal.t_val , WHITE); }
}
void drawTinyNum(uint8_t xPos, uint8_t yPos, char * numChars, uint16_t color){
  uint8_t xOff = 0;
  for(uint8_t i = 0; i < strlen(numChars); i++){ // ASCII Table --> 48(dec) = '0', 57 = '9'
    if(numChars[i] >= 48 &&  numChars[i] <= 57){  display.drawBitmap(xPos+xOff, yPos, tinyNums[numChars[i]-48] , 3, 5, color);}
    xOff += 4;
  }
}


void drawCenter(uint8_t xcenter, uint8_t ycenter, uint8_t r_dot){
  display.setTextSize(1);    
  display.fillCircle(xcenter,ycenter,r_dot,WHITE);
  display.setTextColor(SSD1306_BLACK); 
  display.setCursor(xcenter - 5,ycenter - 9);     
  display.print(".");
  display.setCursor(xcenter - 1,ycenter - 2);     
  display.print("C");
  display.drawRoundRect(xcenter-15, ycenter+11, 31, 11,4, WHITE); // current temperature box
}


//----------------------------------- --------------------------------------------------------------//
//----------------------------------- modular display functions-------------------------------------//
void showWarn(bool warnActive){
  static bool warnLast = false;
  if(warnActive != warnLast){
    display.drawBitmap(75, 44, warning_20x16, 20, 16, warnActive ? WHITE : BLACK);
    warnLast = warnActive;
    display.display();
  }
}
void drawState(bool newState){
  //state = newState;
  if(newState){
    display.drawBitmap(102, 44, StateOff_22x16 , 22, 16, BLACK);
    display.drawBitmap(102, 44,StateOn_22x16 , 22, 16, WHITE);
    }
  else{
    display.drawBitmap(102, 44,StateOn_22x16 , 22, 16, BLACK);
    display.drawBitmap(102, 44, StateOff_22x16 , 22, 16, SSD1306_WHITE);
    }
  display.display();
}

void drawTempRead(float newTemp){ drawPointerandVal(gauge_x, gauge_y, newTemp, dot_r+3,gauge_r-6,20);}

void drawPointerandVal(uint8_t xcenter, uint8_t ycenter, float temp,int r_min, int r_max, float deg_width){
  static float temp_last = -1;
  if(temp_last != -1){drawPointerColor(xcenter, ycenter, temp_last, r_min, r_max,  deg_width, BLACK);}
  drawPointerColor(xcenter, ycenter, temp, r_min, r_max,  deg_width, WHITE);
  temp_last = temp;
}

void drawPointerColor(uint8_t xcenter, uint8_t ycenter, float temp,int r_min, int r_max, float deg_width, uint16_t color){
  float deg = map(temp,20.0,60.0,arcMin,arcMax);
  float i = DEG2RAD(deg+90);
  uint8_t x0 = xcenter -  cos(i) * r_min ;
  uint8_t y0 = ycenter -  sin(i) * r_min ;
  uint8_t x1 = xcenter -  cos(i) * r_max ;
  uint8_t y1 = ycenter -  sin(i) * r_max ;
  i = DEG2RAD(deg+90+deg_width);
  uint8_t x2 = xcenter -  cos(i) * r_min ;
  uint8_t y2 = ycenter -  sin(i) * r_min ;
  i = DEG2RAD(deg+90-deg_width);
  uint8_t x3 = xcenter -  cos(i) * r_min ;
  uint8_t y3 = ycenter -  sin(i) * r_min ;
  display.fillTriangle(x0, y0, x1, y1, x2, y2, color);
  display.fillTriangle(x0, y0, x1, y1, x3, y3, color);
  display.setTextSize(1);   
  display.setTextColor(color); 
  display.setCursor(xcenter-11, ycenter+13);     
  char t_disp[4];
  dtostrf(temp,3,1,t_disp);
  display.print(t_disp);
}

void drawSetP(uint8_t xcenter, uint8_t ycenter, uint8_t r_arc, float temp_set){
  static float temp_set_last = -1;
  if(temp_set_last != -1){ drawSetP_color(xcenter, ycenter, r_arc, temp_set_last, BLACK); }
  drawSetP_color(xcenter, ycenter, r_arc, temp_set, WHITE);
  temp_set_last = temp_set;
}
void drawSetP_color(uint8_t xcenter, uint8_t ycenter, uint8_t r_arc, float temp_set, uint16_t color){
  uint8_t height = 4;
  uint8_t halfbase = 6; //
  float deg = map(temp_set,20.0,60.0,arcMin,arcMax);
 //  
for(int i = 0; i < (sizeof(scaleVals)/sizeof(scaleVals[0])); i++){
// if(scaleVals[i].linePosDeg > deg - (halfbase*2) && scaleVals[i].linePosDeg < deg + (halfbase*2)){
    scale redraw = scaleVals[i]; // copy to tem scale entry
    redraw.t_val[0] = '\0'; // clear the value chars (only redraw the scale line)
    drawScale(gauge_x,gauge_y,gauge_r,redraw);
  //  break;
 // }
}
  float i = DEG2RAD(deg+90);
  uint8_t x0 = xcenter -  cos(i) * (r_arc-1) ;
  uint8_t y0 = ycenter -  sin(i) * (r_arc-1) ;
   i = DEG2RAD(deg+90-halfbase);

  uint8_t x1 = xcenter -  cos(i) * (r_arc - height);
  uint8_t y1 = ycenter -  sin(i) * (r_arc  - height);

   i = DEG2RAD(deg+90+halfbase);
  uint8_t x2 = xcenter -  cos(i) * (r_arc  - height);
  uint8_t y2 = ycenter -  sin(i) * (r_arc  - height) ;
  display.fillTriangle(x0, y0, x1, y1, x2, y2, color);
}
esp:VIN
esp:GND.2
esp:D13
esp:D12
esp:D14
esp:D27
esp:D26
esp:D25
esp:D33
esp:D32
esp:D35
esp:D34
esp:VN
esp:VP
esp:EN
esp:3V3
esp:GND.1
esp:D15
esp:D2
esp:D4
esp:RX2
esp:TX2
esp:D5
esp:D18
esp:D19
esp:D21
esp:RX0
esp:TX0
esp:D22
esp:D23
encoder1:CLK
encoder1:DT
encoder1:SW
encoder1:VCC
encoder1:GND
ssd1306:DATA
ssd1306:CLK
ssd1306:DC
ssd1306:RST
ssd1306:CS
ssd1306:3V3
ssd1306:VIN
ssd1306:GND
joystick1:VCC
joystick1:VERT
joystick1:HORZ
joystick1:SEL
joystick1:GND
pot1:GND
pot1:SIG
pot1:VCC