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

// thanks to Emmett Lathrop Brown
#define ENABLE_FLUX_CAPACITOR 0

// 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 32
#define HEIGHT 32
#define NUM_LEDS ((WIDTH) * (HEIGHT))

CRGB leds[NUM_LEDS + 1];

uint16_t XY(const uint8_t x, const 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;
}

#include "wuLineAA.h"

struct DS1307_tm {
  union {
    struct {
      uint8_t seconds, minutes, hours;
      uint8_t wday, mday, month, year;
    };
    uint8_t raw[7];
  };
} tm;


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

  for (uint8_t i = 0; i < 7; i++)
    tm.raw[i] = bcdToDec(Wire.read());
}


void setup()
{
  FastLED.addLeds<NEOPIXEL, 2>(leds, NUM_LEDS);
  Wire.begin();
  Serial.begin(9600);
  lcd.begin (16, 2);
  lcd.backlight();
  // Use a line below to customize a date and time
  // sec, min, hour, wday, mday, month, year % 100
  // setDateTime(50, 59, 23, 7, 28, 2, 21);
  // setDateTime(00, 29, 04, 4, 21, 2, 15);
  // setDateTime(00, 46, 07, 4, 21, 2, 15);
  // setDateTime(00, 20, 01, 7, 26, 10, 85);
  ds1307_read();
}

void loop()
{
  if (ENABLE_FLUX_CAPACITOR) {
    tm.seconds += ENABLE_FLUX_CAPACITOR;
    while (tm.seconds >= 60) ++tm.minutes, tm.seconds -= 60;
    while (tm.minutes >= 60) ++tm.hours, tm.minutes -= 60;
    while (tm.hours >= 24) ++tm.mday, ++tm.wday, tm.hours -= 24;
    while (tm.wday >= 8) tm.wday -= 7;
    while (tm.mday > 31) ++tm.month, tm.mday = 1; // i'm sure this is correct :p
    while (tm.month > 12) ++tm.year, tm.month = 1;
    ledclock(tm.hours, tm.minutes, tm.seconds);
  } else
    ds1307_read();

  // update LEDs regularly
  ledclock(tm.hours, tm.minutes, tm.seconds);

  // update LCD once per second
  static uint8_t last_seconds = 255;
  if (last_seconds == tm.seconds)
    return;
  last_seconds = tm.seconds;

  // flash the time separator
  char time_sep = ':';
  if (tm.seconds & 1)
    time_sep = ' ';

  // format the first line into a temporary buffer
  char strbuf[17];
  snprintf(strbuf, 17, "%02d%c%02d%c%02d",
            tm.hours, time_sep, tm.minutes, time_sep, tm.seconds);
  lcd.setCursor(4, 0);
  lcd.print(strbuf);

  // update 2nd line only when it changes
  static uint8_t last_wday = 255;
  if (last_wday == tm.wday)
    return;
  last_wday = tm.wday;

  // format the second line
  const char day_str[] = "NulSunMonTueWedThuFriSat";
  uint16_t year = 2000 + tm.year;
  if (year > 2038)
    year -= 100;
  snprintf(strbuf, 17, "%4d-%02d-%02d @@@",
            year, tm.month, tm.mday);
  memcpy(strbuf + 11, day_str + tm.wday * 3, 3);
  lcd.setCursor(1, 1);
  lcd.print(strbuf);
}

// 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));
}

// LED matrix analogue clock

void wuVectorAA(const uint16_t x, const uint16_t y, const uint16_t length, const uint16_t theta, CRGB *col) {
  int16_t dx, dy;
  dx = ((int32_t)cos16(theta) * length) / 32768;
  dy = ((int32_t)sin16(theta) * length) / 32768;
  wuLineAA(x, y, x + dx, y + dy, col);
}

void ledclock(const uint8_t &hours, const uint8_t &minutes, const uint8_t &seconds) {
  FastLED.clear();
  // fill_rainbow(leds, NUM_LEDS, 0, 1);
  CRGB c = 0xffffff;
  // everything is fixed-point, with 8-bits of fraction
  uint16_t centrex = WIDTH * 128 - 128;
  uint16_t centrey = HEIGHT * 128 - 128;
  uint16_t length = WIDTH * 128;
  uint16_t base_theta = 65536 * 3 / 4;

  // second hand with sweep action
  uint16_t theta = seconds * 65536 / 60;
  static uint16_t sweep_theta = theta;
  int32_t diff = theta - sweep_theta;
  if (diff < 0)
    diff += 65536;
  sweep_theta += (diff + 8) / 16;
  wuVectorAA(centrex, centrey, length, base_theta + sweep_theta, &c);

  // minute hand
  length = length * 7 / 8;
  theta = (theta + minutes * 65536) / 60;
  wuVectorAA(centrex, centrey, length, base_theta + theta, &c);

  // hour hand
  length = length * 3 / 4;
  theta = (theta + (hours % 12) * 65536) / 12;
  wuVectorAA(centrex, centrey, length, base_theta + theta, &c);

  FastLED.show();
}
GND5VSDASCLSQWRTCDS1307+