# encoding: UTF-8
require_relative './util/constants'
require_relative 'player'
require_relative 'board'
require_relative 'move'
require_relative 'condition'
require_relative 'player_color'
require_relative 'field_type'

# @author Ralf-Tobias Diekert
# The state of a game
class GameState
  
  # @!attribute [rw] turn
  # @return [Integer] turn number
  attr_accessor :turn
  # @!attribute [rw] startPlayerColor
  # @return [PlayerColor] the start-player's color
  attr_accessor :startPlayerColor
  # @!attribute [rw] currentPlayerColor
  # @return [PlayerColor] the current player's color
  attr_accessor :currentPlayerColor
  # @!attribute [r] red
  # @return [Player] the red player
  attr_reader :red
  # @!attribute [r] blue
  # @return [Player] the blue player
  attr_reader :blue
  # @!attribute [rw] board
  # @return [Board] the game's board
  attr_accessor :board
  # @!attribute [rw] lastMove
  # @return [Move] the last move, that was made
  attr_accessor :lastMove
  # @!attribute [rw] condition
  # @return [Condition] the winner and winning reason
  attr_accessor :condition
  
  def initialize
    self.currentPlayerColor = PlayerColor::RED
    self.startPlayerColor = PlayerColor::RED
    self.board = Board.new(true)
  end
  
  # adds a player to the gamestate
  #
  # @param player [Player] the player, that will be added
  def addPlayer(player) 
    if player.color == PlayerColor::RED 
      @red = player
    else if player.color == PlayerColor::BLUE
        @blue = player
      end
    end
  end

  # gets the current player
  #
  # @return [Player] the current player
  def currentPlayer
    if self.currentPlayerColor == PlayerColor::RED
      return self.red
    else
      return self.blue
    end
  end

  # gets the other (not the current) player
  # 
  # @return [Player] the other (not the current) player
  def otherPlayer
    if self.currentPlayerColor == PlayerColor::RED
      return self.blue 
    else
      return self.red
    end
  end
  
  # gets the other (not the current) player's color
  #
  # @return [PlayerColor] the other (not the current) player's color
  def otherPlayerColor
    return PlayerColor.opponentColor(self.currentPlayerColor)
  end
  
  # gets the start player
  #
  # @return [Player] the startPlayer
  def startPlayer
    if self.startPlayer == PlayerColor::RED 
      return self.red
    else
      return self.blue
    end
  end
  
  # switches current player
  def switchCurrentPlayer
    if currentPlayer.color == PlayerColor::RED
      @currentPlayer = self.blue
    else
      @currentPlayer = self.red
    end
  end
  
  # prepares next turn and sets the last move
  #
  # @param [Move] the last move
  def prepareNextTurn(lastMove)
    @turn++
    @lastMove = lastMove;
    self.switchCurrentPlayer()
  end
  
  # gets the current round
  #
  # @return [Integer] the current round
  def round
    return self.turn / 2
  end
  
  # gets all possible moves
  #
  # @return [Array<Move>] a list of all possible moves
  def getPossibleMoves
    enemyFieldType = currentPlayer.color == PlayerColor::RED ? FieldType::BLUE : FieldType::RED
    moves = Array.new
    for x in 0..(Constants::SIZE-1)
      for y in 0..(Constants::SIZE-1)
        if (self.board.fields[x][y].ownerColor == PlayerColor::NONE &&
              self.board.fields[x][y].type != FieldType::SWAMP &&
              self.board.fields[x][y].type != enemyFieldType)
          moves.push(Move.new(x, y))
        end
      end
    end
    return moves
  end
  
  # performs a move on the gamestate
  #
  # @param move [Move] the move, that will be performed
  # @param player [Player] the player, who makes the move
  def perform(move, player)
    if !move.nil?
      if move.x < Constants::SIZE && move.y < Constants::SIZE && 
          move.x >= 0 && move.y >= 0
        if self.getPossibleMoves.include?(move) 
          self.board.put(move.x, move.y, player)
          player.points = self.pointsForPlayer(player)      
        else
          raise "Der Zug ist nicht möglich, denn der Platz ist bereits besetzt oder nicht besetzbar."
        end
      else
        raise "Startkoordinaten sind nicht innerhalb des Spielfeldes."
      end
    end
  end
  
  # gets a player's points
  #
  # @param player [Player] the player, whos statistics will be returned
  # @return [Integer] the points of the player
  def playerStats(player)
    return self.playerStats(player.color)
  end
  
  # gets a player's points by the player's color
  #
  # @param playerColor [PlayerColor] the player's color, whos statistics will be returned
  # @return [Integer] the points of the player
  def playerStats(playerColor) 
    if playerColor == PlayerColor::RED
      return self.gameStats[0];
    else 
      return self.gameStats[1]
    end
  end
  
  # gets the players' statistics
  #
  # @return [Array<Integer>] the points for both players
  def gameStats
    stats = Array.new(2, Array.new(1))

    stats[0][0] = self.red.points
    stats[1][0] = self.blue.points
    
    return stats
  end
  
  # get the players' names
  #
  # @return [Array<String>] the names for both players
  def playerNames
    return [red.displayName, blue.displayName]
  end
  
  # sets the game-ended condition
  #
  # @param winner [Player] the winner of the game
  # @param reason [String] the winning reason
  def endGame(winner, reason)
    if condition.nil?
      @condition = Condition.new(winner, reason)
    end
  end
  
  # has the game ended?
  #
  # @return [Boolean] true, if the game has allready ended
  def gameEnded?
    return !self.condition.nil?
  end
  
  # gets the game's winner
  #
  # @return [Player] the game's winner
  def winner
    return condition.nil? ? nil : self.condition.winner
  end
  
  # gets the winning reason
  #
  # @return [String] the winning reason
  def winningReason
    return condition.nil? ? nil : self.condition.reason
  end
  
  # calculates a player's points
  #
  # @param player [Player] the player, whos point will be calculated
  # @return [Integer] the points of the player
  def pointsForPlayer(player)
    playerColor = player.color
    longestPath = 0

    outermostFieldsInCircuit = Array.new(Array.new)
    visited = Array.new(Constants::SIZE).map {|j| Array.new(Constants::SIZE).map {|i| false}} #// all by default initialized to false
    for x in 0..(Constants::SIZE-1)
      for y in 0..(Constants::SIZE-1)
        if visited[x][y] == false
          if self.board.fields[x][y].ownerColor == playerColor
            startOfCircuit = Array.new
            startOfCircuit.push(self.board.fields[x][y])
            circuit = self.circuit(startOfCircuit, Array.new)
            for f in circuit
              visited[f.x][f.y] = true
            end
            outermost = Array.new(2)
            if playerColor == PlayerColor::RED

              outermost[0] = self.bottomMostFieldInCircuit(circuit)
              outermost[1] = self.topMostFieldInCircuit(circuit)
            else
              outermost[0] = leftMostFieldInCircuit(circuit)
              outermost[1] = rightMostFieldInCircuit(circuit)
            end
            outermostFieldsInCircuit.push(outermost)

          end
          visited[x][y] = true
        end
      end
    end
    for fields in outermostFieldsInCircuit
      if (playerColor == PlayerColor::RED && fields[1].y - fields[0].y > longestPath)
        longestPath = fields[1].y - fields[0].y
      end
      if (playerColor == PlayerColor::BLUE && fields[1].x - fields[0].x > longestPath)
        longestPath = fields[1].x - fields[0].x
      end
    end
    
    return longestPath # return longestPath
  end

  # the following functions are helpers for the points calculation
  def bottomMostFieldInCircuit(circuit)
    bottomMostField = circuit[0]
    for f in circuit
      if f.y < bottomMostField.y
        bottomMostField = f
      end
    end
    return bottomMostField
  end

  def topMostFieldInCircuit(circuit)
    topMostField = circuit[0]
    for f in circuit
      if f.y > topMostField.y
        topMostField = f
      end
    end
    return topMostField
  end

  def leftMostFieldInCircuit(circuit)
    leftMostField = circuit[0]
    for f in circuit
      if f.x < leftMostField.x
        leftMostField = f
      end
    end
    return leftMostField
  end

  def rightMostFieldInCircuit(circuit)
    rightMostField = circuit[0]
    for f in circuit
      if f.x > rightMostField.x
        rightMostField = f
      end
    end
    return rightMostField
  end

  def circuit(circuit, visited)
    changed = false;
    toBeAddedFields = Array.new
    for f in circuit
      if !visited.include?(f)
        changed = true
        visited.push(f)
        for c in self.board.getConnections(f.x,f.y)
          if !circuit.include?(self.board.fields[c.x2][c.y2])
            toBeAddedFields.push(self.board.fields[c.x2][c.y2])
          end
          if !circuit.include?(self.board.fields[c.x1][c.y1])
            toBeAddedFields.push(self.board.fields[c.x1][c.y1])
          end
        end
      end
    end
    circuit.push(*toBeAddedFields)
    if changed
      return self.circuit(circuit, visited)
    else 
      return circuit
    end
  end
  
end