package fabrik.simulator.pic16f84;

import fabrik.simulator.pic16f84.interfaces.DataRegisterInterface;

import java.util.Arrays;

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

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

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

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


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


    public DataRegister () {
        super();
    }

    @Override
    public void initialize(PICComponentLocator locator) {
        super.initialize(locator);
        System.out.println("DataRegister.\n");
        initOrReset();
    }

    private void initOrReset () {
        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];
        carryFlag = 0;
        zeroFlag = 0;
        digitCarryFlag = 0;
        if (null != ioPorts) ioPorts.resetTRIS();
        System.out.println(Arrays.toString(dataRegister));
    }

    public void initDataRegister() {
        initOrReset();
    }

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

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

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

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

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

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

    public 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 int getBit(int fileAddress, int bit) {
        int address = determineIndirectAndChange (fileAddress);
        if (bank() + address == EECON2)
            return 0;
        return (dataRegister[bank() + address] >> bit) & 1;
    }

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

    public 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 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 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 int programCounter = 0;

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

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

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

    public void resetPC () {
        programCounter = 0;
    }

    public int getPC(){
        return programCounter;
    }

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

    private int zeroFlag = 0;

    public 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 int getZeroFlag(){
        return zeroFlag;
    }

    private int carryFlag = 0;

    public 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 void determineCarryFlag (int result){
        if (result > 0xFF){
            setCarryFlag(1);
        }
        else {
            setCarryFlag(0);
        }
    }

    public int getCarryFlag (){
        return carryFlag;
    }

    private int digitCarryFlag = 0;

    public 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 void determineDigitCarryFlag(int result){
        if (result >> 4  == 1){
            setDigitCarryFlag(1);
        }
        else {
            setDigitCarryFlag(0);
        }
    }

    public int getDigitCarryFlag(){
        return digitCarryFlag;
    }

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


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

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

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


