#include "global.h"
#include "gba/eeprom.h"

#if defined(DEMO_USA) || defined(DEMO_JP)
const u8 unk[] = { 0xff, 0xff, 0xff, 0xff };
const u8 padding[0x18] = {};
#else
typedef struct EEPROMConfig {
    u32 unk_00;
    u16 size;
    u16 waitcnt;
    u8 address_width;
    // u8 filler[3];
} EEPROMConfig;

const char EEPROM_V124[] = "EEPROM_V124";
extern const EEPROMConfig* gEEPROMConfig;
const EEPROMConfig gEEPROMConfig512 = { 0x200, 0x40, 0x300, 0x6 };
const EEPROMConfig gEEPROMConfig8k = { 0x2000, 0x400, 0x300, 0xe };

u16 EEPROMWrite(u16, const u16*, u8);

u16 EEPROMConfigure(u16 unk_1) {
    u16 ret;

    ret = 0;
    if (unk_1 == 4) {
        gEEPROMConfig = &gEEPROMConfig512;
    } else {
        if (unk_1 == 0x40) {
            gEEPROMConfig = &gEEPROMConfig8k;
        } else {
            gEEPROMConfig = &gEEPROMConfig512;
            ret = 1;
        }
    }
    return ret;
}

static void DMA3Transfer(const void* src, void* dest, u16 count) {
    u32 temp;

    u16 IME_save;

    IME_save = REG_IME;
    REG_IME = 0; // disable all interrupts
    temp = REG_WAITCNT & 0xf8ff;
    temp |= gEEPROMConfig->waitcnt; // configure wait state 2
    REG_WAITCNT = temp;
    REG_DMA3SAD = (u32)src;
    REG_DMA3DAD = (u32)dest;
    REG_DMA3CNT = count | 0x80000000;        // enable dma
    while ((REG_DMA3CNT_H & 0x8000) != 0) {} // wait for dma to finish
    REG_IME = IME_save;
}

/**
 * reads 64 bit (8 byte) from eeprom
 *
 * @param address 6/14 bit depending on eeprom size
 * @param data u16[4]
 * @return errorcode, 0 on success
 */
u16 EEPROMRead(u16 address, u16* data) {
    u16 buffer[0x44];

    u16* ptr;
    u8 t1, t2;
    u16 value;

    if (address >= gEEPROMConfig->size) {
        return EEPROM_OUT_OF_RANGE;
    } else {
        ptr = buffer;
        // setup address
        (u8*)ptr += (gEEPROMConfig->address_width << 1) + 1;
        ((u8*)ptr)++;
        for (t1 = 0; t1 < gEEPROMConfig->address_width; t1++) {
            *(ptr--) = address;
            address >>= 1;
        }
        // read request
        *(ptr--) = 1;
        *ptr = 1;
        // send address to eeprom
        DMA3Transfer(buffer, (u16*)0xd000000, gEEPROMConfig->address_width + 3);
        // recieve data
        DMA3Transfer((u16*)0xd000000, buffer, 0x44);
        // 4 bit junk
        ptr = buffer + 4;
        data += 3;
        // copy data into output buffer
        for (t1 = 0; t1 < 4; t1++) {
            value = 0;
            for (t2 = 0; t2 < 0x10; t2++) {
                value <<= 1;
                value |= (*ptr++) & 1;
            }
            *(data--) = value;
        }
        return 0;
    }
}

u16 EEPROMWrite1(u16 address, const u16* data) {
    return EEPROMWrite(address, data, 1);
}

// reading from EEPROM like a status register
#define REG_EEPROM (*(u16*)0xd000000)
// this is the furthest I could get
// 0x080B16AC
NONMATCH("asm/non_matching/code_080B1520/EEPROMWrite.inc", u16 EEPROMWrite(u16 address, const u16* data, u8 unk_3)) {
    u16 buffer[0x52]; // this is one too large?
    vu16 stack_a4;
    vu16 prev_vcount;      // stack + a6
    vu16 current_vcount;   // stack + a6
    vu32 passed_scanlines; // stack + ac

    u32 r2;

    u8 i, j;
    u16* ptr;

    if (address >= gEEPROMConfig->size)
        return EEPROM_OUT_OF_RANGE;

    ptr = buffer + gEEPROMConfig->address_width + 0x42;
    *ptr = 0;
    ptr--;
    // copy data into buffer
    for (i = 0; i <= 3; i++) {
        r2 = *data;
        data++;
        for (j = 0; j <= 0xf; j++) {
            *ptr = r2;
            ptr--;
            r2 = r2 >> 1;
        }
    }
    // copy address to buffer
    for (i = 0; i < gEEPROMConfig->address_width; i++) {
        *ptr = address;
        ptr--;
        address = address >> 1;
    }
    *ptr = 0;
    ptr--;
    *ptr = 1;
    DMA3Transfer(buffer, (u16*)0xd000000, gEEPROMConfig->address_width + 0x43);
    stack_a4 = 0;
    prev_vcount = REG_VCOUNT;
    passed_scanlines = 0;
    if (stack_a4 == 0) {
        if ((REG_EEPROM & 1) != 0)
            goto bad;
    }
    // before here its only regalloc, but after I cant get it to work
    do {
        do {
            do {
                current_vcount = REG_VCOUNT;
                if (current_vcount != prev_vcount) {
                    if (current_vcount >= prev_vcount) {
                        passed_scanlines += current_vcount - prev_vcount;
                    } else {
                        passed_scanlines += (current_vcount + 0xe4) - prev_vcount;
                    }
                    if (passed_scanlines > 0x88) {
                        if (stack_a4 != 0)
                            return 0;
                        if ((REG_EEPROM & 1) != 0)
                            return 0;
                        return 0xc001;
                    }
                    prev_vcount = current_vcount;
                }
            } while (stack_a4 != 0);
        } while ((REG_EEPROM & 1) != 0);
    bad:
        stack_a4++;
    } while (unk_3 != 0);
    return 0;
}
END_NONMATCH

u16 EEPROMCompare(u16 address, const u16* data) {
    u16 ret;

    u16 buffer[4];
    u16* ptr;

    u8 i;

    ret = 0;
    if (address >= gEEPROMConfig->size) {
        return EEPROM_OUT_OF_RANGE;
    }
    EEPROMRead(address, buffer);
    ptr = buffer;
    for (i = 0; i < ARRAY_COUNT(buffer); i++) {
        if (*data++ != *ptr++) {
            ret = EEPROM_COMPARE_FAILED;
            break;
        }
    }
    return ret;
}

const char EEPROM_NOWAIT[] = "EEPROM_NOWAIT";

u16 EEPROMWrite1_check(u16 address, const u16* data) {
    u8 i;
    u16 ret;

    for (i = 0; i < 3; i++) {
        ret = EEPROMWrite1(address, data);
        if (ret == 0) {
            ret = EEPROMCompare(address, data);
            if (ret == 0)
                break;
        }
    }
    return ret;
}

u16 EEPROMWrite0_8k(u16 address, const u16* data) {
    u16 ret;

    if (gEEPROMConfig->unk_00 != 0x200) {
        ret = EEPROMWrite(address, data, 0);
    } else {
        ret = EEPROM_UNSUPPORTED_TYPE;
    }
    return ret;
}

u16 EEPROMWrite0_8k_Check(u16 address, const u16* data) {
    u8 i;
    u16 ret;

    for (i = 0; i < 3; i++) {
        ret = EEPROMWrite0_8k(address, data);
        if (ret == 0) {
            ret = EEPROMCompare(address, data);
            if (ret == 0) {
                break;
            }
        }
    }
    return ret;
}

const char thing[0x1c] = "\xff\xff\xff\xff";

#endif
