// Sketch to draw an analogue clock on the screen
// This uses anti-aliased drawing functions that are built into TFT_eSPI

// Anti-aliased lines can be drawn with sub-pixel resolution and permit lines to be
// drawn with less jaggedness.
// Requires MBed OS 2040 RAspberry pi pico board

// Based on a sketch by DavyLandman:
// https://github.com/Bodmer/TFT_eSPI/issues/905

#include "MBED_RPi_Pico_TimerInterrupt.h"

#define WIFI_SSID      "Your_SSID"
#define WIFI_PASSWORD  "Your_Password"

#include <Arduino.h>
#include <TFT_eSPI.h> // Master copy here: https://github.com/Bodmer/TFT_eSPI
#include <SPI.h>

#include "NotoSansBold15.h"

TFT_eSPI tft = TFT_eSPI();  // Invoke library, pins defined in User_Setup.h
TFT_eSprite face = TFT_eSprite(&tft);

const int BtnFwd = 6;
const int BtnMode = 8;
const int BtnBack = 7;


#define CLOCK_FG   TFT_BLACK
#define CLOCK_BG   TFT_WHITE
#define HILITE_FG  TFT_RED
#define LABEL_FG   TFT_NAVY

#define CLOCK_R       240.0f / 2.0f // Clock face radius (float type)
#define CLOCK_XY      CLOCK_R 
#define H_HAND_LENGTH CLOCK_R/2.0f
#define M_HAND_LENGTH CLOCK_R/1.4f
#define S_HAND_LENGTH CLOCK_R/1.3f

#define FACE_W CLOCK_R * 2 + 1
#define FACE_H CLOCK_R * 2 + 1

// Calculate 1 second increment angles. Hours and minute hand angles
// change every second so we see smooth sub-pixel movement
#define SECOND_ANGLE 360.0 / 60.0
#define MINUTE_ANGLE SECOND_ANGLE / 60.0
#define HOUR_ANGLE   MINUTE_ANGLE / 12.0

// Sprite width and height
#define FACE_W CLOCK_R * 2 + 1
#define FACE_H CLOCK_R * 2 + 1

const int ModeNormal = 0;
const int ModeMinutes = 1;
const int ModeHours = 2;

// Time h:m:s
uint8_t h = 0, m = 0, s = 0;
uint8_t mode = ModeNormal;
float time_secs = h * 3600 + m * 60 + s;

// Init RPI_PICO_Timer, can use any from 0-15 pseudo-hardware timers
MBED_RPI_PICO_Timer ITimer0(0);

void TimerHandler(uint alarm_num)
{ 
  ///////////////////////////////////////////////////////////
  // Always call this for MBED RP2040 before processing ISR
  ///////////////////////////////////////////////////////////
  TIMER_ISR_START(alarm_num);
  // Increment time by 1000 milliseconds
  time_secs += 1;

  // Midnight roll-over
  if (time_secs >= (60 * 60 * 24)) time_secs = 0;

  ////////////////////////////////////////////////////////////
  // Always call this for MBED RP2040 after processing ISR
  TIMER_ISR_END(alarm_num);
  ////////////////////////////////////////////////////////////
}

// =========================================================================
// Setup
// =========================================================================
void setup() {
  Serial.begin(115200);
  pinMode(BtnFwd,INPUT_PULLUP);
  pinMode(BtnBack,INPUT_PULLUP);
  pinMode(BtnMode,INPUT_PULLUP);

  delay(500);
  Serial.println("Booting...");

 // Interval in microsecs
  if (ITimer0.attachInterruptInterval(1000*1000, TimerHandler)) {
    Serial.print(F("Starting ITimer0 OK, millis() = ")); Serial.println(millis());
  }
  else {
    Serial.println(F("Can't set ITimer0. Select another freq. or timer"));
  }

  // Initialise the screen
  tft.init();

  // Ideally set orientation for good viewing angle range because
  // the anti-aliasing effectiveness varies with screen viewing angle
  // Usually this is when screen ribbon connector is at the bottom
  tft.setRotation(0);
  tft.fillScreen(TFT_RED);

  // Create the clock face sprite
  //face.setColorDepth(8); // 8 bit will work, but reduces effectiveness of anti-aliasing
  face.createSprite(FACE_W, FACE_H);

  // Only 1 font used in the sprite, so can remain loaded
  face.loadFont(NotoSansBold15);

  // Draw the whole clock - NTP time not available yet
  renderFace(time_secs);

}

// =========================================================================
// Loop
// =========================================================================
void loop() {
  char time[20];
  static unsigned long targetTime = millis() + 1000;
  static uint8_t lastMode = PinStatus::HIGH;
  uint8_t val = PinStatus::HIGH;

  // Update time periodically
  if (targetTime < millis()) {

    // Update next tick time in 100 milliseconds for smooth movement
    targetTime = millis() + 1000;

    // All graphics are drawn in sprite to stop flicker
    renderFace(time_secs);

  }

  if ( mode > ModeNormal) {
    if (!digitalRead(BtnFwd)) {

      if ( mode == ModeHours ) {
        time_secs += 60 * 60; // Add one minute
      } else {
        time_secs += 60; // Add one minute
      }
      
      // Midnight roll-over
      if (time_secs >= (60 * 60 * 24)) { time_secs -= (60 * 60 * 24); }
      renderFace(time_secs);
    } else if (!digitalRead(BtnBack)) {
      
      if ( mode == ModeHours ) {
        time_secs -= 60 * 60; // Add one minute
      } else {
        time_secs -= 60; // Add one minute
      }
      
      // Midnight roll-over
      if (time_secs < 0 ) { time_secs +=  (60 * 60 * 24); }
      renderFace(time_secs);
    }
  }

  val = digitalRead(BtnMode);
  if (!val && val != lastMode) {

    mode += 1;
    if ( mode > 3) { mode = 0; }
    delay(100);
  }
  lastMode = val;

}

// =========================================================================
// Draw the clock face in the sprite
// =========================================================================
static void renderFace(float t) {
  char time[20];
  float h_angle = t * HOUR_ANGLE;
  float m_angle = t * MINUTE_ANGLE;
  //float s_angle = t * SECOND_ANGLE;
  long r = (unsigned long) time_secs;
  int h = (int)(r/3600);
  r = r%3600;
  int m = (int)(r/60);
  int s = (int)(r % 60);
  int HandColor = CLOCK_FG;

  // The face is completely redrawn - this can be done quickly
  face.fillSprite(TFT_BLACK);

  // Draw the face circle
  face.fillSmoothCircle( CLOCK_XY, CLOCK_XY, CLOCK_R, CLOCK_BG );

  // Set text datum to middle centre and the colour
  face.setTextDatum(MC_DATUM);

  // The background colour will be read during the character rendering
  face.setTextColor(CLOCK_FG, CLOCK_BG);

  // Text offset adjustment
  constexpr uint32_t dialOffset = CLOCK_R - 10;

  float xp = 0.0, yp = 0.0; // Use float pixel position for smooth AA motion

  // Draw digits around clock perimeter
  for (uint32_t h = 1; h <= 12; h++) {
    getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, dialOffset, h * 360.0 / 12);
    face.drawNumber(h, xp, 2 + yp);
  }

  /*
  // Add text (could be digital time...)
  face.setTextColor(LABEL_FG, CLOCK_BG);
  sprintf(time,"%02d:%02d:%02d",h,m,s);
  face.drawString(time,  CLOCK_XY, CLOCK_XY * 0.75);
  */
  if (mode == ModeMinutes) {
    HandColor = HILITE_FG;
  } else {
    HandColor = CLOCK_FG;
  }
  // Draw minute hand
  getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, M_HAND_LENGTH, m_angle);
  face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 6.0f, HandColor);
 // face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.0f, CLOCK_BG);

  if (mode == ModeHours) {
    HandColor = HILITE_FG;
  } else {
    HandColor = CLOCK_FG;
  }
  // Draw hour hand
  getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, H_HAND_LENGTH, h_angle);
  face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 6.0f, HandColor);
  //face.drawWideLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.0f, CLOCK_BG);

  // Draw the central pivot circle
  face.fillSmoothCircle( CLOCK_XY, CLOCK_XY, 4, CLOCK_FG);

  // Draw second hand
  //getCoord( CLOCK_XY, CLOCK_XY, &xp, &yp, S_HAND_LENGTH, s_angle);
  //face.drawWedgeLine( CLOCK_XY, CLOCK_XY, xp, yp, 2.5, 1.0, SECCOND_FG);
  face.pushSprite(0,0, TFT_TRANSPARENT);
}

// =========================================================================
// Get coordinates of end of a line, pivot at x,y, length r, angle a
// =========================================================================
// Coordinates are returned to caller via the xp and yp pointers
#define DEG2RAD 0.0174532925
void getCoord(int16_t x, int16_t y, float *xp, float *yp, int16_t r, float a)
{
  float sx1 = cos( (a - 90) * DEG2RAD);
  float sy1 = sin( (a - 90) * DEG2RAD);
  *xp =  sx1 * r + x;
  *yp =  sy1 * r + y;
}
