#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <TimeLib.h>
#include <DS1307RTC.h>
#include <SD.h>
#include <SPI.h>

LiquidCrystal_I2C lcd(0x27,20,4);

#define BUZZER_PIN 2
#define LED_RED_PIN 3
#define LED_GREEN_PIN 4

#define CMD_ENSTROBE 0x01
#define CMD_DISTROBE 0x02

const int chipSelect = 53; // Change this to your SD module's chip select pin

#define CMD_DRAWPIX 0x10
#define CMD_DRAWSCREEN 0x11

int screenWidth=128;
int screenHeight=64;

typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
  uint8_t a;
} rgba_t;

void enableStrobe(uint8_t strobe)
{
  Wire.beginTransmission(0x21);
  Wire.write(CMD_ENSTROBE);
  Wire.write(strobe);
  Wire.endTransmission();
}

void disableStrobe(uint8_t strobe)
{
  Wire.beginTransmission(0x21);
  Wire.write(CMD_DISTROBE);
  Wire.write(strobe);
  Wire.endTransmission();
}

void drawPixel(uint32_t x, uint32_t y, rgba_t color)
{
  Wire.beginTransmission(0x22);

  Wire.write(CMD_DRAWPIX);

  uint8_t writeValues[4];
  uint8_t posValues[8];

  writeValues[0] = color.r;
  writeValues[1] = color.g;
  writeValues[2] = color.b;
  writeValues[3] = color.a;

  posValues[0] = (x >> 24) & 0xff;
  posValues[1] = (x >> 16) & 0xff;
  posValues[2] = (x >> 8) & 0xff;
  posValues[3] = (x) & 0xff;

  posValues[4] = (y >> 24) & 0xff;
  posValues[5] = (y >> 16) & 0xff;
  posValues[6] = (y >> 8) & 0xff;
  posValues[7] = (y) & 0xff;

  for(int i=0; i<4; i++)
  {
    Wire.write(writeValues[i]);
  }
  for(int i=0; i<8; i++)
  {
    Wire.write(posValues[i]);
  }
  
  Wire.endTransmission();
}
uint32_t x, y=0;

void drawHLine(uint32_t width, uint32_t y, rgba_t color)
{
  for(int x=0; x<width; x++)
  {
    drawPixel(x, y, color);
  }
}
void drawVLine(uint32_t height, uint32_t x, rgba_t color)
{
  for(int y=0; y<height; y++)
  {
    drawPixel(x, y, color);
  }
}

void drawGrid()
{
  for(y=0; y<screenWidth; y+=6)
  {
      drawVLine(screenHeight, y, (rgba_t){.r=0, .g=0, .b=0, .a=0xff});
  }
  for(x=0; x<screenHeight; x+=6)
  {
      drawHLine(screenWidth, x, (rgba_t){.r=0, .g=0, .b=0, .a=0xff});
  }
}

void drawBinaryImage(int offsetX, int offsetY, int w, int h, char* filename)
{
  File file = SD.open(filename, FILE_READ);
  if (file) {
    uint16_t x = 0+offsetX;
    uint16_t y = 0+offsetY;

    while (file.available()) {
      // Read the RGBA data from the file
      uint8_t red = file.read();
      uint8_t green = file.read();
      uint8_t blue = file.read();
      uint8_t alpha = file.read();

      rgba_t color;
      color.r = red;
      color.g = green;
      color.b = blue;
      color.a = alpha;

      drawPixel(x,y, color);

      // Move to the next pixel
      x++;

      // If we reach the end of the display width, move to the next row
      if (x == offsetX+w) {
        x = 0+offsetX;
        y++;
      }
    }
  } else {
    Serial.println("Error opening file");
  }
}

void initMsg(char* msg)
{
  lcd.clear();
  lcd.setCursor(0,0);
  lcd.print(msg);
}

void setup() {
  Serial.begin(9600);

  // Initialize the SD card
  if (!SD.begin(chipSelect)) {
    Serial.println("SD card initialization failed!");
    return;
  }

  // init strobe circ
  Wire.begin();

  // put your setup code here, to run once:
  lcd.init();
  lcd.backlight();

  // Init pins
  initMsg("INIT PINS");
  pinMode(BUZZER_PIN, OUTPUT);
  pinMode(LED_RED_PIN, OUTPUT);
  pinMode(LED_GREEN_PIN, OUTPUT);

  // Notify user about init with buzzer
  initMsg("BEEP");
  tone(BUZZER_PIN, 2000, 1000);
  delay(1000);

  initMsg("Set i2c Speed");
  Wire.setClock(1000000);

  // image
  initMsg("Draw \"bg.bin\"");
  drawBinaryImage(0,0, 128,64, "bg.bin"); // bg
  initMsg("Draw grid");
  drawGrid();

  int imageWidth=128;
  int imageHeight=64;
  uint16_t offsetX=screenWidth/2-(imageWidth/2); // center
  uint16_t offsetY=screenHeight/2-(imageHeight/2); // center

  initMsg("Draw \"output.bin\"");
  drawBinaryImage(offsetX,offsetY, imageWidth,imageHeight, "output.bin"); // bg

  initMsg("");

  lcd.print("Testing Strobe Chip.");
  // disable all strobes.
  for(int i=0; i<4; i++)
  {
    disableStrobe(i);
  }
  delay(1000);
  digitalWrite(LED_GREEN_PIN, HIGH);
}

void loop() {
  // time
  tmElements_t tm;
  lcd.clear();
  lcd.setCursor(0,0);

  lcd.print("Welcome User!");

  if (RTC.read(tm)) {
    lcd.setCursor(0,2);
    print2digits(tm.Hour);
    lcd.write(':');
    print2digits(tm.Minute);
    lcd.write(':');
    print2digits(tm.Second);
    lcd.setCursor(0,3);
    lcd.print(tm.Day);
    lcd.write('/');
    lcd.print(tm.Month);
    lcd.write('/');
    lcd.print(tmYearToCalendar(tm.Year));
  }

  // Loop through strobes (testing)
  for(int i=0; i<4; i++)
  {
    enableStrobe(i);
    delay(500);
    disableStrobe(i);
    delay(500);
  }
}

void print2digits(int number) {
  if (number >= 0 && number < 10) {
    lcd.write('0');
  }
  lcd.print(number);
}
strobe-circBreakout
GND5VSDASCLSQWRTCDS1307+
RGBA DisplayBreakout