#include "global.h"
#include "daycare.h"
#include "event_data.h"
#include "field_effect.h"
#include "constants/items.h"
#include "mail_data.h"
#include "main.h"
#include "menu.h"
#include "constants/moves.h"
#include "name_string_util.h"
#include "overworld.h"
#include "party_menu.h"
#include "pokemon.h"
#include "pokemon_storage_system.h"
#include "random.h"
#include "script.h"
#include "constants/songs.h"
#include "sound.h"
#include "constants/species.h"
#include "string_util.h"
#include "strings2.h"
#include "text.h"
#include "trade.h"

// RAM buffers used to assist with BuildEggMoveset()
static u16 gHatchedEggLevelUpMoves[52];
static u16 gHatchedEggFatherMoves[4];
static u16 gHatchedEggFinalMoves[4];
static u16 gHatchedEggEggMoves[12];
static u16 gHatchedEggMotherMoves[4];

extern u16 gMoveToLearn;

static void ClearDaycareMail(struct DayCareMail *);
static void SetInitialEggData(struct Pokemon *, u16, struct DayCare *);
static u8 GetDaycareCompatibilityScore(struct DayCare *);

#include "data/pokemon/egg_moves.h"

static const u8 *const sCompatibilityMessages[] = {
    DaycareText_GetAlongVeryWell,
    DaycareText_GetAlong,
    DaycareText_DontLikeOther,
    DaycareText_PlayOther
};

const u8 sJapaneseEggNickname[] = _("タマゴ");

u8 *GetMonNick(struct Pokemon *mon, u8 *dest)
{
    s8 nickname[POKEMON_NAME_LENGTH * 2];

    GetMonData(mon, MON_DATA_NICKNAME, nickname);
    return StringCopy10(dest, nickname);
}

u8 *GetBoxMonNick(struct BoxPokemon *mon, u8 *dest)
{
    s8 nickname[POKEMON_NAME_LENGTH * 2];

    GetBoxMonData(mon, MON_DATA_NICKNAME, nickname);
    return StringCopy10(dest, nickname);
}

u8 CountPokemonInDaycare(struct DayCare *daycare)
{
    u8 i, count;
    count = 0;

    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        if (GetBoxMonData(&daycare->mons[i], MON_DATA_SPECIES) != 0)
            count++;
    }

    return count;
}

void InitDaycareMailRecordMixing(struct BoxPokemon * daycareMons, struct RecordMixingDayCareMail * daycareMail)
{
    u8 i;
    u8 numDaycareMons = 0;

    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        if (GetBoxMonData(&daycareMons[i], MON_DATA_SPECIES) != SPECIES_NONE)
        {
            numDaycareMons++;
            if (GetBoxMonData(&daycareMons[i], MON_DATA_HELD_ITEM) == ITEM_NONE)
            {
                daycareMail->itemsHeld[i] = 0;
            }
            else
            {
                daycareMail->itemsHeld[i] = 1;
            }
        }
        else
        {
            daycareMail->itemsHeld[i] = 1;
        }
    }

    daycareMail->numDaycareMons = numDaycareMons;
}

static s8 Daycare_FindEmptySpot(struct BoxPokemon * daycareMons)
{
    u8 i;

    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        if (GetBoxMonData(&daycareMons[i], MON_DATA_SPECIES) == 0)
            return i;
    }

    return -1;
}

static void StorePokemonInDaycare(struct Pokemon * mon, struct DayCare * daycare)
{
    s8 emptySlot;
    u8 mail;
    u8 *names;

    emptySlot = Daycare_FindEmptySpot(daycare->mons);
    if (MonHasMail(mon))
    {
        StringCopy((names = daycare->misc.mail[emptySlot].names), gSaveBlock2.playerName);
        PadNameString(names, 0xFC);
        names += 8;
        GetMonNick(mon, names);
        mail = GetMonData(mon, MON_DATA_MAIL);
        daycare->misc.mail[emptySlot].message = gSaveBlock1.mail[mail];
        TakeMailFromMon(mon);
    }

    daycare->mons[emptySlot] = mon->box;
    BoxMonRestorePP(&daycare->mons[emptySlot]);
    daycare->misc.countersEtc.steps[emptySlot] = 0;
    ZeroMonData(mon);
    party_compaction();
    CalculatePlayerPartyCount();
}

void StoreSelectedPokemonInDaycare(void)
{
    StorePokemonInDaycare(&gPlayerParty[gLastFieldPokeMenuOpened], &gSaveBlock1.daycare);
}

// Shifts the second daycare pokemon slot into the first slot.
static void ShiftDaycareSlots(struct DayCare * daycare)
{
    // This condition is only satisfied when the player takes out the first pokemon from the daycare.
    if (GetBoxMonData(&daycare->mons[1], MON_DATA_SPECIES) != 0 && GetBoxMonData(&daycare->mons[0], MON_DATA_SPECIES) == 0)
    {
        daycare->mons[0] = daycare->mons[1];
        ZeroBoxMonData(&daycare->mons[1]);

        daycare->misc.mail[0] = daycare->misc.mail[1];
        daycare->misc.countersEtc.steps[0] = daycare->misc.countersEtc.steps[1];
        daycare->misc.countersEtc.steps[1] = 0;
        ClearDaycareMail(&daycare->misc.mail[1]);
    }
}

static void ApplyDaycareExperience(struct Pokemon * mon)
{
    s32 i;
    bool8 firstMove;
    u16 learnedMove;

    for (i = 0; i < MAX_LEVEL; i++)
    {
        // Add the mon's gained daycare experience level by level until it can't level up anymore.
        if (TryIncrementMonLevel(mon))
        {
            // Teach the mon new moves it learned while in the daycare.
            firstMove = TRUE;
            while ((learnedMove = MonTryLearningNewMove(mon, firstMove)) != 0)
            {
                firstMove = FALSE;
                if (learnedMove == 0xffff)
                {
                    // Mon already knows 4 moves.
                    DeleteFirstMoveAndGiveMoveToMon(mon, gMoveToLearn);
                }
            }
        }
        else
        {
            break;
        }
    }

    // Re-calculate the mons stats at its new level.
    CalculateMonStats(mon);
}

static u16 TakeSelectedPokemonFromDaycare(struct DayCare * daycare, u8 slot)
{
    u16 species;
    u32 experience;
    struct Pokemon pokemon;

    GetBoxMonNick(&daycare->mons[slot], gStringVar1);
    species = GetBoxMonData(&daycare->mons[slot], MON_DATA_SPECIES);
    ExpandBoxMon(&daycare->mons[slot], &pokemon);

    if (GetMonData(&pokemon, MON_DATA_LEVEL) != MAX_LEVEL)
    {
        experience = GetMonData(&pokemon, MON_DATA_EXP) + daycare->misc.countersEtc.steps[slot];
        SetMonData(&pokemon, MON_DATA_EXP, &experience);
        ApplyDaycareExperience(&pokemon);
    }

    gPlayerParty[PARTY_SIZE - 1] = pokemon;
    if (daycare->misc.mail[slot].message.itemId)
    {
        GiveMailToMon2(&gPlayerParty[PARTY_SIZE - 1], &daycare->misc.mail[slot].message);
        ClearDaycareMail(&daycare->misc.mail[slot]);
    }

    party_compaction();
    ZeroBoxMonData(&daycare->mons[slot]);
    daycare->misc.countersEtc.steps[slot] = 0;
    ShiftDaycareSlots(daycare);
    CalculatePlayerPartyCount();
    return species;
}

u16 TakePokemonFromDaycare(void)
{
    return TakeSelectedPokemonFromDaycare(&gSaveBlock1.daycare, gSpecialVar_0x8004);
}

static u8 GetLevelAfterDaycareSteps(struct BoxPokemon * mon, u32 steps)
{
    struct BoxPokemon tempMon = *mon;

    u32 experience = GetBoxMonData(mon, MON_DATA_EXP) + steps;
    SetBoxMonData(&tempMon, MON_DATA_EXP,  &experience);
    return GetLevelFromBoxMonExp(&tempMon);
}

static u8 GetNumLevelsGainedFromSteps(struct DayCare *daycare, u8 slot)
{
    u8 levelBefore;
    u8 levelAfter;

    levelBefore = GetLevelFromBoxMonExp(&daycare->mons[slot]);
    levelAfter = GetLevelAfterDaycareSteps(&daycare->mons[slot], daycare->misc.countersEtc.steps[slot]);
    return levelAfter - levelBefore;
}

static u8 GetNumLevelsGainedForDaycareSlot(struct DayCare *daycare, u8 slot)
{
    u8 numLevelsGained = GetNumLevelsGainedFromSteps(daycare, slot);
    GetBoxMonNick(&daycare->mons[slot], gStringVar1);
    ConvertIntToDecimalStringN(gStringVar2, numLevelsGained, STR_CONV_MODE_LEFT_ALIGN, 2);
    return numLevelsGained;
}

static u16 GetDaycareCostForSelectedMon(struct DayCare *daycare, u8 slot)
{
    u16 cost;

    u8 numLevelsGained = GetNumLevelsGainedFromSteps(daycare, slot);
    GetBoxMonNick(&daycare->mons[slot], gStringVar1);
    cost = 100 + 100 * numLevelsGained;
    ConvertIntToDecimalStringN(gStringVar2, cost, STR_CONV_MODE_LEFT_ALIGN, 5);
    return cost;
}

void GetDaycareCost(void)
{
    gSpecialVar_0x8005 = GetDaycareCostForSelectedMon(&gSaveBlock1.daycare, gSpecialVar_0x8004);
}

void Debug_AddDaycareSteps(u16 numSteps)
{
    gSaveBlock1.daycare.misc.countersEtc.steps[0] += numSteps;
    gSaveBlock1.daycare.misc.countersEtc.steps[1] += numSteps;
}

u8 GetNumLevelsGainedFromDaycare(void)
{
    if (GetBoxMonData(&gSaveBlock1.daycare.mons[gSpecialVar_0x8004], MON_DATA_SPECIES) != 0)
        return GetNumLevelsGainedForDaycareSlot(&gSaveBlock1.daycare, gSpecialVar_0x8004);

    return 0;
}

static void ClearDaycareMail(struct DayCareMail *mail)
{
    u8 *names;
    u8 *names2;
    s32 i;
    u8 zero = 0;

    for (i = 7, names = &mail->names[7]; i >= 0; i--)
    {
        *names-- = zero;
    }

    names2 = &mail->names[8];
    zero = 0;
    names = &mail->names[18];
    do *names-- = zero; while ((s32)names >= (s32)names2);
    ClearMailStruct(&mail->message);
}

void unref_sub_8041824(struct DayCare *daycare)
{
    u8 slot;
    for (slot = 0; slot < ARRAY_COUNT(daycare->mons); slot++)
    {
        ZeroBoxMonData(&daycare->mons[slot]);
        daycare->misc.countersEtc.steps[slot] = 0;
        ClearDaycareMail(&daycare->misc.mail[slot]);
    }

    daycare->misc.countersEtc.pendingEggPersonality = 0;
    daycare->misc.countersEtc.eggCycleStepsRemaining = 0;
}

// Determines what the species of an Egg would be based on the given species.
// It determines this by working backwards through the evolution chain of the
// given species.
u16 GetEggSpecies(u16 species)
{
    int i, j, k;
    bool8 found;

    // Working backwards up to 5 times seems arbitrary, since the maximum number
    // of times would only be 3 for 3-stage evolutions.
    for (i = 0; i < 5; i++)
    {
        found = FALSE;
        for (j = 1; j < NUM_SPECIES; j++)
        {
            for (k = 0; k < 5; k++)
            {
                if (gEvolutionTable[j][k].targetSpecies == species)
                {
                    species = j;
                    found = TRUE;
                    break;
                }
            }

            if (found)
                break;
        }

        if (j == NUM_SPECIES)
            break;
    }

    return species;
}

static void _TriggerPendingDaycareEgg(struct DayCare *daycare)
{
    daycare->misc.countersEtc.pendingEggPersonality = (Random() % 0xfffe) + 1;
    FlagSet(FLAG_PENDING_DAYCARE_EGG);
}

static void _TriggerPendingDaycareMaleEgg(struct DayCare *daycare)
{
    daycare->misc.countersEtc.pendingEggPersonality = Random() | 0x8000;
    FlagSet(FLAG_PENDING_DAYCARE_EGG);
}

void TriggerPendingDaycareEgg(void)
{
    _TriggerPendingDaycareEgg(&gSaveBlock1.daycare);
}

void TriggerPendingDaycareMaleEgg(void)
{
    _TriggerPendingDaycareMaleEgg(&gSaveBlock1.daycare);
}

// Removes the selected index from the given IV list and shifts the remaining
// elements to the left.
static void RemoveIVIndexFromList(u8 *ivs, u8 selectedIv)
{
    s32 i, j;
    u8 temp[NUM_STATS];

    ivs[selectedIv] = 0xff;
    for (i = 0; i < NUM_STATS; i++)
    {
        temp[i] = ivs[i];
    }

    j = 0;
    for (i = 0; i < NUM_STATS; i++)
    {
        if (temp[i] != 0xff)
            ivs[j++] = temp[i];
    }
}

static void InheritIVs(struct Pokemon *egg, struct DayCare *daycare)
{
    u8 i;
    u8 selectedIvs[3];
    u8 availableIVs[NUM_STATS];
    u8 whichParent[ARRAY_COUNT(selectedIvs)];
    u8 iv;

    // Initialize a list of IV indices.
    for (i = 0; i < NUM_STATS; i++)
    {
        availableIVs[i] = i;
    }

    // Select the 3 IVs that will be inherited.
    for (i = 0; i < ARRAY_COUNT(selectedIvs); i++)
    {
        // Randomly pick an IV from the available list.
        selectedIvs[i] = availableIVs[Random() % (NUM_STATS - i)];

        // Remove the selected IV index from the available IV indicies.
        RemoveIVIndexFromList(availableIVs, selectedIvs[i]);
    }

    // Determine which parent each of the selected IVs should inherit from.
    for (i = 0; i < ARRAY_COUNT(selectedIvs); i++)
    {
        whichParent[i] = Random() % 2;
    }

    // Set each of inherited IVs on the egg mon.
    for (i = 0; i < ARRAY_COUNT(selectedIvs); i++)
    {
        switch (selectedIvs[i])
        {
            case 0:
                iv = GetBoxMonData(&daycare->mons[whichParent[i]], MON_DATA_HP_IV);
                SetMonData(egg, MON_DATA_HP_IV, &iv);
                break;
            case 1:
                iv = GetBoxMonData(&daycare->mons[whichParent[i]], MON_DATA_ATK_IV);
                SetMonData(egg, MON_DATA_ATK_IV, &iv);
                break;
            case 2:
                iv = GetBoxMonData(&daycare->mons[whichParent[i]], MON_DATA_DEF_IV);
                SetMonData(egg, MON_DATA_DEF_IV, &iv);
                break;
            case 3:
                iv = GetBoxMonData(&daycare->mons[whichParent[i]], MON_DATA_SPEED_IV);
                SetMonData(egg, MON_DATA_SPEED_IV, &iv);
                break;
            case 4:
                iv = GetBoxMonData(&daycare->mons[whichParent[i]], MON_DATA_SPATK_IV);
                SetMonData(egg, MON_DATA_SPATK_IV, &iv);
                break;
            case 5:
                iv = GetBoxMonData(&daycare->mons[whichParent[i]], MON_DATA_SPDEF_IV);
                SetMonData(egg, MON_DATA_SPDEF_IV, &iv);
                break;
        }
    }
}

// Counts the number of egg moves a pokemon learns and stores the moves in
// the given array.
static u8 GetEggMoves(struct Pokemon *pokemon, u16 *eggMoves)
{
    u16 eggMoveIdx;
    u16 numEggMoves;
    u16 species;
    u16 i;

    numEggMoves = 0;
    eggMoveIdx = 0;
    species = GetMonData(pokemon, MON_DATA_SPECIES);
    for (i = 0; i < ARRAY_COUNT(gEggMoves) - 1; i++)
    {
        if (gEggMoves[i] == species + EGG_MOVES_SPECIES_OFFSET)
        {
            eggMoveIdx = i + 1;
            break;
        }
    }

    for (i = 0; i < 10; i++)
    {
        if (gEggMoves[eggMoveIdx + i] > EGG_MOVES_SPECIES_OFFSET)
        {
            // TODO: the curly braces around this if statement are required for a matching build.
            break;
        }

        eggMoves[i] = gEggMoves[eggMoveIdx + i];
        numEggMoves++;
    }

    return numEggMoves;
}

void BuildEggMoveset(struct Pokemon *egg, struct BoxPokemon *father, struct BoxPokemon *mother)
{
    u16 numSharedParentMoves;
    u32 numLevelUpMoves;
    u32 numEggMoves;
    u16 i, j;

    numSharedParentMoves = 0;
    for (i = 0; i < 4; i++)
    {
        gHatchedEggMotherMoves[i] = 0;
        gHatchedEggFatherMoves[i] = 0;
        gHatchedEggFinalMoves[i] = 0;
    }
    for (i = 0; i < 10; i++)
        gHatchedEggEggMoves[i] = 0;
    for (i = 0; i < 50; i++)
        gHatchedEggLevelUpMoves[i] = 0;

    numLevelUpMoves = GetLevelUpMovesBySpecies(GetMonData(egg, MON_DATA_SPECIES), gHatchedEggLevelUpMoves);
    for (i = 0; i < 4; i++)
    {
        gHatchedEggFatherMoves[i] = GetBoxMonData(father, MON_DATA_MOVE1 + i);
        gHatchedEggMotherMoves[i] = GetBoxMonData(mother, MON_DATA_MOVE1 + i);
    }

    numEggMoves = GetEggMoves(egg, gHatchedEggEggMoves);

    for (i = 0; i < 4; i++)
    {
        if (gHatchedEggFatherMoves[i] != MOVE_NONE)
        {
            for (j = 0; j < numEggMoves; j++)
            {
                if (gHatchedEggFatherMoves[i] == gHatchedEggEggMoves[j])
                {
                    if (GiveMoveToMon(egg, gHatchedEggFatherMoves[i]) == 0xffff)
                        DeleteFirstMoveAndGiveMoveToMon(egg, gHatchedEggFatherMoves[i]);
                    break;
                }
            }
        }
        else
        {
            break;
        }
    }

    for (i = 0; i < 4; i++)
    {
        if (gHatchedEggFatherMoves[i] != MOVE_NONE)
        {
            for (j = 0; j < NUM_TECHNICAL_MACHINES + NUM_HIDDEN_MACHINES; j++)
            {
                if (gHatchedEggFatherMoves[i] == ItemIdToBattleMoveId(ITEM_TM01_FOCUS_PUNCH + j) && CanMonLearnTMHM(egg, j))
                {
                    if (GiveMoveToMon(egg, gHatchedEggFatherMoves[i]) == 0xffff)
                        DeleteFirstMoveAndGiveMoveToMon(egg, gHatchedEggFatherMoves[i]);
                }
            }
        }
    }

    for (i = 0; i < 4 && gHatchedEggFatherMoves[i] != MOVE_NONE; i++)
    {
        for (j = 0; j < 4; j++)
        {
            if (gHatchedEggFatherMoves[i] == gHatchedEggMotherMoves[j] && gHatchedEggFatherMoves[i] != MOVE_NONE)
                gHatchedEggFinalMoves[numSharedParentMoves++] = gHatchedEggFatherMoves[i];
        }
    }

    for (i = 0; i < 4 && gHatchedEggFinalMoves[i] != MOVE_NONE; i++)
    {
        for (j = 0; j < numLevelUpMoves; j++)
        {
            if (gHatchedEggLevelUpMoves[j] != MOVE_NONE && gHatchedEggFinalMoves[i] == gHatchedEggLevelUpMoves[j])
            {
                if (GiveMoveToMon(egg, gHatchedEggFinalMoves[i]) == 0xffff)
                    DeleteFirstMoveAndGiveMoveToMon(egg, gHatchedEggFinalMoves[i]);
                break;
            }
        }
    }
}

static void RemoveEggFromDayCare(struct DayCare *daycare)
{
    daycare->misc.countersEtc.pendingEggPersonality = 0;
    daycare->misc.countersEtc.eggCycleStepsRemaining = 0;
}

void RejectEggFromDayCare(void)
{
    RemoveEggFromDayCare(&gSaveBlock1.daycare);
}

static void AlterEggSpeciesWithIncenseItem(u16 *species, struct DayCare *daycare)
{
    u16 motherItem, fatherItem;
    if (*species == SPECIES_WYNAUT || *species == SPECIES_AZURILL)
    {
        motherItem = GetBoxMonData(&daycare->mons[0], MON_DATA_HELD_ITEM);
        fatherItem = GetBoxMonData(&daycare->mons[1], MON_DATA_HELD_ITEM);
        if (*species == SPECIES_WYNAUT && motherItem != ITEM_LAX_INCENSE && fatherItem != ITEM_LAX_INCENSE)
        {
            *species = SPECIES_WOBBUFFET;
        }

        if (*species == SPECIES_AZURILL && motherItem != ITEM_SEA_INCENSE && fatherItem != ITEM_SEA_INCENSE)
        {
            *species = SPECIES_MARILL;
        }
    }
}

static u16 DetermineEggSpeciesAndParentSlots(struct DayCare *daycare, u8 *parentSlots)
{
    u16 i;
    u16 species[2];
    u16 eggSpecies;

    // Determine which of the daycare mons is the mother and father of the egg.
    // The 0th index of the parentSlots array is considered the mother slot, and the
    // 1st index is the father slot.
    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        species[i] = GetBoxMonData(&daycare->mons[i], MON_DATA_SPECIES);
        if (species[i] == SPECIES_DITTO)
        {
            parentSlots[0] = i ^ 1;
            parentSlots[1] = i;
        }
        else if (GetBoxMonGender(&daycare->mons[i]) == MON_FEMALE)
        {
            parentSlots[0] = i;
            parentSlots[1] = i ^ 1;
        }
    }

    eggSpecies = GetEggSpecies(species[parentSlots[0]]);
    if (eggSpecies == SPECIES_NIDORAN_F && daycare->misc.countersEtc.pendingEggPersonality & 0x8000)
    {
        eggSpecies = SPECIES_NIDORAN_M;
    }
    if (eggSpecies == SPECIES_ILLUMISE && daycare->misc.countersEtc.pendingEggPersonality & 0x8000)
    {
        eggSpecies = SPECIES_VOLBEAT;
    }

    // Make Ditto the "mother" slot if the other daycare mon is male.
    if (species[parentSlots[1]] == SPECIES_DITTO && GetBoxMonGender(&daycare->mons[parentSlots[0]]) != MON_FEMALE)
    {
        u8 temp = parentSlots[1];
        parentSlots[1] = parentSlots[0];
        parentSlots[0] = temp;
    }

    return eggSpecies;
}

static void _GiveEggFromDaycare(struct DayCare *daycare) // give_egg
{
    struct Pokemon egg;
    u16 species;
    u8 parentSlots[2]; // 0th index is "mother" daycare slot, 1st is "father"
    u8 isEgg;

    species = DetermineEggSpeciesAndParentSlots(daycare, parentSlots);
    AlterEggSpeciesWithIncenseItem(&species, daycare);
    SetInitialEggData(&egg, species, daycare);
    InheritIVs(&egg, daycare);
    BuildEggMoveset(&egg, &daycare->mons[parentSlots[1]], &daycare->mons[parentSlots[0]]);
    isEgg = TRUE;
    SetMonData(&egg, MON_DATA_IS_EGG, &isEgg);
    gPlayerParty[5] = egg;
    party_compaction();
    CalculatePlayerPartyCount();
    RemoveEggFromDayCare(daycare);
}


void CreateEgg(struct Pokemon *mon, u16 species, bool8 setMetLocation)
{
    u8 metLevel;
    u16 ball;
    u8 language;
    u8 metLocation;
    u8 isEgg;

    CreateMon(mon, species, EGG_HATCH_LEVEL, 0x20, FALSE, 0, FALSE, 0);
    metLevel = 0;
    ball = ITEM_POKE_BALL;
    language = LANGUAGE_JAPANESE;
    SetMonData(mon, MON_DATA_POKEBALL, &ball);
    SetMonData(mon, MON_DATA_NICKNAME, sJapaneseEggNickname);
    SetMonData(mon, MON_DATA_FRIENDSHIP, &gBaseStats[species].eggCycles);
    SetMonData(mon, MON_DATA_MET_LEVEL, &metLevel);
    SetMonData(mon, MON_DATA_LANGUAGE, &language);
    if (setMetLocation)
    {
        metLocation = 253; // hot springs; see PokemonSummaryScreen_PrintEggTrainerMemo
        SetMonData(mon, MON_DATA_MET_LOCATION, &metLocation);
    }

    isEgg = TRUE;
    SetMonData(mon, MON_DATA_IS_EGG, &isEgg);
}

static void SetInitialEggData(struct Pokemon *mon, u16 species, struct DayCare *daycare)
{
    u32 personality;
    u16 ball;
    u8 metLevel;
    u8 language;

    personality = daycare->misc.countersEtc.pendingEggPersonality | (Random() << 16);
    CreateMon(mon, species, EGG_HATCH_LEVEL, 0x20, TRUE, personality, FALSE, 0);
    metLevel = 0;
    ball = ITEM_POKE_BALL;
    language = LANGUAGE_JAPANESE;
    SetMonData(mon, MON_DATA_POKEBALL, &ball);
    SetMonData(mon, MON_DATA_NICKNAME, sJapaneseEggNickname);
    SetMonData(mon, MON_DATA_FRIENDSHIP, &gBaseStats[species].eggCycles);
    SetMonData(mon, MON_DATA_MET_LEVEL, &metLevel);
    SetMonData(mon, MON_DATA_LANGUAGE, &language);
}

void GiveEggFromDaycare(void)
{
    _GiveEggFromDaycare(&gSaveBlock1.daycare);
}

static bool8 _ShouldEggHatch(struct DayCare *daycare)
{
    struct BoxPokemon *parent;
    u32 i;
    int numParents;

    numParents = 0;
    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        parent = &daycare->mons[i];
        if (GetBoxMonData(parent, MON_DATA_SANITY_BIT2))
        {
            daycare->misc.countersEtc.steps[i]++;
            numParents++;
        }
    }

    if (daycare->misc.countersEtc.pendingEggPersonality == 0 && numParents == 2 && (daycare->misc.countersEtc.steps[1] % 256) == 255 && GetDaycareCompatibilityScore(daycare) > (u32)(Random() * 100) / 0xffff)
        TriggerPendingDaycareEgg();

    if (++daycare->misc.countersEtc.eggCycleStepsRemaining == 255)
    {
        for (i = 0; i < gPlayerPartyCount; i++)
        {
            if (GetMonData(&gPlayerParty[i], MON_DATA_IS_EGG))
            {
                int friendship = GetMonData(&gPlayerParty[i], MON_DATA_FRIENDSHIP);
                if (friendship == 0)
                {
                    gSpecialVar_0x8004 = i;
                    return TRUE;
                }

                friendship--;
                SetMonData(&gPlayerParty[i], MON_DATA_FRIENDSHIP, &friendship);
            }
        }
    }

    return FALSE;
}

bool8 ShouldEggHatch(void)
{
    return _ShouldEggHatch(&gSaveBlock1.daycare);
}

static bool8 IsEggPending(struct DayCare *daycare)
{
    return (u32)((-daycare->misc.countersEtc.pendingEggPersonality) | daycare->misc.countersEtc.pendingEggPersonality) >> 31;
}

static void _GetDaycareMonNicknames(struct DayCare *daycare)
{
    u8 language;
    if (GetBoxMonData(&daycare->mons[0], MON_DATA_SPECIES) != 0)
    {
        GetBoxMonNick(&daycare->mons[0], gStringVar1);
        language = GetBoxMonData(&daycare->mons[0], MON_DATA_LANGUAGE);
        GetBoxMonData(&daycare->mons[0], MON_DATA_OT_NAME, gStringVar3);
        ConvertInternationalString(gStringVar3, language);
    }

    if (GetBoxMonData(&daycare->mons[1], MON_DATA_SPECIES) != 0)
    {
        GetBoxMonNick(&daycare->mons[1], gStringVar2);
    }
}

u16 GetSelectedDaycareMonNickname(void)
{
    GetBoxMonNick(&gPlayerParty[gLastFieldPokeMenuOpened].box, gStringVar1);
    return GetBoxMonData(&gPlayerParty[gLastFieldPokeMenuOpened].box, MON_DATA_SPECIES);
}

void GetDaycareMonNicknames(void)
{
    _GetDaycareMonNicknames(&gSaveBlock1.daycare);
}

u8 GetDaycareState(void)
{
    // The daycare can be in 4 possible states:
    //   0: default state--no deposited mons, no egg
    //   1: there is an egg waiting for the player to pick it up
    //   2: there is a single pokemon in the daycare
    //   3: there are two pokemon in the daycare, no egg

    u8 numMons;
    if (IsEggPending(&gSaveBlock1.daycare))
    {
        // There is an Egg waiting for the player.
        return 1;
    }

    numMons = CountPokemonInDaycare(&gSaveBlock1.daycare);
    if (numMons != 0)
    {
        return numMons + 1;
    }

    return 0;
}

static bool8 EggGroupsOverlap(u16 *eggGroups1, u16 *eggGroups2)
{
    // Determine if the two given egg group lists contain any of the
    // same egg groups.
    s32 i, j;

    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        for (j = 0; j < DAYCARE_MON_COUNT; j++)
        {
            if (eggGroups1[i] == eggGroups2[j])
            {
                return TRUE;
            }
        }
    }

    return FALSE;
}

static u8 GetDaycareCompatibilityScore(struct DayCare *daycare)
{
    u32 i;
    u16 eggGroups[2][2];
    u16 species[2];
    u32 genders[2];
    u32 otIds[2];
    u32 personality;

    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        species[i] = GetBoxMonData(&daycare->mons[i], MON_DATA_SPECIES);
        otIds[i] = GetBoxMonData(&daycare->mons[i], MON_DATA_OT_ID);
        personality = GetBoxMonData(&daycare->mons[i], MON_DATA_PERSONALITY);
        genders[i] = GetGenderFromSpeciesAndPersonality(species[i], personality);
        eggGroups[i][0] = gBaseStats[species[i]].eggGroup1;
        eggGroups[i][1] = gBaseStats[species[i]].eggGroup2;
    }

    if (eggGroups[0][0] == EGG_GROUP_UNDISCOVERED || eggGroups[1][0] == EGG_GROUP_UNDISCOVERED)
        return 0;

    if (eggGroups[0][0] == EGG_GROUP_DITTO && eggGroups[1][0] == EGG_GROUP_DITTO)
        return 0;

    if (eggGroups[0][0] == EGG_GROUP_DITTO || eggGroups[1][0] == EGG_GROUP_DITTO)
    {
        if (otIds[0] == otIds[1])
            return 20;
        else
            return 50;
    }

    if (genders[0] == genders[1] || genders[0] == MON_GENDERLESS || genders[1] == MON_GENDERLESS)
        return 0;

    if (!EggGroupsOverlap(eggGroups[0], eggGroups[1]))
        return 0;

    if (species[0] == species[1])
    {
        if (otIds[0] == otIds[1])
            return 50;
        else
            return 70;
    }

    if (otIds[0] != otIds[1])
        return 50;
    else
        return 20;
}

u8 GetDaycareCompatibilityScoreFromSave(void)
{
    return GetDaycareCompatibilityScore(&gSaveBlock1.daycare);
}

void SetDaycareCompatibilityString(void)
{
    u8 whichString;
    u8 relationshipScore;

    relationshipScore = GetDaycareCompatibilityScoreFromSave();
    whichString = 0;
    if (relationshipScore == 0)
        whichString = 3;
    if (relationshipScore == 20)
        whichString = 2;
    if (relationshipScore == 50)
        whichString = 1;
    if (relationshipScore == 70)
        whichString = 0;

    StringCopy(gStringVar4, sCompatibilityMessages[whichString]);
}

bool8 NameHasGenderSymbol(const u8 *name, u8 genderRatio)
{
    u8 i;
    u8 flags[2];

    flags[0] = flags[1] = 0;
    for (i = 0; name[i] != EOS; i++)
    {
        if (name[i] == CHAR_MALE) flags[0]++;
        if (name[i] == CHAR_FEMALE) flags[1]++;
    }
    if (genderRatio == MON_MALE && flags[0] && !flags[1])
        return TRUE;
    if (genderRatio == MON_FEMALE && flags[1] && !flags[0])
        return TRUE;
    return FALSE;
}

static u8 *AppendGenderSymbol(u8 *name, u8 gender)
{
    if (gender == MON_MALE)
    {
        if (!NameHasGenderSymbol(name, MON_MALE))
            return StringAppend(name, gOtherText_MaleSymbol3);
    }
    else if (gender == MON_FEMALE)
    {
        if (!NameHasGenderSymbol(name, MON_FEMALE))
            return StringAppend(name, gOtherText_FemaleSymbol3);
    }

    return StringAppend(name, gOtherText_GenderlessSymbol);
}

static u8 *AppendMonGenderSymbol(u8 *name, struct BoxPokemon *boxMon)
{
    return AppendGenderSymbol(name, GetBoxMonGender(boxMon));
}

static void GetDaycareLevelMenuText(struct DayCare *daycare, u8 *dest)
{
    u8 monNames[2][20];
    u8 i;

    *dest = EOS;
    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        GetBoxMonNick(&daycare->mons[i], monNames[i]);
        AppendMonGenderSymbol(monNames[i], &daycare->mons[i]);
    }

    StringCopy(dest, monNames[0]);
    StringAppend(dest, gOtherText_NewLine2);
    StringAppend(dest, monNames[1]);
    StringAppend(dest, gOtherText_NewLine2);
    StringAppend(dest, gOtherText_CancelAndLv);
}

static void GetDaycareLevelMenuLevelText(struct DayCare *daycare, u8 *dest)
{
    u8 i;
    u8 level;

    *dest = EOS;
    for (i = 0; i < DAYCARE_MON_COUNT; i++)
    {
        level = GetLevelAfterDaycareSteps(&daycare->mons[i], daycare->misc.countersEtc.steps[i]);
        dest[0] = 0x34;
        dest[1] = 0xFC;
        dest[2] = 0x14;
        dest[3] = 0x06;
        dest = ConvertIntToDecimalStringN(dest + 4, level, STR_CONV_MODE_RIGHT_ALIGN, 3);
        dest[0] = 0xFC;
        dest[1] = 0x14;
        dest[2] = 0x00;
        dest = StringCopy(dest + 3, gOtherText_NewLine2);
    }

    *dest = EOS;
}

static void HandleDaycareLevelMenuInput(u8 taskId)
{
    if (gMain.newKeys & DPAD_UP)
    {
        if (gTasks[taskId].data[0] != 0)
        {
            gTasks[taskId].data[0] --;
            Menu_MoveCursor(-1);
            PlaySE(SE_SELECT);
        }
    }
    else if (gMain.newKeys & DPAD_DOWN)
    {
        if (gTasks[taskId].data[0] != 2)
        {
            gTasks[taskId].data[0]++;
            Menu_MoveCursor(+1);
            PlaySE(SE_SELECT);
        }
    }
    else if (gMain.newKeys & A_BUTTON)
    {
        Menu_DestroyCursor();
        PlaySE(SE_SELECT);
        gLastFieldPokeMenuOpened = gSpecialVar_Result = gTasks[taskId].data[0];
        DestroyTask(taskId);
        Menu_EraseWindowRect(15, 6, 29, 13);
        EnableBothScriptContexts();
    }
    else if (gMain.newKeys & B_BUTTON)
    {
        Menu_DestroyCursor();
        gLastFieldPokeMenuOpened = gSpecialVar_Result = 2;
        DestroyTask(taskId);
        Menu_EraseWindowRect(15, 6, 29, 13);
        EnableBothScriptContexts();
    }
}

void ShowDaycareLevelMenu(void)
{
    u8 buffer[100];
    Menu_DrawStdWindowFrame(15, 6, 29, 13);
    GetDaycareLevelMenuText(&gSaveBlock1.daycare, buffer);
    Menu_PrintText(buffer, 16, 7);
    GetDaycareLevelMenuLevelText(&gSaveBlock1.daycare, buffer);
    Menu_PrintTextPixelCoords(buffer, 0xce, 0x38, TRUE);
    InitMenu(0, 16, 7, 3, 0, 13);
    CreateTask(HandleDaycareLevelMenuInput, 3);
}

void ChooseSendDaycareMon(void)
{
    OpenPartyMenu(PARTY_MENU_TYPE_DAYCARE, 0);
    gMain.savedCallback = CB2_ReturnToField;
}
