#include "machine.h"
#include "device.h"
#include "opcode.h"
#include <stdexcept>
using namespace std;

Machine::Machine() {
    //nastavi registre
    A = B = L = T = S = X = PC = SW = 0;
    F = 0.0;
    //nastavi naprave
    devices[0] = new InputDevice(cin);
    devices[1] = new OutputDevice(cout);
    devices[2] = new OutputDevice(cerr);

    for (int i = 3; i < 256; i++) {
        string filename = to_string(i) + ".dev";
        devices[i] = new FileDevice(filename);
    }
}

void Machine::resetRegisters() {
    setA(0);
    setB(0);
    setL(0);
    setT(0);
    setS(0);
    setX(0);
    setPC(0);
    setSW(0);
    setF(0.0);
}

void Machine::resetMemory() {
    std::fill(std::begin(memory), std::end(memory), 0);
}

int Machine::getReg(int reg)
{
    switch (reg) {
        case 0: return A;
        case 1: return X;
        case 2: return L;
        case 3: return B;
        case 4: return S;
        case 5: return T;
        case 8: return PC;
        case 9: return SW;
        default: return -1;
    }
}

void Machine::setReg(int reg, int val)
{
    switch (reg) {
        case 0: A = val; break;
        case 1: X = val; break;
        case 2: L = val; break;
        case 3: B = val; break;
        case 4: S = val; break;
        case 5: T = val; break;
        case 8: PC = val; break;
        case 9: SW = val; break;
    }
}

unsigned char Machine::readByte(unsigned int address) {
    if (address > MAX_ADDRESS || address <0) {
        throw std::out_of_range("Memory read out of range");
    }
    return memory[address];
}

void Machine::writeByte(unsigned int address, unsigned char val) {
    if (address > MAX_ADDRESS) {
        throw std::out_of_range("Memory write out of range");
    }
    memory[address] = val;
}

unsigned int Machine::getWord(unsigned int address) {
    if (address + 2 > MAX_ADDRESS) {
        throw std::out_of_range("Memory read out of range");
    }
    unsigned int B1 = memory[address + 2];
    unsigned int B2 = memory[address + 1];
    unsigned int B3 = memory[address];
    return B1 | (B2 << 8) | (B3 << 16);
}

void Machine::setWord(unsigned int address, unsigned int val) {
    if (address + 2 > MAX_ADDRESS) {
        throw std::out_of_range("Memory write out of range");
    }
    memory[address + 2] = val & 0xFF;
    memory[address + 1] = (val >> 8) & 0xFF;
    memory[address] = (val >> 16) & 0xFF;
}

Device* Machine::getDevice(int num) {
    if (num < 0 || num > 255) { return nullptr; }
    return devices[num];
}

void Machine::setDevice(int num, Device* device) {
    if (num < 0 || num > 255 ) { return; }
    delete devices[num];
    devices[num] = device;
}


void Machine::notImplemented(int opcode) {
    std::cerr << "Instruction: " << opcode << " is not yet implemented." << endl;
}

void Machine::invalidOpcode(int opcode) {
    std::cerr << "Invalid opcode: " << to_string(opcode) << endl;
}

void Machine::invalidAddressing() {
    std::cerr << "Invalid addressing used." << endl;
}

// Nalozi in vrni en bajt z naslova PC
// in ga poveca za 1
int Machine::fetch() {
    int address = getReg(8);
    setReg(8, address + 1);
    return readByte(address);
}

bool Machine::execF1(int opcode){
    switch (opcode) {
        case Opcode::FIX:
            setA(static_cast<int>(getF()));
            return true;
        case Opcode::FLOAT:
            setF(static_cast<double>(getA()));
            return true;
        default:
            notImplemented(opcode);
            break;
    }
    return false;
}

bool Machine::execF2(int opcode, int operand) {
    int r1 = (operand >> 4) & 0xF;
    int r2 = operand & 0xF;
    int n;
    switch (opcode) {
        case Opcode::ADDR:
            setReg(r2, getReg(r2) + getReg(r1));
            return true;
        case Opcode::CLEAR: //v clear je le 1 reg podan in sicer v r1
            setReg(r1, 0);
            return true;
        case Opcode::DIVR:
            setReg(r2, getReg(r2) / getReg(r1));
            return true;
        case Opcode::MULR:
            setReg(r2, getReg(r2) * getReg(r1));
            return true;
        case Opcode::RMO:
            setReg(r2, getReg(r1));
            return true;
        case Opcode::SHIFTL:
            n = r2;
            setReg(r1, getReg(r1) << n);
            return true;
        case Opcode::SHIFTR:
            n = r2;
            setReg(r1, getReg(r1) >> n);
            return true;
        case Opcode::SUBR:
            setReg(r2, getReg(r2) - getReg(r1));
            return true;
        default:
            notImplemented(opcode);
            break;
    }
    return false;
}

bool Machine::execSICF3F4(int opcode, int ni, int operand) {
    int x_val = 0;
    int operand2 = fetch();
    int UA = 0;
    int UV = 0;
    int temp = 0;
    bool ready = false;
    int offset = 0;
    if (((operand >> 7) & 0x1) == 1) { //preverimo ali je indeksno naslavljanje
        x_val = getX();
    }
    if (ni == 0) { // stari SIC (neposredno in enostavno naslavljanje)
        UA = (((operand & 0x7F) << 8) | operand2) + x_val; //izracun uporabnega naslova: operand brez bita x + drugi del naslova + x_val

        if (UA < 0 || UA + 2 > MAX_ADDRESS) {
            invalidAddressing();
            return false;
        }

        UV = (memory[UA] << 16) | (memory[UA + 1] << 8) | memory[UA + 2]; //izracunamo operand oz. uporabno vrednost
    } else if (((operand >> 4) & 0x1) == 0) { // ce e bit ni prizgan e je F3, sicer F4
            bool b = ((operand >> 6) & 0x1) == 1;  //preverimo bit za bazno naslavljanje
            bool p = ((operand >> 5) & 0x1) == 1;
            offset  = (((operand & 0x0F) << 8) | operand2);
            if (offset & 0x800) { // če je bit 11 = 1 (negativno)
                offset |= 0xFFFFF000; // nastavi vse višje bite na 1 (sign extend)
            }
            offset += x_val;
            if (!b && p) { //PC - relativno
                UA = getPC() + offset;
            } else if (b && !p) {
                UA = getB() + offset;
            } else if (!b && !p) { //neposredno
                UA = (((operand & 0x0F) << 8) | operand2);
            } else { // b && p je nedovoljeno!
                invalidAddressing();
            }

            if (UA < 0 || UA + 2 > MAX_ADDRESS) {
                invalidAddressing();
                return false;
            }

            if (ni == 2) { //posredno
                int UV_temp = (memory[UA] << 16) | (memory[UA + 1] << 8) | memory[UA + 2];
                UV =  (memory[UV_temp] << 16) | (memory[UV_temp + 1] << 8) | memory[UV_temp + 2];
            } else if (ni == 1) { //takojšnje
                UV = UA;
            } else if (ni == 3) { //enostavno
                UV = (memory[UA] << 16) | (memory[UA + 1] << 8) | memory[UA + 2]; //izracunamo operand oz. uporabno vrednost
            }
    } else {
        int operand3 = fetch();
        bool b = ((operand >> 6) & 0x1) == 1;  //preverimo bit za bazno naslavljanje
        bool p = ((operand >> 5) & 0x1) == 1;
        offset  = (((operand & 0x0F) << 16) | (operand2 << 8) | operand3);
        if (offset & 0x80000) { // če je bit 11 = 1 (negativno)
            offset |= 0xFFF00000; // nastavi vse višje bite na 1 (sign extend)
        }
        offset += x_val;
        if (!b && p) { //PC - relativno
            UA = getPC() + offset;
        } else if (b && !p) {
            UA = getB() + offset;
        } else if (!b && !p) { //neposredno
            UA = (((operand & 0x0F) << 16) | (operand2 << 8) | operand3);
        } else { // b && p je nedovoljeno!
            invalidAddressing();
        }

        if (UA < 0 || UA + 2 > MAX_ADDRESS) {
            invalidAddressing();
            return false;
        }

        if (ni == 2) { //posredno
            int UV_temp = (memory[UA] << 16) | (memory[UA + 1] << 8) | memory[UA + 2];
            UV =  (memory[UV_temp] << 16) | (memory[UV_temp + 1] << 8) | memory[UV_temp + 2];
        } else if (ni == 1) { //takojšnje
            UV = UA;
        } else if (ni == 3) { //enostavno
            UV = (memory[UA] << 16) | (memory[UA + 1] << 8) | memory[UA + 2]; //izracunamo operand oz. uporabno vrednost
        }
    }
    //cout << "UA: " << UA << " UV: " << UV << endl;
    switch (opcode) {
        case Opcode::ADD:
            setA(getA() + UV);
            return true;
        case Opcode::AND:
            setA(getA() & UV);
            return true;
        case Opcode::COMP:
            temp = getA();
            if (temp < UV) {
                setSW(0x40);
            } else if (temp == UV) {
                setSW(0x0);
            } else {
                setSW(0x80);
            }
            return true;
            break;
        case Opcode::DIV:
            if (UV == 0) {
                cerr << "Error: Divison by zero." << endl;
                return false;
            }
            setA(getA() / UV);
            return true;
        case Opcode::J:
            setPC(UA);
            return true;
        case Opcode::JEQ:
            if (getSW() == 0x00) {
                setPC(UA);
            }
            return true;
        case Opcode::JGT:
            if (getSW() == 0x80) {
                setPC(UA);
            }
            return true;
        case Opcode::JLT:
            if (getSW() == 0x40) {
                setPC(UA);
            }
            return true;
        case Opcode::JSUB:
            setL(getPC());
            setPC(UA);
            return true;
        case Opcode::LDA:
            setA(UV);
            return true;
        case Opcode::LDB:
            setB(UV);
            return true;
        case Opcode::LDCH:
            setA((getA() & 0xFFFF00) | (UV & 0xFF));
            return true;
        case Opcode::LDL:
            setL(UV);
            return true;
        case Opcode::LDS:
            setS(UV);
            return true;
        case Opcode::LDT:
            setT(UV);
            return true;
        case Opcode::LDX:
            setX(UV);
            return true;
        case Opcode::MUL:
            setA(getA() * UV);
            return true;
        case Opcode::OR:
            setA(getA() | UV);
            return true;
        case Opcode::RD:
            temp = UV & 0xFF;
            if (devices[temp] != nullptr) {
                setA(devices[temp]->read());
            }
            return true;
        case Opcode::RSUB:
            setPC(getL());
            return true;
        case Opcode::STA:
            temp = getA();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::STB:
            temp = getB();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::STS:
            temp = getS();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::STT:
            temp = getA();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::STCH:
            temp = getA();
            memory[UV] = temp & 0xFF;
            return true;
        case Opcode::STL:
            temp = getL();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::STSW:
            temp = getSW();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::STX:
            temp = getX();
            memory[UA] = (temp >> 16) & 0xFF;
            memory[UA + 1] = (temp >> 8) & 0xFF;
            memory[UA + 2] = temp & 0xFF;
            return true;
        case Opcode::SUB:
            setA(getA() - UV);
            return true;
        case Opcode::TD:
            temp = UV & 0xFF;

            ready = false;
            if (devices[temp] != nullptr) {
                ready = devices[temp]->test();
            }
            if (ready) {
                setSW(0X40);
            } else {
                setSW(0x00);
            }

            return true;
        case Opcode::TIX:
            temp = getX();
            setX(temp + 1);
            if (temp < UV) setSW(0x40);
            else if (temp == UV) setSW(0x00);
            else setSW(0x80);
            return true;
        case Opcode::WD:
            temp = UV & 0xFF;
            if (devices[temp] != nullptr) {
                devices[temp]->write(getA() & 0xFF);
            }
            return true;
        default:
            notImplemented(opcode);
            break;
    }
    return false;
}

void Machine::execute() {
    int opcode = fetch();
    int operand;
    switch (opcode) {
        // Format 1
        case Opcode::FIX:
        case Opcode::FLOAT:
        case Opcode::NORM:
        case Opcode::HIO:
        case Opcode::TIO:
        case Opcode::SIO:
            execF1(opcode);
            break;

        // Format 2
        case Opcode::ADDR:
        case Opcode::CLEAR:
        case Opcode::DIVR:
        case Opcode::MULR:
        case Opcode::RMO:
        case Opcode::SHIFTL:
        case Opcode::SHIFTR:
        case Opcode::SUBR:
        case Opcode::SVC:
            operand = fetch();
            execF2(opcode, operand);
            break;

        // Format SIC/3/4 (SIC/XE)
        default:
            operand = fetch();
            int ni = opcode & 0x3;
            opcode = opcode & 0xFC;
            execSICF3F4(opcode, ni, operand);
            break;
    }
}











