#include "rom.h"
#include "mapper.h"
#include <string.h>
#include <cstdio>
#include <cstdlib>
#include <cmath>

using namespace NES;

ROM::ROM() { //if no rom specified, make dummy rom
    unsigned char dummy_prg[0x4010] = {0};
    dummy_prg[0] = 'N';
    dummy_prg[1] = 'E';
    dummy_prg[2] = 'S';
    dummy_prg[3] = 0x1a;
    dummy_prg[4] = 1;
    this->load_arr(0x4010,dummy_prg);
}

ROM::ROM(const char* src) {
    this->load_file(src);

}
ROM::ROM(long length, unsigned char* data) {
    this->load_arr(length,data);

}

void ROM::load_arr(long length, unsigned char* data) {
    int ind = 0;
    for (int i=0; i<16; i++) {
        header[i]=data[ind];
        ind++;
    }
    if (header[0]=='N' && header[1]=='E' && header[2]=='S' && header[3]==0x1a) {
        valid_rom = true;
    } else {
        return;
    }
    battery_backed = header[6]&0x2;
    printf(battery_backed ? "Battery\n" : "No Battery\n");
    if (valid_rom && (header[7]&0x0C)==0x08) {
        nes2 = true;
    }

    bool trainer_present = header[6]&0x04;
    int mapper_num = ((header[6]&0xF0)>>4)|(header[7]&0xF0);
    switch (mapper_num) {
        case 0:
            mapper = new NROM();
            break;
        case 1:
            mapper = new MMC1();
            break;
        case 2:
            mapper = new UxROM();
            break;
        case 3:
            mapper = new CNROM();
            break;
        case 4:
            mapper = new MMC3();
            break;
        case 40:
            mapper = new NTDEC2722();
            break;
        default:
            mapper = new DEFAULT_MAPPER(mapper_num);
            printf("UNRECOGNIZED MAPPER!\n");
            break;
    }
    if (header[6]&0x08) {
        mirrormode = FOURSCREEN;
    } else {
        mirrormode = (header[6]&0x1) ? VERTICAL : HORIZONTAL;
    }
    if (nes2) {
        uint8_t msb = header[9]&0x0F;
        if (msb == 0x0F) { //use exponent notation
            prgsize = pow(2,(header[4]&0xFC)>>2)*((header[4]&0x3)*2+1);
        } else {
            prgsize = (header[4]|(msb)<<8)*0x4000;
        }
        msb = (header[9]&0xF0);
        if (msb == 0xF0) {
            chrsize = pow(2,(header[5]&0xFC)>>2)*((header[5]&0x3)*2+1);
        } else {
            chrsize = (header[5]|(header[9]&0xF0)<<4)*0x2000;
        }
        
    } else {
        printf("iNES\n");
        printf("%i\n",header[5]);
        prgsize = header[4]*0x4000;
        chrsize = header[5]*0x2000;
    }
    prg = (uint8_t *)malloc(prgsize*sizeof(uint8_t));
    chr = (uint8_t *)malloc(chrsize*sizeof(uint8_t));
    if (trainer_present) { // if trainer is present
        for (int i=0; i<512; i++) {
            trainer[i]=data[ind];
            ind++;
        }
    }

    for (int i=0; i<prgsize; i++) {
        prg[i]=data[ind];
        ind++;
    }
    for (int i=0; i<chrsize; i++) {
        chr[i]=data[ind];
        ind++;
    }
}

void ROM::reset_mapper() {
    int mapper_num = mapper->type;
    delete mapper;
    switch (mapper_num) {
        case 0:
            mapper = new NROM();
            break;
        case 1:
            mapper = new MMC1();
            break;
        case 2:
            mapper = new UxROM();
            break;
        case 3:
            mapper = new CNROM();
            break;
        case 4:
            mapper = new MMC3();
            break;
        case 40:
            mapper = new NTDEC2722();
            break;
        default:
            mapper = new DEFAULT_MAPPER(mapper_num);
            printf("UNRECOGNIZED MAPPER!\n");
            break;
    }
}

void ROM::load_file(const char* src) {
    this->src_filename = src;
    filename_length = strlen(src);
    FILE* rp = std::fopen(src_filename,"rb"); // rom pointer
    std::fseek(rp, 0, SEEK_END);
    long filesize = ftell(rp);
    std::fseek(rp, 0, SEEK_SET);
    unsigned char* data = new unsigned char[filesize];
    for (int i=0; i<filesize; i++) {
        data[i] = std::fgetc(rp);
    }
    load_arr(filesize,data);
    delete[] data;
    std::fclose(rp);

    
}

uint8_t* ROM::get_prg_bank(int bank_num) { //gets banks in 1 KB units
    return prg+0x400*(bank_num%(prgsize/0x400));
}

uint8_t* ROM::get_chr_bank(int bank_num) { //gets banks in 1 KB units
    uint8_t* base = chr;
    if (chrsize==0) { // using chr-ram
        //something else will be done
        //printf("CHR-RAM\n");
        chrsize = 0x2000;
        base = chr_ram;
    }
    return base+0x400*(bank_num%(chrsize/0x400));
}

ROM::~ROM() {
    free(prg);
    free(chr);
    delete mapper;
}