//******************************************
// MAGNAVOX ODYSSEY 2 MODULE
//******************************************
#if defined(ENABLE_ODY2)
// Magnavox Odyssey 2
// Philips Videopac/Videopac+
// Cartridge Pinout
// 30P 3.96mm pitch connector
//
//       FRONT            BACK
//        SIDE            SIDE
//              +------+
//          T0 -| 1  A |- /WR
//          D0 -| 2  B |- GND
//          D1 -| 3  C |- GND
//          D2 -| 4  D |- VCC (+5V)
//          D3 -| 5  E |- CS3
//          D4 -| 6  F |- /PSEN (/CE)
//          D5 -| 7  G |- A0
//          D6 -| 8  H |- A1
//          D7 -| 9  J |- A2
//   A10 (P22) -| 10 K |- A3
//  /CS1 (P14) -| 11 L |- A4
//         P11 -| 12 M |- A5
//         P10 -| 13 N |- A7
//   A11 (P23) -| 14 P |- A6
//    A9 (P21) -| 15 R |- A8 (P20)
//              +------+
//
// NOTE:  ADDRESS A7/A6 PIN ORDER ON PINS N & P.
// NOTE:  MOST CARTS DO NOT CONNECT A10 ON PIN 10.
//
//                           BACK
//      +---------------------------------------------+
//      | A  B  C  D  E  F  G  H  J  K  L  M  N  P  R |
// LEFT |                                             | RIGHT
//      | 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 |
//      +---------------------------------------------+
//                           FRONT
//
// CONTROL PINS:
// T0(PH0)    - SNES RESET
// CS3(PH3)   - SNES /CS
// /CS1(PH4)  - SNES /IRQ
// /WR(PH5)   - SNES /WR
// /PSEN(PH6) - SNES /RD

byte ODY2[] = { 2, 4, 8, 12, 16 };
byte ody2lo = 0;  // Lowest Entry
byte ody2hi = 4;  // Highest Entry

byte ody2mapper;
byte newody2mapper;
byte ody2size;
byte newody2size;

// EEPROM MAPPING
// 07 MAPPER
// 08 ROM SIZE

//******************************************
//  Menu
//******************************************
// Base Menu
static const char* const menuOptionsODY2[] PROGMEM = { FSTRING_SELECT_CART, FSTRING_READ_ROM, FSTRING_SET_SIZE, FSTRING_RESET };

void setup_ODY2() {
  // Request 5V
  setVoltage(VOLTS_SET_5V);

  // Set Address Pins to Output
  // Odyssey 2 uses A0-A13 [A14-A23 UNUSED]
  //A0-A7
  DDRF = 0xFF;
  //A8-A15
  DDRK = 0xFF;
  //A16-A23
  DDRL = 0xFF;

  // Set Control Pins to Output
  //        T0(PH0)   ---(PH1)   CS3(PH3)   /CS1(PH4)  /WR(PH5)   /RD(PH6)
  DDRH |= (1 << 0) | (1 << 1) | (1 << 3) | (1 << 4) | (1 << 5) | (1 << 6);

  // Set TIME(PJ0) to Output (UNUSED)
  DDRJ |= (1 << 0);

  // Set Pins (D0-D7) to Input
  DDRC = 0x00;

  // Setting Control Pins to HIGH
  //        T0(PH0)   ---(PH1)   /CS1(PH4)  /WR(PH5)   /RD(PH6)
  PORTH |= (1 << 0) | (1 << 1) | (1 << 4) | (1 << 5) | (1 << 6);

  // Set CS3(PH3) to LOW
  PORTH &= ~(1 << 3);

  // Set Unused Data Pins (PA0-PA7) to Output
  DDRA = 0xFF;

  // Set Unused Pins HIGH
  PORTA = 0xFF;
  PORTL = 0xFF;       // A16-A23
  PORTJ |= (1 << 0);  // TIME(PJ0)

  checkStatus_ODY2();
  strcpy(romName, "ODYSSEY2");

  mode = CORE_ODY2;
}

void ody2Menu() {
  convertPgm(menuOptionsODY2, 4);
  uint8_t mainMenu = question_box(F("ODYSSEY 2 MENU"), menuOptions, 4, 0);

  switch (mainMenu) {
    case 0:
      // Select Cart
      setCart_ODY2();
      wait();
      setup_ODY2();
      break;

    case 1:
      // Read ROM
      sd.chdir("/");
      readROM_ODY2();
      sd.chdir("/");
      break;

    case 2:
      // Set Size
      setROMSize_ODY2();
      break;

    case 3:
      // reset
      resetArduino();
      break;
  }
}

//******************************************
// READ CODE
//******************************************

uint8_t readData_ODY2(uint16_t addr) {
  PORTF = addr & 0xFF;         // A0-A7
  PORTK = (addr >> 8) & 0xFF;  // A8-A13

  // Set /PSEN (/CE) to LOW
  PORTH &= ~(1 << 6);  // /PSEN LOW (ENABLE)
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  uint8_t ret = PINC;

  // Pull /PSEN (/CE) to HIGH
  PORTH |= (1 << 6);  // /PSEN HIGH (DISABLE)

  return ret;
}

void readSegment_ODY2(uint16_t startaddr, uint16_t endaddr) {
  for (uint16_t addr = startaddr; addr < endaddr; addr += 512) {
    for (int w = 0; w < 512; w++) {
      uint8_t temp = readData_ODY2(addr + w);
      sdBuffer[w] = temp;
    }
    myFile.write(sdBuffer, 512);
  }
}

void bankSwitch_ODY2(uint16_t addr, uint8_t data) {
  PORTF = addr & 0xFF;         // A0-A7
  PORTK = (addr >> 8) & 0xFF;  // A8-A13
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  // Set /CS1(PH4) to LOW
  PORTH &= ~(1 << 4);
  // Set /WR(PH5) to LOW
  PORTH &= ~(1 << 5);
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  DDRC = 0xFF;  // Set to Output
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  PORTC = data;
  NOP;
  NOP;
  NOP;
  NOP;
  NOP;

  // Set /WR(PH5) to HIGH
  PORTH |= (1 << 5);
  // Set /CS1(PH4) to HIGH
  PORTH |= (1 << 4);

  DDRC = 0x00;  // Reset to Input
}

void readROM_ODY2() {
  strcpy(fileName, romName);
  strcat(fileName, ".bin");

  // create a new folder for storing rom file
  EEPROM_readAnything(0, foldern);
  sprintf(folder, "ODY2/ROM/%d", foldern);
  sd.mkdir(folder, true);
  sd.chdir(folder);

  display_Clear();
  print_Msg(F("Saving to "));
  print_Msg(folder);
  println_Msg(F("/..."));
  display_Update();

  // open file on sdcard
  if (!myFile.open(fileName, O_RDWR | O_CREAT)) {
    print_FatalError(create_file_STR);
  }

  // write new folder number back to EEPROM
  foldern++;
  EEPROM_writeAnything(0, foldern);

  if (ody2mapper == 1) {  // A10 CONNECTED
    // Videopac 31:  Musician
    // Videopac 40:  4 in 1 Row/4 en 1 Ligne
    readSegment_ODY2(0x0000, 0x1000);  // 4K
  }
  // A10 NOT CONNECTED
  else if (ody2size > 2) {  // 12K/16K (BANKSWITCH)
    // Videopac+ 55:  Neutron Star         12K = 2K x 6 Banks
    // Videopac+ 58:  Norseman             12K = 2K x 6 Banks
    // Videopac+ 59:  Helicopter Rescue    16K = 2K x 8 Banks
    // Videopac+ 60:  Trans American Rally 16K = 2K x 8 Banks
    uint8_t ody2banks = (ody2size * 4) / 2;
    for (int x = (ody2banks - 1); x >= 0; x--) {
      bankSwitch_ODY2(0x80, ~x);
      readSegment_ODY2(0x0400, 0x0C00);  // 2K x 6/8 = 12K/16K
    }
  } else {                             // STANDARD SIZES
    readSegment_ODY2(0x0400, 0x0C00);  // 2K
    if (ody2size > 0) {
      readSegment_ODY2(0x1400, 0x1C00);  // +2K = 4K
      if (ody2size > 1) {
        readSegment_ODY2(0x2400, 0x2C00);  // +2K = 6K
        readSegment_ODY2(0x3400, 0x3C00);  // +2K = 8K
      }
    }
  }
  myFile.close();

  unsigned long crcsize = ODY2[ody2size] * 0x400;
  calcCRC(fileName, crcsize, NULL, 0);

  println_Msg(FS(FSTRING_EMPTY));
  print_STR(press_button_STR, 1);
  display_Update();
  wait();
}

//******************************************
// ROM SIZE
//******************************************

void setROMSize_ODY2() {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display_Clear();
  if (ody2lo == ody2hi)
    newody2size = ody2lo;
  else {
    uint8_t b = 0;
    int i = ody2lo;

    display_Clear();
    print_Msg(F("ROM Size: "));
    println_Msg(ODY2[i]);
    println_Msg(FS(FSTRING_EMPTY));
#if defined(ENABLE_OLED)
    print_STR(press_to_change_STR, 1);
    print_STR(right_to_select_STR, 1);
#elif defined(ENABLE_LCD)
    print_STR(rotate_to_change_STR, 1);
    print_STR(press_to_select_STR, 1);
#endif
    display_Update();

    while (1) {
      b = checkButton();
      if (b == 2) {  // Previous (doubleclick)
        if (i == ody2lo)
          i = ody2hi;
        else
          i--;

        // Only update display after input because of slow LCD library
        display_Clear();
        print_Msg(F("ROM Size: "));
        println_Msg(ODY2[i]);
        println_Msg(FS(FSTRING_EMPTY));
#if defined(ENABLE_OLED)
        print_STR(press_to_change_STR, 1);
        print_STR(right_to_select_STR, 1);
#elif defined(ENABLE_LCD)
        print_STR(rotate_to_change_STR, 1);
        print_STR(press_to_select_STR, 1);
#endif
        display_Update();
      }
      if (b == 1) {  // Next (press)
        if (i == ody2hi)
          i = ody2lo;
        else
          i++;

        // Only update display after input because of slow LCD library
        display_Clear();
        print_Msg(F("ROM Size: "));
        println_Msg(ODY2[i]);
        println_Msg(FS(FSTRING_EMPTY));
#if defined(ENABLE_OLED)
        print_STR(press_to_change_STR, 1);
        print_STR(right_to_select_STR, 1);
#elif defined(ENABLE_LCD)
        print_STR(rotate_to_change_STR, 1);
        print_STR(press_to_select_STR, 1);
#endif
        display_Update();
      }
      if (b == 3) {  // Long Press - Execute (hold)
        newody2size = i;
        break;
      }
    }
    display.setCursor(0, 56);  // Display selection at bottom
  }
  print_Msg(F("ROM SIZE "));
  print_Msg(ODY2[newody2size]);
  println_Msg(F("K"));
  display_Update();
  delay(1000);
#else
  if (ody2lo == ody2hi)
    newody2size = ody2lo;
  else {
setrom:
    String sizeROM;
    for (int i = 0; i < (ody2hi - ody2lo + 1); i++) {
      Serial.print(F("Select ROM Size:  "));
      Serial.print(i);
      Serial.print(F(" = "));
      Serial.print(ODY2[i + ody2lo]);
      Serial.println(F("K"));
    }
    Serial.print(F("Enter ROM Size: "));
    while (Serial.available() == 0) {}
    sizeROM = Serial.readStringUntil('\n');
    Serial.println(sizeROM);
    newody2size = sizeROM.toInt() + ody2lo;
    if (newody2size > ody2hi) {
      Serial.println(F("SIZE NOT SUPPORTED"));
      Serial.println(FS(FSTRING_EMPTY));
      goto setrom;
    }
  }
  Serial.print(F("ROM Size = "));
  Serial.print(ODY2[newody2size]);
  Serial.println(F("K"));
#endif
  EEPROM_writeAnything(8, newody2size);
  ody2size = newody2size;
}

void checkStatus_ODY2() {
  EEPROM_readAnything(7, ody2mapper);
  EEPROM_readAnything(8, ody2size);
  if (ody2mapper > 1) {
    ody2mapper = 0;
    EEPROM_writeAnything(7, ody2mapper);
  }
  if (ody2size > 4) {
    ody2size = 0;
    EEPROM_writeAnything(8, ody2size);
  }

#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display_Clear();
  println_Msg(F("ODYSSEY 2 READER"));
  println_Msg(FS(FSTRING_CURRENT_SETTINGS));
  println_Msg(FS(FSTRING_EMPTY));
  print_Msg(F("MAPPER:   "));
  println_Msg(ody2mapper);
  print_Msg(F("ROM SIZE: "));
  print_Msg(ODY2[ody2size]);
  println_Msg(F("K"));
  display_Update();
  wait();
#else
  Serial.print(F("CURRENT MAPPER:   "));
  Serial.println(ody2mapper);
  Serial.print(F("CURRENT ROM SIZE: "));
  Serial.print(ODY2[ody2size]);
  Serial.println(F("K"));
  Serial.println(FS(FSTRING_EMPTY));
#endif
}

//******************************************
// CART SELECT CODE
//******************************************

FsFile ody2csvFile;
char ody2game[51];                    // title
char ody2mm[3];                       // mapper (A10)
char ody2rr[3];                       // romsize
char ody2ll[4];                       // linelength (previous line)
unsigned long ody2csvpos;             // CSV File Position
char ody2cartCSV[] = "ody2cart.txt";  // CSV List
char ody2csvEND[] = "EOF";            // CSV End Marker for scrolling

bool readLine_ODY2(FsFile& f, char* line, size_t maxLen) {
  for (size_t n = 0; n < maxLen; n++) {
    int c = f.read();
    if (c < 0 && n == 0) return false;  // EOF
    if (c < 0 || c == '\n') {
      line[n] = 0;
      return true;
    }
    line[n] = c;
  }
  return false;  // line too long
}

bool readVals_ODY2(char* ody2game, char* ody2mm, char* ody2rr, char* ody2ll) {
  char line[59];
  ody2csvpos = ody2csvFile.position();
  if (!readLine_ODY2(ody2csvFile, line, sizeof(line))) {
    return false;  // EOF or too long
  }
  char* comma = strtok(line, ",");
  int x = 0;
  while (comma != NULL) {
    if (x == 0)
      strcpy(ody2game, comma);
    else if (x == 1)
      strcpy(ody2mm, comma);
    else if (x == 2)
      strcpy(ody2rr, comma);
    else if (x == 3)
      strcpy(ody2ll, comma);
    comma = strtok(NULL, ",");
    x += 1;
  }
  return true;
}

bool getCartListInfo_ODY2() {
  bool buttonreleased = 0;
  bool cartselected = 0;
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display_Clear();
  println_Msg(F(" HOLD TO FAST CYCLE"));
  display_Update();
#else
  Serial.println(F("HOLD BUTTON TO FAST CYCLE"));
#endif
  delay(2000);
#if defined(ENABLE_OLED)
  buttonVal1 = (PIND & (1 << 7));  // PD7
#elif defined(ENABLE_LCD)
  boolean buttonVal1 = (PING & (1 << 2));  //PG2
#endif
  if (buttonVal1 == LOW) {  // Button Held - Fast Cycle
    while (1) {             // Scroll Game List
      while (readVals_ODY2(ody2game, ody2mm, ody2rr, ody2ll)) {
        if (strcmp(ody2csvEND, ody2game) == 0) {
          ody2csvFile.seek(0);  // Restart
        } else {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
          display_Clear();
          println_Msg(F("CART TITLE:"));
          println_Msg(FS(FSTRING_EMPTY));
          println_Msg(ody2game);
          display_Update();
#else
          Serial.print(F("CART TITLE:"));
          Serial.println(ody2game);
#endif
#if defined(ENABLE_OLED)
          buttonVal1 = (PIND & (1 << 7));  // PD7
#elif defined(ENABLE_LCD)
          buttonVal1 = (PING & (1 << 2));  //PG2
#endif
          if (buttonVal1 == HIGH) {  // Button Released
            buttonreleased = 1;
            break;
          }
          if (buttonreleased) {
            buttonreleased = 0;  // Reset Flag
            break;
          }
        }
      }
#if defined(ENABLE_OLED)
      buttonVal1 = (PIND & (1 << 7));  // PD7
#elif defined(ENABLE_LCD)
      buttonVal1 = (PING & (1 << 2));      //PG2
#endif
      if (buttonVal1 == HIGH)  // Button Released
        break;
    }
  }
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display.setCursor(0, 56);
  println_Msg(F("FAST CYCLE OFF"));
  display_Update();
#else
  Serial.println(FS(FSTRING_EMPTY));
  Serial.println(F("FAST CYCLE OFF"));
  Serial.println(F("PRESS BUTTON TO STEP FORWARD"));
  Serial.println(F("DOUBLE CLICK TO STEP BACK"));
  Serial.println(F("HOLD TO SELECT"));
  Serial.println(FS(FSTRING_EMPTY));
#endif
  while (readVals_ODY2(ody2game, ody2mm, ody2rr, ody2ll)) {
    if (strcmp(ody2csvEND, ody2game) == 0) {
      ody2csvFile.seek(0);  // Restart
    } else {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
      display_Clear();
      println_Msg(F("CART TITLE:"));
      println_Msg(FS(FSTRING_EMPTY));
      println_Msg(ody2game);
      display.setCursor(0, 48);
#if defined(ENABLE_OLED)
      print_STR(press_to_change_STR, 1);
      print_STR(right_to_select_STR, 1);
#elif defined(ENABLE_LCD)
      print_STR(rotate_to_change_STR, 1);
      print_STR(press_to_select_STR, 1);
#endif
      display_Update();
#else
      Serial.print(F("CART TITLE:"));
      Serial.println(ody2game);
#endif
      while (1) {  // Single Step
        uint8_t b = checkButton();
        if (b == 1) {  // Continue (press)
          break;
        }
        if (b == 2) {  // Reset to Start of List (doubleclick)
          byte prevline = strtol(ody2ll, NULL, 10);
          ody2csvpos -= prevline;
          ody2csvFile.seek(ody2csvpos);
          break;
        }
        if (b == 3) {  // Long Press - Select Cart (hold)
          newody2mapper = strtol(ody2mm, NULL, 10);
          newody2size = strtol(ody2rr, NULL, 10);
          EEPROM_writeAnything(7, newody2mapper);
          EEPROM_writeAnything(8, newody2size);
          cartselected = 1;  // SELECTION MADE
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
          println_Msg(F("SELECTION MADE"));
          display_Update();
#else
          Serial.println(F("SELECTION MADE"));
#endif
          break;
        }
      }
      if (cartselected) {
        cartselected = 0;  // Reset Flag
        return true;
      }
    }
  }
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  println_Msg(FS(FSTRING_EMPTY));
  println_Msg(FS(FSTRING_END_OF_FILE));
  display_Update();
#else
  Serial.println(FS(FSTRING_END_OF_FILE));
#endif

  return false;
}

void checkCSV_ODY2() {
  if (getCartListInfo_ODY2()) {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
    display_Clear();
    println_Msg(FS(FSTRING_CART_SELECTED));
    println_Msg(FS(FSTRING_EMPTY));
    println_Msg(ody2game);
    display_Update();
    // Display Settings
    display.setCursor(0, 56);
    print_Msg(F("CODE: M"));
    print_Msg(newody2mapper);
    print_Msg(F("/R"));
    println_Msg(newody2size);
    display_Update();
#else
    Serial.println(FS(FSTRING_EMPTY));
    Serial.println(FS(FSTRING_CART_SELECTED));
    Serial.println(ody2game);
    // Display Settings
    Serial.print(F("CODE: M"));
    Serial.print(newody2mapper);
    Serial.print(F("/R"));
    Serial.println(newody2size);
    Serial.println(FS(FSTRING_EMPTY));
#endif
  } else {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
    display.setCursor(0, 56);
    println_Msg(FS(FSTRING_NO_SELECTION));
    display_Update();
#else
    Serial.println(FS(FSTRING_NO_SELECTION));
#endif
  }
}

void setCart_ODY2() {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
  display_Clear();
  println_Msg(ody2cartCSV);
  display_Update();
#endif
  sd.chdir();
  sprintf(folder, "ODY2/CSV");
  sd.chdir(folder);  // Switch Folder
  ody2csvFile = sd.open(ody2cartCSV, O_READ);
  if (!ody2csvFile) {
#if (defined(ENABLE_OLED) || defined(ENABLE_LCD))
    display_Clear();
    println_Msg(F("CSV FILE NOT FOUND!"));
    display_Update();
#else
    Serial.println(F("CSV FILE NOT FOUND!"));
#endif
    while (1) {
      if (checkButton() != 0)
        setup_ODY2();
    }
  }
  checkCSV_ODY2();

  ody2csvFile.close();
}
#endif
//******************************************
// End of File
//******************************************