import { WORLD_MAP_HEIGHT, WORLD_MAP_WIDTH } from "./constants";
import units from "./data/units";
import { WORLDMAP_TYPES, type BuildingType, type CostType, type OasisType, type Point, type ProductionType, type ResourcesType } from "./types";
import type { VillageState } from "./village";


function _reduceResources(acc: ResourcesType, item: ResourcesType): ResourcesType {
    return {
        wood: acc.wood + item.wood,
        stone: acc.stone + item.stone,
        iron: acc.iron + item.iron,
        food: acc.food + item.food,
        culture: acc.culture + item.culture,
    };
}


export function getEmptyResources(): ResourcesType {
    return {
        wood: 0,
        stone: 0,
        iron: 0,
        food: 0,
        culture: 0,
    };
}


export function getProduction(villageState: VillageState): ResourcesType {
    let production = getEmptyResources();

    // Add buildings production and intake.
    production = villageState.buildings
        .filter(b => b.behavior.production && b.level > 0)
        .map(b => {
            if (b.behavior.production) {
                return b.behavior.production(villageState, b);
            }
        })
        .reduce(_reduceResources, production);

    // Add units production and intake.
    ['philosopher', 'soldier'].forEach(type => {
        const unit = getUnitSource(type);
        const unitCount = villageState.units[type] || 0;

        // Add food intake.
        const intakePerMinute = unit.behavior.foodIntakePerMinute * unitCount;
        production.food -= intakePerMinute;

        // Add culture production for Philosophers.
        if (type === 'philosopher') {
            const outputPerMinute = unit.behavior.culturePerMinute || 0;
            production.culture += outputPerMinute * unitCount;
        }
    });

    return production;
}


export function getStorage(villageState: VillageState): ProductionType {
    return villageState.buildings
        .filter(b => b.behavior.storage && b.level > 0)
        .map(b => {
            if (b.behavior.storage) {
                return b.behavior.storage(villageState, b);
            }
        })
        .reduce(_reduceResources, getEmptyResources());
}


export function getBuilding(V: VillageState, buildingId: number): BuildingType {
    const building = V.buildings.find(b => b.id === buildingId);
    if (!building) {
        throw new Error(`Cannot find building with id "${buildingId}"`);
    }
    return building;
}


export function getUnitSource(unitType: string) {
    const unit = units.find(u => u.type === unitType);
    if (unit === undefined) {
        throw new Error(`Unknown unit type: "${unitType}"`);
    }
    return unit;
}


export function getRegionsWithMissions(V: VillageState): OasisType[] {
    return V.worldmap.filter(r => r.type === WORLDMAP_TYPES.OASIS);
}


export function getRemainingUnitCount(V: VillageState, unitType: string) {
    let total = V.units[unitType] || 0;
    const missions = getRegionsWithMissions(V)
        .filter(r => r.state.mission?.unitType === unitType)
        .map(r => r.state.mission);
    missions.forEach(m => total -= m?.unitCount || 0);
    return total;
}


export function getKeysAsNumbers(dict: Object): Array<number> {
    return Object.keys(dict).map(i => parseInt(i)).sort((a, b) => a - b);
}


/**
 * Creates an array of shuffled values, using a version of the
 * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
 *
 * @since 0.1.0
 * @category Array
 * @param {Array} array The array to shuffle.
 * @returns {Array} Returns the new shuffled array.
 * @example
 *
 * shuffle([1, 2, 3, 4])
 * // => [4, 1, 3, 2]
 */
export function shuffle<T>(array: Array<T>): Array<T> {
    const length = array.length;
    if (!length) {
        return [];
    }
    let index = -1;
    const lastIndex = length - 1;
    const result = [ ...array ];
    while (++index < length) {
        const rand = index + Math.floor(Math.random() * (lastIndex - index + 1));
        [ result[rand], result[index] ] = [ result[index], result[rand] ];
    }
    return result;
}


/**
 * Return a random integer in the range [ min, max ] (min and max can be returned).
 * @param min integer
 * @param max integer
 * @returns integer
 */
export function random(min: number, max?: number) {
    if (max == null) {
        max = min;
        min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1.0));
}


export function getBuildingUpgradeCost(_V: VillageState, building: BuildingType): CostType {
    const level = building.level + 1;
    return building.cost(level);
}


export function canPayBuildingCost(V: VillageState, building: BuildingType): boolean {
    const cost = getBuildingUpgradeCost(V, building);

    return !(
        cost.wood > V.resources.wood
        || cost.stone > V.resources.stone
        || cost.iron > V.resources.iron
        || cost.food > V.resources.food
    );
}


export function getTownhall(V: VillageState): BuildingType {
    const townhall = V.buildings.find(b => b.type === 'townhall');
    if (!townhall) {
        throw new Error("Unable to find the Town hall");
    }
    return townhall;
}

export function assert(condition: any, msg?: string): asserts condition {
    if (!condition) {
        throw new Error(msg);
    }
}


export function getAdjacentWorldmapCells(cellIndex: number) {
    const cells = [
        cellIndex - WORLD_MAP_WIDTH - 1,
        cellIndex - WORLD_MAP_WIDTH,
        cellIndex - WORLD_MAP_WIDTH + 1,
        cellIndex - 1,
        cellIndex + 1,
        cellIndex + WORLD_MAP_WIDTH - 1,
        cellIndex + WORLD_MAP_WIDTH,
        cellIndex + WORLD_MAP_WIDTH + 1,
    ];

    return cells.filter(c => c >= 0 && c < WORLD_MAP_WIDTH * WORLD_MAP_HEIGHT);
}


export function distanceBetweenCells(a: Point, b: Point) {
    return Math.sqrt(
        Math.pow(b.x - a.x, 2)
        + Math.pow(b.y - a.y, 2)
    );
}


export function indexToPoint(index: number): Point {
    return {
        x: index % WORLD_MAP_WIDTH,
        y: Math.floor(index / WORLD_MAP_WIDTH),
    };
}
