#include "inc/opcodes.h"
#include "inc/memory.h"

dw pc; // program counter
db ac; // accumulator
db x;  // x register
db y;  // y register
db sp; // stack pointer
db sr; // status register
db ir; // instruction register

am address_mode;

dw address;
db operand;

void fetch_operand()
{
    switch (address_mode)
    {
        case immediate:
            address = pc;
            operand = read_byte(address);
            pc = pc + 1;
            break;
        case zero_page:
            address = read_byte(pc);
            operand = read_byte(address);
            pc = pc + 1;
            break;
        case zero_page_x:
            address = read_byte(pc);
            address = address + x;
            operand = read_byte(address);
            pc = pc + 1;
            break;
        case zero_page_y:
            address = read_byte(pc);
            address = address + y;
            operand = read_byte(address);
            pc = pc + 1;
            break;
        case accumulator:
            address = pc;
            operand = ac;
            break;
        case absolute:
            address = read_word(pc);
            operand = read_byte(address);
            pc = pc + 2;
            break;
        case absolute_x:
            address = read_word(pc);
            address = address + x;
            operand = read_byte(address);
            pc = pc + 2;
            break;
        case absolute_y:
            address = read_word(pc);
            address = address + y;
            operand = read_byte(address);
            pc = pc + 2;
            break;
        case indirect_x:
            address = read_byte(pc);
            address = address + x;
            address = read_word(address);
            operand = read_byte(address);
            pc = pc + 1;
            break;
        case indirect_y:
            address = read_byte(pc);
            address = read_word(address);
            address = address + y;
            operand = read_byte(address);
            pc = pc + 1;
            break;
    }
}

void adjustNZ(db a)
{
    if (a == 0) { Z_SET; } else { Z_UNSET;}
    a = a >> 7;
    if (a == 0) { N_SET; } else { N_UNSET; } 
}

db adder(db a, db b)
{
    db r = a + b;
    
    // todo: adjust carry flag
    // todo: adjust overflow flag
    adjustNZ(r);
    
    return r;
}

db subtractor(db a, db b)
{
    db r = a - b;
    
    // todo: adjust carry flag
    // todo: adjust overflow flag
    adjustNZ(r);
    
    return r;
}

void adc()
{
    // add memory to accumulator with carry
    fetch_operand();
    ac = adder(ac, operand + C_IS_SET);
}

void and()
{
    // and memory with accumulator
    fetch_operand();
    ac = ac & operand;
    adjustNZ(ac);
}

void asl()
{
    // shift left one bit (memory or accumulator)
    fetch_operand();
    operand = operand << 1;
    adjustNZ(operand);
}

void bcc()
{
    // branch on carry clear
    if(!C_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void bcs()
{
    // branch on carry set
    if(C_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void beq()
{
    // branch on result zero
    if(Z_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void bit()
{
    // test bits in memory with accumulator
}

void bmi()
{
    // branch on result minus
    if(N_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void bne()
{
    // branch on result not zero
    if(!Z_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void bpl()
{
    // branch on result plus
    if(!N_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void brk()
{
    // force break
}

void bvc()
{
    // branch on overflow clear
    if(!V_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void bvs()
{
    // branch on overflow set
    if(V_IS_SET)
    {
        address = read_byte(pc);
        address = address + pc;
        pc = address;
    }
}

void clc()
{
    // clear carry flag
    C_UNSET;
}

void cld()
{
    // clear decimal mode
    D_UNSET;
}

void cli()
{
    // clear interrupt disable bit
    I_UNSET;
}

void clv()
{
    // clear overflow flag
    V_UNSET;
}

void cmp()
{
    // compare memory with accumulator
}

void cpx()
{
    // compare memory and index x
}

void cpy()
{
    // compare memory and index y
}

void dec()
{
    // decrement memory by one
    operand = operand - 1;
    write_mem(address, operand);
    adjustNZ(operand);
}

void dex()
{
    // decrement index x by one
    x = x - 1;
    adjustNZ(x);
}

void dey()
{
    // decrement index y by one
    y = y - 1;
    adjustNZ(y);
}

void eor()
{
    // exclusive-or memory with accumulator
}

void inc()
{
    // increment memory by one
    operand = operand + 1;
    write_mem(address, operand); 
}

void inx()
{
    // increment index x by one
    x = x + 1;
    adjustNZ(x);
}

void iny()
{
    // increment index y by one
    y = y + 1;
    adjustNZ(y);
}

void jmp()
{
    // jump to new location (indirect)
    address = read_word(pc);
    address = read_word(address);
    pc = address;
}

void jpa()
{
    // jump to new location (absolute)
    address = read_word(pc);
    pc = address;
}

void jsr()
{
    // jump to new location saving return address
}

void lda()
{
    // load accumulator with memory
    ac = operand;
}

void ldx()
{
    // load index x with memory
    x = operand;
}

void ldy()
{
    // load index y with memory
    y = operand;
}

void lsr()
{
    // shift one bit right (memory or accumulator)
}

void nop()
{
    // no operation
}

void ora()
{
    // or memory with accumulator
}

void pha()
{
    // push accumulator on stack
}

void php()
{
    // push processor status on stack
}

void pla()
{
    // pull accumulator from stack
}

void plp()
{
    // pull processor status from stack
}

void rol()
{
    // rotate on bit left (memory or accumulator)
}

void ror()
{
    // rotate on bit right (memory or accumulator)
}

void rti()
{
    // return from interrupt
}

void rts()
{
    // retrun from subroutine
}

void sbc()
{
    // subtract memory from accumulator with borrow
}

void sec()
{
    // set carry flag
    C_SET;
}

void sed()
{
    // set decimal flag
    D_SET;
}

void sei()
{
    // set interrupt disable status
    I_SET;
}

void sta()
{
    // store accumulator in memory
    write_mem(address, ac);
    adjustNZ(y);
}

void stx()
{
    // store index x in memory
    write_mem(address, x);
    adjustNZ(x);
}

void sty()
{
    // store index y in memory
    write_mem(address, y);
    adjustNZ(y);
}

void tax()
{
    // transfer accumulator to index x
    x = ac;
    adjustNZ(x);
}

void tay()
{
    // transfer accumulator to index y
    y = ac;
    adjustNZ(y);
}

void tsx()
{
    // transfer stack pointer to index x
    x = sp;
    adjustNZ(x);
}

void txa()
{
    // transfer index x to accumulator
    ac = x;
    adjustNZ(x);
}

void txs()
{
    // transfer index x to stack pointer
    sp = x;
    adjustNZ(x);
}

void tya()
{
    // transfer index y to accumulator
    ac = y;
    adjustNZ(y);
}
