import fs from 'fs';
import POGOProtos from 'pogo-protos';

import { parseGameMaster } from './parseGameMaster';

import { calculateCp, calculateMaxLevelForLeague, calculateStatAtLevel } from 'app/utils/calculator';

import { ILeaguePokemon, League, MaxCpByLeague } from 'app/models/League';
import { LevelMultipliers } from 'app/models/LevelMultipliers';
import { Grade, IMaxStats, IStats } from 'app/models/Pokemon';

type ICpAndTotalFound = Record<number, Array<IStats>>;
interface IStatsDistribution {
    great : ICpAndTotalFound;
    ultra : ICpAndTotalFound;
    master : ICpAndTotalFound;
    custom : ICpAndTotalFound;
}

const outPath = './dist/db/';

const maxPossibleStats : IMaxStats = {
    baseStamina: 0,
    baseAttack: 0,
    baseDefense: 0,
    level: 40,
};

fs.mkdirSync(outPath, { recursive: true });

(async () => {
    const Pokemon = await parseGameMaster();

    Pokemon.forEach((mon) => {
        const baseHp = mon.stats.baseStamina;
        const baseAtk = mon.stats.baseAttack;
        const baseDef = mon.stats.baseDefense;
        const pokemon : ILeaguePokemon = {
            ...mon,
            statMax: {
                great: {
                    stamina: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    attack: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    defense: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                },
                ultra: {
                    stamina: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    attack: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    defense: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                },
                master: {
                    stamina: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    attack: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    defense: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                },
                custom: {
                    stamina: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    attack: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                    defense: {
                        best: -Infinity,
                        worst: Infinity,
                    },
                },
            },
            pvp: {
                great: [],
                ultra: [],
                master: [],
                custom: [],
            },
        };

        maxPossibleStats.baseStamina = Math.max(baseHp, maxPossibleStats.baseStamina);
        maxPossibleStats.baseAttack = Math.max(baseAtk, maxPossibleStats.baseAttack);
        maxPossibleStats.baseDefense = Math.max(baseDef, maxPossibleStats.baseDefense);

        // calculate stats for all possible IVs
        const combinedStatsDistribution : IStatsDistribution = {
            great: {},
            ultra: {},
            master: {},
            custom: {},
        };
        for (let ivHp = 15; ivHp >= 0; ivHp--) {
            for (let ivAtk = 15; ivAtk >= 0; ivAtk--) {
                for (let ivDef = 15; ivDef >= 0; ivDef--) {
                    let pokemonWithIvs : IStats;

                    Object.keys(MaxCpByLeague).forEach((key) => {
                        const league = key as League;
                        const maxLeagueLevel = calculateMaxLevelForLeague(mon.stats, ivHp, ivAtk, ivDef, league);
                        const maxLeagueCp = calculateCp(mon.stats, maxLeagueLevel, ivHp, ivAtk, ivDef);
                        pokemonWithIvs = {
                            cp: maxLeagueCp,
                            level: maxLeagueLevel,
                            ivHp,
                            ivAtk,
                            ivDef,
                            hp: calculateStatAtLevel(maxLeagueLevel, baseHp, ivHp),
                            atk: calculateStatAtLevel(maxLeagueLevel, baseAtk, ivAtk),
                            def: calculateStatAtLevel(maxLeagueLevel, baseDef, ivDef),
                            total: 0,
                            speciesGrade: Grade.F,
                            metaGrade: Grade.F,
                        };
                        pokemonWithIvs.total = pokemonWithIvs.hp + pokemonWithIvs.atk + pokemonWithIvs.def;

                        const combinedStats = pokemonWithIvs.total;
                        combinedStatsDistribution[league][combinedStats] = combinedStatsDistribution[league][combinedStats] || [];
                        combinedStatsDistribution[league][combinedStats].push(pokemonWithIvs);

                        pokemon.statMax[league].stamina.best = Math.max(pokemon.statMax[league].stamina.best, pokemonWithIvs.hp);
                        pokemon.statMax[league].stamina.worst = Math.min(pokemon.statMax[league].stamina.worst, pokemonWithIvs.hp);
                        pokemon.statMax[league].attack.best = Math.max(pokemon.statMax[league].attack.best, pokemonWithIvs.atk);
                        pokemon.statMax[league].attack.worst = Math.min(pokemon.statMax[league].attack.worst, pokemonWithIvs.atk);
                        pokemon.statMax[league].defense.best = Math.max(pokemon.statMax[league].defense.best, pokemonWithIvs.def);
                        pokemon.statMax[league].defense.worst = Math.min(pokemon.statMax[league].defense.worst, pokemonWithIvs.def);
                    });
                }
            }
        }

        // process the pokemon stats for league-worthiness
        Object.keys(pokemon.pvp).forEach((key) => {
            const league = key as League;

            // NOTE: ordered low to high!
            const orderedCombinedStats = Object.keys(combinedStatsDistribution[league]).map((total) => parseInt(total, 10));
            orderedCombinedStats.sort((a, b) => a - b);

            const len = orderedCombinedStats.length - 1;
            const offset = orderedCombinedStats[0];
            const max = orderedCombinedStats[len] - offset; // index `len` is always `Grade.S`
            for (let index = len; index >= 0; index--) {
                const combinedStats = orderedCombinedStats[index];
                const percent = (combinedStats - offset) / max;
                // remove all `Grade.F` stats (to save space in the DB), except
                // for the very worst one
                if (percent < 0.6 && index !== 0) {
                    delete combinedStatsDistribution[league][combinedStats];
                    continue;
                }

                combinedStatsDistribution[league][combinedStats].forEach((pokemonStats) => {
                    if (index === len) {
                        pokemonStats.speciesGrade = Grade.S;
                    } else {
                        if (percent >= 0.9) {
                            pokemonStats.speciesGrade = Grade.A;
                        } else if (percent >= 0.8) {
                            pokemonStats.speciesGrade = Grade.B;
                        } else if (percent >= 0.7) {
                            pokemonStats.speciesGrade = Grade.C;
                        } else if (percent >= 0.6) {
                            pokemonStats.speciesGrade = Grade.D;
                        }
                    }
                    pokemon.pvp[league].push(pokemonStats);
                });
                combinedStatsDistribution[league][combinedStats].sort((a, b) => {
                    if (a.total !== b.total) {
                        return a.total < b.total ? -1 : 1;
                    }
                    if (a.level !== b.level) {
                        return a.level < b.level ? -1 : 1;
                    }
                    return 0;
                });
            }
        });

        try {
            const filename = mon.form === POGOProtos.Enums.Form.FORM_UNSET ? POGOProtos.Enums.PokemonId[mon.id] : POGOProtos.Enums.Form[mon.form];
            fs.writeFileSync(outPath + filename + '.json', JSON.stringify(pokemon));
        } catch (error) {
            if (error) {
                /* tslint:disable-next-line:no-console */
                return console.error(pokemon.name, error);
            }
        }
    });

    try {
        fs.writeFileSync(outPath + 'order.json', JSON.stringify(Pokemon));
    } catch (error) {
        if (error) {
            /* tslint:disable-next-line:no-console */
            return console.error('order', error);
        }
    }

    try {
        // TODO: add moves
        fs.writeFileSync(outPath + 'config.json', JSON.stringify({ maxPossibleStats }));
    } catch (error) {
        if (error) {
            /* tslint:disable-next-line:no-console */
            return console.error('config', error);
        }
    }
})();
