package pl.edu.pw.mini.sg.mixed.expansion

import pl.edu.pw.mini.sg.game.Payoff
import pl.edu.pw.mini.sg.game.boundedrationality.BoundedRationality
import pl.edu.pw.mini.sg.game.deterministic.single.GameWithPayoff
import pl.edu.pw.mini.sg.game.deterministic.{ DeterministicGame, PlayerVisibleState, PureStrategy }
import pl.edu.pw.mini.sg.game.deterministic.single.SinglePlayerGame

sealed trait MoveType[DM] {
  val m: DM
}
final case class Both[DM](m: DM) extends MoveType[DM]
final case class Desired[DM](m: DM) extends MoveType[DM]
final case class Better[DM](m: DM) extends MoveType[DM]

sealed trait PayoffDifferenceGame[
  DM, AM, DS <: PlayerVisibleState[DM],
  AS <: PlayerVisibleState[AM],
  G <: DeterministicGame[DM, AM, DS, AS, G]
] extends SinglePlayerGame[MoveType[DM], DS, PayoffDifferenceGame[DM, AM, DS, AS, G]] with GameWithPayoff {
  protected val moveConstructor : DM => MoveType[DM]
  override def possibleMoves: List[MoveType[DM]] = playerState.possibleMoves.map(moveConstructor)
}


final case class PayoffDifferenceGameBoth[
  DM, AM, DS <: PlayerVisibleState[DM],
  AS <: PlayerVisibleState[AM],
  G <: DeterministicGame[DM, AM, DS, AS, G]
](
  gameStateDesired: G,
  gameStateBetter: G,
  desiredStrategy: PureStrategy[AM, AS],
  betterStrategy: PureStrategy[AM, AS]
)(
  implicit br: BoundedRationality
) extends PayoffDifferenceGame[DM, AM, DS, AS, G] {
  require(gameStateBetter.defenderState == gameStateDesired.defenderState)
  override def makeMove(move: MoveType[DM]): PayoffDifferenceGame[DM,AM,DS,AS,G] = {
    val nextStateDesired = gameStateDesired.makeMove(move.m, desiredStrategy.move)
    val nextStateBetter = gameStateBetter.makeMove(move.m, betterStrategy.move)
    lazy val nextStrategyDesired = desiredStrategy.nextState(nextStateDesired.attackerState)
    lazy val nextStrategyBetter = betterStrategy.nextState(nextStateBetter.attackerState)
    if(nextStateDesired.defenderState == nextStateBetter.defenderState) {
      PayoffDifferenceGameBoth(nextStateDesired, nextStateBetter, nextStrategyDesired, nextStrategyBetter)
    } else {
      if(nextStateBetter.isLeaf) {
        PayoffDifferenceGameDesired(nextStateDesired, nextStateBetter, nextStrategyDesired, nextStrategyBetter)
      } else {
        PayoffDifferenceGameBetter(nextStateDesired, nextStateBetter, nextStrategyDesired, nextStrategyBetter)
      }
    }
  }
  override def playerState: DS = gameStateBetter.defenderState
  override val moveConstructor = Both.apply
  def distortedDes = br.payoffChanger(new Payoff(gameStateDesired.defenderPayoff, gameStateDesired.attackerPayoff))
  def distortedBet = br.payoffChanger(new Payoff(gameStateBetter.defenderPayoff, gameStateBetter.attackerPayoff))
  override def attackerPayoff : Double = distortedDes.attacker - distortedBet.attacker
  override def defenderPayoff : Double = distortedDes.defender - distortedBet.defender
}

final case class PayoffDifferenceGameDesired[
  DM, AM, DS <: PlayerVisibleState[DM],
  AS <: PlayerVisibleState[AM],
  G <: DeterministicGame[DM, AM, DS, AS, G]
](
  gameStateDesired: G,
  gameStateBetter: G,
  desiredStrategy: PureStrategy[AM, AS],
  betterStrategy: PureStrategy[AM, AS]
)(implicit 
  br: BoundedRationality
) extends PayoffDifferenceGame[DM, AM, DS, AS, G] {
  override def playerState: DS = gameStateDesired.defenderState

  override def makeMove(move: MoveType[DM]): PayoffDifferenceGame[DM,AM,DS,AS,G] = {
    val nextStateDesired = gameStateDesired.makeMove(move.m, desiredStrategy.move)
    val nextStrategyDesired = desiredStrategy.nextState(nextStateDesired.attackerState)
    if(gameStateBetter.isLeaf)
      PayoffDifferenceGameDesired(nextStateDesired, gameStateBetter, nextStrategyDesired, betterStrategy)
    else
      PayoffDifferenceGameBetter(nextStateDesired, gameStateBetter, nextStrategyDesired, betterStrategy)
  }
  def distortedDes = br.payoffChanger(new Payoff(gameStateDesired.defenderPayoff, gameStateDesired.attackerPayoff))
  def distortedBet = br.payoffChanger(new Payoff(gameStateBetter.defenderPayoff, gameStateBetter.attackerPayoff))
  override def attackerPayoff : Double = distortedDes.attacker - distortedBet.attacker
  override def defenderPayoff : Double = distortedDes.defender - distortedBet.defender
  override val moveConstructor = Desired.apply
}

final case class PayoffDifferenceGameBetter[
  DM, AM, DS <: PlayerVisibleState[DM],
  AS <: PlayerVisibleState[AM],
  G <: DeterministicGame[DM, AM, DS, AS, G]
](
  gameStateDesired: G,
  gameStateBetter: G,
  desiredStrategy: PureStrategy[AM, AS],
  betterStrategy: PureStrategy[AM, AS]
)(
  implicit br: BoundedRationality
)extends PayoffDifferenceGame[DM, AM, DS, AS, G] {
  override def playerState: DS = gameStateBetter.defenderState

  override def makeMove(move: MoveType[DM]): PayoffDifferenceGame[DM,AM,DS,AS,G] =  {
    val nextStateBetter = gameStateBetter.makeMove(move.m, betterStrategy.move)
    val nextStrategyBetter = betterStrategy.nextState(nextStateBetter.attackerState)
    if(gameStateDesired.isLeaf)
      PayoffDifferenceGameBetter(gameStateDesired, nextStateBetter, desiredStrategy, nextStrategyBetter)
    else
      PayoffDifferenceGameDesired(gameStateDesired, nextStateBetter, desiredStrategy, nextStrategyBetter)
  }

  def distortedDes = br.payoffChanger(new Payoff(gameStateDesired.defenderPayoff, gameStateDesired.attackerPayoff))
  def distortedBet = br.payoffChanger(new Payoff(gameStateBetter.defenderPayoff, gameStateBetter.attackerPayoff))
  override def attackerPayoff : Double = distortedDes.attacker - distortedBet.attacker
  override def defenderPayoff : Double = distortedDes.defender - distortedBet.defender
  override val moveConstructor = Better.apply
}

//TODO -- w pewnym momencie będę musiał umieć oddzielić historie ruchów, żeby zrobić właściwą ekspansję.
