#include "global.h"
#include "main.h"
#include "constants/songs.h"
#include "constants/event_objects.h"
#include "mauville_old_man.h"
#include "event_data.h"
#include "string_util.h"
#include "text.h"
#include "easy_chat.h"
#include "script.h"
#include "random.h"
#include "event_scripts.h"
#include "task.h"
#include "menu.h"
#include "m4a.h"
#include "bard_music.h"
#include "sound.h"
#include "strings.h"
#include "overworld.h"
#include "field_message_box.h"
#include "script_menu.h"
#include "trader.h"
#include "m4a.h"
#include "constants/mauville_old_man.h"

static void InitGiddyTaleList(void);
static void StartBardSong(bool8 useTemporaryLyrics);
static void Task_BardSong(u8 taskId);
static void StorytellerSetup(void);
static void Storyteller_ResetFlag(void);

static u8 sSelectedStory;

struct BardSong gBardSong;

static EWRAM_DATA u16 sUnknownBardRelated = 0;
static EWRAM_DATA struct MauvilleManStoryteller * sStorytellerPtr = NULL;
static EWRAM_DATA u8 sStorytellerWindowId = 0;

static const u16 sDefaultBardSongLyrics[BARD_SONG_LENGTH] = {
    EC_WORD_SHAKE,
    EC_WORD_IT,
    EC_WORD_DO,
    EC_WORD_THE,
    EC_WORD_DIET,
    EC_WORD_DANCE
};

static const u8 * const sGiddyAdjectives[] = {
    GiddyText_SoPretty,
    GiddyText_SoDarling,
    GiddyText_SoRelaxed,
    GiddyText_SoSunny,
    GiddyText_SoDesirable,
    GiddyText_SoExciting,
    GiddyText_SoAmusing,
    GiddyText_SoMagical
};

// Non-random lines Giddy can say. Not all are strictly
// questions, but most are, and the player will receive
// a Yes/No prompt afterwards regardless.
static const u8 * const sGiddyQuestions[GIDDY_MAX_QUESTIONS] = {
    GiddyText_ISoWantToGoOnAVacation,
    GiddyText_IBoughtCrayonsWith120Colors,
    GiddyText_WouldntItBeNiceIfWeCouldFloat,
    GiddyText_WhenYouWriteOnASandyBeach,
    GiddyText_WhatsTheBottomOfTheSeaLike,
    GiddyText_WhenYouSeeTheSettingSunDoesIt,
    GiddyText_LyingBackInTheGreenGrass,
    GiddyText_SecretBasesAreSoWonderful
};

static void SetupBard(void)
{
    u16 i;
    struct MauvilleManBard *bard = &gSaveBlock1Ptr->oldMan.bard;

    bard->id = MAUVILLE_MAN_BARD;
    bard->hasChangedSong = FALSE;
    bard->language = gGameLanguage;
    for (i = 0; i < BARD_SONG_LENGTH; i++)
        bard->songLyrics[i] = sDefaultBardSongLyrics[i];
}

static void SetupHipster(void)
{
    struct MauvilleManHipster *hipster = &gSaveBlock1Ptr->oldMan.hipster;

    hipster->id = MAUVILLE_MAN_HIPSTER;
    hipster->alreadySpoken = FALSE;
    hipster->language = gGameLanguage;
}

static void SetupStoryteller(void)
{
    StorytellerSetup();
}

static void SetupGiddy(void)
{
    struct MauvilleManGiddy *giddy = &gSaveBlock1Ptr->oldMan.giddy;

    giddy->id = MAUVILLE_MAN_GIDDY;
    giddy->taleCounter = 0;
    giddy->language = gGameLanguage;
}

static void SetupTrader(void)
{
    TraderSetup();
}

void SetMauvilleOldMan(void)
{
    u16 trainerId = (gSaveBlock2Ptr->playerTrainerId[1] << 8) | gSaveBlock2Ptr->playerTrainerId[0];


    // Determine man based on the last digit of the player's trainer ID.
    switch ((trainerId % 10) / 2)
    {
    case MAUVILLE_MAN_BARD:
        SetupBard();
        break;
    case MAUVILLE_MAN_HIPSTER:
        SetupHipster();
        break;
    case MAUVILLE_MAN_TRADER:
        SetupTrader();
        break;
    case MAUVILLE_MAN_STORYTELLER:
        SetupStoryteller();
        break;
    case MAUVILLE_MAN_GIDDY:
        SetupGiddy();
        break;
    }
    SetMauvilleOldManObjEventGfx();
}

u8 GetCurrentMauvilleOldMan(void)
{
    return gSaveBlock1Ptr->oldMan.common.id;
}

void Script_GetCurrentMauvilleMan(void)
{
    gSpecialVar_Result = GetCurrentMauvilleOldMan();
}

void HasBardSongBeenChanged(void)
{
    gSpecialVar_Result = (&gSaveBlock1Ptr->oldMan.bard)->hasChangedSong;
}

void SaveBardSongLyrics(void)
{
    u16 i;
    struct MauvilleManBard *bard = &gSaveBlock1Ptr->oldMan.bard;

    StringCopy(bard->playerName, gSaveBlock2Ptr->playerName);

    for (i = 0; i < TRAINER_ID_LENGTH; i++)
        bard->playerTrainerId[i] = gSaveBlock2Ptr->playerTrainerId[i];

    for (i = 0; i < BARD_SONG_LENGTH; i++)
        bard->songLyrics[i] = bard->temporaryLyrics[i];

    bard->hasChangedSong = TRUE;
}

// Copies lyrics into gStringVar4
static void PrepareSongText(void)
{
    struct MauvilleManBard *bard = &gSaveBlock1Ptr->oldMan.bard;
    u16 * lyrics = gSpecialVar_0x8004 == 0 ? bard->songLyrics : bard->temporaryLyrics;
    u8 * wordEnd = gStringVar4;
    u8 * str = wordEnd;
    u16 lineNum;

    // Put three words on each line
    for (lineNum = 0; lineNum < 2; lineNum++)
    {
        wordEnd = CopyEasyChatWord(wordEnd, *(lyrics++));
        while (wordEnd != str)
        {
            if (*str == CHAR_SPACE)
                *str = CHAR_BARD_WORD_DELIMIT;
            str++;
        }

        str++;
        *(wordEnd++) = CHAR_SPACE;

        wordEnd = CopyEasyChatWord(wordEnd, *(lyrics++));
        while (wordEnd != str)
        {
            if (*str == CHAR_SPACE)
                *str = CHAR_BARD_WORD_DELIMIT;
            str++;
        }

        str++;
        *(wordEnd++) = CHAR_NEWLINE;

        wordEnd = CopyEasyChatWord(wordEnd, *(lyrics++));
        while (wordEnd != str)
        {
            if (*str == CHAR_SPACE)
                *str = CHAR_BARD_WORD_DELIMIT;
            str++;
        }

        if (lineNum == 0)
        {
            *(wordEnd++) = EXT_CTRL_CODE_BEGIN;
            *(wordEnd++) = EXT_CTRL_CODE_FILL_WINDOW;
        }
    }
}

void PlayBardSong(void)
{
    StartBardSong(gSpecialVar_0x8004);
    ScriptContext1_Stop();
}

void GetHipsterSpokenFlag(void)
{
    gSpecialVar_Result = (&gSaveBlock1Ptr->oldMan.hipster)->alreadySpoken;
}

void SetHipsterSpokenFlag(void)
{
    (&gSaveBlock1Ptr->oldMan.hipster)->alreadySpoken = TRUE;
}

void HipsterTryTeachWord(void)
{
    u16 phrase = GetNewHipsterPhraseToTeach();

    if (phrase == EC_EMPTY_WORD)
    {
        gSpecialVar_Result = FALSE;
    }
    else
    {
        CopyEasyChatWord(gStringVar1, phrase);
        gSpecialVar_Result = TRUE;
    }
}

void GiddyShouldTellAnotherTale(void)
{
    struct MauvilleManGiddy *giddy = &gSaveBlock1Ptr->oldMan.giddy;

    if (giddy->taleCounter == GIDDY_MAX_TALES)
    {
        gSpecialVar_Result = FALSE;
        giddy->taleCounter = 0;
    }
    else
    {
        gSpecialVar_Result = TRUE;
    }
}

void GenerateGiddyLine(void)
{
    struct MauvilleManGiddy *giddy = &gSaveBlock1Ptr->oldMan.giddy;

    if (giddy->taleCounter == 0)
        InitGiddyTaleList();

    // A line from Giddy is either a line following this format:
    // "{random word} is so {adjective}! Don't you agree?",
    // or one of the texts in sGiddyQuestions.
    if (giddy->randomWords[giddy->taleCounter] != EC_EMPTY_WORD)
    {
        u8 *stringPtr;
        u32 adjective = Random();
        adjective %= ARRAY_COUNT(sGiddyAdjectives);

        stringPtr = CopyEasyChatWord(gStringVar4, giddy->randomWords[giddy->taleCounter]);
        stringPtr = StringCopy(stringPtr, GiddyText_Is);
        stringPtr = StringCopy(stringPtr, sGiddyAdjectives[adjective]);
        StringCopy(stringPtr, GiddyText_DontYouAgree);
    }
    else
    {
        StringCopy(gStringVar4, sGiddyQuestions[giddy->questionList[giddy->questionNum++]]);
    }

    // 10% chance for Giddy to stop telling tales.
    if (!(Random() % 10))
        giddy->taleCounter = GIDDY_MAX_TALES;
    else
        giddy->taleCounter++;

    gSpecialVar_Result = TRUE;
}

static void InitGiddyTaleList(void)
{
    struct MauvilleManGiddy *giddy = &gSaveBlock1Ptr->oldMan.giddy;
    u16 wordGroupsAndCount[][2] = {
        {EC_GROUP_POKEMON,   0},
        {EC_GROUP_LIFESTYLE, 0},
        {EC_GROUP_HOBBIES,   0},
        {EC_GROUP_MOVE_1,    0},
        {EC_GROUP_MOVE_2,    0},
        {EC_GROUP_POKEMON_NATIONAL, 0}
    };
    u16 i;
    u16 totalWords;
    u16 temp;
    u16 var; // re-used

    // Shuffle question list
    for (i = 0; i < GIDDY_MAX_QUESTIONS; i++)
        giddy->questionList[i] = i;
    for (i = 0; i < GIDDY_MAX_QUESTIONS; i++)
    {
        var = Random() % (i + 1);
        SWAP(giddy->questionList[i], giddy->questionList[var], temp);
    }

    // Count total number of words in above word groups
    totalWords = 0;
    for (i = 0; i < ARRAY_COUNT(wordGroupsAndCount); i++)
    {
        wordGroupsAndCount[i][1] = EasyChat_GetNumWordsInGroup(wordGroupsAndCount[i][0]);
        totalWords += wordGroupsAndCount[i][1];
    }

    giddy->questionNum = 0;
    temp = 0;
    for (i = 0; i < GIDDY_MAX_TALES; i++)
    {
        var = Random() % 10;
        if (var < 3 && temp < GIDDY_MAX_QUESTIONS)
        {
            // 30% chance for word to be empty (in which case Giddy
            // will say one of his non-random questions), unless
            // the limit for questions has been reached already.
            giddy->randomWords[i] = EC_EMPTY_WORD;
            temp++;
        }
        else
        {
            // Pick a random word id, then advance through the word
            // groups until the group where that id landed.
            s16 randWord = Random() % totalWords;
            for (var = 0; i < ARRAY_COUNT(wordGroupsAndCount); var++)
                if ((randWord -= wordGroupsAndCount[var][1]) <= 0)
                    break;
            if (var == ARRAY_COUNT(wordGroupsAndCount))
                var = 0;

            // Save the randomly selected word
            giddy->randomWords[i] = GetRandomEasyChatWordFromUnlockedGroup(wordGroupsAndCount[var][0]);
        }
    }
}
static void ResetBardFlag(void)
{
    (&gSaveBlock1Ptr->oldMan.bard)->hasChangedSong = FALSE;
}

static void ResetHipsterFlag(void)
{
    (&gSaveBlock1Ptr->oldMan.hipster)->alreadySpoken = FALSE;
}

static void ResetTraderFlag(void)
{
    Trader_ResetFlag();
}

static void ResetStorytellerFlag(void)
{
    Storyteller_ResetFlag();
}

void ResetMauvilleOldManFlag(void)
{
    switch (GetCurrentMauvilleOldMan())
    {
    case MAUVILLE_MAN_BARD:
        ResetBardFlag();
        break;
    case MAUVILLE_MAN_HIPSTER:
        ResetHipsterFlag();
        break;
    case MAUVILLE_MAN_STORYTELLER:
        ResetStorytellerFlag();
        break;
    case MAUVILLE_MAN_TRADER:
        ResetTraderFlag();
        break;
    case MAUVILLE_MAN_GIDDY:
        break;
    }
    SetMauvilleOldManObjEventGfx();
}

// States and task data for Task_BardSong.
// The function BardSing receives this task as an
// argument and reads its state as well.
enum {
    BARD_STATE_INIT,
    BARD_STATE_WAIT_BGM,
    BARD_STATE_GET_WORD,
    BARD_STATE_HANDLE_WORD,
    BARD_STATE_WAIT_WORD,
    BARD_STATE_PAUSE,
};

#define tState              data[0]
#define tWordState          data[1]
#define tDelay              data[2]
#define tCharIndex          data[3]
#define tCurrWord           data[4]
#define tUseTemporaryLyrics data[5]

#define MACRO1(a) (((a) & 3) + (((a) / 8) & 1))
#define MACRO2(a) (((a) % 4) + (((a) / 8) & 1))

static void StartBardSong(bool8 useTemporaryLyrics)
{
    u8 taskId = CreateTask(Task_BardSong, 80);

    gTasks[taskId].tUseTemporaryLyrics = useTemporaryLyrics;
}

static void EnableTextPrinters(void)
{
    gDisableTextPrinters = FALSE;
}

static void DisableTextPrinters(struct TextPrinterTemplate * printer, u16 a1)
{
    gDisableTextPrinters = TRUE;
}

static void DrawSongTextWindow(const u8 * str)
{
    DrawDialogueFrame(0, 0);
    AddTextPrinterParameterized(0, FONT_NORMAL, str, 0, 1, 1, DisableTextPrinters);
    gDisableTextPrinters = TRUE;
    CopyWindowToVram(0, COPYWIN_FULL);
}

static void BardSing(struct Task *task, struct BardSong *song)
{
    switch (task->tState)
    {
    case BARD_STATE_INIT:
    {
        struct MauvilleManBard *bard = &gSaveBlock1Ptr->oldMan.bard;
        u16 *lyrics;
        s32 i;

        // Copy lyrics
        if (gSpecialVar_0x8004 == 0)
            lyrics = bard->songLyrics;
        else
            lyrics = bard->temporaryLyrics;
        for (i = 0; i < BARD_SONG_LENGTH; i++)
            song->lyrics[i] = lyrics[i];
        song->currWord = 0;
    }
        break;
    case BARD_STATE_WAIT_BGM:
        break;
    case BARD_STATE_GET_WORD:
    {
        u16 word = song->lyrics[song->currWord];
        song->sound = GetWordSounds(word);
        GetWordPhonemes(song, MACRO1(word));
        song->currWord++;
        if (song->sound->var00 != 0xFF)
            song->state = 0;
        else
        {
            song->state = 3;
            song->phonemeTimer = 2;
        }
        break;
    }
    case BARD_STATE_HANDLE_WORD:
    case BARD_STATE_WAIT_WORD:
    {
        const struct BardSound *sound = &song->sound[song->currPhoneme];

        switch (song->state)
        {
        case 0:
            song->phonemeTimer = song->phonemes[song->currPhoneme].length;
            if (sound->var00 <= 50)
            {
                u8 num = sound->var00 / 3;
                m4aSongNumStart(PH_TRAP_HELD + 3 * num);
            }
            song->state = 2;
            song->phonemeTimer--;
            break;
        case 2:
            song->state = 1;
            if (sound->var00 <= 50)
            {
                song->volume = 0x100 + sound->volume * 16;
                m4aMPlayVolumeControl(&gMPlayInfo_SE2, TRACKS_ALL, song->volume);
                song->pitch = 0x200 + song->phonemes[song->currPhoneme].pitch;
                m4aMPlayPitchControl(&gMPlayInfo_SE2, TRACKS_ALL, song->pitch);
            }
            break;
        case 1:
            if (song->voiceInflection > 10)
                song->volume -= 2;
            if (song->voiceInflection & 1)
                song->pitch += 64;
            else
                song->pitch -= 64;
            m4aMPlayVolumeControl(&gMPlayInfo_SE2, TRACKS_ALL, song->volume);
            m4aMPlayPitchControl(&gMPlayInfo_SE2, TRACKS_ALL, song->pitch);
            song->voiceInflection++;
            song->phonemeTimer--;
            if (song->phonemeTimer == 0)
            {
                song->currPhoneme++;
                if (song->currPhoneme != 6 && song->sound[song->currPhoneme].var00 != 0xFF)
                    song->state = 0;
                else
                {
                    song->state = 3;
                    song->phonemeTimer = 2;
                }
            }
            break;
        case 3:
            song->phonemeTimer--;
            if (song->phonemeTimer == 0)
            {
                m4aMPlayStop(&gMPlayInfo_SE2);
                song->state = 4;
            }
            break;
        }
    }
        break;
    case BARD_STATE_PAUSE:
        break;
    }
}

static void Task_BardSong(u8 taskId)
{
    struct Task *task = &gTasks[taskId];

    BardSing(task, &gBardSong);

    switch (task->tState)
    {
    case BARD_STATE_INIT:
        PrepareSongText();
        DrawSongTextWindow(gStringVar4);
        task->tWordState = 0;
        task->tDelay = 0;
        task->tCharIndex = 0;
        task->tCurrWord = 0;
        FadeOutBGMTemporarily(4);
        task->tState = BARD_STATE_WAIT_BGM;
        break;
    case BARD_STATE_WAIT_BGM:
        if (IsBGMPausedOrStopped())
            task->tState = BARD_STATE_GET_WORD;
        break;
    case BARD_STATE_GET_WORD:
    {
        struct MauvilleManBard *bard = &gSaveBlock1Ptr->oldMan.bard;
        u8 *str = &gStringVar4[task->tCharIndex];
        u16 wordLen = 0;

        // Read letters until delimiter
        while (*str != CHAR_SPACE
            && *str != CHAR_NEWLINE
            && *str != EXT_CTRL_CODE_BEGIN
            && *str != EOS)
        {
            str++;
            wordLen++;
        }

        if (!task->tUseTemporaryLyrics)
            sUnknownBardRelated = MACRO2(bard->songLyrics[task->tCurrWord]);
        else
            sUnknownBardRelated = MACRO2(bard->temporaryLyrics[task->tCurrWord]);

        gBardSong.length /= wordLen;
        if (gBardSong.length <= 0)
            gBardSong.length = 1;
        task->tCurrWord++;

        if (task->tDelay == 0)
        {
            task->tState = BARD_STATE_HANDLE_WORD;
            task->tWordState = 0;
        }
        else
        {
            task->tState = BARD_STATE_PAUSE;
            task->tWordState = 0;
        }
    }
        break;
    case BARD_STATE_PAUSE:
        // Wait before singing next word
        if (task->tDelay == 0)
            task->tState = BARD_STATE_HANDLE_WORD;
        else
            task->tDelay--;
        break;
    case BARD_STATE_HANDLE_WORD:
        if (gStringVar4[task->tCharIndex] == EOS)
        {
            // End song
            FadeInBGM(6);
            m4aMPlayFadeOutTemporarily(&gMPlayInfo_SE2, 2);
            EnableBothScriptContexts();
            DestroyTask(taskId);
        }
        else if (gStringVar4[task->tCharIndex] == CHAR_SPACE)
        {
            // Handle space
            EnableTextPrinters();
            task->tCharIndex++;
            task->tState = BARD_STATE_GET_WORD;
            task->tDelay = 0;
        }
        else if (gStringVar4[task->tCharIndex] == CHAR_NEWLINE)
        {
            // Handle newline
            task->tCharIndex++;
            task->tState = BARD_STATE_GET_WORD;
            task->tDelay = 0;
        }
        else if (gStringVar4[task->tCharIndex] == EXT_CTRL_CODE_BEGIN)
        {
            // Handle ctrl code
            task->tCharIndex += 2;  // skip over control codes
            task->tState = BARD_STATE_GET_WORD;
            task->tDelay = 8;
        }
        else if (gStringVar4[task->tCharIndex] == CHAR_BARD_WORD_DELIMIT)
        {
            // Handle word boundary
            gStringVar4[task->tCharIndex] = CHAR_SPACE;  // Replace with a real space
            EnableTextPrinters();
            task->tCharIndex++;
            task->tDelay = 0;
        }
        else
        {
            // Handle regular word
            switch (task->tWordState)
            {
            case 0:
                EnableTextPrinters();
                task->tWordState++;
                break;
            case 1:
                task->tWordState++;
                break;
            case 2:
                task->tCharIndex++;
                task->tWordState = 0;
                task->tDelay = gBardSong.length;
                task->tState = BARD_STATE_WAIT_WORD;
                break;
            }
        }
        break;
    case BARD_STATE_WAIT_WORD:
        // Wait for word to finish being sung.
        // BardSing will continue to play it.
        task->tDelay--;
        if (task->tDelay == 0)
            task->tState = BARD_STATE_HANDLE_WORD;
        break;
    }
    RunTextPrintersAndIsPrinter0Active();
}

void SetMauvilleOldManObjEventGfx(void)
{
    VarSet(VAR_OBJ_GFX_ID_0, OBJ_EVENT_GFX_BARD);
}

// Language fixers?

void SanitizeMauvilleOldManForRuby(union OldMan * oldMan)
{
    s32 i;
    u8 playerName[PLAYER_NAME_LENGTH + 1];

    switch (oldMan->common.id)
    {
    case MAUVILLE_MAN_TRADER:
    {
        struct MauvilleOldManTrader * trader = &oldMan->trader;
        for (i = 0; i < NUM_TRADER_ITEMS; i++)
        {
            if (trader->language[i] == LANGUAGE_JAPANESE)
                ConvertInternationalString(trader->playerNames[i], LANGUAGE_JAPANESE);
        }
        break;
    }
    case MAUVILLE_MAN_STORYTELLER:
    {
        struct MauvilleManStoryteller * storyteller = &oldMan->storyteller;
        for (i = 0; i < NUM_STORYTELLER_TALES; i++)
        {
            if (storyteller->gameStatIDs[i] != 0)
            {
                memcpy(playerName, storyteller->trainerNames[i], PLAYER_NAME_LENGTH);
                playerName[PLAYER_NAME_LENGTH] = EOS;
                if (IsStringJapanese(playerName))
                {
                    memset(playerName, CHAR_SPACE, PLAYER_NAME_LENGTH + 1);
                    StringCopy(playerName, gText_Friend);
                    memcpy(storyteller->trainerNames[i], playerName, PLAYER_NAME_LENGTH);
                    storyteller->language[i] = GAME_LANGUAGE;
                }
            }
        }
        break;
    }
    }
}

// Unused
static void SetMauvilleOldManLanguage(union OldMan * oldMan, u32 language1, u32 language2, u32 language3)
{
    s32 i;

    switch (oldMan->common.id)
    {
    case MAUVILLE_MAN_TRADER:
    {
        struct MauvilleOldManTrader * trader = &oldMan->trader;

        for (i = 0; i < NUM_TRADER_ITEMS; i++)
        {
            if (IsStringJapanese(trader->playerNames[i]))
                trader->language[i] = language1;
            else
                trader->language[i] = language2;
        }
    }
    break;
    case MAUVILLE_MAN_STORYTELLER:
    {
        struct MauvilleManStoryteller * storyteller = &oldMan->storyteller;

        for (i = 0; i < NUM_STORYTELLER_TALES; i++)
        {
            if (IsStringJapanese(storyteller->trainerNames[i]))
                storyteller->language[i] = language1;
            else
                storyteller->language[i] = language2;
        }
    }
    break;
    case MAUVILLE_MAN_BARD:
    {
        struct MauvilleManBard * bard = &oldMan->bard;

        if (language3 == LANGUAGE_JAPANESE)
            bard->language = language1;
        else
            bard->language = language2;
    }
    break;
    case MAUVILLE_MAN_HIPSTER:
    {
        struct MauvilleManHipster * hipster = &oldMan->hipster;

        if (language3 == LANGUAGE_JAPANESE)
            hipster->language = language1;
        else
            hipster->language = language2;
    }
    break;
    case MAUVILLE_MAN_GIDDY:
    {
        struct MauvilleManGiddy * giddy = &oldMan->giddy;

        if (language3 == LANGUAGE_JAPANESE)
            giddy->language = language1;
        else
            giddy->language = language2;
    }
    break;
    }
}

void SanitizeReceivedEmeraldOldMan(union OldMan * oldMan, u32 version, u32 language)
{
    u8 playerName[PLAYER_NAME_LENGTH + 1];
    s32 i;
    if (oldMan->common.id == MAUVILLE_MAN_STORYTELLER && language == LANGUAGE_JAPANESE)
    {
        struct MauvilleManStoryteller * storyteller = &oldMan->storyteller;

        for (i = 0; i < NUM_STORYTELLER_TALES; i++)
        {
            if (storyteller->gameStatIDs[i] != 0)
            {
                memcpy(playerName, storyteller->trainerNames[i], PLAYER_NAME_LENGTH);
                playerName[PLAYER_NAME_LENGTH] = EOS;
                if (IsStringJapanese(playerName))
                    storyteller->language[i] = LANGUAGE_JAPANESE;
                else
                    storyteller->language[i] = GAME_LANGUAGE;
            }
        }
    }
}

void SanitizeReceivedRubyOldMan(union OldMan * oldMan, u32 version, u32 language)
{
    bool32 isRuby = (version == VERSION_SAPPHIRE || version == VERSION_RUBY);

    switch (oldMan->common.id)
    {
    case MAUVILLE_MAN_TRADER:
    {
        struct MauvilleOldManTrader * trader = &oldMan->trader;
        s32 i;

        if (isRuby)
        {
            for (i = 0; i < NUM_TRADER_ITEMS; i++)
            {
                u8 * str = trader->playerNames[i];
                if (str[0] == EXT_CTRL_CODE_BEGIN && str[1] == EXT_CTRL_CODE_JPN)
                {
                    StripExtCtrlCodes(str);
                    trader->language[i] = LANGUAGE_JAPANESE;
                }
                else
                    trader->language[i] = language;
            }
        }
        else
        {
            for (i = 0; i < NUM_TRADER_ITEMS; i++)
            {
                if (trader->language[i] == LANGUAGE_JAPANESE)
                {
                    StripExtCtrlCodes(trader->playerNames[i]);
                }
            }
        }
    }
    break;
    case MAUVILLE_MAN_STORYTELLER:
    {

        struct MauvilleManStoryteller * storyteller = &oldMan->storyteller;
        s32 i;

        if (isRuby)
        {
            for (i = 0; i < NUM_STORYTELLER_TALES; i++)
            {
                if (storyteller->gameStatIDs[i] != 0)
                    storyteller->language[i] = language;
            }
        }
    }
    break;
    case MAUVILLE_MAN_BARD:
    {
        struct MauvilleManBard * bard = &oldMan->bard;

        if (isRuby)
        {
            bard->language = language;
        }
    }
    break;
    case MAUVILLE_MAN_HIPSTER:
    {
        struct MauvilleManHipster * hipster = &oldMan->hipster;

        if (isRuby)
        {
            hipster->language = language;
        }
    }
    break;
    case MAUVILLE_MAN_GIDDY:
    {
        struct MauvilleManGiddy * giddy = &oldMan->giddy;

        if (isRuby)
        {
            giddy->language = language;
        }
    }
    break;
    }
}

struct Story
{
    u8 stat;
    u8 minVal;
    const u8 *title;
    const u8 *action;
    const u8 *fullText;
};

static const struct Story sStorytellerStories[] = {
    // The 50 below is replaced with GAME_STAT_SAVED_GAME
    {
        50, 1,
        MauvilleCity_PokemonCenter_1F_Text_SavedGameTitle,
        MauvilleCity_PokemonCenter_1F_Text_SavedGameAction,
        MauvilleCity_PokemonCenter_1F_Text_SavedGameStory
    },
    {
        GAME_STAT_STARTED_TRENDS, 1,
        MauvilleCity_PokemonCenter_1F_Text_TrendsStartedTitle,
        MauvilleCity_PokemonCenter_1F_Text_TrendsStartedAction,
        MauvilleCity_PokemonCenter_1F_Text_TrendsStartedStory
    },
    {
        GAME_STAT_PLANTED_BERRIES, 1,
        MauvilleCity_PokemonCenter_1F_Text_BerriesPlantedTitle,
        MauvilleCity_PokemonCenter_1F_Text_BerriesPlantedAction,
        MauvilleCity_PokemonCenter_1F_Text_BerriesPlantedStory
    },
    {
        GAME_STAT_TRADED_BIKES, 1,
        MauvilleCity_PokemonCenter_1F_Text_BikeTradesTitle,
        MauvilleCity_PokemonCenter_1F_Text_BikeTradesAction,
        MauvilleCity_PokemonCenter_1F_Text_BikeTradesStory
    },
    {
        GAME_STAT_GOT_INTERVIEWED, 1,
        MauvilleCity_PokemonCenter_1F_Text_InterviewsTitle,
        MauvilleCity_PokemonCenter_1F_Text_InterviewsAction,
        MauvilleCity_PokemonCenter_1F_Text_InterviewsStory
    },
    {
        GAME_STAT_TRAINER_BATTLES, 1,
        MauvilleCity_PokemonCenter_1F_Text_TrainerBattlesTitle,
        MauvilleCity_PokemonCenter_1F_Text_TrainerBattlesAction,
        MauvilleCity_PokemonCenter_1F_Text_TrainerBattlesStory
    },
    {
        GAME_STAT_POKEMON_CAPTURES, 1,
        MauvilleCity_PokemonCenter_1F_Text_PokemonCaughtTitle,
        MauvilleCity_PokemonCenter_1F_Text_PokemonCaughtAction,
        MauvilleCity_PokemonCenter_1F_Text_PokemonCaughtStory
    },
    {
        GAME_STAT_FISHING_CAPTURES, 1,
        MauvilleCity_PokemonCenter_1F_Text_FishingPokemonCaughtTitle,
        MauvilleCity_PokemonCenter_1F_Text_FishingPokemonCaughtAction,
        MauvilleCity_PokemonCenter_1F_Text_FishingPokemonCaughtStory
    },
    {
        GAME_STAT_HATCHED_EGGS, 1,
        MauvilleCity_PokemonCenter_1F_Text_EggsHatchedTitle,
        MauvilleCity_PokemonCenter_1F_Text_EggsHatchedAction,
        MauvilleCity_PokemonCenter_1F_Text_EggsHatchedStory
    },
    {
        GAME_STAT_EVOLVED_POKEMON, 1,
        MauvilleCity_PokemonCenter_1F_Text_PokemonEvolvedTitle,
        MauvilleCity_PokemonCenter_1F_Text_PokemonEvolvedAction,
        MauvilleCity_PokemonCenter_1F_Text_PokemonEvolvedStory
    },
    {
        GAME_STAT_USED_POKECENTER, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedPokemonCenterTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedPokemonCenterAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedPokemonCenterStory
    },
    {
        GAME_STAT_RESTED_AT_HOME, 1,
        MauvilleCity_PokemonCenter_1F_Text_RestedAtHomeTitle,
        MauvilleCity_PokemonCenter_1F_Text_RestedAtHomeAction,
        MauvilleCity_PokemonCenter_1F_Text_RestedAtHomeStory
    },
    {
        GAME_STAT_ENTERED_SAFARI_ZONE, 1,
        MauvilleCity_PokemonCenter_1F_Text_SafariGamesTitle,
        MauvilleCity_PokemonCenter_1F_Text_SafariGamesAction,
        MauvilleCity_PokemonCenter_1F_Text_SafariGamesStory
    },
    {
        GAME_STAT_USED_CUT, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedCutTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedCutAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedCutStory
    },
    {
        GAME_STAT_USED_ROCK_SMASH, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedRockSmashTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedRockSmashAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedRockSmashStory
    },
    {
        GAME_STAT_MOVED_SECRET_BASE, 1,
        MauvilleCity_PokemonCenter_1F_Text_MovedBasesTitle,
        MauvilleCity_PokemonCenter_1F_Text_MovedBasesAction,
        MauvilleCity_PokemonCenter_1F_Text_MovedBasesStory
    },
    {
        GAME_STAT_USED_SPLASH, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedSplashTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedSplashAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedSplashStory
    },
    {
        GAME_STAT_USED_STRUGGLE, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedStruggleTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedStruggleAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedStruggleStory
    },
    {
        GAME_STAT_SLOT_JACKPOTS, 1,
        MauvilleCity_PokemonCenter_1F_Text_SlotJackpotsTitle,
        MauvilleCity_PokemonCenter_1F_Text_SlotJackpotsAction,
        MauvilleCity_PokemonCenter_1F_Text_SlotJackpotsStory
    },
    {
        GAME_STAT_CONSECUTIVE_ROULETTE_WINS, 2,
        MauvilleCity_PokemonCenter_1F_Text_RouletteWinsTitle,
        MauvilleCity_PokemonCenter_1F_Text_RouletteWinsAction,
        MauvilleCity_PokemonCenter_1F_Text_RouletteWinsStory
    },
    {
        GAME_STAT_ENTERED_BATTLE_TOWER, 1,
        MauvilleCity_PokemonCenter_1F_Text_BattleTowerChallengesTitle,
        MauvilleCity_PokemonCenter_1F_Text_BattleTowerChallengesAction,
        MauvilleCity_PokemonCenter_1F_Text_BattleTowerChallengesStory
    },
    {
        GAME_STAT_POKEBLOCKS, 1,
        MauvilleCity_PokemonCenter_1F_Text_MadePokeblocksTitle,
        MauvilleCity_PokemonCenter_1F_Text_MadePokeblocksAction,
        MauvilleCity_PokemonCenter_1F_Text_MadePokeblocksStory
    },
    {
        GAME_STAT_ENTERED_CONTEST, 1,
        MauvilleCity_PokemonCenter_1F_Text_EnteredContestsTitle,
        MauvilleCity_PokemonCenter_1F_Text_EnteredContestsAction,
        MauvilleCity_PokemonCenter_1F_Text_EnteredContestsStory
    },
    {
        GAME_STAT_WON_CONTEST, 1,
        MauvilleCity_PokemonCenter_1F_Text_WonContestsTitle,
        MauvilleCity_PokemonCenter_1F_Text_WonContestsAction,
        MauvilleCity_PokemonCenter_1F_Text_WonContestsStory
    },
    {
        GAME_STAT_SHOPPED, 1,
        MauvilleCity_PokemonCenter_1F_Text_TimesShoppedTitle,
        MauvilleCity_PokemonCenter_1F_Text_TimesShoppedAction,
        MauvilleCity_PokemonCenter_1F_Text_TimesShoppedStory
    },
    {
        GAME_STAT_USED_ITEMFINDER, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedItemFinderTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedItemFinderAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedItemFinderStory
    },
    {
        GAME_STAT_GOT_RAINED_ON, 1,
        MauvilleCity_PokemonCenter_1F_Text_TimesRainedTitle,
        MauvilleCity_PokemonCenter_1F_Text_TimesRainedAction,
        MauvilleCity_PokemonCenter_1F_Text_TimesRainedStory
    },
    {
        GAME_STAT_CHECKED_POKEDEX, 1,
        MauvilleCity_PokemonCenter_1F_Text_CheckedPokedexTitle,
        MauvilleCity_PokemonCenter_1F_Text_CheckedPokedexAction,
        MauvilleCity_PokemonCenter_1F_Text_CheckedPokedexStory
    },
    {
        GAME_STAT_RECEIVED_RIBBONS, 1,
        MauvilleCity_PokemonCenter_1F_Text_ReceivedRibbonsTitle,
        MauvilleCity_PokemonCenter_1F_Text_ReceivedRibbonsAction,
        MauvilleCity_PokemonCenter_1F_Text_ReceivedRibbonsStory
    },
    {
        GAME_STAT_JUMPED_DOWN_LEDGES, 1,
        MauvilleCity_PokemonCenter_1F_Text_LedgesJumpedTitle,
        MauvilleCity_PokemonCenter_1F_Text_LedgesJumpedAction,
        MauvilleCity_PokemonCenter_1F_Text_LedgesJumpedStory
    },
    {
        GAME_STAT_WATCHED_TV, 1,
        MauvilleCity_PokemonCenter_1F_Text_TVWatchedTitle,
        MauvilleCity_PokemonCenter_1F_Text_TVWatchedAction,
        MauvilleCity_PokemonCenter_1F_Text_TVWatchedStory
    },
    {
        GAME_STAT_CHECKED_CLOCK, 1,
        MauvilleCity_PokemonCenter_1F_Text_CheckedClockTitle,
        MauvilleCity_PokemonCenter_1F_Text_CheckedClockAction,
        MauvilleCity_PokemonCenter_1F_Text_CheckedClockStory
    },
    {
        GAME_STAT_WON_POKEMON_LOTTERY, 1,
        MauvilleCity_PokemonCenter_1F_Text_WonLotteryTitle,
        MauvilleCity_PokemonCenter_1F_Text_WonLotteryAction,
        MauvilleCity_PokemonCenter_1F_Text_WonLotteryStory
    },
    {
        GAME_STAT_USED_DAYCARE, 1,
        MauvilleCity_PokemonCenter_1F_Text_UsedDaycareTitle,
        MauvilleCity_PokemonCenter_1F_Text_UsedDaycareAction,
        MauvilleCity_PokemonCenter_1F_Text_UsedDaycareStory
    },
    {
        GAME_STAT_RODE_CABLE_CAR, 1,
        MauvilleCity_PokemonCenter_1F_Text_RodeCableCarTitle,
        MauvilleCity_PokemonCenter_1F_Text_RodeCableCarAction,
        MauvilleCity_PokemonCenter_1F_Text_RodeCableCarStory
    },
    {
        GAME_STAT_ENTERED_HOT_SPRINGS, 1,
        MauvilleCity_PokemonCenter_1F_Text_HotSpringsTitle,
        MauvilleCity_PokemonCenter_1F_Text_HotSpringsAction,
        MauvilleCity_PokemonCenter_1F_Text_HotSpringsStory
    }
};

static const s32 sNumStories = ARRAY_COUNT(sStorytellerStories);
static const u32 sUnused = 8;

static void StorytellerSetup(void)
{
    s32 i;
    sStorytellerPtr = &gSaveBlock1Ptr->oldMan.storyteller;

    sStorytellerPtr->id = MAUVILLE_MAN_STORYTELLER;
    sStorytellerPtr->alreadyRecorded = FALSE;
    for (i = 0; i < NUM_STORYTELLER_TALES; i++)
    {
        sStorytellerPtr->gameStatIDs[i] = 0;
        sStorytellerPtr->trainerNames[0][i] = EOS;  // Maybe they meant storyteller->trainerNames[i][0] instead?
    }
}

static void Storyteller_ResetFlag(void)
{
    sStorytellerPtr = &gSaveBlock1Ptr->oldMan.storyteller;

    sStorytellerPtr->id = MAUVILLE_MAN_STORYTELLER;
    sStorytellerPtr->alreadyRecorded = FALSE;
}

static u32 StorytellerGetGameStat(u8 stat)
{
    if (stat == 50)
        stat = GAME_STAT_SAVED_GAME;
    return GetGameStat(stat);
}

static const struct Story *GetStoryByStat(u32 stat)
{
    s32 i;

    for (i = 0; i < sNumStories; i++)
    {
        if (sStorytellerStories[i].stat == stat)
            return &sStorytellerStories[i];
    }
    return &sStorytellerStories[sNumStories - 1];
}

static const u8 *GetStoryTitleByStat(u32 stat)
{
    return GetStoryByStat(stat)->title;
}

static const u8 *GetStoryTextByStat(u32 stat)
{
    return GetStoryByStat(stat)->fullText;
}

static const u8 *GetStoryActionByStat(u32 stat)
{
    return GetStoryByStat(stat)->action;
}

static u8 GetFreeStorySlot(void)
{
    u8 i;

    for (i = 0; i < NUM_STORYTELLER_TALES; i++)
    {
        if (sStorytellerPtr->gameStatIDs[i] == 0)
            break;
    }
    return i;
}

static u32 StorytellerGetRecordedTrainerStat(u32 trainer)
{
    u8 *ptr = sStorytellerPtr->statValues[trainer];

    return ptr[0] | (ptr[1] << 8) | (ptr[2] << 16) | (ptr[3] << 24);
}

static void StorytellerSetRecordedTrainerStat(u32 trainer, u32 val)
{
    u8 *ptr = sStorytellerPtr->statValues[trainer];

    ptr[0] = val;
    ptr[1] = val >> 8;
    ptr[2] = val >> 16;
    ptr[3] = val >> 24;
}

static bool32 HasTrainerStatIncreased(u32 trainer)
{
    if (StorytellerGetGameStat(sStorytellerPtr->gameStatIDs[trainer]) > StorytellerGetRecordedTrainerStat(trainer))
        return TRUE;
    else
        return FALSE;
}

static void GetStoryByStattellerPlayerName(u32 player, void *dst)
{
    u8 *name = sStorytellerPtr->trainerNames[player];

    memset(dst, EOS, PLAYER_NAME_LENGTH + 1);
    memcpy(dst, name, PLAYER_NAME_LENGTH);
}

static void StorytellerSetPlayerName(u32 player, const u8 * src)
{
    u8 * name = sStorytellerPtr->trainerNames[player];
    memset(name, EOS, PLAYER_NAME_LENGTH);
    memcpy(name, src, PLAYER_NAME_LENGTH);
}


static void StorytellerRecordNewStat(u32 player, u32 stat)
{
    sStorytellerPtr->gameStatIDs[player] = stat;
    StorytellerSetPlayerName(player, gSaveBlock2Ptr->playerName);
    StorytellerSetRecordedTrainerStat(player, StorytellerGetGameStat(stat));
    ConvertIntToDecimalStringN(gStringVar1, StorytellerGetGameStat(stat), STR_CONV_MODE_LEFT_ALIGN, 10);
    StringCopy(gStringVar2, GetStoryActionByStat(stat));
    sStorytellerPtr->language[player] = gGameLanguage;
}

static void ScrambleStatList(u8 * arr, s32 count)
{
    s32 i;

    for (i = 0; i < count; i++)
        arr[i] = i;
    for (i = 0; i < count; i++)
    {
        u32 a = Random() % count;
        u32 b = Random() % count;
        u8 temp;
        SWAP(arr[a], arr[b], temp);
    }
}

static bool8 StorytellerInitializeRandomStat(void)
{
    u8 storyIds[sNumStories];
    s32 i, j;

    ScrambleStatList(storyIds, sNumStories);
    for (i = 0; i < sNumStories; i++)
    {
        u8 stat = sStorytellerStories[storyIds[i]].stat;
        u8 minVal = sStorytellerStories[storyIds[i]].minVal;

        for (j = 0; j < NUM_STORYTELLER_TALES; j++)
        {
            if (sStorytellerPtr->gameStatIDs[j] == stat)
                break;
        }
        if (j == NUM_STORYTELLER_TALES && StorytellerGetGameStat(stat) >= minVal)
        {
            sStorytellerPtr->alreadyRecorded = TRUE;
            if (GetFreeStorySlot() == NUM_STORYTELLER_TALES)
                StorytellerRecordNewStat(sSelectedStory, stat);
            else
                StorytellerRecordNewStat(GetFreeStorySlot(), stat);
            return TRUE;
        }
    }
    return FALSE;
}

static void StorytellerDisplayStory(u32 player)
{
    u8 stat = sStorytellerPtr->gameStatIDs[player];

    ConvertIntToDecimalStringN(gStringVar1, StorytellerGetRecordedTrainerStat(player), STR_CONV_MODE_LEFT_ALIGN, 10);
    StringCopy(gStringVar2, GetStoryActionByStat(stat));
    GetStoryByStattellerPlayerName(player, gStringVar3);
    ConvertInternationalString(gStringVar3, sStorytellerPtr->language[player]);
    ShowFieldMessage(GetStoryTextByStat(stat));
}

static void PrintStoryList(void)
{
    s32 i;
    s32 width = GetStringWidth(FONT_NORMAL, gText_Exit, 0);
    for (i = 0; i < NUM_STORYTELLER_TALES; i++)
    {
        s32 curWidth;
        u16 gameStatID = sStorytellerPtr->gameStatIDs[i];

        if (gameStatID == 0)
            break;
        curWidth = GetStringWidth(FONT_NORMAL, GetStoryTitleByStat(gameStatID), 0);
        if (curWidth > width)
            width = curWidth;
    }
    sStorytellerWindowId = CreateWindowFromRect(0, 0, ConvertPixelWidthToTileWidth(width), GetFreeStorySlot() * 2 + 2);
    SetStandardWindowBorderStyle(sStorytellerWindowId, 0);
    for (i = 0; i < NUM_STORYTELLER_TALES; i++)
    {
        u16 gameStatID = sStorytellerPtr->gameStatIDs[i];
        if (gameStatID == 0)
            break;
        AddTextPrinterParameterized(sStorytellerWindowId, FONT_NORMAL, GetStoryTitleByStat(gameStatID), 8, 16 * i + 1, TEXT_SKIP_DRAW, NULL);
    }
    AddTextPrinterParameterized(sStorytellerWindowId, FONT_NORMAL, gText_Exit, 8, 16 * i + 1, TEXT_SKIP_DRAW, NULL);
    InitMenuInUpperLeftCornerNormal(sStorytellerWindowId, GetFreeStorySlot() + 1, 0);
    CopyWindowToVram(sStorytellerWindowId, COPYWIN_FULL);
}

static void Task_StoryListMenu(u8 taskId)
{
    struct Task *task = &gTasks[taskId];
    s32 selection;

    switch (task->data[0])
    {
    case 0:
        PrintStoryList();
        task->data[0]++;
        break;
    case 1:
        selection = Menu_ProcessInput();
        if (selection == MENU_NOTHING_CHOSEN)
            break;
        if (selection == MENU_B_PRESSED || selection == GetFreeStorySlot())
        {
            gSpecialVar_Result = 0;
        }
        else
        {
            gSpecialVar_Result = 1;
            sSelectedStory = selection;
        }
        ClearToTransparentAndRemoveWindow(sStorytellerWindowId);
        DestroyTask(taskId);
        EnableBothScriptContexts();
        break;
    }
}

// Sets gSpecialVar_Result to TRUE if player selected a story
void StorytellerStoryListMenu(void)
{
    CreateTask(Task_StoryListMenu, 80);
}

void Script_StorytellerDisplayStory(void)
{
    StorytellerDisplayStory(sSelectedStory);
}

u8 StorytellerGetFreeStorySlot(void)
{
    sStorytellerPtr = &gSaveBlock1Ptr->oldMan.storyteller;
    return GetFreeStorySlot();
}

// Returns TRUE if stat has increased
bool8 StorytellerUpdateStat(void)
{
    u8 stat;
    sStorytellerPtr = &gSaveBlock1Ptr->oldMan.storyteller;
    stat = sStorytellerPtr->gameStatIDs[sSelectedStory];

    if (HasTrainerStatIncreased(sSelectedStory) == TRUE)
    {
        StorytellerRecordNewStat(sSelectedStory, stat);
        return TRUE;
    }
    return FALSE;
}

bool8 HasStorytellerAlreadyRecorded(void)
{
    sStorytellerPtr = &gSaveBlock1Ptr->oldMan.storyteller;

    if (sStorytellerPtr->alreadyRecorded == FALSE)
        return FALSE;
    else
        return TRUE;
}

bool8 Script_StorytellerInitializeRandomStat(void)
{
    sStorytellerPtr = &gSaveBlock1Ptr->oldMan.storyteller;
    return StorytellerInitializeRandomStat();
}

