#include "common.h"

/*  Defines  */

enum STATE_T : uint8_t {
    STATE_INIT = 0,
    STATE_TOP,
    STATE_RECORD,
    STATE_CREDIT,
    STATE_STARTED,
};

#define IMG_GODDESS1_W  72
#define IMG_GODDESS1_H  40
#define IMG_GODDESS2_W  48
#define IMG_GODDESS2_H  24
#define IMG_HAND_W      26
#define IMG_HAND_H      32
#define IMG_TITLE_W     72
#define IMG_TITLE_H     24

/*  Typedefs  */

/*  Local Functions  */

static void handleInit(void);
static void handleTop(void);
static void handleAnyButton(void);
static void updateTitleDots(void);
static void newTitleDot(int8_t x, int8_t y);

static void onTop(void);
static void onInstruction(void);
static void onStart(void);
static void onRecord(void);
static void onCredit(void);

static void drawTop(void);
static void drawRecord(void);
static void drawCredit(void);

/*  Local Functions (macros)  */

#define callHandlerFunc(n)  ((void (*)(void)) pgm_read_ptr(handlerFuncTable + n))()
#define callDrawerFunc(n)   ((void (*)(void)) pgm_read_ptr(drawerFuncTable + n))()

/*  Local Constants  */

PROGMEM static const uint8_t imgGoddess1[360] = { // 72x40
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x50, 0x60,
    0x70, 0x30, 0x70, 0x48, 0xD4, 0xB8, 0xD0, 0xD8, 0xD4, 0xD8, 0x8C, 0xB8, 0x70, 0xE0, 0x10, 0x22,
    0x77, 0xAE, 0x5C, 0xA8, 0x54, 0xEA, 0x55, 0xAA, 0xE5, 0xAA, 0x54, 0xA8, 0x74, 0xAA, 0x5C, 0xA8,
    0x74, 0xA8, 0x5C, 0xA8, 0x40, 0xA8, 0x10, 0xA8, 0x10, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5C, 0xFE, 0xC6, 0x80,
    0x14, 0x3C, 0x14, 0xC4, 0x44, 0x00, 0x51, 0xFC, 0x5C, 0x6E, 0x45, 0x8E, 0x57, 0x0F, 0x71, 0xE9,
    0x55, 0xAA, 0x61, 0xAA, 0x3F, 0xAA, 0x11, 0x2A, 0xCD, 0xEA, 0xF1, 0xAA, 0x05, 0xAA, 0x7D, 0xAA,
    0xFE, 0xFA, 0xDE, 0xEA, 0x75, 0xAA, 0x95, 0x8A, 0x04, 0xAA, 0x90, 0xEA, 0xF6, 0xFA, 0xFD, 0xFB,
    0xFC, 0xEA, 0xD7, 0xAE, 0x14, 0xA8, 0xD0, 0xA0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x3C, 0x57, 0x7F, 0x45, 0x0E, 0xF5, 0xF9, 0x55, 0x20, 0x17, 0x0F, 0x1D, 0xB8, 0x75, 0x30,
    0x11, 0xFB, 0x5D, 0xFE, 0xD5, 0xC2, 0xD1, 0xA3, 0xD5, 0xAE, 0x5C, 0xBA, 0x77, 0x2E, 0x73, 0xBA,
    0x55, 0xAA, 0xB5, 0xBE, 0xFE, 0xBE, 0x7D, 0xAA, 0xF7, 0xE3, 0x67, 0xAA, 0xE7, 0xEE, 0xDF, 0xAB,
    0xD6, 0xAA, 0xD1, 0xEA, 0xE7, 0xEA, 0xD0, 0xEA, 0xF4, 0xEA, 0xDF, 0xAF, 0x65, 0x2E, 0x3F, 0xBE,
    0x5F, 0x2A, 0x10, 0xE8, 0x70, 0xE0, 0xC0, 0x80, 0x1D, 0x3E, 0x31, 0x2C, 0x50, 0x20, 0x55, 0xEF,
    0x45, 0x00, 0x15, 0x6E, 0x5D, 0x00, 0x5D, 0x7E, 0x75, 0xE0, 0x51, 0xC2, 0x55, 0x03, 0x07, 0x8B,
    0x55, 0x53, 0x55, 0xA0, 0x44, 0x0F, 0x15, 0x2E, 0x14, 0x9C, 0x55, 0x8A, 0xC5, 0xF4, 0xF5, 0xFC,
    0xFC, 0xFE, 0xFF, 0xFF, 0xFF, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xF7, 0xEF, 0xEF, 0xDF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xFE, 0xFD, 0x00, 0x15, 0xFC, 0x5D, 0xFB, 0x55, 0x08, 0x1C, 0x0E, 0x05, 0x02,
    0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x05, 0x4F, 0x55, 0x8E, 0x15, 0x8C, 0xC4, 0x80, 0x5C, 0xBE,
    0x50, 0xE0, 0x54, 0xB8, 0x54, 0x28, 0x00, 0x0F, 0x14, 0x7F, 0xF1, 0xC0, 0xDC, 0xF8, 0xF4, 0xFC,
    0xD5, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE, 0xFC, 0xFD, 0xF9,
    0xF1, 0xF3, 0xF5, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F, 0xDF, 0x9F, 0x1E, 0x06, 0x80, 0x20, 0x11, 0x08,
    0x11, 0x08, 0x05, 0x02, 0x00, 0x00, 0x00, 0x00
};

PROGMEM static const uint8_t imgGoddess2[144] = { // 48x24
    0x01, 0x06, 0x07, 0x0C, 0x04, 0x08, 0x5C, 0x24, 0x45, 0x00, 0x45, 0x8A, 0x55, 0x98, 0x14, 0x00,
    0x01, 0xE9, 0xFD, 0xFB, 0xF1, 0xEF, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
    0xFF, 0xFF, 0xFF, 0xDF, 0xBF, 0xBF, 0x7F, 0x7F, 0x7F, 0x7F, 0xFF, 0xFF, 0xDF, 0x6F, 0x20, 0x0F,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0xC1, 0xF8,
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFD, 0xFB, 0xF7, 0xE7, 0xCF, 0x8F, 0x1F, 0x3F,
    0x3F, 0x7F, 0x7F, 0xFF, 0xFF, 0xFF, 0x7D, 0x3D, 0x1F, 0x07, 0x03, 0x01, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x80, 0x60, 0x18, 0x04, 0x02, 0x02, 0x04, 0x04, 0x08, 0x0A, 0x17, 0x37,
    0x2F, 0x5F, 0x5F, 0xBF, 0xBF, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x0E,
    0x14, 0x20, 0x40, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

PROGMEM static const uint8_t imgHand[104] = { // 26x32
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0xC0, 0xE0,
    0x60, 0x00, 0xF8, 0xFC, 0x00, 0x8E, 0xFF, 0x78, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x80, 0xF0,
    0xB8, 0xDC, 0xCE, 0xEF, 0xEF, 0xE7, 0xF7, 0xF7, 0xF6, 0xF0, 0x7C, 0x7E, 0x3F, 0x33, 0x1C, 0x07,
    0x0B, 0x04, 0x07, 0x01, 0x00, 0x00, 0xC0, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0x3F, 0x07, 0x03, 0x03,
    0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0xFE,
    0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

PROGMEM static const uint8_t imgTitle[216] = { // 72x24
    0x00, 0xFC, 0xFC, 0xFC, 0x6C, 0x6C, 0x6C, 0xFE, 0xFE, 0xFE, 0x6C, 0x6C, 0x6C, 0x6C, 0xFE, 0xFE,
    0xFE, 0x6C, 0x6C, 0x6C, 0xFC, 0xFC, 0xFC, 0x00, 0x00, 0xF0, 0xF6, 0xF6, 0x36, 0xB6, 0xB6, 0xB6,
    0xB6, 0x36, 0xFE, 0xFE, 0xFE, 0xFE, 0x36, 0xB6, 0xB6, 0xB6, 0xB6, 0x36, 0xF6, 0xF6, 0xF0, 0x00,
    0x00, 0xFE, 0xFE, 0xFE, 0xC6, 0xFE, 0xFE, 0xFE, 0xC6, 0xFE, 0xFE, 0xFE, 0x00, 0x06, 0x06, 0x86,
    0xC6, 0xE6, 0x76, 0x3E, 0x1E, 0x0E, 0x06, 0x00, 0x00, 0x1B, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB,
    0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0x1B, 0x00,
    0x00, 0x0F, 0x6F, 0x6F, 0x60, 0x6D, 0x6D, 0x6D, 0x6D, 0x60, 0x6F, 0x6F, 0x6F, 0x6F, 0x60, 0x6D,
    0x6D, 0x6D, 0x6D, 0x60, 0x6F, 0x6F, 0x0F, 0x00, 0x00, 0x9F, 0x9F, 0x9F, 0x98, 0xFF, 0xFF, 0xFF,
    0x98, 0x9F, 0x9F, 0x9F, 0x00, 0x03, 0x03, 0xC3, 0xE3, 0xF3, 0x3B, 0x1F, 0x0F, 0x07, 0x03, 0x00,
    0x00, 0x30, 0x37, 0x37, 0x37, 0x3F, 0x3E, 0x3E, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36, 0x36,
    0x3E, 0x3E, 0x3F, 0x37, 0x37, 0x37, 0x30, 0x00, 0x00, 0x03, 0x33, 0x3B, 0x3F, 0x3F, 0x3F, 0x37,
    0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x33, 0x37, 0x3F, 0x3F, 0x3F, 0x3B, 0x33, 0x03, 0x00,
    0x00, 0x31, 0x31, 0x31, 0x31, 0x3F, 0x3F, 0x3F, 0x31, 0x31, 0x31, 0x31, 0x00, 0x30, 0x30, 0x3F,
    0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

PROGMEM static const char creditText[] = \
        "- " APP_TITLE " -\0\0\0" APP_RELEASED "\0PROGRAMMED BY OBONO\0\0" \
        "THIS PROGRAM IS\0RELEASED UNDER\0THE MIT LICENSE.\0\e";

PROGMEM static void(*const handlerFuncTable[])(void) = {
    handleInit, handleTop, handleAnyButton, handleAnyButton
};

PROGMEM static void(*const drawerFuncTable[])(void) = {
    drawTop, drawTop, drawRecord, drawCredit
};

/*  Local Variables  */

static STATE_T  state;
static uint16_t titleFrames;
static uint8_t  dotsIndex;

/*---------------------------------------------------------------------------*/
/*                              Main Functions                               */
/*---------------------------------------------------------------------------*/

void initTitle(void)
{
    clearMenuItems();
    addMenuItem(F("INSTRUCTION"), onInstruction);
    addMenuItem(F("GAME START"), onStart);
    addMenuItem(F("RECORD"), onRecord);
    addMenuItem(F("CREDIT"), onCredit);
    setMenuCoords(50, 40, 78, 23, false, true);
    if (isTitleAnimation) {
        state = STATE_INIT;
        setMenuItemPos((record.hiscore[0] == 0) ? 0 : 1);
        counter = FPS;
    } else {
        setMenuItemPos(1);
        onTop();
    }
}

MODE_T updateTitle(void)
{
    callHandlerFunc(state);
    randomSeed(rand() ^ micros()); // Shuffle random
    return (state == STATE_STARTED) ? MODE_GAME : MODE_TITLE;
}

void drawTitle(void)
{
    if (state == STATE_STARTED) return;
    if (isInvalid) {
        ab.clear();
        callDrawerFunc(state);
    }
    if (state == STATE_TOP) drawMenuItems(isInvalid);
    isInvalid = false;
}

/*---------------------------------------------------------------------------*/
/*                             Control Functions                             */
/*---------------------------------------------------------------------------*/

static void handleInit(void)
{
    if (--counter == 0 || ab.buttonDown(A_BUTTON | B_BUTTON)) onTop();
    isInvalid = true;
}

static void handleTop(void)
{
    handleMenu();
    titleFrames++;
    updateTitleDots();
    if (random(16) == 0) {
        newTitleDot(28 + random(16), 40 + random(8));
    }
    isInvalid = true;
}

static void handleAnyButton(void)
{
    if (ab.buttonDown(A_BUTTON | B_BUTTON)) {
        playSoundClick();
        state = STATE_TOP;
        isInvalid = true;
    }
}

/*---------------------------------------------------------------------------*/

static void updateTitleDots(void)
{
    for (FLYING_T *p = dots; p < &dots[DOTS_MAX]; p++) {
        if (p->y > 0) p->y += p->vy;
    }
}

static void newTitleDot(int8_t x, int8_t y)
{
    FLYING_T *p = &dots[dotIndex];
    p->x = x << DECIMAL_BITS;
    p->y = y << DECIMAL_BITS;
    p->vx = 0;
    p->vy = -random(17) - 16;
    dotIndex = (dotIndex + 1) % DOTS_MAX;
}

/*---------------------------------------------------------------------------*/
/*                               Menu Handlers                               */
/*---------------------------------------------------------------------------*/

static void onTop(void)
{
    state = STATE_TOP;
    isTitleAnimation = false;
    memset(dots, 0, sizeof(dots));
    titleFrames = 0;
    isInvalid = true;
}

static void onInstruction(void)
{
    playSoundClick();
    isInstruction = true;
    state = STATE_STARTED;
}

static void onStart(void)
{
    isInstruction = false;
    state = STATE_STARTED;
}

static void onRecord(void)
{
    playSoundClick();
    state = STATE_RECORD;
    isInvalid = true;
}

static void onCredit(void)
{
    playSoundClick();
    state = STATE_CREDIT;
    isInvalid = true;
}

/*---------------------------------------------------------------------------*/
/*                              Draw Functions                               */
/*---------------------------------------------------------------------------*/

static void drawTop(void)
{
    int8_t goddessX, handX, handY, titleY;
    if (state == STATE_INIT) {
        goddessX = -32 + counter * 2;
        handX = 24 + counter * 5 / 2;
        handY = 40 + counter / 2;
        titleY = -counter;
    } else {
        goddessX = -32;
        handX = 20 + cos(titleFrames * 0.013f) * 4.0f;
        handY = 42 - cos(titleFrames * 0.007f) * 2.0f;
        titleY = 0;
    }
    ab.drawBitmap(goddessX, 0, imgGoddess1, IMG_GODDESS1_W, IMG_GODDESS1_H, WHITE);
    ab.drawBitmap(goddessX + 10, 40, imgGoddess2, IMG_GODDESS2_W, IMG_GODDESS2_H, WHITE);
    ab.drawBitmap(handX, handY, imgHand, IMG_HAND_W, IMG_HAND_H, WHITE);
    ab.drawBitmap(56, titleY, imgTitle, IMG_TITLE_W, IMG_TITLE_H, WHITE);
    if (state == STATE_TOP) {
        ab.printEx(62, IMG_TITLE_H, F(APP_TITLE));
        if (lastScore > 0) ab.printEx(0, 0, lastScore);
        drawDots();
    }
}

static void drawRecord(void)
{
    ab.printEx(22, 4, F("BEST 10 SCORES"));
    ab.drawFastHLine(0, 12, 128, WHITE);
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 5; j++) {
            int r = i * 5 + j;
            ab.printEx(i * 60 + 4 - (r == 9) * 6, j * 6 + 14, F("["));
            ab.print(r + 1);
            ab.print(F("] "));
            ab.print(record.hiscore[r]);
        }
    }
    ab.drawFastHLine(0, 44, 128, WHITE);
    ab.printEx(16, 48, F("PLAY COUNT "));
    ab.print(record.playCount);
    ab.printEx(16, 54, F("PLAY TIME"));
    drawTime(82, 54, record.playFrames);
}

static void drawCredit(void)
{
    drawText(creditText, 11);
}
