#include "global.h"
#include "asm.h"
#include "common.h"
#include "structures.h"
#include "screen.h"
#include "main.h"
#include "area.h"
#include "room.h"
#include "fileselect.h"
#include "game.h"
#include "flags.h"
#include "kinstone.h"
#include "functions.h"
#include "message.h"

typedef struct {
    u8 _0;
    u8 _1;
    u8 _2;
    u8 _3;
} DungeonLayout;

extern u8 gUnk_03003DE0;
extern u8 gzHeap[0x1000];
extern u8 gUnk_0201AEE0[0x2000];
extern u8 gUnk_080CA11C[];

extern void (*gUnk_080C9CAC[])(void);

static void StoreKeyInput(Input* input, u32 keyInput);
void ClearOAM(void);
void ResetScreenRegs(void);
void sub_0801E0E0(u32);
void sub_0801E24C(u32, u32);
void sub_0801E290(u32, u32, u32);
s32 sub_0801E8B0(u32);

extern u32 sub_0807CB24(u32, u32);

typedef struct {
    u16 paletteId;
    u8 destPaletteNum;
    u8 numPalettes;
} PaletteGroup;

typedef struct {
    union {
        int raw;
        struct {
            u8 filler0[0x3];
            u8 unk3;
        } bytes;
    } unk0;
    u32 dest;
    u32 unk8;
} GfxItem;

typedef struct {
    u8 filler[0xA00];
} struct_02017AA0;
extern struct_02017AA0 gUnk_02017AA0[];
extern u8 gUnk_03003DE4;

typedef struct {
    u8 evt_type;
    u8 entity_idx;
    u8 _2[6];
    u16 x;
    u16 y;
    u8 _c[4];
    u8 _10;
    u8 _11;
    u16 flag;
} struct_080FE320;
extern struct_080FE320 gUnk_080FE320[];

extern const PaletteGroup* gPaletteGroups[];
extern const u8 gGlobalGfxAndPalettes[];
extern u32 gUsedPalettes;
extern u16 gPaletteBuffer[];
extern const GfxItem* gGfxGroups[];

u32 DecToHex(u32 value) {
    u32 result;
    register u32 r1 asm("r1");

    if (value >= 100000000) {
        return 0x99999999;
    }

    result = Div(value, 10000000) * 0x10000000;
    result += Div(r1, 1000000) * 0x1000000;
    result += Div(r1, 100000) * 0x100000;
    result += Div(r1, 10000) * 0x10000;
    result += Div(r1, 1000) * 0x1000;
    result += Div(r1, 100) * 0x100;
    result += Div(r1, 10) * 0x10;
    return result + r1;
}

u32 ReadBit(void* src, u32 bit) {
    return (*((u8*)src + bit / 8) >> (bit % 8)) & 1;
}

NONMATCH("asm/non_matching/common/WriteBit.inc", u32 WriteBit(void* src, u32 bit)) {
    u8* b;
    u32 mask;
    u32 orig;

    b = (u8*)(bit / 8 + (u32)src);
    mask = 1 << (bit % 8);
    orig = *b;
    *b |= mask;
    orig &= mask;
    return orig;
}
END_NONMATCH

NONMATCH("asm/non_matching/common/ClearBit.inc", u32 ClearBit(void* src, u32 bit)) {
    u8* b;
    u32 mask;
    u32 orig;

    b = (u8*)(bit / 8 + (u32)src);
    mask = 1 << (bit % 8);
    orig = *b;
    *b &= ~mask;
    orig &= mask;
    return orig;
}
END_NONMATCH

void MemFill16(u32 value, void* dest, u32 size) {
    DmaFill16(3, value, dest, size);
}

void MemFill32(u32 value, void* dest, u32 size) {
    DmaFill32(3, value, dest, size);
}

void MemClear(void* dest, u32 size) {
    u32 zero = 0;

    // alignment check
    switch (((uintptr_t)dest | size) % 4) {
        case 0:
            MemFill32(0, dest, size);
            break;
        case 2:
            MemFill16(0, dest, size);
            break;
        default:
            do {
                *(u8*)dest = zero;
                dest++;
                size--;
            } while (size != 0);
    }
}

void MemCopy(const void* src, void* dest, u32 size) {
    switch (((uintptr_t)src | (uintptr_t)dest | size) % 4) {
        case 0:
            DmaCopy32(3, src, dest, size);
            break;
        case 2:
            DmaCopy16(3, src, dest, size);
            break;
        default:
            do {
                *(u8*)dest = *(u8*)src;
                src++;
                dest++;
            } while (--size);
    }
}

void ReadKeyInput(void) {
    u32 keyInput = ~REG_KEYINPUT & KEYS_MASK;
    StoreKeyInput(&gInput, keyInput);
}

static void StoreKeyInput(Input* input, u32 keyInput) {
    u32 heldKeys = input->heldKeys;
    u32 difference = keyInput & ~heldKeys;
    input->newKeys = difference;
    if (keyInput == heldKeys) {
        if (--input->unk7 == 0) {
            input->unk7 = 4;
            input->unk4 = keyInput;
        } else {
            input->unk4 = 0;
        }
    } else {
        input->unk7 = 20;
        input->unk4 = difference;
    }
    input->heldKeys = keyInput;
}

void LoadPaletteGroup(u32 group) {
    const PaletteGroup* paletteGroup = gPaletteGroups[group];
    while (1) {
        u32 destPaletteNum = paletteGroup->destPaletteNum;
        u32 numPalettes = paletteGroup->numPalettes & 0xF;
        if (numPalettes == 0) {
            numPalettes = 16;
        }
        LoadPalettes(&gGlobalGfxAndPalettes[paletteGroup->paletteId * 32], destPaletteNum, numPalettes);
        if ((paletteGroup->numPalettes & 0x80) == 0) {
            break;
        }
        paletteGroup++;
    }
}

void LoadPalettes(const u8* src, s32 destPaletteNum, s32 numPalettes) {
    u16* dest;
    u32 size = numPalettes * 32;
    u32 usedPalettesMask = 1 << destPaletteNum;
    while (--numPalettes > 0) {
        usedPalettesMask |= (usedPalettesMask << 1);
    }
    gUsedPalettes |= usedPalettesMask;
    dest = &gPaletteBuffer[destPaletteNum * 16];
    DmaCopy32(3, src, dest, size);
}

void SetColor(u32 colorIndex, u32 color) {
    gPaletteBuffer[colorIndex] = color;
    gUsedPalettes |= 1 << (colorIndex / 16);
}

void SetFillColor(u32 color, u32 disable_layers) {
    if (disable_layers) {
        gScreen.lcd.displayControlMask = ~(DISPCNT_OBJ_ON | DISPCNT_BG_ALL_ON);
    } else {
        gScreen.lcd.displayControlMask = ~0;
    }
    SetColor(0, color);
}

void LoadGfxGroup(u32 group) {
    u32 terminator;
    u32 dmaCtrl;
    int gfxOffset;
    const u8* src;
    u32 dest;
    int size;
    const GfxItem* gfxItem = gGfxGroups[group];
    while (1) {
        u32 loadGfx = FALSE;
        u32 ctrl = gfxItem->unk0.bytes.unk3;
        ctrl &= 0xF;
        switch (ctrl) {
            case 0x7:
                loadGfx = TRUE;
                break;
            case 0xD:
                return;
            case 0xE:
                if (gSaveHeader->language != 0 && gSaveHeader->language != 1) {
                    loadGfx = TRUE;
                }
                break;
            case 0xF:
                if (gSaveHeader->language != 0) {
                    loadGfx = TRUE;
                }
                break;
            default:
                if (ctrl == gSaveHeader->language) {
                    loadGfx = TRUE;
                }
                break;
        }

        if (loadGfx) {
            gfxOffset = gfxItem->unk0.raw & 0xFFFFFF;
            src = &gGlobalGfxAndPalettes[gfxOffset];
            dest = gfxItem->dest;
            size = gfxItem->unk8;
            dmaCtrl = 0x80000000;
            if (size < 0) {
                if (dest >= VRAM) {
                    LZ77UnCompVram(src, (void*)dest);
                } else {
                    LZ77UnCompWram(src, (void*)dest);
                }
            } else {
                DmaSet(3, src, dest, dmaCtrl | ((u32)size >> 1));
            }
        }

        terminator = gfxItem->unk0.bytes.unk3;
        terminator &= 0x80;
        gfxItem++;
        if (!terminator) {
            break;
        }
    }
}

// regalloc
void sub_0801D898(void* dest, void* src, u32 word, u32 size) {
    u32 v6;
    u32 i;

    if (size & 0x8000)
        v6 = 0x40;
    else
        v6 = 0x20;

    size &= (short)~0x8000;
    do {
        DmaSet(3, src, dest, word | 0x80000000) src += word * 2;
        dest += v6 * 2;
    } while (--size);
}

ASM_FUNC("asm/non_matching/common/zMalloc.inc", void* zMalloc(u32 size));

ASM_FUNC("asm/non_matching/common/zFree.inc", void zFree(void* ptr));

void zMallocInit(void) {
    MemClear(gzHeap, sizeof(gzHeap));
}

void DispReset(bool32 refresh) {
    gMain.interruptFlag = 1;
    gUnk_03003DE0 = 0;
    gFadeControl.active = 0;
    gScreen._6d = 0;
    gScreen._6c = 0;
    DmaStop(0);
    REG_DISPCNT = 0;
    ClearOAM();
    ResetScreenRegs();
    MemClear((void*)0x600C000, 0x20);
    MemClear(gBG0Buffer, 0x800);
    gScreen.bg0.updated = refresh;
}

void ClearOAM(void) {
    u8* d = (u8*)gOAMControls.oam;
    u8* mem = (u8*)0x07000000;
    u32 i;
    for (i = 128; i != 0; --i) {
        *(u16*)d = 0x2A0;
        d += 8;
        *(u16*)mem = 0x2A0;
        mem += 8;
    }
}

void ResetScreenRegs(void) {
    MemClear(&gScreen, sizeof(gScreen));
    gScreen.bg0.tilemap = &gBG0Buffer;
    gScreen.bg0.control = 0x1F0C;
    gScreen.bg1.tilemap = &gBG1Buffer;
    gScreen.bg1.control = 0x1C01;
    gScreen.bg2.tilemap = &gBG2Buffer;
    gScreen.bg2.control = 0x1D02;
    gScreen.bg3.tilemap = &gBG3Buffer;
    gScreen.bg3.control = 0x1E03;
    gScreen.lcd.displayControl = 0x140;
    gScreen.lcd.displayControlMask = 0xffff;
}

u32 sub_0801DB94(void) {
    return gRoomTransition.player_status.dungeon_map_y >> 11;
}

ASM_FUNC("asm/non_matching/common/DrawDungeonMap.inc", void DrawDungeonMap(u32 floor, struct_02019EE0* data, u32 size));

void sub_0801DD58(u32 area, u32 room) {
    RoomHeader* hdr = gAreaRoomHeaders[area] + room;
    gArea.pCurrentRoomInfo->map_x = hdr->map_x;
    gArea.pCurrentRoomInfo->map_y = hdr->map_y;
}

void LoadDungeonMap(void) {
    LoadResourceAsync(gUnk_0201AEE0, 0x6006000, sizeof(gUnk_0201AEE0));
}

ASM_FUNC("asm/non_matching/common/DrawDungeonFeatures.inc", void DrawDungeonFeatures(u32 room, void* data, u32 size));

u32 sub_0801DF10(DungeonLayout* lyt) {
    u32 offset;

    if (lyt->_3 == 1)
        offset = 0x300;
    else
        offset = GetFlagBankOffset(lyt->_0);
    return offset;
}

ASM_FUNC("asm/non_matching/common/sub_0801DF28.inc", void sub_0801DF28(u32 a1, u32 a2, u32 a3));

u32 sub_0801DF60(u32 a1, u8* p) {
    return (p[a1 >> 2] >> (2 * (~a1 & 3))) & 3;
}

u32 sub_0801DF78(u32 a1, u32 a2) {
    switch (a1) {
        case 0:
        case 1:
        default:
            return a1;
        case 2:
            return a2;
        case 3:
            return 7;
    }
}

u32 sub_0801DF90(u8* a1, u32 a2) {
    if (a1 == NULL)
        return 0;

    for (; *a1 != 0; a1 += 8) {
        if (*a1 == 1)
            return CheckLocalFlagByBank(a2, a1[1]);
    }
    return 0;
}

void sub_0801DFB4(Entity* entity, u32 a2, u32 a3, u32 a4) {
    MemClear(&gFuseInfo, sizeof(gFuseInfo));
    gFuseInfo._6 = a2;
    gFuseInfo._8 = a3;
    gFuseInfo._a = a4;
    gFuseInfo.ent = entity;
    gFuseInfo._3 = gUnk_03003DF0.unk_2;
    if (entity != NULL) {
        gFuseInfo._4 = entity->updatePriority;
        entity->updatePriority = 2;
    }
    gFuseInfo._0 = 0;
}

u32 sub_0801E00C(void) {
    gUnk_080C9CAC[gFuseInfo.action]();
    return gFuseInfo._0;
}

void sub_0801E02C(void) {
    sub_0801E0E0(gFuseInfo._6);
    gFuseInfo._0 = 3;
    gFuseInfo.action = 1;
}

void sub_0801E044(void) {
    if ((gMessage.doTextBox & 0x7F) == 0) {
        MenuFadeIn(4, 0);
        gFuseInfo._0 = 4;
        gFuseInfo.action = 2;
        SoundReq(SFX_6B);
    }
}

void sub_0801E074(void) {
    u32 tmp;
    switch (gFuseInfo._0) {
        case 5:
            tmp = gFuseInfo._8;
            break;
        case 6:
            tmp = gFuseInfo._a;
            break;
        default:
            return;
    }
    sub_0801E0E0(tmp);
    gFuseInfo.action = 3;
}

void sub_0801E0A0(void) {
    if ((gMessage.doTextBox & 0x7f) == 0) {
        if (gFuseInfo.ent != NULL) {
            gFuseInfo.ent->updatePriority = gFuseInfo._4;
        }
        gFuseInfo._0 = gFuseInfo._0 == 6 ? 2 : 1;
    }
}

void sub_0801E0E0(u32 idx) {
    if (idx != 0) {
        if (gFuseInfo.ent != NULL) {
            MessageNoOverlap(idx, gFuseInfo.ent);
        } else {
            MessageFromTarget(idx);
        }
    }
}

void sub_0801E104(void) {
    gScreen.lcd.displayControl &= ~0x6000;
    gScreen._6c = 0;
}

void sub_0801E120(void) {
    gScreen.lcd.displayControl |= 0x2000;
    gScreen.controls.windowInsideControl = 0x3F37;
    gScreen.controls.windowOutsideControl = 0x3F;
    gScreen.controls.window0HorizontalDimensions = 0;
    gScreen.controls.window0VerticalDimensions = 160;
}

void sub_0801E154(u32 a1) {
    sub_0801E24C(a1, 0);
}

void sub_0801E160(u32 a1, u32 a2, u32 a3) {
    MemClear(&gUnk_02017AA0[gUnk_03003DE4], sizeof(gUnk_02017AA0[gUnk_03003DE4]));
    sub_0801E290(a1, a2, a3);
    sub_0805622C(&gUnk_02017AA0[gUnk_03003DE4], 0x4000040, 0xA2600001);
}

void sub_0801E1B8(u32 a1, u32 a2) {
    gScreen.lcd.displayControl |= 0x2000;
    gScreen.controls.windowInsideControl = a1;
    gScreen.controls.windowOutsideControl = a2;
    gScreen.controls.window0HorizontalDimensions = 0;
    gScreen.controls.window0VerticalDimensions = 160;
}

void sub_0801E1EC(u32 a1, u32 a2, u32 a3) {
    MemClear(&gUnk_02017AA0[gUnk_03003DE4], sizeof(gUnk_02017AA0[gUnk_03003DE4]));
    sub_0801E24C(a3, 0);
    sub_0801E290(a1, a2, a3);
    sub_0805622C(&gUnk_02017AA0[gUnk_03003DE4], 0x4000040, 0xA2600001);
}

ASM_FUNC("asm/non_matching/common/sub_0801E24C.inc", void sub_0801E24C(u32 a1, u32 a2));

ASM_FUNC("asm/non_matching/common/sub_0801E290.inc", void sub_0801E290(u32 a1, u32 a2, u32 a3));

ASM_FUNC("asm/non_matching/common/sub_0801E31C.inc", void sub_0801E31C(u32 a1, u32 a2, u32 a3, u32 a4));

ASM_FUNC("asm/non_matching/common/sub_0801E49C.inc", void sub_0801E49C(u32 a1, u32 a2, u32 a3, u32 a4));

ASM_FUNC("asm/non_matching/common/sub_0801E64C.inc", void sub_0801E64C(u32 a1, u32 a2, u32 a3, u32 a4, u32 a5));

ASM_FUNC("asm/non_matching/common/sub_0801E6C8.inc", void sub_0801E6C8(u32 a1));

ASM_FUNC("asm/non_matching/common/sub_0801E738.inc", void sub_0801E738(u32 a1));

void sub_0801E798(u32 a1) {
    s32 idx = sub_0801E8B0(a1);
    if (idx >= 0) {
        s32 next = gSave.unk12B[idx] - 1;
        if (next <= 0) {
            gSave.unk118[idx] = 0;
            next = 0;
        }
        gSave.unk12B[idx] = next;
    }
}

u32 sub_0801E7D0(u32 a1) {
    s32 tmp = sub_0801E8B0(a1);
    if (tmp < 0) {
        return 0;
    }
    return gSave.unk12B[tmp];
}

u32 CheckKinstoneFused(u32 idx) {
    if (idx > 100 || idx < 1) {
        return 0;
    }
    return ReadBit(&gSave.unk241, idx);
}

u32 sub_0801E810(u32 idx) {
    if (idx > 100 || idx < 1) {
        return 0;
    }
    return ReadBit(&gSave.unk24E, idx);
}

ASM_FUNC("asm/non_matching/common/sub_0801E82C.inc", void sub_0801E82C(void));

s32 sub_0801E8B0(u32 idx) {
    u32 i;

    for (i = 0; i < 18; ++i) {
        if (idx == gSave.unk118[i])
            return i;
    }
    return -1;
}

void sub_0801E8D4(void) {
    u32 i;
    for (i = 10; i <= 100; ++i) {
        if (CheckKinstoneFused(i) && !sub_0801E810(i)) {
            u32 evt_type = gUnk_080C9CBC[i].evt_type;
            struct_080FE320* s = &gUnk_080FE320[evt_type];
#if !defined EU && !defined JP
            u32 flag = s->flag;
#endif
            u32 tmp;
            switch (s->_10) {
                case 0:
                    tmp = 0;
                    break;
                case 1:
                    tmp = s->_11;
                    break;
                case 2:
                    tmp = 0xf;
                    break;
                case 3:
                    tmp = 0x10;
                    break;
                case 4:
                    tmp = 0x11;
                    break;
#if !defined EU && !defined JP
                case 5:
                    tmp = 4;
                    flag = 0x83;
                    break;
                case 6:
                    tmp = 4;
                    flag = 0x84;
                    break;
                case 7:
                    tmp = 4;
                    flag = 0x87;
                    break;
                case 8:
                    tmp = 4;
                    flag = 0x88;
                    break;
                case 9:
                    tmp = 4;
                    flag = 0x8b;
                    break;
#ifndef DEMO_JP
                case 10:
                    tmp = 5;
                    flag = 0x88;
                    break;
#endif
#endif
            }
#if !defined EU && !defined JP
            if (sub_0807CB24(tmp, flag)) {
#else
            if (sub_0807CB24(tmp, s->flag)) {
#endif
                WriteBit(&gSave.unk24E, i);
            }
        }
    }
}

ASM_FUNC("asm/non_matching/common/sub_0801E99C.inc", u32 sub_0801E99C(u32 a1));

u32 sub_0801EA74(void) {
    s32 r = (s32)Random() % 18;
    u32 i;
    for (i = 0; i < 18; ++i) {
        u32 n = gUnk_080CA11C[r];
        if (!CheckKinstoneFused(n))
            return n;
        r = (r + 1) % 18;
    }
    return 0xF2;
}
