#include <FreeRTOS_SAMD21.h>
#include <semphr.h>
#include <Adafruit_NeoPixel.h>
#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include <Fonts/FreeMonoBold9pt7b.h>
#include <Fonts/FreeMonoBold18pt7b.h>
#include <Fonts/FreeMono12pt7b.h>
//FreeMono18pt7b.h
//FreeMono24pt7b.h
//FreeMono9pt7b.h
//FreeMonoBold12pt7b.h
//FreeMonoBold18pt7b.h
//FreeMonoBold24pt7b.h
//FreeMonoBold9pt7b.h

// Various tunables
#define ERROR_LED_PIN 13
#define ERROR_LED_LIGHTUP_STATE HIGH
#define PIXEL_PIN            40 // pin of pixel
#define PIXEL_MAX_BRIGHTNESS 48 // 255 max
#define PIXEL_MIN_BRIGHTNESS 4 // 0 min
#define BREATHE_DELAY        5 // milliseconds
#define BUTTON_UP            6 // MUST be an interrupt pin
#define BUTTON_DOWN          5 // MUST be an interrupt pin
#define BUTTON_OK            7 // MUST be an interrupt pin
// TFT pins
#define TFT_DC               9
#define TFT_CS               10
#define TFT_MOSI             13
#define TFT_CLK              12
#define TFT_RST              8
#define TFT_MISO             11

// Init hardware used in tasks
Adafruit_NeoPixel pixel(1, PIXEL_PIN, NEO_GRB + NEO_KHZ800);

// Adafruit 2.2 TFT -- 320x240 resolution, up to 18-bit (262,144) color
// Use hardware SPI (on Uno, #13, #12, #11) and the above for CS/DC
//Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
// If using the breakout, change pins as desired
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI, TFT_CLK, TFT_RST, TFT_MISO);

// Button Semaphores
SemaphoreHandle_t sem_btn_up;
SemaphoreHandle_t sem_btn_down;
SemaphoreHandle_t sem_btn_ok;

// define two tasks for Blink & AnalogRead
void TaskBlink( void *pvParameters );
void InterruptHandlerButtonUp();
void InterruptHandlerButtonDown();
void InterruptHandlerButtonOk();
void TaskLCD(void *pvParameters);

// UI screens
void screenClear();
void screenSoilStemma();
void screenSoilCatnip();
void screenWaterLevelETape();

// Color conversion (RGB888 -> RGB565 used by Adafruit GFX)
uint16_t RGB565(uint8_t r, uint8_t g, uint8_t b) {
  return ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3);
}

// the setup function runs once when you press reset or power the board
void setup() {

  vNopDelayMS(1000); // prevents usb driver crash on startup, do not omit this

  // Set the led the rtos will blink when we have a fatal rtos error
  // RTOS also Needs to know if high/low is the state that turns on the led.
  // Error Blink Codes:
  //    3 blinks - Fatal Rtos Error, something bad happened. Think really hard about what you just changed.
  //    2 blinks - Malloc Failed, Happens when you couldnt create a rtos object.
  //               Probably ran out of heap.
  //    1 blink  - Stack overflow, Task needs more bytes defined for its stack!
  //               Use the taskMonitor thread to help gauge how much more you need
  vSetErrorLed(ERROR_LED_PIN, ERROR_LED_LIGHTUP_STATE);

  // initialize serial communication at 9600 bits per second:
  //Serial.begin(9600);

  //while (!Serial) {
  //  ; // wait for serial port to connect. Needed for native USB, on LEONARDO, MICRO, YUN, and other 32u4 based boards.
  //}

  pixel.begin();
  pixel.clear();
  pixel.setBrightness(PIXEL_MAX_BRIGHTNESS);
  pixel.setPixelColor(0, pixel.Color(0, 0, 255));
  pixel.show();

  tft.begin();
  tft.setRotation(3);
  screenClear();
  tft.setFont(&FreeMonoBold18pt7b);
  tft.setCursor(tft.width() / 2 - 138, tft.height() / 2 + 10);
  tft.println("Press Up/Down");

  pinMode(BUTTON_UP, INPUT_PULLUP);
  pinMode(BUTTON_DOWN, INPUT_PULLUP);
  pinMode(BUTTON_OK, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(BUTTON_UP), InterruptHandlerButtonUp, RISING);
  attachInterrupt(digitalPinToInterrupt(BUTTON_DOWN), InterruptHandlerButtonDown, RISING);
  attachInterrupt(digitalPinToInterrupt(BUTTON_OK), InterruptHandlerButtonOk, RISING);

  sem_btn_up = xSemaphoreCreateBinary();
  sem_btn_down = xSemaphoreCreateBinary();
  sem_btn_ok = xSemaphoreCreateBinary();

  // Now set up two tasks to run independently.
  xTaskCreate(
    TaskBlink
    ,  (const portCHAR *)"Blink"   // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  0  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );

  xTaskCreate(
    TaskLCD
    ,  (const portCHAR *)"LCD"   // A name just for humans
    ,  128  // This stack size can be checked & adjusted by reading the Stack Highwater
    ,  NULL
    ,  2  // Priority, with 3 (configMAX_PRIORITIES - 1) being the highest, and 0 being the lowest.
    ,  NULL );

  // Now the task scheduler, which takes over control of scheduling individual tasks, is automatically started.
  vTaskStartScheduler();
}

void loop() {
  // Empty. Things are done in Tasks.
}

/*--------------------------------------------------*/
/*---------------------- Tasks ---------------------*/
/*--------------------------------------------------*/

void TaskBlink(void *pvParameters) {
  (void) pvParameters;

  while (1) {
    pixel.setPixelColor(0, pixel.Color(0, 255, 0)); // Pulse heartbeat green -- reset in case another task interrupts w/ a different color
    for (int i = PIXEL_MIN_BRIGHTNESS ; i < PIXEL_MAX_BRIGHTNESS ; i++) {
      pixel.setBrightness(i);
      pixel.show();
      vTaskDelay( 50 / portTICK_PERIOD_MS );
    }
    for (int i = PIXEL_MAX_BRIGHTNESS ; i > PIXEL_MIN_BRIGHTNESS ; i--) {
      pixel.setBrightness(i);
      pixel.show();
      vTaskDelay( 50 / portTICK_PERIOD_MS );
    }
  }
}


void TaskLCD(void *pvParameters) {
  (void) pvParameters;

  void (*screens[3])() = {screenSoilStemma, screenSoilCatnip, screenWaterLevelETape};
  int current = 0;

  while (1) {
    if (xSemaphoreTake(sem_btn_up, 50 / portTICK_PERIOD_MS) == pdPASS) {
      screenClear();
      tft.fillRect(tft.width() - 50, 0, 50, 20, RGB565(0, 0, 0));
      tft.setFont(&FreeMonoBold9pt7b);
      tft.setCursor(tft.width() - 50, 15);
      tft.print("Up");
      current++;
      if (current >= sizeof(screens) / sizeof(screens[0])) {
        current = 0;
      }
      screens[current]();
    }
    if (xSemaphoreTake(sem_btn_down, 50 / portTICK_PERIOD_MS) == pdPASS) {
      screenClear();
      tft.fillRect(tft.width() - 50, 0, 50, 20, RGB565(0, 0, 0));
      tft.setFont(&FreeMonoBold9pt7b);
      tft.setCursor(tft.width() - 50, 15);
      tft.print("Down");
      current--;
      if (current < 0) {
        current = sizeof(screens) / sizeof(screens[0]) - 1;
      }
      screens[current]();
    }
    if (xSemaphoreTake(sem_btn_ok, 50 / portTICK_PERIOD_MS) == pdPASS) {
      tft.fillRect(tft.width() - 50, 0, 50, 20, RGB565(0, 0, 0));
      tft.setFont(&FreeMonoBold9pt7b);
      tft.setCursor(tft.width() - 50, 15);
      tft.print("OK");
    }
  }
}

void InterruptHandlerButtonUp() {
  xSemaphoreGiveFromISR(sem_btn_up, NULL);
}

void InterruptHandlerButtonDown() {
  xSemaphoreGiveFromISR(sem_btn_down, NULL);
}

void InterruptHandlerButtonOk() {
  xSemaphoreGiveFromISR(sem_btn_ok, NULL);
}

void screenClear() {
  tft.setCursor(0, 0);
  tft.fillScreen(RGB565(0, 0, 0));
}

void screenSoilStemma() {
  tft.setFont(&FreeMonoBold18pt7b);
  tft.setCursor(0, 25);
  tft.println("Soil");
  tft.setFont(&FreeMono12pt7b);
  tft.println("Adafruit STEMMA");
  tft.println("Soil Sensor");
  tft.println("i2c address: 0x36");
  tft.println("seesaw address: 0x36");
  tft.println("");
  tft.setFont(&FreeMonoBold9pt7b);
  tft.println("--Press OK To Read Value--");
  tft.println("");
  tft.println("Value: [value]");
}


void screenSoilCatnip() {
  tft.setFont(&FreeMonoBold18pt7b);
  tft.setCursor(0, 25);
  tft.println("Soil");
  tft.setFont(&FreeMono12pt7b);
  tft.println("Catnip Soil Sensor");
  tft.println("i2c address: 0x20");
  tft.println("");
  tft.setFont(&FreeMonoBold9pt7b);
  tft.println("--Press OK To Read Value--");
  tft.println("");
  tft.println("Capacitance: [value]");
  tft.println("Temperature: [value]");
  tft.println("Light: [value]");
}

void screenWaterLevelETape() {
  tft.setFont(&FreeMonoBold18pt7b);
  tft.setCursor(0, 25);
  tft.println("Water Level");
  tft.setFont(&FreeMono12pt7b);
  tft.println("Milone eTape");
  tft.println("Analog Pin #0");
  tft.println("");
  tft.setFont(&FreeMonoBold9pt7b);
  tft.println("--Press OK To Read Value--");
  tft.println("");
  tft.println("Value: [value]");
}
