#include "global.h"
#include "contest_painting.h"
#include "data2.h"
#include "decompress.h"
#include "ewram.h"
#include "image_processing_effects.h"
#include "main.h"
#include "menu.h"
#include "palette.h"
#include "random.h"
#include "scanline_effect.h"
#include "sprite.h"
#include "string_util.h"
#include "strings.h"
#include "text.h"

static u8 sHoldState;
static u16 sMosaicVal;
static u16 sFrameCounter;
static u8 sVarsInitialized;

u16 (*gContestMonPixels)[][32];
struct ImageProcessingContext gImageProcessingContext;
u8 sCaptionBuffer[76];
struct ContestWinner *gContestPaintingWinner;
u16 *gContestPaintingMonPalette;

static const u16 gPictureFramePalettes[][16] =
{
    INCBIN_U16("graphics/picture_frame/bg0.gbapal"),
    INCBIN_U16("graphics/picture_frame/bg1.gbapal"),
    INCBIN_U16("graphics/picture_frame/bg2.gbapal"),
    INCBIN_U16("graphics/picture_frame/bg3.gbapal"),
    INCBIN_U16("graphics/picture_frame/bg4.gbapal"),
    INCBIN_U16("graphics/picture_frame/bg5.gbapal"),
    {0},
    {0},
};
const u8 emptySpace[8 * 32] = {0};
const u8 gPictureFrameTiles_0[] = INCBIN_U8("graphics/picture_frame/frame0.4bpp.rl");
const u8 gPictureFrameTiles_1[] = INCBIN_U8("graphics/picture_frame/frame1.4bpp.rl");
const u8 gPictureFrameTiles_2[] = INCBIN_U8("graphics/picture_frame/frame2.4bpp.rl");
const u8 gPictureFrameTiles_3[] = INCBIN_U8("graphics/picture_frame/frame3.4bpp.rl");
const u8 gPictureFrameTiles_4[] = INCBIN_U8("graphics/picture_frame/frame4.4bpp.rl");
const u8 gPictureFrameTiles_5[] = INCBIN_U8("graphics/picture_frame/frame5.4bpp.rl");
const u8 gPictureFrameTilemap_0[] = INCBIN_U8("graphics/picture_frame/frame0_map.bin.rl");
const u8 gPictureFrameTilemap_1[] = INCBIN_U8("graphics/picture_frame/frame1_map.bin.rl");
const u8 gPictureFrameTilemap_2[] = INCBIN_U8("graphics/picture_frame/frame2_map.bin.rl");
const u8 gPictureFrameTilemap_3[] = INCBIN_U8("graphics/picture_frame/frame3_map.bin.rl");
const u8 gPictureFrameTilemap_4[] = INCBIN_U8("graphics/picture_frame/frame4_map.bin.rl");
const u8 gPictureFrameTilemap_5[] = INCBIN_U8("graphics/picture_frame/frame5_map.bin.rl");

const u8 *const sContestRankNames[] =
{
    OtherText_Cool,
    OtherText_Beauty2,
    OtherText_Cute,
    OtherText_Smart,
    OtherText_Tough,
};

const struct LabelPair sMuseumCaptions[] =
{
    // COOL
    {OtherText_NonstopSuperCool, OtherText_Terminator6},
    {OtherText_GoodLookingPoke, OtherText_Terminator7},
    {OtherText_MarvelousGreat, OtherText_Terminator8},
    // BEAUTY
    {OtherText_CenturyLastVenus, OtherText_Terminator9},
    {OtherText_Terminator10, OtherText_DazzlingSmile},
    {OtherText_PokeCenterIdol, OtherText_Terminator11},
    // CUTE
    {OtherText_LovelyAndSweet, OtherText_Terminator12},
    {OtherText_ThePretty, OtherText_WinningPortrait},
    {OtherText_GiveUsWink, OtherText_Terminator13},
    // SMART
    {OtherText_SmartnessMaestro, OtherText_Terminator15},
    {OtherText_ChosenPokeAmong, OtherText_Terminator15},
    {OtherText_TheExcellent, OtherText_ItsMomentOfElegance},
    // TOUGH
    {OtherText_PowerfullyMuscular, OtherText_Terminator16},
    {OtherText_StrongErEst, OtherText_Terminator17},
    {OtherText_MightyTough, OtherText_Exclamation},
};

const struct OamData sContestPaintingMonOamData =
{
    .y = 0,
    .affineMode = ST_OAM_AFFINE_OFF,
    .objMode = ST_OAM_OBJ_NORMAL,
    .mosaic = TRUE,
    .bpp = ST_OAM_8BPP,
    .shape = SPRITE_SHAPE(64x64),
    .x = 0,
    .matrixNum = 0,
    .size = SPRITE_SIZE(64x64),
    .tileNum = 0,
    .priority = 0,
    .paletteNum = 0,
    .affineParam = 0,
};

const u16 sBgPalette[] = {RGB_BLACK, RGB_BLACK};

static void ShowContestPainting();
static void CB2_HoldContestPainting(void);
static void HoldContestPainting(void);
static void InitContestPaintingWindow(bool8 isForArtist);
static void PrintContestPaintingCaption(u8 contestType, bool8 isForArtist);
static void InitContestPaintingBg(void);
static void InitContestPaintingVars(bool8 reset);
static void VBlankCB_ContestPainting(void);
static void _InitContestMonPixels(u8 *spriteGfx, u16 *palette, u16 *destPixels);
static void CreateContestPaintingPicture(u8 contestWinnerId, bool8 isForArtist);

void SetContestWinnerForPainting(u32 contestWinnerId)
{
    // probably fakematching
    struct ContestWinner *curWinner = &eCurContestWinner;
    u8 *saveIdx = (u8*)&eCurContestWinnerSaveIdx;
    u8 *isForArtist = (u8*)&eCurContestWinnerIsForArtist;
    *curWinner = gSaveBlock1.contestWinners[contestWinnerId - 1];
	*saveIdx = contestWinnerId - 1;
	*isForArtist = FALSE;
}

void CB2_ContestPainting(void)
{
    ShowContestPainting();
}

static void ShowContestPainting(void)
{
    switch (gMain.state)
    {
    case 0:
        ScanlineEffect_Stop();
        SetVBlankCallback(NULL);
        gContestPaintingWinner = &eCurContestWinner;
        InitContestPaintingVars(TRUE);
        InitContestPaintingBg();
        gMain.state++;
        break;
    case 1:
    {
        ResetPaletteFade();
        DmaFill32Large(3, 0, (void *)(VRAM + 0x0), 0x18000, 0x1000);
        ResetSpriteData();
        gMain.state++;
        break;
    }
    case 2:
        SeedRng(gMain.vblankCounter1);
        InitKeys();
        InitContestPaintingWindow(eCurContestWinnerIsForArtist);
        gMain.state++;
        break;
    case 3:
        CreateContestPaintingPicture(eCurContestWinnerSaveIdx, eCurContestWinnerIsForArtist);
        gMain.state++;
        break;
    case 4:
        PrintContestPaintingCaption(eCurContestWinnerSaveIdx, eCurContestWinnerIsForArtist);
        LoadPalette(sBgPalette, 0, 1 * 2);
        DmaClear32(3, PLTT, 0x400);
        BeginFastPaletteFade(2);
        SetVBlankCallback(VBlankCB_ContestPainting);
        sHoldState = 0;
        REG_DISPCNT = DISPCNT_MODE_0 | DISPCNT_OBJ_1D_MAP | DISPCNT_BG0_ON | DISPCNT_BG1_ON | DISPCNT_OBJ_ON;
        SetMainCallback2(CB2_HoldContestPainting);
        break;
    }
}

static void CB2_HoldContestPainting(void)
{
    HoldContestPainting();
    UpdatePaletteFade();
}

static void CB2_QuitContestPainting(void)
{
    SetMainCallback2(gMain.savedCallback);
}

static void HoldContestPainting(void)
{
    switch (sHoldState)
    {
    case 0:
        if (!gPaletteFade.active)
            sHoldState = 1;
        if (sVarsInitialized&& sFrameCounter != 0)
            sFrameCounter--;
        break;
    case 1:
        if ((gMain.newKeys & A_BUTTON) || (gMain.newKeys & B_BUTTON))
        {
            sHoldState++;
            BeginNormalPaletteFade(0xFFFFFFFF, 0, 0, 16, RGB_BLACK);
        }
        if (sVarsInitialized != 0)
            sFrameCounter = 0;
        break;
    case 2:
        if (!gPaletteFade.active)
            SetMainCallback2(CB2_QuitContestPainting);
        if (sVarsInitialized && sFrameCounter < 30)
            sFrameCounter++;
        break;
    }
}

static void InitContestPaintingWindow(u8 isForArtist)
{
    InitMenuWindow(&gWindowTemplate_ContestPainting);
    Text_LoadWindowTemplate(&gWindowTemplate_ContestPainting);
}

static void PrintContestPaintingCaption(u8 contestType, u8 isForArtist)
{
    u8 xPos, yPos;
    u8 *ptr;
    u8 type;

    if (isForArtist == TRUE)
        return;
    ptr = sCaptionBuffer;
    type = gContestPaintingWinner->contestCategory;
    if (contestType < 8)
    {
        ptr = StringCopy(ptr, sContestRankNames[type]);
        ptr = StringCopy(ptr, gContestText_ContestWinner);
#if ENGLISH
        ptr = StringCopy(ptr, gContestPaintingWinner->trainerName);
#elif GERMAN
        ptr = StringCopy10(ptr, gContestPaintingWinner->nickname);
#endif

        // {LATIN}
        ptr[0] = EXT_CTRL_CODE_BEGIN;
        ptr[1] = 0x16;
        ptr += 2;

        ptr = StringCopy(ptr, gOtherText_Unknown1);
#if ENGLISH
        ptr = StringCopy10(ptr, gContestPaintingWinner->nickname);
#elif GERMAN
        ptr = StringCopy(ptr, gContestPaintingWinner->trainerName);
#endif

        xPos = 6;
        yPos = 14;
    }
    else
    {
        ptr = StringCopy(ptr, sMuseumCaptions[type].prefix);
        ptr = StringCopy10(ptr, gContestPaintingWinner->nickname);
        ptr = StringCopy(ptr, sMuseumCaptions[type].suffix);

        xPos = 3;
        yPos = 14;
    }
    Menu_PrintTextPixelCoords(sCaptionBuffer, xPos * 8 + 1, yPos * 8, 1);
}

static void InitContestPaintingBg(void)
{
    REG_DISPCNT = 0;
    REG_IE |= INTR_FLAG_VBLANK;
    REG_BG0CNT = BGCNT_PRIORITY(2) | BGCNT_CHARBASE(0) | BGCNT_SCREENBASE(12) | BGCNT_MOSAIC | BGCNT_16COLOR | BGCNT_TXT256x256;
    REG_BG1CNT = BGCNT_PRIORITY(1) | BGCNT_CHARBASE(1) | BGCNT_SCREENBASE(10) | BGCNT_MOSAIC | BGCNT_16COLOR | BGCNT_TXT256x256;
    REG_BLDCNT = 0;
    REG_BLDALPHA = 0;
    REG_BLDY = 0;
}

static void InitContestPaintingVars(bool8 reset)
{
    if (reset == FALSE)
    {
        sVarsInitialized = FALSE;
        sMosaicVal = 0;
        sFrameCounter = 0;
    }
    else
    {
        sVarsInitialized = TRUE;
        sMosaicVal = 15;
        sFrameCounter = 30;
    }
}

static void ContestPaintingMosaic(void)
{
    if (sVarsInitialized == FALSE)
    {
        REG_MOSAIC = 0;
        return;
    }

    REG_BG1CNT = BGCNT_PRIORITY(1) | BGCNT_CHARBASE(1) | BGCNT_SCREENBASE(10) | BGCNT_MOSAIC | BGCNT_16COLOR | BGCNT_TXT256x256;
    sMosaicVal = sFrameCounter / 2;

    REG_MOSAIC = (sMosaicVal << 12) | (sMosaicVal << 8) | (sMosaicVal << 4) | (sMosaicVal << 0);
}

static void VBlankCB_ContestPainting(void)
{
    ContestPaintingMosaic();
    LoadOam();
    ProcessSpriteCopyRequests();
    TransferPlttBuffer();
}

static void InitContestMonPixels(u16 species, u8 arg1)
{
    const void *pal;

    pal = GetMonSpritePalFromOtIdPersonality(species, gContestPaintingWinner->otId, gContestPaintingWinner->personality);
    LZDecompressVram(pal, gContestPaintingMonPalette);

    if (arg1 == 0)
    {
        HandleLoadSpecialPokePic(
            &gMonFrontPicTable[species],
            gMonFrontPicCoords[species].coords,
            gMonFrontPicCoords[species].y_offset,
            (void *)EWRAM,
            gMonSpriteGfx_Sprite_ptr[1],
            species,
            (u32)gContestPaintingWinner->personality
        );
        _InitContestMonPixels((u8*)gMonSpriteGfx_Sprite_ptr[1], (u16*)gContestPaintingMonPalette, (u16*)gContestMonPixels);
    }
    else
    {
        HandleLoadSpecialPokePic(
            &gMonBackPicTable[species],
            gMonBackPicCoords[species].coords,
            gMonBackPicCoords[species].y_offset,
            (void *)EWRAM,
            gMonSpriteGfx_Sprite_ptr[0],
            species,
            (u32)gContestPaintingWinner->personality
        );
        _InitContestMonPixels((u8*)gMonSpriteGfx_Sprite_ptr[0], (u16*)gContestPaintingMonPalette, (u16*)gContestMonPixels);
    }
}

static void _InitContestMonPixels(u8 *spriteGfx, u16 *palette, u16 *destPixels)
{
    u16 tileY, tileX, pixelY, pixelX;

    /*
        Raw arithmetics are required to match this function.
        At least it's the first known way to match it. The extreme
        sensitivity of this match and Game Freak proving to not have
        been a fan of C's advanced array features, preferring to
        calculate dimensions manually (as seen in other functions),
        tell that it may have been this way in the original code.
    */
    for (tileY = 0; tileY < 8; tileY++)
    {
        for (tileX = 0; tileX < 8; tileX++)
        {
            for (pixelY = 0; pixelY < 8; pixelY++)
            {
                for (pixelX = 0; pixelX < 8; pixelX++)
                {
                    u8 colorIndex = spriteGfx[(((tileY * 8) + tileX) * 32) + (pixelY << 2) + (pixelX >> 1)];
                    /*
                        The shifts have to be there to match r0 and r2's order in one instruction:
                                add     r5, r2, r0
                        This also makes agbcc's expression order parsing even more super sensitive
                        and obscene when it comes to parentheses affecting regalloc regardless if
                        unnecessary, requiring tileX must be placed in the front to match, or else
                        regalloc breaks again and does this a few instructions above:
                                add     r0, r3, r7 <- regswap
                    */

                    if (pixelX & 1)
                        colorIndex /= 16;
                    else
                        colorIndex %= 16;

                    // Same order as above needs to be written here, or else this happens:
                    //         add     r0, r7, r1 <- regswap
                    if (colorIndex == 0)
                        *(u16 *)(destPixels + (((tileY * 8) + pixelY) << 6) + ((tileX * 8) + pixelX)) = 0x8000;
                    else
                        *(u16 *)(destPixels + (((tileY * 8) + pixelY) << 6) + ((tileX * 8) + pixelX)) =
                            palette[colorIndex];
                }
            }
        }
    }
}

static void LoadContestPaintingFrame(u8 contestWinnerId, u8 isForArtist)
{
    u8 x, y;

    LoadPalette(gPictureFramePalettes, 0, sizeof(gPictureFramePalettes));
    if (isForArtist == TRUE)
    {
        switch (gContestPaintingWinner->contestCategory / 3)
        {
        case CONTEST_COOL:
            RLUnCompVram(gPictureFrameTiles_0, (void *)VRAM);
            RLUnCompWram(gPictureFrameTilemap_0, gContestMonPixels);
            break;
        case CONTEST_BEAUTY:
            RLUnCompVram(gPictureFrameTiles_1, (void *)VRAM);
            RLUnCompWram(gPictureFrameTilemap_1, gContestMonPixels);
            break;
        case CONTEST_CUTE:
            RLUnCompVram(gPictureFrameTiles_2, (void *)VRAM);
            RLUnCompWram(gPictureFrameTilemap_2, gContestMonPixels);
            break;
        case CONTEST_SMART:
            RLUnCompVram(gPictureFrameTiles_3, (void *)VRAM);
            RLUnCompWram(gPictureFrameTilemap_3, gContestMonPixels);
            break;
        case CONTEST_TOUGH:
            RLUnCompVram(gPictureFrameTiles_4, (void *)VRAM);
            RLUnCompWram(gPictureFrameTilemap_4, gContestMonPixels);
            break;
        }

#define VRAM_PICTURE_DATA(x, y) (((u16 *)(VRAM + 0x6000))[(y) * 32 + (x)])

        // Set the background
        for (y = 0; y < 20; y++)
        {
            for (x = 0; x < 32; x++)
                VRAM_PICTURE_DATA(x, y) = 0x1015;
        }

        // Copy the image frame
        for (y = 0; y < 10; y++)
        {
            for (x = 0; x < 18; x++)
                VRAM_PICTURE_DATA(x + 6, y + 2) = (*gContestMonPixels)[y + 2][x + 6];
        }

        // Re-set the entire top row to the first top frame part
        for (x = 0; x < 16; x++)
            VRAM_PICTURE_DATA(x + 7, 2) = (*gContestMonPixels)[2][7];

#undef VRAM_PICTURE_DATA
    }
    else if (contestWinnerId < 8)
    {
        RLUnCompVram(gPictureFrameTiles_5, (void *)VRAM);
        RLUnCompVram(gPictureFrameTilemap_5, (void *)(VRAM + 0x6000));
    }
    else
    {
        switch (gContestPaintingWinner->contestCategory / 3)
        {
        case CONTEST_COOL:
            RLUnCompVram(gPictureFrameTiles_0, (void *)VRAM);
            RLUnCompVram(gPictureFrameTilemap_0, (void *)(VRAM + 0x6000));
            break;
        case CONTEST_BEAUTY:
            RLUnCompVram(gPictureFrameTiles_1, (void *)VRAM);
            RLUnCompVram(gPictureFrameTilemap_1, (void *)(VRAM + 0x6000));
            break;
        case CONTEST_CUTE:
            RLUnCompVram(gPictureFrameTiles_2, (void *)VRAM);
            RLUnCompVram(gPictureFrameTilemap_2, (void *)(VRAM + 0x6000));
            break;
        case CONTEST_SMART:
            RLUnCompVram(gPictureFrameTiles_3, (void *)VRAM);
            RLUnCompVram(gPictureFrameTilemap_3, (void *)(VRAM + 0x6000));
            break;
        case CONTEST_TOUGH:
            RLUnCompVram(gPictureFrameTiles_4, (void *)VRAM);
            RLUnCompVram(gPictureFrameTilemap_4, (void *)(VRAM + 0x6000));
            break;
        }
    }
}

static void InitPaintingMonOamData(u8 contestWinnerId)
{
    gMain.oamBuffer[0] = sContestPaintingMonOamData;
    gMain.oamBuffer[0].tileNum = 0;

    if (contestWinnerId > 1)
    {
        gMain.oamBuffer[0].x = 88;
        gMain.oamBuffer[0].y = 24;
    }
    else // duplicated code branch
    {
        gMain.oamBuffer[0].x = 88;
        gMain.oamBuffer[0].y = 24;
    }
}

static u8 GetImageEffectForContestWinner(u8 contestWinnerId)
{
    u8 contestType;

    if (contestWinnerId < 8)
        contestType = gContestPaintingWinner->contestCategory;
    else
        contestType = gContestPaintingWinner->contestCategory / 3;

    switch (contestType)
    {
    case CONTEST_COOL:
        return CONTESTRESULT_COOL;
    case CONTEST_BEAUTY:
        return CONTESTRESULT_BEAUTY;
    case CONTEST_CUTE:
        return CONTESTRESULT_CUTE;
    case CONTEST_SMART:
        return CONTESTRESULT_SMART;
    case CONTEST_TOUGH:
        return CONTESTRESULT_TOUGH;
    }

    return contestType;
}

static void AllocPaintingResources(void)
{
    gContestPaintingMonPalette = eContestPaintingBuffers.palette;
    gContestMonPixels = (void *)eContestPaintingBuffers.pixels;
}

static void DoContestPaintingImageProcessing(u8 imageEffect)
{
    gImageProcessingContext.canvasPixels = gContestMonPixels;
    gImageProcessingContext.canvasPalette = gContestPaintingMonPalette;
    gImageProcessingContext.paletteStart = 0;
    gImageProcessingContext.personality = gContestPaintingWinner->personality % 256;
    gImageProcessingContext.columnStart = 0;
    gImageProcessingContext.rowStart = 0;
    gImageProcessingContext.columnEnd = 64;
    gImageProcessingContext.rowEnd = 64;
    gImageProcessingContext.canvasWidth = 64;
    gImageProcessingContext.canvasHeight = 64;

    switch (imageEffect)
    {
    case CONTESTRESULT_SMART:
    case CONTESTRESULT_TOUGH:
        gImageProcessingContext.quantizeEffect = QUANTIZE_EFFECT_GRAYSCALE;
        break;
    case CONTESTRESULT_COOL:
    case CONTESTRESULT_BEAUTY:
    case CONTESTRESULT_CUTE:
    default:
        gImageProcessingContext.quantizeEffect = QUANTIZE_EFFECT_STANDARD_LIMITED_COLORS;
        break;
    }

    gImageProcessingContext.bgType = 2;
    gImageProcessingContext.effect = imageEffect;
    gImageProcessingContext.dest = OBJ_VRAM0;

    ApplyImageProcessingEffects(&gImageProcessingContext);
    ApplyImageProcessingQuantization(&gImageProcessingContext);
    ConvertImageProcessingToGBA(&gImageProcessingContext);

    LoadPalette(gContestPaintingMonPalette, 256, 256 * 2);
}

static void CreateContestPaintingPicture(u8 contestWinnerId, u8 isForArtist)
{
    AllocPaintingResources();
    InitContestMonPixels(gContestPaintingWinner->species, 0);
    DoContestPaintingImageProcessing(GetImageEffectForContestWinner(contestWinnerId));
    InitPaintingMonOamData(contestWinnerId);
    LoadContestPaintingFrame(contestWinnerId, isForArtist);
}
