#include "mbc3.h"
#include "mem.h"
#include "sm83.h"
#include <stdlib.h>
#include <SDL2/SDL.h>

namespace mbc3
{
    #define ROM_BANK_SIZE 0x4000
    #define RAM_BANK_SIZE 0x2000

    uint8_t *rom;
    uint8_t exram[4 * RAM_BANK_SIZE];

    uint8_t current_rom_bank = 1;
    uint8_t current_ram_bank = 0;
    bool ram_enabled = false;
    bool rtc_mode = false; // false = ROM banking mode, true = RAM banking mode
    uint8_t rtc_reg = 0;
    uint8_t rtc_latch = 1;

    uint8_t RTC[5] {0,0,0,0,0};
    uint8_t RTC_latched[5] {0,0,0,0,0};

    void reset()
    {
        for (int i=0; i<4*RAM_BANK_SIZE; ++i) { exram[i] = 0; }
    }

    void tick()
    {
        RTC[0]++;
        if (RTC[0]==60) {
            RTC[0] = 0;
            RTC[1]++;
            if (RTC[1]==60) {
                RTC[1] = 0;
                RTC[2]++;
                if (RTC[2]==24) {
                    if (RTC[3]==0xff) {
                        RTC[3]=0;
                        if (RTC[4]&0x01) {
                            RTC[4] = RTC[4] & 0xfe;
                            RTC[4] = (RTC[4] & 0x80) ? RTC[4] & 0x7f : RTC[4] | 0x80;
                        } else {
                            RTC[4] = RTC[4] | 0x01;
                        }
                    } else RTC[3]++;
                }
            }
        }
    }

    void latchClock()
    {
        for (int i=0; i<5; ++i) RTC_latched[i] = RTC[i];
    }

    uint8_t readRom(uint16_t address)
    {
        if (address < 0x4000) {
            // ROM Bank 0
            return rom[address];
        } else {
            // Switchable ROM bank
            uint32_t banked_address = (current_rom_bank * ROM_BANK_SIZE) + (address - 0x4000);
            return rom[banked_address];
        }
    }

    void writeRom(uint16_t address, uint8_t value)
    {
        if (address < 0x2000) {
            // Enable/disable RAM
            ram_enabled = (value & 0x0F) == 0x0A;
        } else if (address < 0x4000) {
            // Select ROM bank
            if (value == 0) value = 1; // Bank 0 is not allowed
            current_rom_bank = current_rom_bank = (value & 0x7F);
        } else if (address < 0x6000) {
            // Select RAM bank or upper bits of ROM bank
            if (value<=0x03) {
                rtc_mode = false;
                current_ram_bank = value & 0x03; // 2 bits for RAM bank
            } else if ( (value >= 0x8) && (value <= 0x0c) ) {
                rtc_mode = true;
                rtc_reg = value-8;
            }
        } else {
            // Select banking mode
            if ( (rtc_latch == 0) && (value == 1) ) {
                latchClock();
            }
            rtc_latch = value;
        }
    }

    uint8_t readRam(uint16_t address)
    {
        if (ram_enabled) {
            if (rtc_mode) {
                return RTC_latched[rtc_reg];
            } else {
                uint32_t banked_address = (current_ram_bank * RAM_BANK_SIZE) + (address - 0xa000);
                return exram[banked_address];
            }
        }
        return 0xff; // Return open bus value when RAM is disabled
    }

    void writeRam(uint16_t address, uint8_t value)
    {
        if (ram_enabled) {
            if (rtc_mode) {
                RTC[rtc_reg] = RTC_latched[rtc_reg] = value;
            } else {
                uint32_t banked_address = (current_ram_bank * RAM_BANK_SIZE) + (address - 0xa000);
                exram[banked_address] = value;
            }
        }
    }

    void init(uint8_t *rom, uint32_t rom_size, uint32_t ram_size)
    {
        mbc3::rom = rom;
        reset();
    }
}