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

CRGB leds[NUM_LEDS + 1];
byte heat[WIDTH][HEIGHT];
byte *heat1d = (byte *) heat;

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 wuVectorAA8(const uint16_t x, const uint16_t y, const uint16_t length, const uint16_t theta) {
  int16_t dx, dy;
  dx = ((int32_t)cos16(theta) * length) / 32768;
  dy = ((int32_t)sin16(theta) * length) / 32768;
  wuLineAA8(x, y, x + dx, y + dy);
}

void ledclock(const uint8_t &hours, const uint8_t &minutes, const uint8_t &seconds) {
  // FastLED.clear();
  // 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;
  wuVectorAA8(centrex, centrey, length, base_theta + sweep_theta);

  // minute hand
  theta = (theta + minutes * 65536) / 60;
  uint16_t min_theta = theta;
  wuVectorAA8(centrex, centrey, length * 7 / 8, base_theta + theta);

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


  for (int16_t i = NUM_LEDS; i--; )
    leds[i] = HeatColor(heat1d[i]);

  CRGB col = 0x0;
  wuVectorAA(centrex, centrey, length, base_theta + sweep_theta, &col);
  wuVectorAA(centrex, centrey, length * 7 / 8, base_theta + min_theta, &col);
  wuVectorAA(centrex, centrey, length * 3 / 4, base_theta + theta, &col);


  FastLED.show();
  Fire2012(random(192));
}


void Fire2012(uint8_t activity) {
  for (uint8_t h = 0; h < WIDTH; h++) {
    // Step 1.  Cool down every cell a little
    for ( uint8_t i = 0; i < HEIGHT; i++) {
      heat[i][h] = qsub8( heat[i][h],  random8(33));
    }

    // Step 2.  Heat from each cell drifts 'up' and diffuses a little
    uint8_t hleft = h > 0 ? (h - 1) : 0;
    uint8_t hright = h < WIDTH - 1 ? (h + 1) : WIDTH - 1;
    for ( uint8_t k = 0 ; k < HEIGHT - 1; k++) {
      heat[k][h] = (heat[k][h]
                  + heat[k + 1][hleft]
                  + heat[k + 1][h]
                  + heat[k + 1][hright] ) / 4;
    }

    if ( random8() < activity ) {
      heat[HEIGHT - 1][h] = qadd8( heat[HEIGHT - 1][h], random8(activity));
    }

  }
}