#!/usr/bin/ruby
###############################################################################
# Ants server. 
#
# Synopsis
# ants_server [host [port [number_of_players [rounds]]]]
#
# Waits until "number_of_players" clients have connected and simulates "rounds"
# rounds of ant live.
###############################################################################
# (c) 2004 Brian Amberg
# This code is licenced under the GPL
###############################################################################
# $Revision$
###############################################################################

Thread.abort_on_exception = true

require 'socket' # TCP communication
require 'thread' # Multi Threading.
require 'ants'

require 'observable'
require 'ants_server.view'

# Server specific classes
module AntGame::Server
  include AntGame
  
  # A cell of the world.
  #
  # Signals +:update+ whenever its state changes.
  class Cell
    attr_reader :ants, :pheromone, :food, :home
    attr_reader :x, :y
    include Observable

    def initialize(x, y)
      @x = x
      @y = y
      @ants = []
      @pheromone = {}
      self.food = 0
      self.home = nil
    end

    def food=(food)
      @food = food
      self.home.food = food if self.home
      signal_event(:update)
    end

    def home=(player)
      @home = player
      signal_event(:update)
    end

    def to_s
      self.ants.map{|a|a.player.id}.uniq.join.ljust(2) + \
      self.pheromone.keys.map{|p|p.id}.join.ljust(2) + \
      (self.food > 0 ? self.food.to_s : ' ') + \
      (self.home ? self.home.id.to_s : ' ')
    end

    def add_ant(ant)
      self.pheromone[ant.player] = 1.0
      self.ants << ant
      signal_event(:update)
    end

    def remove_ant(ant)
      self.ants.delete(ant)
      signal_event(:update)
    end

    # Ages the cell one iteration more
    #
    # Is called every time a new round is started.
    # At the moment this only decays the pheromone level.
    def age
      self.pheromone.each_key do | player | self.pheromone[player] *= 0.9 end
      signal_event(:update)    
    end
  end

  # The games world
  class World
    attr_reader :width, :height

    include Observable
    include Enumerable

    def initialize(width, height)
      @width = width; @height = height
      @cells = Array.new(width) { | x | Array.new(height) { | y | Cell.new(x, y) } }
      @ants = []
    end

    def to_s
      @cells.collect { | col | col.collect { | cell | cell.to_s }.join('|') }.join("\n" + ('------+' * (self.width)) + "\n")
    end

    def [](x, y)
      return nil if (x < 0) or (y < 0) or (width <= x) or (height <= y)
      @cells[x][y]
    end

    def each
      @cells.each do | col | col.each do | cell | yield cell end end
    end

    def create_ant(player, x, y)
      ant = ServerAnt.new(self, player, x, y, @ants.length)
      self[x, y].add_ant(ant)
      @ants << ant
      signal_event(:ant_added, ant)
      ant
    end

    def each_ant
      @ants.each do |ant| yield ant end
    end

    # Choose a random order of the ants and let each ant act
    # its scheduled action.
    #
    # Also tells each cell that a new round has begun, such that it for example
    # decays the pheromone concentration.
    def act
      self.each do | cell | cell.age end
      @ants.sort_by{ rand }.each do | ant | ant.act end
    end

    def add_food(count)
      while count > 0 do
        x, y = rand(self.width), rand(self.height)
        unless self[x,y].home
          self[x,y].food += 1
          count -= 1
        end
      end
    end
  end


  class ServerAnt < Ant
    attr_accessor :player, :world, :next_action
    attr_reader :id
    include Observable

    def initialize(world, player, x, y, id)
      super(x, y)
      @id = id
      self.world = world
      self.player = player
    end

    def act
      return if dead?
      begin
        self.send self.next_action
        self.next_action = Actions::WAIT
        signal_event(:update)
      rescue => e
        $stderr.puts "Ant should act unknown action #{@next_action} (#{e})"
        raise
      end
    end
    
    # Waiting refreshes the ants energie.
    #
    # In each round a part of the lost energie is request
    # * If the ant is at home 1/4 of the missing energie is refreshed.
    # * Otherwise 1/10 of the missing energie is refreshed.
    def wait
      if cell.home == self.player
        self.energie += (1.0 - self.energie) * 0.25
      else
        self.energie += (1.0 - self.energie) * 0.1
      end
    end

    def take_food
      if !self.has_food and self.world[self.x, self.y].food > 0
        self.world[self.x, self.y].food -= 1
        self.has_food = true
      end
    end

    # Chooses one of the other ants randomly
    # and makes it drop its food if it has.
    #
    # Subtract from the available energie of both ants.
    #
    # The amount of damage depends on the energie of the fighting ants
    # and the number of friends and foes in the cell.
    #
    # Energie is added again when the ant is sleeping
    def attack
      # Choose a foe
      friend, foe = cell.ants.partition { | ant | ant.player == self.player }
      return if foe.empty?
      other = foe[rand(foe.length)]

      # Drop the food
      self.drop_food
      other.drop_food

      # Fight
      e_s, e_o = self.energie, other.energie
      penalty_s = (2.0 * (e_o / e_s) + (foe.length / friend.length) / 3.0)
      penalty_o = (2.0 * (e_s / e_o) + (foe.length / friend.length) / 3.0)
      penalty_s = 2 if penalty_s > 2
      penalty_o = 2 if penalty_o > 2

      self.energie -= 0.5 * rand * penalty_s
      other.energie -= 0.5 * rand * penalty_o
    end

    def energie=(energie)
      energie = 0 if energie < 0
      @energie = energie

      cell.remove_ant(self) if dead?
      signal_event(:update)
    end
    
    def drop_food
      if self.has_food
        self.world[self.x, self.y].food += 1 
        self.has_food = false
      end
    end

    def go
      oldcell = cell
      case self.direction
      when :west 
        self.x -= 1 if self.x > 0
      when :east
        self.x += 1 if self.x < self.world.width - 1
      when :north
        self.y -= 1 if self.y > 0
      when :south
        self.y += 1 if self.y < self.world.height - 1
      end
      oldcell.remove_ant(self)
      cell.add_ant(self)
    end

    def turn_left
      self.direction = case self.direction
                       when :west then :south
                       when :south then :east
                       when :east then :north
                       when :north then :west
                       end
    end

    def turn_right
      self.direction = case self.direction
                       when :west then :north
                       when :north then :east
                       when :east then :south
                       when :south then :west
                       end
    end

    # Encode the ants state and what it sees for transmission to the client
    def encode
      result = AntPropertys.from_ant(self)
    end

    private
    def cell
      cell = self.world[self.x, self.y]
    end

    def cell_north
      cell = self.world[self.x, self.y - 1]
    end

    def cell_east
      cell = self.world[self.x + 1, self.y]
    end

    def cell_south
      cell = self.world[self.x, self.y + 1]
    end

    def cell_west
      cell = self.world[self.x - 1, self.y]
    end

    public
    def has_food=(has_food)
      @has_food = has_food
      signal_event(:update)
    end
  end

  class AntServer
    attr_reader :world, :round, :rounds, :players, :food

    include Observable

    def initialize(host, port, playercount, rounds)
      @server = TCPServer.new(host, port)
      @rounds = rounds
      @round = 0
      @semaphore = Mutex.new
      @playercount = playercount
      @players = []
      # Create world
      @world = World.new(16, 16)
      @food = 30
    end

    def wait_for_players
      # Wait for enough players to connect
      while (self.players.length < @playercount) and (socket = @server.accept)
        client = Player.new(socket)
        client.id = self.players.length
        client.ants = create_ants(client)
        add_client(client)
      end
      
      # Start Game
      play
    end

    def create_ants(player)
      begin
        x = rand(self.world.width)
        y = rand(self.world.height)
      end while self.world[x, y].home
      self.world[x, y].home = player
      (0..5).collect{ self.world.create_ant(player, x, y) }
    end

    def play
      puts "Creating food"
      self.world.add_food(self.food)
      puts "Starting game"
      self.rounds.times do
        signal_event(:new_round)
        @round += 1
        # Inform clients of the current state
        self.players.map do | client | # Start one request thread per client
          Thread.new(client) do | c | c.request_actions end
        end.each do | thread | # Wait for all clients
          thread.join 
        end
        # Let the ants act. The order is random.
        self.world.act
        #puts "\n\nState now:"
        #puts self.world.to_s
      end
      signal_event(:finished)
    end

    def add_client(client)
      puts "Client connected"
      @semaphore.synchronize do self.players << client  end
      client.on_terminate do | c | remove_client(c) end
    end

    def remove_client(client)
      puts "Client disconnected"
      @semaphore.synchronize do self.players.delete(client) end
    end
  end

  class Player
    attr_accessor :ants
    attr_reader :id
    attr_accessor :food

    def initialize(socket)
      @socket = socket
      @on_received = @on_terminate = nil
      @food = 0
    end

    def id=(id)
      @id = id
      @socket.print "#{Protocoll::PLAYER_ID} <#{self.id}>\n"
    end

    def close
      @on_terminate.call(self) if @on_terminate
      @socket.close
    end

    # Ask client what to do next with his ants.
    #
    # Raises EClientQuit exception if the client expresses his wish to quit.
    def request_actions
      @socket.print "#{Protocoll::NEXT_ROUND}\n"
      self.ants.each_with_index do | ant, index |
        next if ant.dead?
        @socket.print "#{Protocoll::ANT} <#{Marshal.dump(Protocoll::AntPropertys.from_ant(ant)).hex_encode}>\n"
        line = @socket.gets("\n")
        case line
        when /^#{Protocoll::QUIT}/ 
            raise "Client wants to quit" # FIXME: Use EClientQuit exception class
        when /^#{Protocoll::ANT} <(.*)>/
            ant.next_action = $1.to_sym
        else
          puts "Command not understood '#{line}'"
        end
      end
    end

    def on_terminate(&on_terminate)
      @on_terminate = on_terminate
    end
  end
end

include AntGame::Server

host, port = ARGV[0] || 'localhost', ARGV[1] || 11112
player_count = (ARGV[2] || 2).to_i
rounds = (ARGV[3] || 1000).to_i
cs = AntServer.new(host, port, player_count, rounds)

view = AntGame::ServerView::AntServerView.new(cs)
server_thread = Thread.new(cs) do | s | s.wait_for_players end

view.register(:quit) do server_thread.exit end # FIXME: Use some nicer method to quit the server, or remove the quit possibility.
view.start

server_thread.join