import chunk from "lodash/chunk"
import shuffle from "lodash/shuffle"
import sum from 'lodash/sum'
import { Action, AddPlayerAction, ChooseWonderAction, PlanTurnAction, RemovePlayerAction, StartGameAction } from "./action"
import { defeatPoints, initial, maxPlayers, minPlayers, numAges, Player, sampleState, startingGold, State, victoryPoints } from "./basic"
import { structures } from "./structure"
import { buildDeck, sumCountableMap } from "./util"

function beginAge(state: State): State {
  const age = state.age === undefined ? 0 : state.age + 1
  const deck = shuffle(buildDeck(age, state.players.length).map(s => s.name)) // BAD
  const handSize = Math.ceil(deck.length / state.players.length)
  const hands = chunk(deck, handSize)
  return {
    ...state,
    age,
    turnsRemaining: handSize - 1,
    players: state.players.map((p, i) => ({ ...p, hand: hands[i] })),
  }
}
function countShields(player: Player): number {
  return sum(player.structures.map(({ structure }) => structures.get(structure)?.shields))
}
function battleResult(state: State, playerIdx: number, delta: number): number | undefined {
  const playerShields = countShields(state.players[playerIdx])
  const otherShields = countShields(state.players[(playerIdx + delta + state.players.length) % state.players.length])
  if (playerShields > otherShields) {
    return victoryPoints[state.age!]
  } else if (playerShields < otherShields) {
    return defeatPoints
  }
}
function endAge(state: State): State {
  return {
    ...state,
    turnsRemaining: undefined,
    discard: [...state.discard, ...state.players.flatMap(p => p.hand)],
    players: state.players.map((p, i) => {
      const left = battleResult(state, i, 1)
      const right = battleResult(state, i, -1)
      return {
        ...p,
        hand: [],
        leftBattles: [...p.leftBattles, ...(left === undefined ? [] : [left])],
        rightBattles: [...p.rightBattles, ...(right === undefined ? [] : [right])],
      }
    }),
  }
}
function doTurn(state: State): State {
  state = {
    ...state,
    players: state.players.map((player, playerIdx) => {
      const turnPlan = player.turnPlan!
      const left = state.players[(playerIdx + 1) % state.players.length]
      const right = state.players[(playerIdx - 1 + state.players.length) % state.players.length]
      const card = structures.get(player.hand[turnPlan.cardIdx])!

      return {
        ...player,
        gold: player.gold
          + left.turnPlan!.goldRight + right.turnPlan!.goldLeft
          + (turnPlan.action === 'discard' ? 3 : 0)
          + (turnPlan.action !== 'structure' ? 0 :
            (card.gold ?? 0)
            + sumCountableMap(player, card.goldPer ?? new Map())
            + sumCountableMap(left, card.goldPerNeighbor ?? new Map())
            + sumCountableMap(right, card.goldPerNeighbor ?? new Map())
          ),
        hand: [
          ...player.hand.slice(0, turnPlan.cardIdx),
          ...player.hand.slice(turnPlan.cardIdx + 1)
        ],
        structures: [
          ...player.structures,
          ...(turnPlan.action !== 'structure' ? [] : [
            { age: state.age!, structure: player.hand[turnPlan.cardIdx] }
          ]),
        ]
      }
    }),
    turnsRemaining: state.turnsRemaining! - 1,
  }
  if (state.turnsRemaining === 0) {
    state = endAge(state)
    if (state.age === numAges - 1) {
      return {
        ...state,
        stage: 'finished',
        age: undefined,
      }
    } else {
      return beginAge(state)
    }
  } else {
    const delta = state.age! % 2 === 1 ? -1 : 1
    return {
      ...state,
      players: state.players.map((p, i) => ({
        ...p,
        hand: state.players[(i + delta + state.players.length) % state.players.length].hand,
      })),
    }
  }
}
const reducers: { [type: string]: (state: State, action: any) => State } = {
  'add player': (state, action: AddPlayerAction) => {
    if (state.stage !== 'starting' || state.players.some(p => p.name === action.name)) { return state }
    return {
      ...state,
      players: [...state.players, {
        name: action.name,
        gold: startingGold,
        structures: [],
        hand: [],
        leftBattles: [],
        rightBattles: [],
      }],
    }
  },
  'remove player': (state, action: RemovePlayerAction) => {
    if (state.stage !== 'starting') {
      return state
    }
    return {
      ...state,
      players: state.players.filter(p => p.name !== action.name),
    }
  },
  'choose wonder': (state, action: ChooseWonderAction) => {
    if (state.stage !== 'starting' || state.players.some(p => p.wonder === action.wonder)) {
      return state
    }
    return {
      ...state,
      players: state.players.map(p => p.name === action.player ? { ...p, wonder: action.wonder } : p),
    }
  },
  'start game': (state, action: StartGameAction) => {
    if (state.stage !== 'starting' || state.players.length < minPlayers || state.players.length > maxPlayers || state.players.some(p => p.wonder === undefined)) {
      return state
    }
    return beginAge({
      ...state,
      stage: 'play',
      players: action.shuffle.map(i => state.players[i]),
    })
  },
  'plan turn': (state, action: PlanTurnAction) => {
    if (state.stage !== 'play') {
      return state
    }
    const newState = {
      ...state,
      players: state.players.map(
        p => p.name !== action.player ? p : { ...p, turnPlan: action.turnPlan }
      ),
    }
    return newState.players.every(p => p.turnPlan !== undefined) ? doTurn(newState) : newState
  },
  'reset': () => initial,
  'load sample': () => sampleState,
}
export function reducer(state: State, action: Action): State {
  return reducers[action.type](state, action)
}
