require 'string.rb'
require 'robots.rb'
require 'sp/sp_ext'
require  'worldmap'

$preset = []
$bad_pos = {}
class Packages
  attr_reader :by_dest
  
  def initialize()
    @by_dest = {}
  end

  def parse(line)
    ltok  = line.to_ints
    npack = ltok.size/4
    if npack > 0
      $cur_packages = {}
      @by_dest = {}
      npack.times do
        pid, xd, yd, wt = ltok.slice!(0..3)
        if(wt < $player.local_cap)
          if( wt > 0 )
            pos = to_pos(xd,yd)
            if $sp[pos] < 254 
               p = @by_dest[pos]
               if (!p) || (wt > p.weight)
                 @by_dest[pos] = $cur_packages[pid] = Package.new(pid, xd, yd, wt)
               end
             end
          end
        end
      end
    end
    $cur_packages
  end
  
  def dests
    @by_dest.keys
  end
  
  def pick_some(pos)
    if @by_dest[pos]
      id = @by_dest[pos].id
      "1 Pick #{id}"
    else
      for d,p in @by_dest
        if $sp[p.dest] < 254
          return "1 Pick #{p.id}"
        end
      end
    end
  end
  
  def nonempty
    @by_dest.size > 0
  end
end


module AI
  include Consts
  PLANE_WEIGHT   = 3
  WATER_PENALTY  = 10
  BLOCK1_PENALTY = 2
  BLOCK2_PENALTY = 5
  DANGER_PENALTY = 10
  MAX_PACKS      = 40
  
  def log(*x)
  end

  def alone
    $robots.size < 2
  end

  def near_robot(p = $player.pos)
    (near_info(p) & ROBOT_FL) != 0
  end

  def info(p=$player.pos)
    $sp.info(p)
  end
  
  def danger_on(pos)
    danger_pos(pos) &&
      (near_or(pos) { |p| ( p != $player.pos ) && 
           ambush_pos(p) && 
           (robot_pos(p) || near_robot(p))  }
       )
  end
  
  def move_dir_string(from,to)
    if from < to
      if to == from+1
        "E"
      else
        "N"
      end
    else
      if to == from-1
        "W"
      else
        "S"
      end
    end
  end
  
  def follow
    pos = $player.pos
    return nil if( pos != @next_pos )
    return nil if (@next_pos = $sp.next(pos)) < 0
    log "moving to "+pos_to_s(@next_pos);
    if danger_on(@next_pos)
      log "Danger on "+pos_to_s(@next_pos)
    end
    s = move_dir_string(pos,@next_pos)
    "-1 Move #{s}"
  end

  def search
    if( $robots.size > 1 )
      log "weighted search..."
      $sp.weighted_search($player.pos)
    else 
      log "unweighted search..."
      $sp.unweighted_search($player.pos)
    end
  end
  
  def targeted_search(dests)
    res = [-1,-1,-1]
    if( $robots.size > 1 )
      log "weighted targeted search..."
      res = $sp.targeted_weighted_search($player.pos,dests)
    else
      log "unweighted targeted search..."
      res = $sp.targeted_search($player.pos,dests)
    end
    $sp.cleanup_targeted
  end
  

  def local_obj(&block)
    @local_obj = block
    @revise = @revise_period
    block.call
  end

  def avoidable_push_possible
    log "checking for avoidable push"
    false
  end

  def collect_cargo(packs)
    weight,dist,pos = targeted_search(packs.dests)
    if( (alone) || ($player.num_packs < MAX_PACKS) )
      if pos > -1 
        log "Best package: "+pos_to_s(pos)
        return packs.pick_some(pos) if dist < $fuel+3*$player.num_packs
      end
      nil
    end
  end

  def avoid_push
    log "trying to avoid push"
    nil
  end

  def deliver_cargo
    weight,dist,dest = targeted_search($player.dests)
    if dest > -1 
      log("heading to destination "+pos_to_s(dest))
      @next_pos = $player.pos
      return local_obj { follow }
    end
    nil
  end

  def try_find_cargo
    weight,dist,dest = search()
    if dest > -1 
      log("heading to cargo "+pos_to_s(dest))
      @next_pos = $player.pos
      return local_obj { follow }
    end
    nil
  end

  def find_cargo
    log "looking for cargo..."
    try_find_cargo || ($sp.mark_targets && try_find_cargo)
  end
  
  def get_agressive
    log "Getting angry..."
    nil
  end
  
  def find_objective(cur_packages)
    log "looking for an objecive #{cur_packages}"
    if cur_packages && cur_packages.nonempty 
      if( avoidable_push_possible )
        if( $player.enough_cargo )
          log "Avoiding avoidable push"
          return avoid_push() 
        end
      end
      log "Collecting cargo..."
      return collect_cargo(cur_packages)
    else
      $sp.unmark($player.pos)
      log "No suitable packages found..."
      if $player.has_cargo
        deliver_cargo || get_agressive
      else
        find_cargo || get_agressive
      end
    end
  end
  
  def targeted_search(dests)
    log "Targeted search..."
    log dests.join(",")
    if( $robots.size > 1 )
      $sp.targeted_weighted_search($player.pos,dests)
    else
      $sp.targeted_search($player.pos,dests)
    end
  end
  
  def choose_dest(dests)
    targeted_search(dests)
    weight,steps,dest = targeted_search($player.pos.dests)
    if( steps > $fuel )
      weight,step,dest = $sp.targeted_search($player.pos,dests)
    end
    return dest
  end
  
  def handle_dangers
    log "Handling dangers..."
    if danger_on($player.pos)
      log "Danger!!!" # No time for this...
    end
    log "No danger..."
    nil
  end
  
  def handle_local_obj
    @local_obj.call if @local_obj && ( (@revise -= 1) > 0 )
    nil
  end
  
  def revise_objective(cur_packages)
    log "revising objective..."
    find_objective(cur_packages) || "1 Pick 1000" # I have no idea...
  end
  
  def get_move(cur_packages)
    $sp.unmark($player.pos) if (!cur_packages) && (!$bad_pos[$player.pos])
    @revise_period ||= 0
    log "There are packages on this location..." if cur_packages 
    handle_dangers || $player.drop_largest ||
      (cur_packages && revise_objective(cur_packages)) || 
      handle_local_obj || revise_objective(cur_packages)
  end
end

module Interactive
  def get_move(cur_packages)
    gets
  end
end

class GameClient
  PWBW="?~@#"
  PLAIN = PWBW[0]
  WATER = PWBW[1]
  BASE  = PWBW[2]
  WALL  = PWBW[3]
  WALL_S = "#"

  include AI
# include Interactive

  def initialize(socket) 	
    @sock = socket # to allow unit testing 
    @score = 0
    @packs = Packages.new
    @cur_packs = []
    @sock.puts "Player"
    read_map(@sock)
    read_player(@sock)
  end
  
  def log(*x)
  end
  
  def get(x,y)
	@bmap[x+y*$w]
  end
  
  def [](x,y)
     @bmap[x+y*$w]
  end

  def read_map(sock)
    w, h    = sock.gets.to_ints
    mbuf = ""
    w+=2
    mbuf << (WALL_S * w)
    h.times { 
      l = sock.gets
      l[-1] = WALL_S
      mbuf << "\##{l}" 
    }
    mbuf << (WALL_S * w)
    h+=2
    @width  = w
    @height = h
    @bmap  =  mbuf
    $width = $w = @width
    $world = World.new(@width,@height,convert_map(@bmap))
    $sp = PathSearch.new(@width,@height,convert_map(@bmap))
    $pattern = Regexp.new("."*$w)
    log $world
    log "Map #{w}x#{h} is read..."
  end

  def map_to_s    
    str = ""
    map = @bmap.dup
    # Put robots on the map !
    for r in $robots
      map[r.pos] = ( (r.id == $player.id) ? (?M) : (?O) )
    end
    map.scan($pattern).reverse_each { |s| str << s+"\n" }
    str
  end
  
  def read_player(sock)
    my_id,@cap,$fuel = *(sock.gets.to_ints)
    $robots = RobotMgr.new(my_id)
    $own_id = my_id
    $cap = @cap
  end
  
  def log_info
    puts "map:"
    puts map_to_s
    for r in $robots
      if r.id != $own_id
        r.put($stdout,@cap,"???")
      end
    end
    $player.put($stdout,@cap,$fuel)
  end
  
  def put_choices(ps)
    choices = {}
    for i,p in ps
      choices["#{p.weight} (#{p.xdest},#{p.ydest})"] ||= p.id
    end
    
    for k,v in choices
      log "weight #{k} : id=#{v}"
    end
  end

  def perform_move(sock,cur_packages)
    log "Number of robots : #{$robots.size}"
    move = get_move(cur_packages && @packs)
    log move
#   move = "1 invalid 0" if(gets.chomp.size > 0)
    $fuel -= move.split[0].to_i.abs
    sock.puts move  
  end

  def convert_map(m)
    s1 = "~@ #0-9"
    s2 = "     "
    s2[0],s2[1],s2[2],s2[3] = 254,0,1,255,1
    m.tr(s1,s2)
  end
    def preprocess
    log $world
  end

  def extract_pos(id,l)
    log id
    log l
    pos = -1
    l = l.split("#")
    for s in l
      s.strip!
      if s && ( s.size > 0 )
        log "s : #{s}"
        gi = (s.split)[0]
        log gi
        if gi.to_i == id
          if( s =~ /X\s(\d+)/ )
            pos = $1.to_i
          else
            raise "X coordinate could not be read"
          end
          if( s =~ /Y\s(\d+)/ )
            pos += $1.to_i * $w
            return pos
          else
            raise "Y coordinate could not be read"
          end
        end
      end
    end
    
    raise "Own robots initial position is not given..." if pos < 0
    pos
  end
  
  def show_weights
    map = []
    @height.times do |j|
      map[j] = " "*$w
      @width.times do |i|
        w = $sp[to_pos(i,j)]
        if w == 254 
          map[j][i] = "~"
        elsif w == 255
          map[j][i] = "#"
        else
          map[j][i] = "0123456789abcdefghijklmnopqrstuvwxyz"[(w+2)/3]
        end
      end
    end
    map.reverse_each do |s|
      log s
    end
  end
  
  def game_loop(sock)
    i=0
    preprocess
    l_init = sock.gets
    pos = extract_pos($own_id,l_init)
    log "initial position : "+pos_to_s(pos)
    $sp.block_unreachable(pos,@bmap)
    $sp.base_update
    $sp.reweight(WATER_PENALTY,BLOCK1_PENALTY,BLOCK2_PENALTY,DANGER_PENALTY)
    show_weights
    while($robots.parse(l_init||sock.gets))
      l_init=nil
 #     log_info
      l=sock.gets
      cur_packages=nil
      if l.strip.size > 0
        log "The following packages are available:"
        cur_packages = @packs.parse(l)
      end
      log "Move #{i} : "
      perform_move(sock,cur_packages);
      i+=1
    end
    log "Your robot is killed..."
  end

  include AI  
end
