#include "global.h"
#include "structures.h"
#include "functions.h"
#include "screen.h"
#include "common.h"

static u32 sub_080501C0(FadeControl* ctl);
static u32 sub_08050230(FadeControl* ctl);
static u32 sub_080502A4(FadeControl* ctl);

extern u32 gUsedPalettes;
extern u16 gPaletteBuffer[];
extern u16 gUnk_080FC3C4[];

// function pointer to overlay (0x03005e98) in ram calls rom function MakeFadeBuff256
extern u32 ram_MakeFadeBuff256;
typedef void (*fptrMakeFadeBuff256)(u8*, u8*, u16, u8);

void SetBrightness(u32 brightness) {
    gSaveHeader->brightness = brightness;
    gUsedPalettes = 0xffffffff;
}

void FadeVBlank(void) {
    fptrMakeFadeBuff256 func;
    u32 usedPalettesTmp, palIdx;

    struct_020354C0* ptrUnk = gUnk_020354C0;
    usedPalettesTmp = gUsedPalettes;
    gUsedPalettes = 0;
    palIdx = 0;

    while (usedPalettesTmp != 0) {
        if ((usedPalettesTmp & 1) == 1) {
            func = (fptrMakeFadeBuff256)&ram_MakeFadeBuff256;
            func(&((u8*)gPaletteBuffer)[palIdx], &PAL_RAM[palIdx], ptrUnk->unk2, ptrUnk->unk1);
        }
        palIdx += 0x20;

        ptrUnk++;
        usedPalettesTmp >>= 1;
    }
}

void InitFade(void) {
    MemClear(&gFadeControl, sizeof(gFadeControl));
    MemClear(&gUnk_020354C0, sizeof(gUnk_020354C0));
    gFadeControl.mask = 0xffffffff;
}

void ResetFadeMask(void) {
    MemClear(&gUnk_020354C0, sizeof(gUnk_020354C0));
    gFadeControl.mask = 0xFFFFFFFF;
}

static void sub_08050024(void) {
    sub_0801E104();
    SetFade(5, 256);
}

void SetFadeProgress(u32 arg0) {
    if ((gFadeControl.type & 1) != 0) {
        gFadeControl.sustain = arg0;
    } else {
        gFadeControl.progress = arg0;
    }
}

void SetFade(u32 type, u32 speed) {
    gFadeControl.speed = speed;
    gFadeControl.type = type;
    gFadeControl.active = 1;
    gFadeControl.progress = 0x100;
    gFadeControl.sustain = 0;
    if (gFadeControl.type & 2) {
        gFadeControl.color = 0xf8;
    } else {
        gFadeControl.color = 0;
    }
    if (type & 8) {
        gOAMControls.spritesOffset = 1;
        gScreen.bg1.control |= BGCNT_MOSAIC;
        gScreen.bg2.control |= BGCNT_MOSAIC;
        gScreen.bg3.control |= BGCNT_MOSAIC;
    }
    if (type & 0x10) {
        sub_0801E1B8(gFadeControl.win_inside_cnt, gFadeControl.win_outside_cnt);
        sub_0801E1EC(gFadeControl.iris_x, gFadeControl.iris_y, gFadeControl.iris_size);
        if ((type & 1) == 0) {
            gFadeControl.type &= ~4;
            ResetFadeMask();
            gUsedPalettes = 0xffffffff;
        }
    }
}

void SetFadeInverted(u32 speed) {
    gFadeControl.speed = speed;
    gFadeControl.type ^= 1;
    gFadeControl.active = 1;
    gFadeControl.progress = 256;
}

void SetFadeIris(u32 x, u32 y, u32 type, u32 speed) {
    if ((type & 1) != 0) {
        gFadeControl.iris_size = 0x96;
    } else {
        gFadeControl.iris_size = 0;
    }
    gFadeControl.iris_x = x;
    gFadeControl.iris_y = y;
    gFadeControl.win_inside_cnt = 0x3f3f;
    gFadeControl.win_outside_cnt = 0;
    SetFade(type, speed);
}

void FadeMain(void) {
    FadeControl* ctl = &gFadeControl;
    u32 flags = ctl->type & 0x1C;
    u32 active = 0;
    u32 bit;

    if (ctl->active) {
        ctl->progress -= ctl->speed;
        if ((s16)ctl->progress <= (s16)ctl->sustain)
            ctl->progress = ctl->sustain;
        while (flags) {
            bit = (~flags + 1) & flags;
            flags ^= bit;
            switch (bit) {
                case 4:
                    active |= sub_080501C0(ctl);
                    break;
                case 8:
                    active |= sub_08050230(ctl);
                    break;
                case 16:
                    active |= sub_080502A4(ctl);
                    break;
            }
        }
        ctl->active = active;
    }
}

static u32 sub_080501C0(FadeControl* ctl) {
    u32 v1;
    u32 v2;
    struct_020354C0* v3;
    u32 i;

    if (ctl->type & 1) {
        v1 = 256 - (s16)ctl->progress;
    } else {
        v1 = (s16)ctl->progress;
    }
    v2 = gFadeControl.mask;
    v3 = gUnk_020354C0;
    for (i = 0; i < 0x20; ++i, ++v3) {
        if (v2 & 1) {
            v3->unk0 = 1;
            v3->unk2 = v1;
        } else {
            v3->unk0 = 0;
            v3->unk2 = 0;
        }
        v3->unk1 = ctl->color;
        v2 >>= 1;
    }
    gUsedPalettes = 0xffffffff;

    return !!((s16)ctl->sustain ^ (s16)ctl->progress);
}

static u32 sub_08050230(FadeControl* ctl) {
    u32 type = ctl->type;
    u32 idx = ((s16)ctl->progress >> 4) & 0xF;
    if (type & 1)
        idx = 0xF - idx;
    gScreen.controls.mosaicSize = gUnk_080FC3C4[idx];
    if (ctl->progress != 0)
        return 1;

    // fade is finished
    gOAMControls.spritesOffset = 0;
    if ((type & 1) == 0) {
        // reset registers if fading in
        gScreen.bg0.control &= ~BGCNT_MOSAIC;
        gScreen.bg1.control &= ~BGCNT_MOSAIC;
        gScreen.bg2.control &= ~BGCNT_MOSAIC;
        gScreen.bg3.control &= ~BGCNT_MOSAIC;
    }
    return 0;
}

static u32 sub_080502A4(FadeControl* ctl) {
    if (ctl->type & 1) {
        s32 delta = (u16)gFadeControl.iris_size - gFadeControl.speed;
        gFadeControl.iris_size -= gFadeControl.speed;
        if (delta << 16 <= 0)
            gFadeControl.iris_size = 0;
        sub_0801E1EC(gFadeControl.iris_x, gFadeControl.iris_y, gFadeControl.iris_size);
        if (!gFadeControl.iris_size)
            return 0;
    } else {
        gFadeControl.iris_size += gFadeControl.speed;
        sub_0801E1EC(gFadeControl.iris_x, gFadeControl.iris_y, gFadeControl.iris_size);
        if (gFadeControl.iris_size > 150) {
            sub_0801E104();
            return 0;
        }
    }
    return 1;
}
