package fabrik.simulator.pic16f84;

import java.util.Arrays;

public class DataRegister {
    private static final int INDF = 0x0;
    private static final int PCL = 0x2;
    private static final int STATUS = 0x3;
    private static final int FSR = 0x4;
    private static final int PORTA = 0x5;
    private static final int PORTB = 0x6;
    private static final int PCLATH = 0xA;
    private static final int INTCON = 0xB;

    private static final int EEDATA = 0x08;
    private static final int EEADR = 0x09;
    private static final int EECON1 = 0x88;
    private static final int EECON2 = 0x89;

    private static final int TRISA = 0x85;
    private static final int TRISB = 0x86;

    private static final int C = 0x0;
    private static final int DC = 0x1;
    private static final int Z = 0x2;
    private static final int RP0 = 0x5;


    private static final int [] dataRegister =  new int[0xFF];
    private static final int [] syncedRegisters = {INDF, PCL, STATUS, FSR, PCLATH, INTCON};
    private static final int [] eepromRegisters = {EEDATA, EEADR, EECON1, EECON2};
    public static final int [] ioRegisters = {PORTA, PORTB, TRISA, TRISB};

    public static void initDataRegister() {
        dataRegister[PCL] = 0b0;
        dataRegister[STATUS] = 0b00011000;
        dataRegister[PCLATH] = 0b0;
        dataRegister[INTCON] = 0b0;

        dataRegister[0x81] = 0b11111111;
        dataRegister[0x80 + PCL] = dataRegister[PCL];
        dataRegister[0x80 + STATUS] = dataRegister[STATUS];
        dataRegister[TRISA] = 0b11111111;
        dataRegister[TRISB] = 0b11111111;
        dataRegister[0x80 + PCLATH] = dataRegister[PCLATH];
        dataRegister[0x80 + INTCON] = dataRegister[INTCON];
        System.out.println(Arrays.toString(dataRegister));
    }

    private static boolean isSyncedRegister (int address){
        for (int register : syncedRegisters){
            if (address == register || address == 0x80 + register){
                return true;
            }
        }
        return false;
    }

    public static int[] getDataRegister() {
        return dataRegister;
    }

    private static int determineIndirectAndChange (int address){
        if (address == INDF || address == 0x80 + INDF) {
            return dataRegister[FSR];
        }
        else
            return address;
    }

    public static int getFSR (){
        return dataRegister[FSR];
    }

    private static int bank() {
        return ((dataRegister[STATUS] >> RP0) & 1) * 0x80;
    }

    public static int getRegister(int fileAddress){
        int address = determineIndirectAndChange (fileAddress);
        if (bank () + address == EECON2)
            return 0;
        return dataRegister[bank() + address];
    }

    public static void setRegister(int fileAddress, int content){
        int address = determineIndirectAndChange (fileAddress);
        if (Arrays.stream(ioRegisters).anyMatch(i -> i == address)){
            IOPorts.setRegister(bank() + address, content);
            return;
        }
        if (Arrays.stream(eepromRegisters).anyMatch(i -> i == address)){
            EEPROM.parse(bank() + address, content, 0b11);
            return;
        }
        if (fileAddress == PCL || fileAddress == 0x80 + PCL){
            programCounter = content + (dataRegister[PCLATH] << 8);
        }
        if (!isSyncedRegister(address))
            dataRegister[bank() + address] = content;
        else {
            dataRegister[address] = content;
            dataRegister[0x80 + address] = content;
            if (address == STATUS){
                setFlags();
            }
        }
        if (address == 1)
            PreScaler.resetFromRegister();
    }

    public static int getBit(int fileAddress, int bit) {
        int address = determineIndirectAndChange (fileAddress);
        if (bank() + address == EECON2)
            return 0;
        return (dataRegister[bank() + address] >> bit) & 1;
    }

    public static int getDirectBit(int address, int bit){
        return (dataRegister[address] >> bit) & 1;
    }

    public static void clearBit(int fileAddress, int bit) {
        int address = determineIndirectAndChange (fileAddress);
        if (Arrays.stream(ioRegisters).anyMatch(i -> i == address)){
            IOPorts.clearBit(bank() + address, bit);
            return;
        }
        if (Arrays.stream(eepromRegisters).anyMatch(i -> i == address)){
            EEPROM.parse(bank () + address, bit, 0b00);
            return;
        }
        if (!isSyncedRegister(address)) {
            if (getBit(address, bit) == 1) {
                dataRegister[bank() + address] -= (int) Math.pow(2, bit);
            }
        }
        else {
            if (getBit(address, bit) == 1) {
                dataRegister[address] -= (int) Math.pow(2, bit);
                dataRegister[0x80 + address] = dataRegister[address];
                if (address == STATUS){
                    setFlags();
                }
            }
        }
        if (address == 1)
            PreScaler.resetFromRegister();
    }

    public static void setBit(int fileAddress, int bit) {
        int address = determineIndirectAndChange (fileAddress);
        if (Arrays.stream(ioRegisters).anyMatch(i -> i == address)){
            IOPorts.setBit(bank() + address, bit);
            return;
        }
        if (Arrays.stream(eepromRegisters).anyMatch(i -> i == address)){
            EEPROM.parse(bank () + address, bit, 0b10);
            return;
        }
        if (!isSyncedRegister(address)) {
            if (getBit(address, bit) == 0) {
                dataRegister[bank() + address] += (int) Math.pow(2, bit);
            }
        }
        else {
            if (getBit(address, bit) == 0) {
                dataRegister[address] += (int) Math.pow(2, bit);
                dataRegister[0x80 + address] = dataRegister[address];
                if (address == STATUS){
                    setFlags();
                }
            }
        }
        if (address == 1)
            PreScaler.resetFromRegister();
    }

    public static void setDirectBit (int fileAddress, int bit, int value){
        if (getDirectBit(fileAddress, bit) == 0 && value == 1){
            dataRegister[fileAddress] += (int) Math.pow(2, bit);
        } else if (getDirectBit(fileAddress, bit) == 1 && value == 0){
            dataRegister[fileAddress] -= (int) Math.pow(2, bit);
        }
    }

    private static int programCounter = 0;

    private static void writeToPCL(){
        dataRegister[PCL] = programCounter & 0xFF;
        dataRegister[0x80 + PCL] = dataRegister[PCL];
    }

    public static void increasePC (){
        if (programCounter != 0x3FF) {
            programCounter++;
        }
        else {
            programCounter = 0;
        }
        writeToPCL();
    }

    public static void setPC (int value){
        programCounter = value + ((getRegister(PCLATH) & 0b11000) << 8);
        writeToPCL();
    }

    public static void resetPC () {
        programCounter = 0;
    }

    public static int getPC(){
        return programCounter;
    }

    private static void setFlags() {
        carryFlag = getDirectBit(STATUS, C);
        digitCarryFlag = getDirectBit(STATUS, DC);
        zeroFlag = getDirectBit(STATUS, Z);
    }

    private static int zeroFlag = 0;

    public static void determineZeroFlag(int result){
        if (result == 0){
            zeroFlag = 1;
            if (getBit(STATUS, Z) == 0){
                dataRegister[STATUS] += 0b100;
                dataRegister[0x80 + STATUS] = dataRegister[STATUS];
            }
        }
        else {
            zeroFlag = 0;
            if (getBit(STATUS, Z) == 1){
                dataRegister[STATUS] -= 0b100;
                dataRegister[0x80 + STATUS] = dataRegister[STATUS];
            }
        }
    }

    public static int getZeroFlag(){
        return zeroFlag;
    }

    private static int carryFlag = 0;

    public static void setCarryFlag(int value){
        carryFlag = value;
        if (value == 1){
            if (getBit(STATUS, C) == 0){
                dataRegister[STATUS] += 0b1;
                dataRegister[0x80 + STATUS] = dataRegister[STATUS];
            }
        }
        else {
            if (getBit(STATUS, C) == 1){
                dataRegister[STATUS] -= 0b1;
                dataRegister[0x80 + STATUS] = dataRegister[STATUS];
            }
        }
    }

    public static void determineCarryFlag (int result){
        if (result > 0xFF){
            setCarryFlag(1);
        }
        else {
            setCarryFlag(0);
        }
    }

    public static int getCarryFlag (){
        return carryFlag;
    }

    private static int digitCarryFlag = 0;

    public static void setDigitCarryFlag(int value){
        digitCarryFlag = value;
        if (value == 1){
            if (getBit(STATUS, DC) == 0){
                dataRegister[STATUS] += 0b10;
                dataRegister[0x80 + STATUS] = dataRegister[STATUS];
            }
        }
        else {
            if (getBit(STATUS, DC) == 1){
                dataRegister[STATUS] -= 0b10;
                dataRegister[0x80 + STATUS] = dataRegister[STATUS];
            }
        }
    }

    public static void determineDigitCarryFlag(int result){
        if (result > 0xF){
            setDigitCarryFlag(1);
        }
        else {
            setDigitCarryFlag(0);
        }
    }

    public static int getDigitCarryFlag(){
        return digitCarryFlag;
    }

    public static void setDirectRegister(int fileAddress, int content) {
        dataRegister[fileAddress] = content;
    }


    public static int getPCL() {
        return PCL;
    }
    public static int getSTATUS() {
        return STATUS;
    }

    public static int getPCLATH() {
        return PCLATH;
    }
    public static int getINTCON() {
        return INTCON;
    }

    public static int getDirectRegister(int address) {
        return dataRegister[address];
    }

}


