import unittest
import resources
import gamecontroller
from board import Board
from buildingcolumn import BuildingColumn
from resources import Resources, Firms, Goods, FirmsOrGoods
from cards import *
import copy
import sharevalues

# For each turn, given the current share score, give the estimated final share
# value
finalShareScore = sharevalues.final1
maxShareScore = sharevalues.max1

# For each turn, given the current confidence marker, give the confidence marker
# level at turn j
predictedConfidenceMarker = [
 [
   [i for i in range(60)], # At turn 1, predicting for turn 1
   [max(0, i - 3) for i in range(60)],
   [max(0, i - 6) for i in range(60)],
   [max(0, i - 9) for i in range(60)],
   [max(0, i - 12) for i in range(60)],
   [max(0, i - 15) for i in range(60)],
 ],
 [
   [i for i in range(60)], # At turn 2, predicting for turn 1
   [i for i in range(60)], # At turn 2, predicting for turn 2
   [max(0, i - 3) for i in range(60)],
   [max(0, i - 6) for i in range(60)],
   [max(0, i - 9) for i in range(60)],
   [max(0, i - 12) for i in range(60)],
 ],
 [
   [i for i in range(60)], # At turn 3, predicting for turn 1
   [i for i in range(60)], # At turn 3, predicting for turn 2
   [i for i in range(60)], # At turn 3, predicting for turn 3
   [max(0, i - 3) for i in range(60)],
   [max(0, i - 6) for i in range(60)],
   [max(0, i - 9) for i in range(60)],
 ],
 [
   [i for i in range(60)], # At turn 4, predicting for turn 1
   [i for i in range(60)], # At turn 4, predicting for turn 2
   [i for i in range(60)], # At turn 4, predicting for turn 3
   [i for i in range(60)], # At turn 4, predicting for turn 4
   [max(0, i - 3) for i in range(60)],
   [max(0, i - 6) for i in range(60)],
 ],
 [
   [i for i in range(60)], # At turn 5, predicting for turn 1
   [i for i in range(60)], # At turn 5, predicting for turn 2
   [i for i in range(60)], # At turn 5, predicting for turn 3
   [i for i in range(60)], # At turn 5, predicting for turn 4
   [i for i in range(60)], # At turn 5, predicting for turn 5
   [max(0, i - 3) for i in range(60)],
 ],
 [
   [i for i in range(60)], # At turn 6, predicting for turn 1
   [i for i in range(60)], # At turn 6, predicting for turn 2
   [i for i in range(60)], # At turn 6, predicting for turn 3
   [i for i in range(60)], # At turn 6, predicting for turn 4
   [i for i in range(60)], # At turn 6, predicting for turn 5
   [i for i in range(60)], # At turn 6, predicting for turn 6
 ],
]

class BasicAi:
  def __init__(self, player, valuationRate = 1.1, loanMultiplier = 0.4,
      goodsMultiplier = 0.7):
    self.player = player
    self.valuationRate = valuationRate
    self.loanMultiplier = loanMultiplier
    self.goodsMultiplier = goodsMultiplier

  def takeAction(self, gameController):
    board = gameController.board
    bestMove = { 'value': -1000, 'move': None }
    for cardIndex, card in enumerate(board.cardsAvailable):
      value = self.computeGetCardValue(gameController, cardIndex)
      if value > bestMove['value']:
        bestMove['value'] = value
        bestMove['action'] = gameController.actionGetCard
        bestMove['params'] = [cardIndex]
    for goods in Goods:
      value = self.computeGetMoneyOnGoodsValue(gameController, goods)
      if value > bestMove['value']:
        bestMove['value'] = value
        bestMove['action'] = gameController.actionGetMoneyOnGoods
        bestMove['params'] = [goods]
    for circleIndex, circle in enumerate(board.investmentActionCircles):
      for firms in self.generateFirmTuples(len(circle['counts'])):
        value = self.computeInvestValue(gameController, circleIndex, firms)
        if value > bestMove['value']:
          bestMove['value'] = value
          bestMove['action'] = gameController.actionInvest
          bestMove['params'] = [circleIndex, firms]
    for firm in Firms:
      value = self.computeBuildValue(gameController, firm)
      if value > bestMove['value']:
        bestMove['value'] = value
        bestMove['action'] = gameController.actionBuild
        bestMove['params'] = [firm]
    for card in self.player.cards:
      if card.name == 'share':
        value = self.computeSellShareValue(gameController, card)
        if value > bestMove['value']:
          bestMove['value'] = value
          bestMove['action'] = gameController.actionSellShare
          bestMove['params'] = [card]
    for card in self.player.cards:
      if card.name == 'loan':
        value = self.computePayLoanValue(gameController, card)
        if value > bestMove['value']:
          bestMove['value'] = value
          bestMove['action'] = gameController.actionPayLoan
          bestMove['params'] = [card]
    value = self.computeSkipActionValue(gameController)
    if value > bestMove['value']:
      bestMove['value'] = value
      bestMove['action'] = gameController.actionSkipAction
      bestMove['params'] = []
    bestMove['action'](self.player, *bestMove['params'])

  def computePayoffForCard(self, card, level):
    card = copy.copy(card)
    directPayoff = card.payoff(level, self.player.bonusCards)
    return directPayoff + \
        (card.amount * self.goodsMultiplier if card.name == 'factory' else 0)

  def computeGetCardValue(self, gameController, cardIndex):
    turn = gameController.turnIndex
    board = gameController.board
    card = board.getCardByIndex(cardIndex)
    goods = board.goodsOfCardByIndex(cardIndex)
    valueToAdvanceOnFirmTrack = 0
    if goods:
      valueToAdvanceOnFirmTrack = self.computeAdvanceOnFirmValue(
          gameController, resources.mainFirm(goods), 1)

    # Estimate 1 more level per round
    valueForCard = 0
    level = self.player.getLevel()
    for i in range(turn, 6):
      valueForCard *= self.valuationRate
      valueForCard += self.computePayoffForCard(card, level)
      level += 1

    costForUpkeep = 0
    confidenceMarker = board.confidenceMarker
    for i in range(turn, 6):
      prediction = predictedConfidenceMarker[turn][i][confidenceMarker]
      interests = board.getInterestsForMarker(prediction)
      costForUpkeep *= self.valuationRate
      costForUpkeep += card.upkeep * interests

    # TODO: That's a bit rough...
    extraValue = card.finalPayoff
    if card.name == 'plusLevel':
      extraValue = 2 * card.plusLevel * (6 - turn)
    elif card.name == 'loanGoods':
      extraValue = card.amount * self.loanMultiplier * self.goodsMultiplier * \
          pow(self.valuationRate, 6 - turn - 1)
    elif card.name == 'loan':
      extraValue = card.amount * self.loanMultiplier * \
          pow(self.valuationRate, 6 - turn - 1)

    return valueToAdvanceOnFirmTrack + valueForCard + extraValue - costForUpkeep

  def computeGetMoneyOnGoodsValue(self, gameController, goods):
    turn = gameController.turnIndex
    board = gameController.board
    return -(-board.revenues[goods] / 2) * \
        pow(self.valuationRate, 6 - turn - 1) + \
        self.computeAdvanceOnFirmValue(
            gameController, resources.mainFirm(goods), 1) + 0.1

  def computeInvestValue(self, gameController, circleIndex, firms):
    board = gameController.board
    circle = board.investmentActionCircles[circleIndex]
    if circle['playerId'] is not None:
      return -1000
    counts = circle['counts']
    value = sum([self.computeAdvanceOnFirmValue(gameController, firm, count)
        for firm, count in zip(firms, counts)])
    return value + 0.1 * (5 - circleIndex)

  def computeBuildValue(self, gameController, firm):
    board = gameController.board
    if not self.player.canPayForBuilding(board.buildingColumn[firm]):
      return -1000
    return self.computeBuildingValue(gameController, firm)

  def computeSellShareValue(self, gameController, card):
    turn = gameController.turnIndex
    board = gameController.board
    shareScore = board.shareScore[card.firm]
    currentValue = board.getShareValueForScore(shareScore)
    finalValue = board.getShareValueForScore(finalShareScore[turn][shareScore])
    return card.multiplicity * (currentValue - finalValue) * \
        pow(self.valuationRate, 6 - turn - 1)

  def computePayLoanValue(self, gameController, card):
    turn = gameController.turnIndex
    board = gameController.board

    if self.player.amount < -card.finalPayoff:
      return -1000

    costForUpkeep = 0
    confidenceMarker = board.confidenceMarker
    for i in range(turn, 6):
      prediction = predictedConfidenceMarker[turn][i][confidenceMarker]
      interests = board.getInterestsForMarker(prediction)
      costForUpkeep += card.upkeep * interests

    return costForUpkeep * pow(self.valuationRate, 6 - turn - 1)

  def computeSkipActionValue(self, gameController):
    turn = gameController.turnIndex
    return gameController.amountToSkipAction * \
        pow(self.valuationRate, 6 - turn - 1)

  def computeBuildingValue(self, gameController, firm):
    turn = gameController.turnIndex
    board = gameController.board
    column = board.buildingColumn[firm]
    buildingCard = column.getLargest()
    shareCard = buildingCard.flip()
    roofCard = column.roof
    # TODO: This disregards the confidence marker.
    oldShareScore = board.shareScore[firm]
    newShareScore = oldShareScore + roofCard.progress

    oldShareValue = board.getShareValueForScore(
        maxShareScore[turn][oldShareScore])
    newShareValue = board.getShareValueForScore(
        maxShareScore[turn][newShareScore])

    valueDiff = newShareValue - oldShareValue
    valueForOldShares = 0
    for card in self.player.cards:
      if card.name == 'share' and card.firm == firm:
        valueForOldShares += card.multiplicity * valueDiff

    valueForNewShares = shareCard.multiplicity * newShareValue

    payoffBefore = 0
    payoffAfter = 0
    level = self.player.getLevel()
    for card in self.player.cards:
      payoffBefore += self.computePayoffForCard(card, level)
    newLevel = level + roofCard.flip().level - \
        (0 if not self.player.levelCard else self.player.levelCard.level)
    for card in self.player.cards:
      payoffAfter += self.computePayoffForCard(card, newLevel)
    valueForLevelIncrease = (payoffAfter - payoffBefore)
    for i in range(turn, 6):
      valueForLevelIncrease *= self.valuationRate
      valueForLevelIncrease += (payoffAfter - payoffBefore)

    # TODO: Factories shouldn't be counted in cost.
    cost = (buildingCard.level * roofCard.cardCount) * \
        pow(self.valuationRate, 6 - turn - 1)

    return valueForOldShares + valueForNewShares + valueForLevelIncrease - cost

  def computeAdvanceOnFirmValue(self, gameController, firm, count):
    turn = gameController.turnIndex
    board = gameController.board
    revenues = board.revenues[firm]