/*
ROM dump was made by Conventional Memories 2024-02-01 13:51
Disassembled by JDat on 2024-02-01 19:12
Disassmbeled with custom version of 8039dasm.c tweaked for 8741A
Diassembler downloaded from https://github.com/daveho/asm48
8039dasm.c roots come from MAME project (see asm48 github repo for more information)
*/

/*
Known commands in CCPROM function cpCatchAll
catchWriteDelayRepeat:
    // set theData in RAM[0x21] and RAM[0x22]
    // used by long routine
    // keyboard repeat rate and repeat delay?
    kbcSend(0x81, commandPort);     // 0b1000 0001
    kbcSend(theData, dataPort);
    kbcSend(0x82, commandPort);     // 0b1000 0010
    kbcSend(theData, dataPort);
    break;
catchRepeat:
    // repeat last key or last 3 keys?
    // interract with long routine
    kbcSend(0x08, commandPort);     // 0b0000 1000
    break;
catchKeyboard:
    // enable keyboard scan
    kbcSend(0x01, commandPort);     // 0b0000 0001
    break;
catchWatchdog:
    // disable? watchdog?
    kbcSend(0x02, commandPort);     // 0b0000 0010
    break;
catchDma:
    // bit7 must be clear.
    // will set pinPAL
    // other bits change global state
    kbcSend(0x10, commandPort);     // 0b0001 0000
    break;
catchResetWatchDog:
    kbcSend(0xC0, commandPort);     // 0b1100 0000
    break;
catchSetWatchDog:
    // interract with watchog counter
    // set theData in RAM[0x23]
    kbcSend(0x83, commandPort);     // 0b1000 0011
    kbcSend(theData, dataPort);
*/
/*
CPU to KBC protocol:
Address A0 clear - data
Address A0 set - command byte
Commands:
bit 7 clear: change master states (See RAM[0x1F] aga R7 bank 1)
* bit 0 - disable/enable keyboard scanning
* bit 1 - WDT1 related
* bit 2 - WDT0 related
* bit 3 - related to "long routine". repeat last key? Uset in CpCatchRepeat CCPROM
* bit 4 - state for pinPAL (DMA)
* bit 5 - not used
* bit 6 - not used if bit 7 - clear

bit 7 set, bit 6 clear: data will follow on data port
* bits 0-2 (0,1,2,3) - write into one of config parameter registers (located in RAM 0x20-0x23)
* 0 - related to WDT0, Not used in CCPROM for 1101.
* 1 - key repeat rate/delay between key press
* 2 - key repeat rate/delay between key press
* 3 - related to WDT1. Used in CCPROM code to set WDT1 timeout

Other data: simply restart WDT1 to prevent WDT1 from activating.

KBC to CPU protocol:
* Will rise interrupt when KBC want send data to CPU.
* unknown parameters for bits 4-7 in bus status register
* 0xFF - WDT0 timeout occured
* (Following statmet is wrong!) 0xFE - mentioned in CCPROM. Not found in KBC code
* 0xFD - WDT1 timeout occured
* other - key scan code. status bits 5-7 probably provide modifier keys state
* can send remainig data from key buffer if they exist. Check/send on timer IRQ.
Mechanism not know yet.
*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdbool.h>

#include "keyMatrix.h"
#include "hwInterface.h"

// nice routines from friend
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitToggle(value, bit) ((value) ^= (1UL << (bit)))
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))

// used in long routine
// need to research and rename/remove
#define bit0 0
#define bit1 1
#define bit2 2
#define bit3 3
#define bit4 4
// for extIRQ
#define bit6 6
#define bit7 7

#define RAM0x27 0
#define RAM0x28 1

#define RAM0x29 2
#define RAM0x2A 3
uint8_t unknown[4]; // RAM[0x27-0x2A]

// config flags from CPU used only in extIRQ
#define bitCpuConfig 6
#define bitGlobalSet 7

// global state flags
#define enableKeyScan 0         // Keyboard scan enabled
#define enableDMA 1             // WDT1 enable
#define enableWDT 2             // WDT0 enable
#define enableLongRoutine 3     // used in "long routine"
#define enablePinPAL 4          // pinPAL status controlled by CPU
//#define stateBit5 5             // Not used
//#define stateBit5 6             // Not used
#define keyOverFlowFlag 7       // set in timerIRQ when key read counter overflows

uint8_t globalState = 0;

#define TIMERCONST 0xAD
#define MAINLOOPCONST 2
bool timerOverFlowFlag;

uint8_t dmaCnt;

// default parameters after boot
// can be altered by CPU
#define DEFAULTWDT              1
#define DEFAULTKEYREPEATRATE    15
#define DEFAULTKEYREPEATDELAY   2
#define DEFAULTDMA              50

// defines to substitue parameter locations
#define wdtData 0
#define keyReapeatRate 1
#define keyPressDelay 2
#define dmaData 3

uint8_t cpuConfig[4] = {DEFAULTWDT,
                        DEFAULTKEYREPEATRATE,
                        DEFAULTKEYREPEATDELAY,
                        DEFAULTDMA
                        };   // RAM[0x20-0x23]

#define flagShiftKey 5
#define flagCodeKey 6
#define flagCtrlKey 7

#define keyCountMax     3
uint8_t keyMatrixBuffer[keyCountMax]; // RAM[0x24-0x26]
uint8_t keyCount;           // RAM[0x37]

#define parseBufMAX 4

struct _keyParseEntry {
    uint8_t keyCode;
    uint8_t flags;      // related to ST4-ST7 flags
    uint8_t cnt;        // some kind of counter?
};

void resetDMAWDT();
void irqTimer();
void kbcLoop();
void kbcSetup();
void irqExt();
void scanKeyMatrix();
void scanModKeys();
void sendKey();
uint8_t translateScanCode(uint8_t data, uint8_t flags);
void sendData(uint8_t data, uint8_t flags);

int main(int argc, char **argv) {

    kbcSetup();
    while (true) {
        irqTimer();
        irqExt();
        kbcLoop();
    }

    return 0;
}

void resetDMAWDT() {
    dmaCnt = cpuConfig[dmaData];
}

void irqTimer() {
    static uint8_t keyReadCnt = MAINLOOPCONST;

    timerStop();
    if ( busReadOBF() == 0) {       // really == 0, maybe != 0, check code again!
        pinSetIRQ4(LOW);
        // some unknown magic related to data sending
        if ( unknown[RAM0x28] ) {
            busWriteData(unknown[RAM0x27]);
            busWriteStatus(unknown[RAM0x28]);
            unknown[RAM0x28] = 0;
            pinSetIRQ4(HIGH);
        } else {
            busWriteStatus(unknown[RAM0x29]);
        }
    }

    timerSet(TIMERCONST);
    timerStart();

    if ( bitRead(globalState, enableWDT) ) {
        static uint8_t WDT = DEFAULTWDT;    // R6b1; must be WDT = RAM[0x20] = 1;
        WDT--;
        // really != 0, maybe == 0, check code again!
        if (WDT != 0) {             // WDT overflow, notify CPU
            WDT = cpuConfig[wdtData];
            sendData(0xFF, unknown[RAM0x29]);
        }
    }

    keyReadCnt--;
    if (keyReadCnt == 0) {
        keyReadCnt = MAINLOOPCONST;
        bitSet(globalState, keyOverFlowFlag);
    }

}

void kbcLoop() {
    // Why it fucks with interrupt?
    // Why not use simple NOPs?
    enableExternalInterrupt();
    disableExternalInterrupt();

    // check for flag, set in timer interrupt
    if ( bitRead(globalState, keyOverFlowFlag) == 0 ) return;

    bitClear(globalState, keyOverFlowFlag); // we will handle this, so clear!
    enableExternalInterrupt();      // stop fucking with ext interrupt and accept data from CPU

    // this is strange, dmaCnt not reinitialised
    // can wrap around
    // dmaCnt reloaded with resetDMAWDT() in other places
    // CPU will try to react to DMA timeout with resetting DMA
    if ( bitRead(globalState, enableDMA) ) {
        dmaCnt--;
        if ( dmaCnt == 0 ) {    // DMA timeout
            pinSetPAL(LOW);     // disable DMA
            sendData(0xFD, 0);  // notify CPU
        }
    }

    // double check asm code. maybe we need to execute sendKey() without scaning keys?
    if ( bitRead(globalState, enableKeyScan) ) {
        scanModKeys();
        scanKeyMatrix();
        if ( keyCount < keyCountMax ) sendKey();      // 3 substituted to constant
    }
}

void kbcSetup() {
    // lot of variables initialises when defined,
    // so setup looks really simple now
    pinSetWTF(LOW);
    pinSetNMI(LOW);
    pinSetIRQ4(LOW);

    resetDMAWDT();

    timerSet(TIMERCONST);
    pinSetWTF(HIGH);
    timerStart();
    enableExternalInterrupt();
    timerEnableIRQ();
    // do keyboard loop when return from here
}

void irqExt() {
    static uint8_t configRegPtr = 0;    // RAM[0x38]; assing 0 just because to make gcc and lint happy
    uint8_t busData;

    busData = busReadData();

    if ( busReadF1() == 0) {        // check for data mode
        cpuConfig[configRegPtr] = busData;
        resetDMAWDT();
        return;	// Return from Interrupt
    }

    // command mode
    if ( busData == 0) {
        disableExternalInterrupt();
        return;     // Return from Interrupt
    }

    if ( bitRead(busData, bit7) == 0) {
        // set global state from CPU data
        globalState = busData;
        // set pinPAL state from CPU data
        uint8_t tmp;
        tmp = bitRead(busData, enablePinPAL) ? HIGH : LOW;
        pinSetPAL(tmp);
        resetDMAWDT();
        return;     // Return from Interrupt
    }
    if ( bitRead(busData, bit6) == 0) {
        // use only 4 cells 0 - 3
        configRegPtr = busData & 0b00000011;
    }
    resetDMAWDT();
    // Return from Interrupt
}

void scanKeyMatrix() {
    keyCount = 0;
    for (uint8_t column = 8; column > 0; column--) {
        uint8_t rowPins;

        pinSetColumn(column);       // set column in 3-to-8 decoder

        rowPins = ~portRead();      // invert because pull-up logic in hardware
        if ( rowPins == 0 ) continue;   // no key pressed in this column, skip to next column

        for (uint8_t row = 8; row > 0; row--) {
            if ( bitRead(rowPins, 8 - row) ) {  // note: reverse relative to row
                if ( keyCount >= keyCountMax ) return;
                // columns to bits 5,4,3; rows to bits 2,1,0
                keyMatrixBuffer[keyCount] = ( (column << 3) & 0b00111000 ) | (row & 0b00000111);
                keyCount++;
            }
        }
    }
}

void scanModKeys() {
    uint8_t modKeyFlags = 0;

    if ( pinT1() == 0) bitSet(modKeyFlags, flagCodeKey);
    if ( pinT0() == 0) bitSet(modKeyFlags, flagShiftKey);

    pinSetColumn(0b010);    // set column for Ctrl key
    if ( bitRead(portRead(), bit0) == 0) { // note: "== 0" because pull-up logic
        bitSet(modKeyFlags, flagCtrlKey);
    }

    // some unknown magic related to key modifier flags
    if ( unknown[RAM0x2A] == modKeyFlags ) {
        unknown[RAM0x29] = modKeyFlags;
    } else {
        unknown[RAM0x2A] = modKeyFlags;
    }
}

uint8_t translateScanCode(uint8_t data, uint8_t flags) {
    // fixme: can reduce variable count for optimisation
    uint8_t keyModFlags;
    uint8_t keyCode;
    uint8_t keyMatrixVal;
    uint8_t lookupPtr;

    keyMatrixVal = (data << 2) & 0xFC;      // key from matrix to bits 7,6,5,4,3,2
    keyModFlags = (flags >> 5) & 0x03;      // mod keys to bits 1,0
    lookupPtr = keyModFlags | keyMatrixVal; // prepeare translation table pointer
    keyCode = keyMatrix[lookupPtr];

    // Ctrl key pressed; mask bits 6,5 (0x9F) in keyCode
    if ( bitRead(flags, flagCtrlKey) ) keyCode = keyCode & 0b10011111;

    return keyCode;
}

void sendData(uint8_t data, uint8_t flags) {

    if ( data == 0xFE ) {
        pinSetNMI(HIGH);
        return;     // Return from subroutine.
    }

    if ( busReadOBF() == 0) {   // CPU output register empty
        pinSetIRQ4(LOW);
        busWriteData(data);
        busWriteStatus(flags);
        pinSetIRQ4(HIGH);
        return;     // Return from subroutine.
    }

    // CPU output register full
    // do some unknown magic
    if ( unknown[RAM0x28] != 0) {
        unknown[RAM0x28] = data;
        unknown[RAM0x27] = 0xFE;
    } else {
        unknown[RAM0x27] = data;
        unknown[RAM0x28] = flags;
    }
}

void sendKey() {
    // bit7 - Ctrl key pressed
    // bit6 - Code key pressed
    // bit5 - Shift key pressed
    // bit4 - if false: process this record
    // bit3 - can be affected by CPU
    // bit2 - ping-pong related to keyPressDelay and keyRepeatDelay
    // bit1 - ping-pong related to keyPressDelay and keyRepeatDelay
    // bit0 - NMI release related
    static struct _keyParseEntry parseBuffer[parseBufMAX];

    // clear bit4 flag
    for (uint8_t i = 0; i < parseBufMAX; i++) {
        bitClear(parseBuffer[i].flags, bit4);
    }

    // check parseBuffer and send key to CPU
    if ( keyCount != 0) {
        for (uint8_t j = 0; j < keyCount; j++) {
            for (uint8_t i = 0; i < parseBufMAX; i++) {
                if ( parseBuffer[i].flags != 0 ) {
                    // double check for comparition to equal corectness
                    if ( parseBuffer[i].keyCode == keyMatrixBuffer[j] ) {
                        if ( bitRead(parseBuffer[i].flags, bit3) != 0) {
                            parseBuffer[i].cnt--;
                            if ( parseBuffer[i].cnt == 0 ) {
                                uint8_t tmp8;
                                parseBuffer[i].flags &= 0xE0;
                                tmp8 = translateScanCode(keyMatrixBuffer[j], unknown[RAM0x29]);
                                sendData(tmp8, parseBuffer[i].flags);
                                //printf("Send_bit3: %02X\tfl: %02X\n", tmp8, parseBuffer[i].flags);
                                parseBuffer[i].cnt = cpuConfig[keyReapeatRate];
                                bitSet(parseBuffer[i].flags, bit2);
                            }
                            bitSet(parseBuffer[i].flags, bit4);
                            keyMatrixBuffer[j] = 0xFF;
                            break;
                        }

                        if ( bitRead(parseBuffer[i].flags, bit2) != 0) {
                            parseBuffer[i].cnt--;
                            if ( parseBuffer[i].cnt == 0 ) {
                                if ( bitRead(globalState, enableLongRoutine) == 0) {
                                    parseBuffer[i].cnt = cpuConfig[keyReapeatRate];
                                    bitSet(parseBuffer[i].flags, bit4);
                                    keyMatrixBuffer[j] = 0xFF;
                                    break;
                                }

                                parseBuffer[i].flags &= 0xF0;
                                bitSet(parseBuffer[i].flags, bit1);
                                parseBuffer[i].cnt = cpuConfig[keyPressDelay];
                            }
                            bitSet(parseBuffer[i].flags, bit4);
                            keyMatrixBuffer[j] = 0xFF;
                            break;
                        }

                        if ( bitRead(parseBuffer[i].flags, bit1) != 0) {
                            parseBuffer[i].cnt--;
                            if ( parseBuffer[i].cnt == 0 ) {
                                // doble check flag comparision for corectness
                                if ( (parseBuffer[i].flags & 0xE0) == (unknown[RAM0x29] & 0xE0) ) {
                                    uint8_t R5b0, tmp8;
                                    R5b0 = parseBuffer[i].flags & 0xE0;
                                    bitSet(R5b0, bit4);
                                    tmp8 = translateScanCode(keyMatrixBuffer[j], unknown[RAM0x29]);
                                    sendData(tmp8, R5b0);
                                    //printf("Send_bit1: %02X\tfl: %02X\n", tmp8, R5b0);
                                    parseBuffer[i].cnt = cpuConfig[keyPressDelay];
                                    bitSet(parseBuffer[i].flags, bit4);
                                    keyMatrixBuffer[j] = 0xFF;
                                    break;
                                }
                                parseBuffer[i].cnt = cpuConfig[keyReapeatRate];
                                parseBuffer[i].flags = unknown[RAM0x29] & 0xE0;
                                bitSet(parseBuffer[i].flags, bit2);
                            }
                            bitSet(parseBuffer[i].flags, bit4);
                            keyMatrixBuffer[j] = 0xFF;
                            break;
                        }

                        parseBuffer[i].cnt = 1;     // Why cnt  = 1 ?
                        parseBuffer[i].flags = parseBuffer[i].flags & 0xE0;
                        bitSet(parseBuffer[i].flags, bit4);
                        keyMatrixBuffer[j] = 0xFF;
                        break;
                    }
                }
                // break to here
            }
        }
    }

    // NMI related
    for (uint8_t i = 0; i < parseBufMAX; i++) {
        if ( ( parseBuffer[i].flags != 0 ) && ( bitRead(parseBuffer[i].flags, bit4) == 0) ) {
            if ( bitRead(parseBuffer[i].flags, bit0) != 0) {
                parseBuffer[i].cnt--;
                if ( parseBuffer[i].cnt == 0 ) {
                    parseBuffer[i].flags = 0;  // reset all flags?
                    pinSetNMI(LOW);
                }
            } else {
                parseBuffer[i].flags = parseBuffer[i].flags & 0xF0;
                bitSet(parseBuffer[i].flags, bit0);
                parseBuffer[i].cnt = 2;         // Why 2 instead of cpuConfig data?
            }
        }
    }

    // fill parseBuffer from keyMatrixBuffer ???
    if ( keyCount != 0 ) {
        for (uint8_t j = 0; j < keyCount; j++) {
            if ( ( keyMatrixBuffer[j] != 0xFF ) && ( keyMatrixBuffer[j] != 0x10 ) ) {
                uint8_t i = 0;
                while (parseBuffer[i].flags != 0) {
                    i++;
                }
                parseBuffer[i].keyCode = keyMatrixBuffer[j];
                parseBuffer[i].flags = unknown[RAM0x29] & 0xE0;
                bitSet(parseBuffer[i].flags, bit3);
                parseBuffer[i].cnt = 1;

                for (uint8_t k = 0; k < parseBufMAX; k++) {
                    if ( (parseBuffer[k].flags == 0) || (bitRead(parseBuffer[k].flags, bit3) != 0) || (bitRead(parseBuffer[k].flags, bit0) != 0) ){
                        // break or continue???
                        continue;
                        //break;
                    }
                    parseBuffer[k].flags = parseBuffer[k].flags & 0xF0;
                    bitSet(parseBuffer[k].flags, bit2);
                    parseBuffer[k].cnt = cpuConfig[keyReapeatRate];
                    // continue point???
                }
            }
            // break point???
        }
    }
    // Return from subroutine
}
