// Sand clock
//Yaroslaw Turbin, 12-01-2021
//16x16 FastLed matrix demo
//not for commercial use )

#include <FastLED.h>
#include <LiquidCrystal_I2C.h>
#include <Wire.h>

// RTC module at address 0x68
#define DS1307_ADDRESS 0x68

// LCD module at address 0x27
LiquidCrystal_I2C lcd(0x27, 20, 4);

uint8_t clear = 0x00;

#define WIDTH 16
#define HEIGHT 16
#define NUM_ROWS HEIGHT
#define NUM_COLS WIDTH
#define NUM_LEDS ((WIDTH) * (HEIGHT))

CRGB leds[NUM_LEDS + 1];
CRGB ledsbuff[NUM_LEDS + 1];


uint16_t XY(uint8_t x, uint8_t y) {
  if (x >= WIDTH) return NUM_LEDS;
  if (y >= HEIGHT) return NUM_LEDS;
  // if (y & 1)
  //   return (y + 1) * WIDTH - 1 - x;
  // else
  return y * WIDTH + x;
}

void setup()
{
  FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS);
  LEDS.clear();
  Wire.begin();
  Serial.begin(9600);
  lcd.begin (16, 2);
  lcd.backlight();
  // Use a line below to customize a date and time
  // sec, min, hour, month, day, day of week, year % 100
  // setDateTime(40, 59, 23, 1, 4, 1, 21);
}

void loop()
{

  timeLCD();
  SandMachine();
  PrintTime();
  FastLED.show();
}

void SandMachine() {
  EVERY_N_MILLISECONDS(80) {
    updatesand();
    randomdot();
  }
  EVERY_N_MILLISECONDS(10000) {
    randomdel();
    falldown();
    falldown();
    falldown();
  }
  memcpy8(leds, ledsbuff, NUM_LEDS * 3);
}

void PrintTime() {
  static byte numbers;
  static byte colorNumber;
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(clear);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_ADDRESS, 0x07);
  uint8_t seconds = bcdToDec(Wire.read());
  uint8_t minutes = bcdToDec(Wire.read());
  uint8_t hours = bcdToDec(Wire.read() & 0xff);
  uint8_t wday = bcdToDec(Wire.read());
  uint8_t mday = bcdToDec(Wire.read());
  uint8_t month = bcdToDec(Wire.read());
  uint8_t year = bcdToDec(Wire.read());

  colorNumber = 170;
  prnCHR(0, 2, hours / 10, colorNumber);
  prnCHR(4, 2, hours % 10, colorNumber);
  prnCHR(9, 2, minutes / 10, colorNumber);
  prnCHR(13, 2, minutes % 10, colorNumber);


}

void prnCHR(byte x, byte y, byte number, byte numberhue) {
  const static boolean Numbers [150] = {
    1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, //0
    0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, //1
    0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, //2
    1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 1, //3
    1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, //4
    1, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, //5
    1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, //6
    1, 1, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, //7
    1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, //8
    1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1
  };  //9

  byte numbind = number * 15;
  for (byte j = y; j < (y + 5); j++) {
    for (byte i = x; i < (x + 3); i++) {
      if (Numbers[numbind++]) leds[XY(i, j)] = CHSV(numberhue + j * 8 + beatsin8(20, 0, 20), 255, 255);
    }
  }
}

void randomdot() {
#define randomdothue 30
  byte a = NUM_COLS / 2; //random8(NUM_COLS/4)+NUM_COLS*3/8; //
  if (!random8(4)) ledsbuff[XY(a, 0)].setHue(random8(randomdothue, randomdothue + 45));
}

void updatesand () {
  int index, indexXadd1Y, indexXsub1Y, indexXYadd1;
  for (int y = NUM_ROWS - 1; y > 0; y--) {
    for (int x = 1; x < NUM_COLS - 1; x++) {
      index = XY(x, y); indexXadd1Y = XY(x + 1, y); indexXsub1Y = XY(x - 1, y); indexXYadd1 = XY(x, y - 1);
      if (!ledsbuff[index] && !ledsbuff[indexXYadd1]) continue;
      if (!ledsbuff[index] && ledsbuff[indexXYadd1]) {
        ledsbuff[index] = ledsbuff[indexXYadd1];
        ledsbuff[indexXYadd1] = 0;
      }
      if (ledsbuff[index] && ledsbuff[indexXYadd1] && !ledsbuff[indexXsub1Y] && !ledsbuff[indexXadd1Y])
        if (random8(2)) {
          ledsbuff[indexXsub1Y] = ledsbuff[indexXYadd1];
          ledsbuff[indexXYadd1] = 0;
        }
        else {
          ledsbuff[indexXadd1Y] = ledsbuff[indexXYadd1];
          ledsbuff[indexXYadd1] = 0;
        }
      if (ledsbuff[index] && ledsbuff[indexXYadd1] && !ledsbuff[indexXsub1Y] && ledsbuff[indexXadd1Y]) {
        ledsbuff[indexXsub1Y] = ledsbuff[indexXYadd1];
        ledsbuff[indexXYadd1] = 0;
      }
      if (ledsbuff[index] && ledsbuff[indexXYadd1] && ledsbuff[indexXsub1Y] && !ledsbuff[indexXadd1Y]) {
        ledsbuff[indexXadd1Y] = ledsbuff[indexXYadd1];
        ledsbuff[indexXYadd1] = 0;
      }
    }
  }
}

void randomdel() {
  for (int i = 0; i < NUM_LEDS; i++) {
    if (!random8(3)) ledsbuff[i] = 0;
  }
}

void falldown() {
  int indexXYsub1, index;
  for (int y = NUM_ROWS - 1; y > 0; y--) {
    for (int x = 0; x < NUM_COLS; x++) {
      indexXYsub1 = XY(x, y - 1); index = XY(x, y);
      if (!ledsbuff[index] && ledsbuff[indexXYsub1]) {
        ledsbuff[index] = ledsbuff[indexXYsub1];
        ledsbuff[indexXYsub1] = 0;
      }
    }
  }
}

// Set the date and time of the DS1307
void setDateTime(uint8_t seconds, uint8_t minutes, uint8_t hours,
                 uint8_t wday, uint8_t mday, uint8_t month, uint8_t year)
{
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(clear); // Write clear, so that it can receive data

  // The lines below write in the CI the date and time values ​​
  // that were placed in the variables above
  Wire.write(decToBcd(seconds));
  Wire.write(decToBcd(minutes));
  Wire.write(decToBcd(hours));
  Wire.write(decToBcd(wday));
  Wire.write(decToBcd(mday));
  Wire.write(decToBcd(month));
  Wire.write(decToBcd(year));
  Wire.write(clear);
  Wire.endTransmission();
}

uint8_t decToBcd(uint8_t value)
{
  // Converts the decimal number to BCD
  return ((value / 10 * 16) + (value % 10));
}

uint8_t bcdToDec(uint8_t value)
{
  // Converts from BCD to decimal
  return ((value / 16 * 10) + (value % 16));
}


void timeLCD() {
  // Read the values ​​(date and time) of the DS1307 module
  Wire.beginTransmission(DS1307_ADDRESS);
  Wire.write(clear);
  Wire.endTransmission();
  Wire.requestFrom(DS1307_ADDRESS, 0x07);

  uint8_t seconds = bcdToDec(Wire.read());
  uint8_t minutes = bcdToDec(Wire.read());
  uint8_t hours = bcdToDec(Wire.read() & 0xff);
  uint8_t wday = bcdToDec(Wire.read());
  uint8_t mday = bcdToDec(Wire.read());
  uint8_t month = bcdToDec(Wire.read());
  uint8_t year = bcdToDec(Wire.read());

  // Shows the data on the display
  lcd.setCursor(0, 0);
  lcd.print("    ");

  // Adds 0 (clear) if the time is less than 10
  if (hours < 10)
    lcd.print("0");

  lcd.print(hours);
  if (seconds & 1)
    lcd.print(":");
  else
    lcd.print(" ");

  // Adds 0 (clear) if minutes are less than 10
  if (minutes < 10)
    lcd.print("0");

  lcd.print(minutes);

  if (seconds & 1)
    lcd.print(":");
  else
    lcd.print(" ");

  if (seconds < 10)
    lcd.print("0");

  lcd.print(seconds);

  lcd.setCursor(2, 1);

  // Show the day of the week
  switch (wday - 1)
  {
    case 0: lcd.print("Sun");
      break;
    case 1: lcd.print("Mon");
      break;
    case 2: lcd.print("Tue");
      break;
    case 3: lcd.print("Wed");
      break;
    case 4: lcd.print("Thu");
      break;
    case 5: lcd.print("Fri");
      break;
    case 6: lcd.print("Sat");
  }

  lcd.setCursor(6, 1);

  // Adds 0 (clear) if day of the month is less than 10
  if (mday < 10)
    lcd.print("0");

  lcd.print(mday);
  lcd.print("/");

  // Add 0 (clear) if month is less than 10
  if (month < 10)
    lcd.print("0");

  lcd.print(month);
  lcd.print("/");
  lcd.print(year);
}
GND5VSDASCLSQWRTCDS1307+