import { defeatPoints, goldPerPoint, maxPlayers, minPlayers, numAges, Player, Science, scienceSetPoints, State } from "./basic"
import isInteger from 'lodash/isInteger'
import sampleSize from 'lodash/sampleSize'
import compact from 'lodash/compact'
import sum from 'lodash/sum'
import min from 'lodash/min'
import max from 'lodash/max'
import head from 'lodash/head'
import tail from 'lodash/tail'
import { Countable, Structure, structures, structuresGuilds } from "./structure"

export function buildDeck(age: number, numPlayers: number) {
  if (age < 0 || age >= numAges || !isInteger(age)) {
    throw new Error(`Unsupported age ${age}`)
  }
  if (numPlayers < minPlayers || numPlayers > maxPlayers || !isInteger(numPlayers)) {
    throw new Error(`Unsupported player count ${numPlayers}`)
  }
  const cards: Structure[] = [...structures.values()].flatMap(structure =>
    structure.appears?.[age]
      ?.filter(minPlayers => numPlayers <= minPlayers)
      ?.map(() => structure)
    ?? []
  )
  if (age === numAges - 1) {
    cards.push(...sampleSize(structuresGuilds, numPlayers + 2)) // BAD
  }
  return cards
}

export function sumMaps<V>(ms?: (Map<V, number> | undefined)[]) {
  const sum = new Map<V, number>()
  for (const m of ms ?? []) {
    for (const [k, v] of m ?? []) {
      sum.set(k, (sum.get(k) ?? 0) + v)
    }
  }
  return sum
}

export function playerStats(p: Player) {
  const structObjs = compact(p.structures.map(({ structure }) => structures.get(structure)))
  return {
    structObjs,
    resources: sumMaps(structObjs.map(s => s.resources)),
    shields: sum(structObjs.map(s => s.shields)) ?? 0,
    sciences: compact(structObjs.map(s => s.sciences)),
  }
}

export function sumCountable(player: Player, countable: Countable): number {
  switch (countable) {
    case 'wonder stage':
      return 0 // TODO
    case 'defeat':
      return [...player.leftBattles, ...player.rightBattles].filter(b => b === defeatPoints).length
    default:
      return player.structures.filter(({ structure }) => structures.get(structure)?.type === countable).length
  }
}

export function sumCountableMap(player: Player, map: Map<Countable, number>): number {
  return sum([...map.entries()].map(([countable, number]) => number * sumCountable(player, countable)))
}

export function scoreScience(
  sciences: Set<Science>[],
  counts: Map<Science, number> = new Map([['engineering', 0], ['research', 0], ['education', 0]]),
): number {
  if (sciences.length === 0) {
    return (scienceSetPoints * (min([...counts.values()]) ?? 0))
      + sum([...counts.values()].map(c => c * c))
  } else {
    return max([...head(sciences)!.values()].map(science =>
      scoreScience(tail(sciences), new Map([...counts, [science, (counts.get(science) ?? 0) + 1]]))
    )) ?? 0
  }
}

export function countPoints(state: State, playerIdx: number): number {
  const player = state.players[playerIdx]
  const left = state.players[(playerIdx + 1) % state.players.length]
  const right = state.players[(playerIdx - 1 + state.players.length) % state.players.length]
  return sum(player.leftBattles) + sum(player.rightBattles)
    + sum(player.structures.map(({ structure }) => {
      const card = structures.get(structure)!
      return (card.points ?? 0)
        + sumCountableMap(player, card.pointsPer ?? new Map())
        + sumCountableMap(left, card.pointsPerNeighbor ?? new Map())
        + sumCountableMap(right, card.pointsPerNeighbor ?? new Map())
    }))
    + Math.floor(player.gold / goldPerPoint)
    + scoreScience(player.structures.flatMap(({ structure }) => {
      const sciences = structures.get(structure)?.sciences
      return sciences === undefined ? [] : [sciences]
    }))
}
