//calculate time between last play of game and current time;
//increment RTC by said amount of seconds
auto Cartridge::rtcLoad() -> void {
  uint64 timestamp = 0;
  for(auto n : range(8)) timestamp.byte(n) = rtc.data[8 + n];
  if(!timestamp) return;  //new save file

  timestamp = time(0) - timestamp;
  while(timestamp--) rtcTickSecond();
}

//save time when game is unloaded
auto Cartridge::rtcSave() -> void {
  uint64 timestamp = time(0);
  for(auto n : range(8)) rtc.data[8 + n] = timestamp.byte(n);
}

auto Cartridge::rtcTickSecond() -> void {
  if(++rtc.second() < 60) return;
  rtc.second() = 0;

  if(++rtc.minute() < 60) return;
  rtc.minute() = 0;

  if(++rtc.hour() < 60) return;
  rtc.hour() = 0;

  rtc.weekday() += 1;
  rtc.weekday() %= 7;

  uint daysInMonth[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
  if(rtc.year() && (rtc.year() % 100) && !(rtc.year() % 4)) daysInMonth[1]++;

  if(++rtc.day() < daysInMonth[rtc.month()]) return;
  rtc.day() = 0;

  if(++rtc.month() < 12) return;
  rtc.month() = 0;

  ++rtc.year();
}

auto Cartridge::rtcCheckAlarm() -> void {
  if(!rtc.alarm.bit(5)) return;

  if(rtc.hour() == rtc.alarmHour && rtc.minute() == rtc.alarmMinute) {
    cpu.raise(CPU::Interrupt::Cartridge);
  } else {
    cpu.lower(CPU::Interrupt::Cartridge);
  }
}

auto Cartridge::rtcStatus() -> uint8 {
  return 0x80;
}

auto Cartridge::rtcCommand(uint8 data) -> void {
  rtc.command = data;

  //RESET
  if(rtc.command == 0x10) {
    rtc.year() = 0;
    rtc.month() = 0;
    rtc.day() = 0;
    rtc.weekday() = 0;
    rtc.hour() = 0;
    rtc.minute() = 0;
    rtc.second() = 0;
  }

  //ALARM_FLAG
  if(rtc.command == 0x12) {
    rtc.index = 0;
  }

  //SET_DATETIME
  if(rtc.command == 0x14) {
    rtc.index = 0;
  }

  //GET_DATETIME
  if(rtc.command == 0x15) {
    rtc.index = 0;
  }

  //SET_ALARM
  if(rtc.command == 0x18) {
    rtc.index = 0;
  }
}

auto Cartridge::rtcRead() -> uint8 {
  uint8 data = 0;

  static auto encode = [](uint8 data) -> uint8 {
    return ((data / 10) << 4) + (data % 10);
  };

  //GET_DATETIME
  if(rtc.command == 0x15) {
    switch(rtc.index) {
    case 0: data = encode(rtc.year()); break;
    case 1: data = encode(rtc.month() + 1); break;
    case 2: data = encode(rtc.day() + 1); break;
    case 3: data = encode(rtc.weekday()); break;
    case 4: data = encode(rtc.hour()); break;
    case 5: data = encode(rtc.minute()); break;
    case 6: data = encode(rtc.second()); break;
    }
    if(++rtc.index >= 7) rtc.command = 0;
  }

  return data;
}

auto Cartridge::rtcWrite(uint8 data) -> void {
  static auto decode = [](uint8 data) -> uint8 {
    return (data >> 4) * 10 + (data & 0x0f);
  };

  //ALARM_FLAG
  if(rtc.command == 0x12) {
    if(data.bit(6)) rtc.alarm = data;  //todo: is bit6 really required to be set?
    rtc.command = 0;
    rtcCheckAlarm();
  }

  //SET_DATETIME
  if(rtc.command == 0x14) {
    switch(rtc.index) {
    case 0: rtc.year() = decode(data); break;
    case 1: rtc.month() = decode(data) - 1; break;
    case 2: rtc.day() = decode(data) - 1; break;
    case 3: rtc.weekday() = decode(data); break;
    case 4: rtc.hour() = decode(data); break;
    case 5: rtc.minute() = decode(data); break;
    case 6: rtc.second() = decode(data); break;
    }
    if(++rtc.index >= 7) rtc.command = 0;
  }

  //SET_ALRM
  if(rtc.command == 0x18) {
    switch(rtc.index) {
    case 0: rtc.alarmHour = decode(data.bit(0,6)); break;
    case 1: rtc.alarmMinute = decode(data); break;
    }
    if(++rtc.index >= 2) rtc.command = 0;
  }
}
