#include <Arduino.h>
#include <avr/eeprom.h>
#include "mainControl.h"
#include "lcdControl.h"
#include "ledControl.h"
#include "waiting.h"
#include <Time.h>
#include <TimeLib.h>

//#define SERIALDEBUG

#define INTERVAL_LCD_OFF 4000  // ボタン放してから表示オフまでの時間[ms]
#define INTERVAL_LCD_BLINK 500 // 設定中のLCD点滅間隔[ms]
#define THRESH_BTN_HOLD 1000   // +-ボタン長押しの閾値[ms]
#define INTERVAL_BTN_HOLD 100  // +-ボタン長押し中の増減間隔[ms]
#define NORTIFY_ALARM_ON 84    // アラーム有効状態を知らせる音の種類（インデックス）

#define INI_HOUR        0
#define INI_MINUTE      0
#define INI_SECOND      0
#define INI_YEAR     2018
#define INI_MONTH       4
#define INI_DAY         1
#define INI_ALARM_HOUR  0
#define INI_ALARM_MIN   1

#define MIN_DATE_VALUE  946684800  //  2000/1/1 (<=)
#define MAX_DATE_VALUE 4102444800  //  2100/1/1 (>)

//#define ADDR_START_EEPROM_DATE  400

enum State {
  NONE = 0,
  ON_ALARM,
  AFTER_STOP,
  DISP_TOP = 0x10,
  DISP_TIME = DISP_TOP,
  DISP_DATE,
  DISP_YEAR,
  DISP_ALARM,
  DISP_LAST = DISP_ALARM,
  SET_TOP = 0x20,
  SET_HOUR = SET_TOP,
  SET_MIN,
  SET_MON,
  SET_DAY,
  SET_YEAR,
  SET_AL_HOUR,
  SET_AL_MIN,
  SET_LAST = SET_AL_MIN,
  NORTIFY_AL_TOP = 0x30,
  NORTIFY_AL_ON = NORTIFY_AL_TOP,
  NORTIFY_AL_OFF,
  NORTIFY_AL_LAST = NORTIFY_AL_OFF
};
State state = NONE;
bool fAlarmEnabled = false;    // Alarm function on/off
bool fAlarmStarted;           // Flag for Alarm started.

byte alarmHour;
byte alarmMinute;

void dispLED();
void checkAlarmButton();
void preserveDate();
//void rewriteDate();
void dispTime();
void dispDate();
void dispYear();
void dispAlarm();
void dispHour();
void dispMin();
void dispMonth();
void dispDay();
void dispAlarmHour();
void dispAlarmMin();

void initUserApp() {
  initLCD();
  initLED();
  rewriteDate();
  //setTime(INI_HOUR, INI_MINUTE, 0, INI_DAY, INI_MONTH, INI_YEAR);
  //alarmHour = INI_ALARM_HOUR;
  //alarmMinute = INI_ALARM_MIN;
}

void checkButton() {
  static unsigned long start = 0UL;
  static unsigned long startHold = 0UL;
  static unsigned long startJudgeHold = 0UL;
  static bool fDown = false;
  static bool fHold = false;
  int delta = 0;

  if(isPushed(INDEX_TOUCH_DSIP)) {            // DISP
    if(fDown == false) {
      if(state == NONE) {
        state = DISP_TOP;
      } else if(state >= DISP_TOP && state < DISP_LAST) {
        state = (State)(state + 1);
      } else if(state == DISP_LAST) {
        state = DISP_TOP;
      } else if(state >= SET_TOP && state <= SET_LAST) {
        state = DISP_TOP;
      } else {
      }
      fDown = true;
    }
  } else if(isPushed(INDEX_TOUCH_SET)) {     // SET
    if(fDown == false) {
      if (state == NONE) {
        state = SET_TOP;
      } else if(state >= DISP_TOP && state <= DISP_LAST) {
        state = SET_TOP;
      } else if(state >= SET_TOP && state < SET_LAST) {
        state = (State)(state + 1);
      } else if(state == SET_LAST) {
        state = SET_TOP;
      } else {
      }
//      state = state < 4 ? 3 : (state > 8 ? 3 : state); 
//      state++;
      fDown = true;
    }
  } else if(isPushed(INDEX_TOUCH_INC)) {     // INC
    if(fDown == false) {
      fDown = true;
      delta = 1;
      startJudgeHold = millis();
    } else {
      if(fHold == false && (millis() - startJudgeHold > THRESH_BTN_HOLD)) {
        startHold = millis();
        fHold = true;
      }
    }
  } else if(isPushed(INDEX_TOUCH_DEC)) {     // DEC
    if(fDown == false) {
      fDown = true;
      delta = -1;
      startJudgeHold = millis();
    } else {
      if(fHold == false && (millis() - startJudgeHold > THRESH_BTN_HOLD)) {
        startHold = millis();
        fHold = true;
      }
    }
  } else if(isPushed(INDEX_TOUCH_ALARM)) {   // Alarm
    if(fDown == false) {
      fDown = true;
      if(state == NONE) {
        state = fAlarmEnabled ? NORTIFY_AL_ON : NORTIFY_AL_OFF;
        if(fAlarmEnabled) buzzerControl(NORTIFY_ALARM_ON, 100);
      } else if (state == DISP_ALARM || state == SET_AL_HOUR || state == SET_AL_MIN) {
        fAlarmEnabled = !fAlarmEnabled;
        if(fAlarmEnabled) buzzerControl(NORTIFY_ALARM_ON, 100);
      } else if (state == NORTIFY_AL_ON || state == NORTIFY_AL_OFF) {
        fAlarmEnabled = !fAlarmEnabled;
        state = fAlarmEnabled ? NORTIFY_AL_ON : NORTIFY_AL_OFF;
        if(fAlarmEnabled) buzzerControl(NORTIFY_ALARM_ON, 100);
      } else if (state == ON_ALARM) {
        fAlarmEnabled = false;
      }
#ifdef SERIALDEBUG
      Serial.print("Alarm toggled: ");
      Serial.println(fAlarmEnabled ? "ON" : "OFF");
#endif
//      if(fAlarmStarted) fAlarmStarted = false;
    }
  } else {
    if(fDown == true) {
      fDown = false;
      fHold = false;
      start = millis();
    }
  }
  
  if(fHold && millis() - startHold > INTERVAL_BTN_HOLD) {
    if(isPushed(INDEX_TOUCH_INC)) {
      delta = 1;
    } else if(isPushed(INDEX_TOUCH_DEC)) {
      delta = -1;
    }
    startHold = millis();
  }

  if(delta != 0 && (state >= SET_TOP && state <= SET_LAST)) {
    int oldSec = second();
    int oldMin = minute();
    int oldHour = hour();
    int oldDay = day();
    int oldMon = month();
    int oldYear = year();
    int newHour = oldHour;
    int newMin = oldMin;
    int newDay = oldDay;
    int newMon = oldMon;
    int newYear = oldYear;

    if (state == SET_HOUR) {
      if (delta < 0 && oldHour == 0)
        newHour = 23;
      else if (delta > 0 && oldHour == 23)
        newHour = 0;
      else
        newHour += delta;
    } else if (state == SET_MIN) {
      if (delta < 0 && oldMin == 0)
        newMin = 59;
      else if (delta > 0 && oldMin == 59)
        newMin = 0;
      else
        newMin += delta;
    } else if (state == SET_MON) {
      if (delta < 0) {
        if (oldMon == 3 && oldDay == 31) {
          newMon = 2;  newDay = LEAP_YEAR(oldYear) ? 29 : 28;
	} else if (oldMon == 5 && oldDay == 31) {
          newMon = 4;  newDay = 30;
        } else if (oldMon == 7 && oldDay == 31) {
          newMon = 6;  newDay = 30;
        } else if (oldMon == 10 && oldDay == 31) {
          newMon = 9;  newDay = 30;
        } else if (oldMon == 12 && oldDay == 31) {
          newMon = 11; newDay = 30;
        } else {
          newMon += delta;
	}
      } else {
        if (oldMon == 1 && oldDay >= 29) {
          newMon = 2;  newDay = LEAP_YEAR(oldYear) ? 29 : 28;
	} else if (oldMon == 3 && oldDay == 31) {
          newMon = 4;  newDay = 30;
	} else if (oldMon == 5 && oldDay == 31) {
          newMon = 6;  newDay = 30;
	} else if (oldMon == 8 && oldDay == 31) {
          newMon = 9;  newDay = 30;
	} else if (oldMon == 10 && oldDay == 31) {
          newMon = 11; newDay = 30;
	} else {
          newMon += delta;
	}
      }
    } else if (state == SET_DAY) {
      if (delta < 0 && oldDay == 1) {
        if (oldMon == 2 && LEAP_YEAR(oldYear)) newDay = 29;
	else newDay = DAYS[oldMon - 1];
      } else if (delta > 0 && oldMon != 2 && oldDay == DAYS[oldMon - 1]) {
        newDay = 1;
      } else if (delta > 0 && oldMon == 2 && oldDay == 29) {
        newDay = 1;
      } else
        newDay += delta;
    } else if (state == SET_YEAR) {
      newYear += delta;
      if (oldMon == 2 && oldDay == 29) newDay = 28;
      if (newYear > 2099) newYear = 2000;
      if (newYear < 2000) newYear = 2099;
    } else if (state == SET_AL_HOUR) {
      alarmHour += delta;
      if(alarmHour > 23) alarmHour = 0;
      if(alarmHour < 0) alarmHour = 23;
    } else if (state == SET_AL_MIN) {
      alarmMinute += delta;
      if(alarmMinute > 59) alarmMinute = 0;
      if(alarmMinute < 0) alarmMinute = 59;
    }
    setTime(newHour, newMin, oldSec, newDay, newMon, newYear);
  }

  if(!fAlarmStarted && !fDown && millis() - start > INTERVAL_LCD_OFF) {
    state = NONE;
    fDown = true;
    dispNone();
    //ledControl(0, 0, 0);
  }
}

void dispClock() {
  static unsigned long startBlink;
  static bool fBlink;

  switch(state) {
    case NONE:
      break;
    case DISP_TIME:
    case NORTIFY_AL_ON:
    case NORTIFY_AL_OFF:
      dispTime();
      break;
    case DISP_DATE:
      dispDate();
      break;
    case DISP_YEAR:
      dispYear();
      break;
    case DISP_ALARM:
      dispAlarm();
      break;
    case SET_HOUR:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispTime();
      else
        dispMin();
      break;
    case SET_MIN:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispTime();
      else
        dispHour();
      break;
    case SET_MON:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispDate();
      else
        dispDay();
      break;
    case SET_DAY:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispDate();
      else
        dispMonth();
      break;
    case SET_YEAR:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispYear();
      else
        dispNone();
      break;
    case SET_AL_HOUR:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispAlarm();
      else
        dispAlarmMin();
      break;
    case SET_AL_MIN:
      if(startBlink == 0xffffffff) {
        startBlink = millis();
        fBlink = true;
      }
      if(fBlink)
        dispAlarm();
      else
        dispAlarmHour();
      break;
    case ON_ALARM:
    case AFTER_STOP:
      dispTime();
      return;
    default:
      startBlink = 0xffffffff;
      fBlink = false;
      state = DISP_TIME;
      return;
  }
  if(millis() - startBlink > INTERVAL_LCD_BLINK) {
    fBlink = !fBlink;
    startBlink = millis();
  }
}

void dispLED() {
  if(state == DISP_ALARM || state == SET_AL_HOUR || state == SET_AL_MIN || state == NORTIFY_AL_ON || state == NORTIFY_AL_OFF) {
    if(fAlarmEnabled)
      ledControl(0, 0, 255);
    else
      ledControl(255, 0, 0);
  } else if(state >= DISP_TOP) {
    ledControl(0, 0, 0);
  }
}

void preserveDate() {
/*
  unsigned long current = now();
  EEPROM.write(ADDR_START_EEPROM_DATE    , (current      ) & 0xff);
  EEPROM.write(ADDR_START_EEPROM_DATE + 1, (current >>  8) & 0xff);
  EEPROM.write(ADDR_START_EEPROM_DATE + 2, (current >> 16) & 0xff);
  EEPROM.write(ADDR_START_EEPROM_DATE + 3, (current >> 24) & 0xff);
*/
}

void rewriteDate() {
  unsigned long date = 0UL;
  for(int i = 0; i < 4; i++) {
    //byte tmp = EEPROM.read(ADDR_START_EEPROM_DATE + i);
    byte tmp = eeprom_read_byte((uint8_t *)ADDR_START_EEPROM_DATE + i);
#ifdef SERIALDEBUG
    Serial.println(tmp, HEX);
#endif
    date |= ((unsigned long)tmp) << (8*i);
  }
  date = date < MIN_DATE_VALUE ? MIN_DATE_VALUE : date;
  date = date > (MAX_DATE_VALUE - 3600 * 24) ? MAX_DATE_VALUE - 3600 * 24 : date;
  setTime(date);
  int tMin = hour() * 60 + minute() + 1;
  alarmHour = tMin / 60 % 24;
  alarmMinute = tMin % 60;
}

void checkAlarm() {
  if(fAlarmEnabled && hour() == alarmHour && minute() == alarmMinute) {
    state = ON_ALARM;
  }
}

void baseCheck() {
  checkUSB();
  checkButton();
  dispClock();
}

bool check() {
  baseCheck();
  //if (state == NONE || (state >= DISP_TOP && state < NORTIFY_AL_LAST)) {
  if (state == NONE) {
    checkAlarm();
  } else if (state == ON_ALARM) {
    if (!fAlarmStarted) {// 初めてアラーム条件が満たされたときにこの処理に入る。
      fAlarmStarted = true;
      onAlarm();         // アラームボタンが押されるまでここでブロック。
      state = AFTER_STOP;
      if (fAlarmEnabled) {
        fAlarmEnabled = false;
        fAlarmStarted = false;
      }
      waiting(2000UL);
      return false;
    }
    if (!fAlarmEnabled) {
      fAlarmStarted = false;
      return true;
    }
    return false;
  } else if (state == AFTER_STOP) {
    buzzerOff();
  }
  while (state >= DISP_TOP && state <= SET_LAST) {
    baseCheck();
    dispLED();
  }
  return false;
}

bool getAlarmStarted() {
  return fAlarmStarted;
}

void dispHour() {
  int hr = hour();
  dispInfo(hr / 10, hr % 10, 10, 10, true);
#ifdef SERIALDEBUG
  Serial.print((hr / 10) == 0 ? " " : "");
  Serial.print(hr);
  Serial.print(":");
  Serial.println("__");
#endif
}
void dispMin() {
  int min = minute();
  dispInfo(10, 10, min / 10, min % 10, true);
#ifdef SERIALDEBUG
  Serial.print("__");
  Serial.print(":");
  Serial.print((min / 10) == 0 ? " " : "");
  Serial.println(min);
#endif
}
void dispMonth() {
  int mon = month();
  dispInfo(mon < 10 ? 10 : 1, mon % 10 + 0x80, 10, 10);
#ifdef SERIALDEBUG
  Serial.print((mon / 10) == 0 ? " " : "");
  Serial.print(mon);
  Serial.print("/");
  Serial.println("__");
#endif
}
void dispDay() {
  int d = day();
  int dVal = d / 10;
  dispInfo(10, 10 + DOT, dVal == 0 ? 10 : dVal, d % 10);
#ifdef SERIALDEBUG
  Serial.print("__");
  Serial.print("/");
  Serial.print((d / 10) == 0 ? " " : "");
  Serial.println(d);
#endif
}
void dispAlarmHour() {
  dispInfo(alarmHour / 10, alarmHour % 10, 10, 10 + DOT, true);
#ifdef SERIALDEBUG
  Serial.print((alarmHour / 10) == 0 ? " " : "");
  Serial.print(alarmHour);
  Serial.print(":");
  Serial.println("__.");
#endif
}
void dispAlarmMin() {
  dispInfo(10, 10, alarmMinute / 10, alarmMinute % 10 + DOT, true);
#ifdef SERIALDEBUG
  Serial.print("__");
  Serial.print(":");
  Serial.print((alarmMinute / 10) == 0 ? " " : "");
  Serial.print(alarmMinute);
  Serial.println(".");
#endif
}

void dispTime() {
  int hr = hour();
  int min = minute();
  dispInfo(hr / 10, hr % 10, min / 10, min % 10, true);
#ifdef SERIALDEBUG
  Serial.print((hr / 10) == 0 ? " " : "");
  Serial.print(hr);
  Serial.print(":");
  Serial.print((min / 10) == 0 ? " " : "");
  Serial.println(min);
#endif
}

void dispDate() {
  int mon = month();
  int d = day();
  int dVal = d / 10;
  dispInfo(mon < 10 ? 10 : 1, mon % 10 + DOT, dVal == 0 ? 10 : dVal, d % 10);
#ifdef SERIALDEBUG
  Serial.print((mon / 10) == 0 ? " " : "");
  Serial.print(mon);
  Serial.print("/");
  Serial.print((d / 10) == 0 ? " " : "");
  Serial.println(d);
#endif
}

void dispYear() {
  int y = year();
  int d1 = y / 1000;
  int d2 = (y % 1000) / 100;
  int d3 = (y % 100) / 10;
  int d4 = (y % 10);
  dispInfo(d1, d2, d3, d4);
#ifdef SERIALDEBUG
  Serial.print(d1);
  Serial.print(d2);
  Serial.print(d3);
  Serial.println(d4);
#endif
}

void dispAlarm() {
  dispInfo(alarmHour / 10, alarmHour % 10,
    alarmMinute / 10, alarmMinute % 10 + DOT, true);
#ifdef SERIALDEBUG
  Serial.print((alarmHour / 10) == 0 ? " " : "");
  Serial.print(alarmHour);
  Serial.print(":");
  Serial.print((alarmMinute / 10) == 0 ? " " : "");
  Serial.print(alarmMinute);
  Serial.println(".");
#endif
}

