#define PIN_CLK 13
#define PIN_RST A4
#define PIN_RW  A5
#define PIN_CE  A3
#define PIN_BTN PC13
#define CLK_DELAY 100

#define ROM_SIZE  0x7fff

const char ADDR[] = { PC4, PB13, PB14, PB15, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14 };
const char DATA[] = { PB1, PB2, PB12, PA11, PA12, PC5, PC6, PC8 };

uint8_t running = 0;
uint8_t start = 0;

PROGMEM static uint8_t rom[ROM_SIZE];
uint16_t rom_idx = 0;
uint32_t rom_load_start = 0;

enum states {
  IDLE,
  WRITE // write rom to memory
};

enum states state = IDLE;

uint16_t readAddress() {
  uint16_t addr = 0;

  for(uint8_t i = 1; i < 16; i++) {
    addr = (addr << 1) + (digitalRead(ADDR[i]) ? 1 : 0);
  }

  return addr;
}
uint16_t readState() {
  uint8_t bit = 0;
  uint16_t addr = 0;
  uint8_t data = 0;
  uint8_t i = 0;
  // check chip enable, 0 = enabled; 1 = disabled
  uint8_t CE = digitalRead(PIN_CE) ? 0 : 1;

  // debugging lines start with D:
  Serial.print("D: ");

  for(i = 1; i < 16; i++) {
    bit = digitalRead(ADDR[i]) ? 1 : 0;
    addr = (addr << 1) + bit;

    Serial.print(bit); 
  }

  Serial.print(" ");

  for(i = 0; i < 8; i++) {
    pinMode(DATA[i], INPUT);

    bit = digitalRead(DATA[i]) ? 1 : 0;
    data = (data << 1) + bit;

    Serial.print(bit); 

    // set DATA pinMode based on Chip Enable
    pinMode(DATA[i], CE ? OUTPUT : INPUT);
  }

  Serial.printf(
    " %04x %c %02x %s\r\n",
    addr,
    (digitalRead(PIN_RW) ? 'r' : 'W'),
    data,
    CE ? "CE" : "!CE"
  );

  return addr;
}

void resetHigh() {
  Serial.println("D: Setting reset HIGH");

  pinMode(PIN_RST, OUTPUT);
  digitalWrite(PIN_RST, HIGH);
}

void resetLow() {
  Serial.println("D: Setting reset LOW");

  pinMode(PIN_RST, OUTPUT);
  digitalWrite(PIN_RST, LOW);
}

void startClock() {
  running = 1;

  Serial.println("D: starting clock");
}

void stopClock() {
  running = 0;

  Serial.println("D: stoping clock");
}

unsigned int lastUserButton = 0;

void advanceClock() {
  //pinMode(PIN_RST, OUTPUT);
  //digitalWrite(PIN_RST, HIGH);

  digitalWrite(PIN_CLK, !digitalRead(PIN_CLK));
  delay(CLK_DELAY);

  digitalWrite(PIN_CLK, !digitalRead(PIN_CLK));
  delay(CLK_DELAY);
}

void onUserButton() {
  if(millis() - lastUserButton > 100) {
    readState();

    advanceClock();

    lastUserButton = millis();
  }
}

void onChipEnable() {
  uint8_t CE = digitalRead(PIN_CE) ? 0 : 1;

  //Serial.printf("CE: %s\n", CE ? "Disabled" : "Enabled");

  for(uint8_t i = 0; i < 8; i++) {
    pinMode(DATA[i], CE ? OUTPUT : INPUT);
  }

  //setOutByte(rom[readAddress()]);
}

void setOutByte(char b) {
  for(uint8_t i = 0; i < 8; i++) {
    pinMode(DATA[i], OUTPUT);

    if((b >> (7 - i)) & 1)
      digitalWrite(DATA[i], HIGH);
    else
      digitalWrite(DATA[i], LOW);
  }
}

void reset() {
  uint8_t CE = digitalRead(PIN_CE) ? 0 : 1;

  // set data pins according to Chip Enable state
  // 1 = disabled; 0 = enabled
  for(uint8_t i = 0; i < 8; i++) {
    pinMode(DATA[i], CE ? OUTPUT : INPUT);
    digitalWrite(DATA[i], LOW);
  }
}

void clearProgMem() {
  uint8_t i = 0;

  // clear rom
  bzero((void*)&rom, ROM_SIZE);

  Serial.println("D: memory cleared");
}

void onAddressChange() {
  uint16_t addr = readAddress();
  uint8_t byt = rom[addr];
  setOutByte(byt);
  //Serial.printf("%04x: %02x\r\n", addr, byt);
}

void setup() {
  uint8_t i = 0;

  Serial.begin(115200);

  for(i = 0; i < 16; i++) {
    pinMode(ADDR[i], INPUT);
    attachInterrupt(digitalPinToInterrupt(ADDR[i]), onAddressChange, CHANGE);
  }

  reset();

  //setOutByte(0xea);

  pinMode(PIN_CLK, OUTPUT);
  pinMode(PIN_RST, INPUT);
  pinMode(PIN_BTN, INPUT);
  pinMode(PIN_RW, INPUT);
  pinMode(PIN_CE, INPUT);
  attachInterrupt(digitalPinToInterrupt(PIN_BTN), onUserButton, FALLING);
  attachInterrupt(digitalPinToInterrupt(PIN_CE), onChipEnable, CHANGE);

  digitalWrite(PIN_CLK, LOW);
  digitalWrite(PIN_RST, LOW);

  start = millis();

  Serial.println("D: clearing rom");

  clearProgMem();
}

void loop() {
  if(state == WRITE) {
    while(true && rom_idx < 0x7ffe && millis() - rom_load_start < 3000) {
      // freshen the start time
      if(Serial.available() > 0) rom_load_start = millis();

      while(Serial.available() > 0) {
        rom[rom_idx++] = Serial.read();
      }
    }

    state = IDLE;

    Serial.printf("Read 0x%04X bytes into memory\r\n", rom_idx);
    Serial.printf("0x7ffc: 0x%02X 0x%02X\r\n", rom[0x7ffc], rom[0x7ffd]);

    return;
  }

  uint8_t CE = digitalRead(PIN_CE) ? 0 : 1;

  if(running) {
    uint16_t addr = readState();

    if(CE) {
      //setOutByte(rom[addr]);
      //Serial.printf("Setting output %02X\r\n", rom[addr]);
    }

    advanceClock();
  }

  if(Serial.available() > 0) {
    char c = Serial.read();

    //Serial.print(c);

    if(c == '0') resetHigh();
    if(c == '1') resetLow();
    if(c == '3') readState();
    if(c == '4') startClock();
    if(c == '5') stopClock();
    if(c == '6') reset();
    if(c == '7') clearProgMem();
    if(c == '8') {
      for(uint16_t i = 0; i < 0x100; i++) {
        if(i % 16 == 0) Serial.printf("0x%04X: ", i);

        Serial.printf("%02x", rom[i]);

        if(i % 2 == 1) Serial.print(" ");

        if(i % 16 == 15) Serial.println();
      }
    }

    if(c == 'l') {
      // activate load mode and reset counter
      state = WRITE;
      rom_idx = 0;
      rom_load_start = millis();
      bzero((void*)&rom, ROM_SIZE);

      Serial.println("D: ready for ROM");

      return;
    }

    /*
    if(c == '2') {
      // only leave in for debugging
      while(!Serial.available()) {};

      char b = Serial.read();

      setOutByte(b);
    }
    */

    // flush serial queue
    // while(Serial.available() > 0) Serial.read();
  }
}
