/*
 * Este programa utiliza una pantalla TTGO con un microcontrolador ESP32 para mostrar un reloj analógico.
 * El programa realiza las siguientes acciones:
 * - Inicializa la pantalla y dibuja la carátula del reloj, incluyendo los marcadores de horas y minutos.
 * - Obtiene la hora de compilación del programa.
 * - Actualiza las posiciones de las manecillas del reloj cada segundo.
 * - Redibuja las manecillas en sus nuevas posiciones para representar el tiempo actual.
 * 
 * Funciones del programa:
 * - setup(): Inicializa la pantalla y dibuja la carátula del reloj.
 * - loop(): Actualiza y redibuja las manecillas del reloj cada segundo.
 * - conv2d(const char* p): Convierte dos caracteres en un número de dos dígitos.
 */
#include <SPI.h>
#include <TFT_eSPI.h> // Incluye la librería específica para el hardware

#define PI_DIV_180 0.0174532925 // Macro para eliminar magic number en conversion de grad -> rad

TFT_eSPI tft = TFT_eSPI(); // Crea un objeto de la clase TFT_eSPI

// Variables para almacenar los multiplicadores x e y de las manecillas del reloj
float sx = 0, sy = 1, mx = 1, my = 0, hx = -1, hy = 0; 
float sdeg = 0, mdeg = 0, hdeg = 0;
// Variables para almacenar las coordenadas x e y previas de las manecillas
uint16_t osx = 120, osy = 120, omx = 120, omy = 120, ohx = 120, ohy = 120;
uint16_t x0 = 0, x1 = 0, yy0 = 0, yy1 = 0;
uint32_t targetTime = 0; // Tiempo objetivo para el próximo timeout de 1 segundo

static uint8_t conv2d(const char* p); // Declaración anticipada de la función conv2d
// Obtiene la hora, minutos y segundos de la hora de compilación del programa
uint8_t hh = conv2d(__TIME__), mm = conv2d(__TIME__ + 3), ss = conv2d(__TIME__ + 6);

bool initial = 1; // Variable para marcar la inicialización

void setup(void) {
  tft.init(); // Inicializa la pantalla
  tft.setRotation(0); // Establece la orientación de la pantalla
  
  // Dibuja la carátula del reloj
  tft.fillCircle(120, 120, 118, TFT_GREEN); // Dibuja un círculo verde grande
  tft.fillCircle(120, 120, 110, TFT_BLACK); // Dibuja un círculo negro más pequeño sobre el anterior

  // Dibuja las 12 líneas de las horas
  for (int i = 0; i < 360; i += 30) {
    sx = cos((i - 90) * PI_DIV_180); // Calcula la coordenada x del extremo de la línea
    sy = sin((i - 90) * PI_DIV_180); // Calcula la coordenada y del extremo de la línea
    x0 = sx * 114 + 120; // Calcula la coordenada x del inicio de la línea
    yy0 = sy * 114 + 120; // Calcula la coordenada y del inicio de la línea
    x1 = sx * 100 + 120; // Calcula la coordenada x del final de la línea
    yy1 = sy * 100 + 120; // Calcula la coordenada y del final de la línea

    tft.drawLine(x0, yy0, x1, yy1, TFT_GREEN); // Dibuja la línea
  }

  // Dibuja los 60 puntos de los minutos
  for (int i = 0; i < 360; i += 6) {
    sx = cos((i - 90) * PI_DIV_180); // Calcula la coordenada x del punto
    sy = sin((i - 90) * PI_DIV_180); // Calcula la coordenada y del punto
    x0 = sx * 102 + 120; // Calcula la coordenada x del punto
    yy0 = sy * 102 + 120; // Calcula la coordenada y del punto
    tft.drawPixel(x0, yy0, TFT_WHITE); // Dibuja el punto

    // Dibuja puntos más grandes en las posiciones de los cuadrantes principales
    if (i == 0 || i == 180) tft.fillCircle(x0, yy0, 2, TFT_WHITE);
    if (i == 90 || i == 270) tft.fillCircle(x0, yy0, 2, TFT_WHITE);
  }

  targetTime = millis() + 1000; // Establece el tiempo objetivo para el primer timeout de 1 segundo
}

void loop() {
  if (targetTime < millis()) { // Comprueba si ha pasado 1 segundo
    targetTime += 1000; // Actualiza el tiempo objetivo para el próximo segundo
    ss++; // Incrementa los segundos
    if (ss == 60) { // Si los segundos alcanzan 60
      ss = 0; // Resetea los segundos
      mm++; // Incrementa los minutos
      if (mm > 59) { // Si los minutos alcanzan 60
        mm = 0; // Resetea los minutos
        hh++; // Incrementa las horas
        if (hh > 23) { // Si las horas alcanzan 24
          hh = 0; // Resetea las horas
        }
      }
    }

    // Precalcula los grados y las coordenadas x e y de las manecillas para una actualización rápida de la pantalla
    sdeg = ss * 6; // Convierte segundos en grados (0-59 -> 0-354)
    mdeg = mm * 6 + sdeg * 0.01666667; // Convierte minutos y segundos en grados
    hdeg = hh * 30 + mdeg * 0.0833333; // Convierte horas, minutos y segundos en grados
    hx = cos((hdeg - 90) * PI_DIV_180); // Calcula la coordenada x de la manecilla de las horas
    hy = sin((hdeg - 90) * PI_DIV_180); // Calcula la coordenada y de la manecilla de las horas
    mx = cos((mdeg - 90) * PI_DIV_180); // Calcula la coordenada x de la manecilla de los minutos
    my = sin((mdeg - 90) * PI_DIV_180); // Calcula la coordenada y de la manecilla de los minutos
    sx = cos((sdeg - 90) * PI_DIV_180); // Calcula la coordenada x de la manecilla de los segundos
    sy = sin((sdeg - 90) * PI_DIV_180); // Calcula la coordenada y de la manecilla de los segundos

    if (ss == 0 || initial) { // Si es el primer segundo o es la inicialización
      initial = 0; // Marca que la inicialización ha terminado
      // Borra las posiciones previas de las manecillas de las horas y los minutos
      tft.drawLine(ohx, ohy, 120, 121, TFT_BLACK);
      ohx = hx * 62 + 121; // Calcula la nueva coordenada x de la manecilla de las horas
      ohy = hy * 62 + 121; // Calcula la nueva coordenada y de la manecilla de las horas
      tft.drawLine(omx, omy, 120, 121, TFT_BLACK);
      omx = mx * 84 + 120; // Calcula la nueva coordenada x de la manecilla de los minutos
      omy = my * 84 + 121; // Calcula la nueva coordenada y de la manecilla de los minutos
    }

    // Redibuja las nuevas posiciones de las manecillas
    tft.drawLine(osx, osy, 120, 121, TFT_BLACK); // Borra la manecilla de los segundos anterior
    osx = sx * 90 + 121; // Calcula la nueva coordenada x de la manecilla de los segundos
    osy = sy * 90 + 121; // Calcula la nueva coordenada y de la manecilla de los segundos
    tft.drawLine(osx, osy, 120, 121, TFT_RED); // Dibuja la nueva manecilla de los segundos en rojo
    tft.drawLine(ohx, ohy, 120, 121, TFT_WHITE); // Dibuja la nueva manecilla de las horas en blanco
    tft.drawLine(omx, omy, 120, 121, TFT_WHITE); // Dibuja la nueva manecilla de los minutos en blanco
    tft.drawLine(osx, osy, 120, 121, TFT_RED); // Dibuja nuevamente la manecilla de los segundos en rojo para evitar parpadeo

    tft.fillCircle(120, 121, 3, TFT_RED); // Dibuja un círculo rojo en el centro del reloj para cubrir el centro de las manecillas
  }
}

// Función para convertir dos caracteres en un número
static uint8_t conv2d(const char* p) {
  uint8_t v = 0;
  if ('0' <= *p && *p <= '9')
    v = *p - '0';
  return 10 * v + *++p - '0'; // Convierte dos caracteres en un número de dos dígitos
}