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

import { ApiService } from 'api/ApiService';

import { IBaseStatsRank, IPokemon } from 'app/models/Pokemon';

interface ICalculateRelativeStats {
    id : POGOProtos.Enums.PokemonId;
    form : POGOProtos.Enums.Form;
    value : number;
}

export const parseGameMaster = async () => {
    const apiService = new ApiService();
    // const pokemonForms : { [ key in keyof typeof POGOProtos.Enums.PokemonId ]? : Array<POGOProtos.Enums.Form> } = {};
    // const pokemonData : { [ key in keyof typeof POGOProtos.Enums.PokemonId ]? : Array<IPokemon> } = {};
    const pokemonDataByPokemonId : Map<POGOProtos.Enums.PokemonId, Array<IPokemon>> = new Map();
    const pokemonFormsByPokemonId : Map<POGOProtos.Enums.PokemonId, Array<POGOProtos.Enums.Form>> = new Map();

    const pokemonBaseStamina : Array<ICalculateRelativeStats> = [];
    const pokemonBaseAttack : Array<ICalculateRelativeStats> = [];
    const pokemonBaseDefense : Array<ICalculateRelativeStats> = [];
    const pokemonOrderById : Record<string, IBaseStatsRank> = {};

    const GameMasterProto = fs.readFileSync('externals/pokemongo-game-master/versions/latest/GAME_MASTER.protobuf');
    const GameMaster = POGOProtos.Networking.Responses.DownloadItemTemplatesResponse.decode(GameMasterProto);

    const version = GameMaster.timestamp_ms;

    for (const entry of GameMaster.item_templates) {
        if (!entry.template_id) {
            continue;
        }

        if (entry.template_id.match(/^V(\d{4})_POKEMON_(\w+)$/) && entry.pokemon_settings) {
            const pokemonId = entry.pokemon_settings.pokemon_id || POGOProtos.Enums.PokemonId.MISSINGNO;
            const pokemonData = pokemonDataByPokemonId.get(pokemonId) || [];
            const dex = parseInt(RegExp.$1, 10);

            const defaultName = POGOProtos.Enums.PokemonId[pokemonId].toLowerCase()
                .split(' ')
                .map((s) => s.charAt(0).toUpperCase() + s.substring(1))
                .join(' ');
            const speciesInfo = await apiService.getPokemonSpeciesInfo(pokemonId, dex, defaultName);

            const mon = {
                ...speciesInfo,
                id: pokemonId,
                form: entry.pokemon_settings.form || POGOProtos.Enums.Form.FORM_UNSET,
                family: entry.pokemon_settings.family_id || POGOProtos.Enums.PokemonFamilyId.FAMILY_UNSET,
                types: {
                    type1: entry.pokemon_settings.type || POGOProtos.Enums.PokemonType.POKEMON_TYPE_NONE,
                    type2: entry.pokemon_settings.type_2 || null,
                },
                stats: {
                    baseAttack: (entry.pokemon_settings.stats || {}).base_attack || 0,
                    baseDefense: (entry.pokemon_settings.stats || {}).base_defense || 0,
                    baseStamina: (entry.pokemon_settings.stats || {}).base_stamina || 0,
                },
                statsRank: {
                    attackRank: -1,
                    defenseRank: -1,
                    staminaRank: -1,
                },
            };
            pokemonData.push(mon);
            pokemonDataByPokemonId.set(pokemonId, pokemonData);

            const key = POGOProtos.Enums.PokemonId[mon.id] + POGOProtos.Enums.Form[mon.form];

            pokemonBaseStamina.push({
                id: mon.id,
                form: mon.form,
                value: mon.stats.baseStamina,
            });
            pokemonBaseAttack.push({
                id: mon.id,
                form: mon.form,
                value: mon.stats.baseAttack,
            });
            pokemonBaseDefense.push({
                id: mon.id,
                form: mon.form,
                value: mon.stats.baseDefense,
            });
            pokemonOrderById[key] = {
                staminaRank: -1,
                attackRank: -1,
                defenseRank: -1,
            };

        } else if (entry.template_id.indexOf('FORMS_V') === 0 && entry.form_settings) {
            // typings say `forms` can be undefined, but in practice, it's not
            if (entry.form_settings.forms && entry.form_settings.forms.length > 0) {
                const pokemonId = entry.form_settings.pokemon || POGOProtos.Enums.PokemonId.MISSINGNO;
                // console.log(entry);
                let pokemonData = pokemonFormsByPokemonId.get(pokemonId) || [];
                pokemonData = entry.form_settings.forms.reduce((output : Array<POGOProtos.Enums.Form>, form) => {
                    if (form.form) {
                        output.push(form.form);
                    }
                    return output;
                }, pokemonData);
                pokemonFormsByPokemonId.set(pokemonId, pokemonData);
            }
        }
    }

    // remove pokemon placeholders for ones that have forms
    for (const [ pokemonId, forms ] of pokemonFormsByPokemonId) {
        let pokemonData = pokemonDataByPokemonId.get(pokemonId);
        if (typeof pokemonData === 'undefined' || pokemonData.length === 1) {
            continue;
        }
        pokemonData = pokemonData.reduce((data : Array<IPokemon>, pokemon) => {
            const pokemonHasForm = forms.some((form) => form === pokemon.form);
            if (pokemonHasForm) {
                data.push(pokemon);
            }
            return data;
        }, []);
        pokemonDataByPokemonId.set(pokemonId, pokemonData);
    }

    // const orderedPokemon = Object.values(pokemonData).sort((a, b) => {
    const orderedPokemon = Array.from(pokemonDataByPokemonId.values()).sort((a, b) => {
        return a[0].order - b[0].order;
    });
    orderedPokemon.forEach((value) => {
        value.sort((a, b) => {
            return a.form - b.form;
        });
    });

    let flatOrderedPokemon : Array<IPokemon> = [];
    flatOrderedPokemon = flatOrderedPokemon.concat(...orderedPokemon);

    // calculate pokemon relative rankings

    pokemonBaseStamina.sort((a, b) => {
        return a.value - b.value;
    });
    pokemonBaseStamina.forEach((stats, index, array) => {
        const key = POGOProtos.Enums.PokemonId[stats.id] + POGOProtos.Enums.Form[stats.form];
        pokemonOrderById[key].staminaRank = Math.floor((index / (array.length - 1)) * 100);
    });
    pokemonBaseAttack.sort((a, b) => {
        return a.value - b.value;
    });
    pokemonBaseAttack.forEach((stats, index, array) => {
        const key = POGOProtos.Enums.PokemonId[stats.id] + POGOProtos.Enums.Form[stats.form];
        pokemonOrderById[key].attackRank = Math.floor((index / (array.length - 1)) * 100);
    });
    pokemonBaseDefense.sort((a, b) => {
        return a.value - b.value;
    });
    pokemonBaseDefense.forEach((stats, index, array) => {
        const key = POGOProtos.Enums.PokemonId[stats.id] + POGOProtos.Enums.Form[stats.form];
        pokemonOrderById[key].defenseRank = Math.floor((index / (array.length - 1)) * 100);
    });
    flatOrderedPokemon.forEach((mon) => {
        const key = POGOProtos.Enums.PokemonId[mon.id] + POGOProtos.Enums.Form[mon.form];
        mon.statsRank = { ...pokemonOrderById[key] };
    });

    return flatOrderedPokemon;
};
