#pragma once
#include <RTClib.h>
#include <time.h>
#include <math.h>
#include "lesson_clock_core.h"

namespace SchoolTimer {
constexpr uint8_t buttonPin=5, rtcAddress=0x68;
RTC_DS3231 rtc;
LessonClock::Button button;
LessonClock::Bell bell;
LessonClock::Mode mode=LessonClock::CHARACTER;
bool valid=false, connected=false, covered=false, priorityOverlay=false;
uint32_t epoch=0, polled=0, drawn=0, previewUntil=0, alertUntil=0;
int preview=-1, alert=-1;
char serialLine[40];
uint8_t serialLength=0;
bool serialOverflow=false;

bool readTime(uint32_t& value) {
  Wire.beginTransmission(rtcAddress);
  Wire.write(0x0F);

  if (Wire.endTransmission(false)!=0 || Wire.requestFrom(rtcAddress,uint8_t(1))!=1) {
    connected=false;
    return false;
  }

  connected=true;

  if (Wire.read() & 0x80) {
    return false; // Oscillator-stop flag: demand explicit time setting.
  }

  Wire.beginTransmission(rtcAddress);
  Wire.write(0);

  if (Wire.endTransmission(false)!=0 || Wire.requestFrom(rtcAddress,uint8_t(7))!=7) {
    return false;
  }

  uint8_t b[7];

  for (auto& v:b) {
    v=Wire.read();
  }

  auto bcd=[](uint8_t n)->int { return (n>>4)*10+(n&15); };

  if ((b[0]&15)>9 || (b[2]&15)>9 || (b[1]&15)>9 || (b[4]&15)>9 || (b[5]&15)>9 || (b[6]&15)>9 || (b[5]&0x80)) {
    return false;
  }

  int hour=bcd(b[2]&0x3F);

  if (b[2]&0x40) {
    int h=bcd(b[2]&0x1F);

    if (h<1 || h>12) {
      return false;
    }

    hour=h%12+((b[2]&0x20)?12:0);
  }

  DateTime t(2000+bcd(b[6]),bcd(b[5]&0x1F),bcd(b[4]&0x3F),hour,bcd(b[1]&0x7F),bcd(b[0]&0x7F));

  if (!t.isValid() || t.year()<2024 || hour>23 || t.minute()>59 || t.second()>59) {
    return false;
  }

  value=t.unixtime();
  return true;
}
void reply(const char* message) {

  if (Serial.availableForWrite()>=int(strlen(message))) {
    Serial.print(message);
  }

}
void serialCommands() {
  // Bounded input work per loop; never wait for a USB newline or client.
  for (int n=0;n<32 && Serial.available();++n) {
    char c=Serial.read();

    if (c=='\r') {
      continue;
    }

    if (c=='\n') {
      serialLine[serialLength]=0;

      if (!serialOverflow && strncmp(serialLine,"TIME ",5)==0) {
        char* end=nullptr;
        unsigned long value=strtoul(serialLine+5,&end,10);

        if (end!=serialLine+5 && *end==0 && value>=1704067200UL && value<4102444800UL && rtc.begin(&Wire)) {
          rtc.adjust(DateTime(uint32_t(value)));
          valid=readTime(epoch) && epoch>=value && epoch-value<=1;
          bell.previous=0;
          preview=-1;
          reply(valid ? "RTC SET OK UTC\n" : "RTC SET FAILED\n");
        } else {
          reply("RTC SET FAILED: TIME <UTC epoch>, RTC required\n");
        }

      }

      serialLength=0;
      serialOverflow=false;
    } else if (serialLength<sizeof(serialLine)-1) {
      serialLine[serialLength++]=c;
    } else {
      serialOverflow=true;
    }

  }
}
void begin() {
  pinMode(buttonPin,INPUT);
  setenv("TZ",LessonClock::timezone,1);
  tzset();
  polled=millis()-2000;
}
void text(Adafruit_SSD1306& d,int x,int y,const char* s,int size=1) {
  d.setTextSize(size);
  d.setCursor(x,y);
  d.print(s);
}
void render(Adafruit_SSD1306& d,const tm& local,LessonClock::Period p,bool silence,bool showAlert) {
  d.clearDisplay();
  d.setTextColor(SSD1306_WHITE);
  d.setTextWrap(false);
  char buf[24];

  if (!valid) {
    text(d,0,12,connected ? "SET CLOCK" : "NO RTC",2);
    text(d,0,36,"DS3231 + USB setup");
    text(d,0,54,"Hold: next mode");
    return;
  }

  if (mode==LessonClock::CLOCK && !silence && !showAlert) {
    static const char* days[]={"SUN","MON","TUE","WED","THU","FRI","SAT"};
    snprintf(buf,sizeof(buf),"%s %02d.%02d.%04d",days[local.tm_wday],local.tm_mday,local.tm_mon+1,local.tm_year+1900);
    text(d,22,2,buf);
    snprintf(buf,sizeof(buf),"%02d:%02d",local.tm_hour,local.tm_min);
    text(d,19,20,buf,3);
    snprintf(buf,sizeof(buf),"%02d sec",local.tm_sec);
    text(d,46,51,buf);
    return;
  }

  snprintf(buf,sizeof(buf),"%02d:%02d",local.tm_hour,local.tm_min);
  text(d,98,0,buf);

  if (silence) {
    text(d,0,0,"REMEMBER");
    text(d,61,20,"SILENCE");
  } else if (showAlert) {
    snprintf(buf,sizeof(buf),"LESSON %d",alert+1);
    text(d,0,0,buf);
    text(d,65,20,"START");
  } else if (preview>=0) {
    snprintf(buf,sizeof(buf),"LESSON %d",preview+1);
    text(d,0,0,buf);
    int start=LessonClock::lessons[preview].start, end=LessonClock::lessons[preview].end;
    snprintf(buf,sizeof(buf),"%02d:%02d",start/3600,(start/60)%60);
    text(d,31,18,buf,2);
    snprintf(buf,sizeof(buf),"to %02d:%02d",end/3600,(end/60)%60);
    text(d,37,39,buf);
    text(d,0,55,"Click: next / auto 8s");
    return;
  } else {
    const char* title=p.phase==LessonClock::LESSON ? "LESSON" : p.phase==LessonClock::BREAK ? "BREAK" : p.phase==LessonClock::BEFORE ? "BEFORE" : p.phase==LessonClock::WEEKEND ? "DAY OFF" : "ALL DONE";
    snprintf(buf,sizeof(buf),"%s%s%d",title,p.index>=0 ? " " : "",p.index>=0 ? p.index+1 : 0);

    if (p.index<0) {
      snprintf(buf,sizeof(buf),"%s",title);
    }

    text(d,0,0,buf);
    text(d,65,19,"LEFT");
  }

  d.drawCircle(28,37,23,SSD1306_WHITE);
  float fraction=p.duration>0 ? float(p.remaining)/p.duration : 0;
  int segments=int(fraction*60);

  for (int i=0;i<segments;++i) {
    float a=-1.5707963f+i*6.2831853f/60;
    d.drawLine(28+int(cosf(a)*19),37+int(sinf(a)*19),28+int(cosf(a)*22),37+int(sinf(a)*22),SSD1306_WHITE);
  }

  if (p.duration==0) {
    text(d,66,32,"--:--",2);
  } else if (p.remaining>=3600) {
    snprintf(buf,sizeof(buf),"%02d:%02d",p.remaining/3600,(p.remaining/60)%60);
    text(d,66,32,buf,2);
    text(d,76,51,"hh:mm");
  } else {
    snprintf(buf,sizeof(buf),"%02d:%02d",p.remaining/60,p.remaining%60);
    text(d,66,32,buf,2);
    text(d,76,51,"mm:ss");
  }

  d.setTextSize(1);
}
// Returns true while the timer/notification owns the screen.
bool tick(Adafruit_SSD1306& d,uint32_t now,bool& resume,bool& characterClick) {
  resume=false;
  characterClick=false;
  serialCommands();

  if (now-polled >= (connected?500u:2000u)) {
    polled=now;
    valid=readTime(epoch);

    if (!valid) {
      bell.previous=0;
    }

  }

  tm local{};
  time_t t=epoch;

  if (valid) {
    localtime_r(&t,&local);
  }

  int second=local.tm_hour*3600+local.tm_min*60+local.tm_sec;
  auto p=LessonClock::period(second,local.tm_wday);
  auto gesture=button.update(digitalRead(buttonPin)==HIGH,now);

  if (gesture==LessonClock::HOLD && mode!=LessonClock::GAMES) {
    mode=LessonClock::nextMode(mode);
    preview=-1;
    drawn=now-200;
  }

  if (gesture==LessonClock::CLICK && mode==LessonClock::TIMER && valid) {
    preview=preview<0 ? LessonClock::nextLesson(second) : (preview+1)%LessonClock::count;
    previewUntil=now+8000;
    drawn=now-200;
  }

  if (preview>=0 && int32_t(now-previewUntil)>=0) {
    preview=-1;
  }

  if (valid) {
    int event=bell.update(epoch,second,local.tm_wday);

    if (event>=0) {
      alert=event;
      alertUntil=now+4000;
      preview=-1;
    }

  }

  bool silence=valid && p.phase==LessonClock::SILENCE;
  bool showAlert=valid && alert>=0 && int32_t(alertUntil-now)>0;
  priorityOverlay=silence || showAlert;
  bool owns=LessonClock::screenOwned(mode,silence,showAlert);
  characterClick=gesture==LessonClock::CLICK && !owns;
  resume=covered && !owns;
  covered=owns;

  if (owns && (mode!=LessonClock::GAMES || priorityOverlay) && now-drawn>=200) {
    drawn=now;
    render(d,local,p,silence,showAlert);
    d.display();
  }

  return owns;
}
}
