
#include <timer.h>
#include <interrupts.h>
#include <audio.h>

static timer_context ctx = {0};

bool selected_bit() {
    switch(ctx.tac & 0b11) {
        case 0b00:
            return (ctx.div & (1 << 9));
            break;

        case 0b01:
            return (ctx.div & (1 << 3));
            break;

        case 0b10:
            return (ctx.div & (1 << 5));
            break;

        case 0b11:
            return (ctx.div & (1 << 7));
            break;
    }
}

void inc_tima() {
    ctx.tima++;
    if(ctx.tima == 0x00){
        ctx.tima_needs_reset = true;
    }
}

void timer_init() {
    ctx.div = 0XAC00;
    ctx.tac = 0xF8;
}

void timer_tick() {
  	u16 prev_div = ctx.div;
    bool old = selected_bit();
    ctx.div++;
    bool new = selected_bit();

    if((prev_div & (1 << 1)) && (!(ctx.div & (1 << 1)))){
        if(ctx.tima_just_reset)
            ctx.tima_just_reset = false;
        if(ctx.tima_needs_reset){
            ctx.tima = ctx.tma;
            ctx.tima_needs_reset = false;
            ctx.tima_just_reset = true;
            cpu_request_interrupt(IT_TIMER);
        }
    }

    if(ctx.tac & 0b100 && old && !new) {
        inc_tima();
    }

    audio_sample_tick();

    if((prev_div & (1 << 12)) && (!(ctx.div & (1 << 12)))){
        audio_tick();
    }

    if((prev_div & (1 << 0)) && (!(ctx.div & (1 << 0)))){
        audio_period_tick();
    }
}

void timer_write(u16 address, u8 value){
    switch(address) {
        case 0xFF04:
            //DIV
            if(selected_bit()) {
                inc_tima();
            }
            ctx.div = 0;
            break;

        case 0xFF05:
            //TIMA
            if(ctx.tima_just_reset)
                return;
            ctx.tima_needs_reset = false;
            ctx.tima = value;
            break;

        case 0xFF06:
            //TMA
            if(ctx.tima_just_reset)
                ctx.tima = value;
            ctx.tma = value;
            break;

        case 0xFF07:
            //TAC
            bool current = selected_bit();
            bool old_enable = ctx.tac & 0b100;
            ctx.tac = value;
            bool new = selected_bit();
            if(((current && !new) && (ctx.tac & 0b100)) || (current && old_enable && !(ctx.tac & 0b100))) {
                inc_tima();
            }
            break;
    }
}

u8 timer_read(u16 address) {
        switch(address) {
        case 0xFF04:
            //DIV
            return ctx.div >> 8;

        case 0xFF05:
            //TIMA
            return ctx.tima;

        case 0xFF06:
            //TMA
            return ctx.tma;

        case 0xFF07:
            //TAC
            return ctx.tac;
    }
}

timer_context *timer_get_context() {
    return &ctx;
}

void timer_save_state(timer_state* state) {
    state->ctx = ctx;
}

void timer_load_state(const timer_state* state) {
    ctx = state->ctx;
}