

class Property

  attr_reader :ident, :values


  def initialize(ident)
    @ident= ident
    @values= Array.new
  end

  def to_s
    buf =  ''
    buf << @ident
    @values.each { |str| buf << '['+ str.to_s + ']' }
    buf
  end

  def action(g)
  end
  def undo(g)
  end

  def set_node(node)
    @node= node
  end

  def <=>(other)
	  return( self.prior <=> other.prior)
  end
end

class PUnknown < Property
end

module Coords

  def a_to_i(char)
    Coords.a_to_i(char)
  end

  def Coords.a_to_i(char)
    if char.nil?
      $stderr.puts "hmmmm I think there is a bug round here:"
      $stderr.puts caller
    end
    begin
      if char >= ?a and char <= ?z
        char - ?a
      elsif char >= ?A and char <= ?Z
        char - ?A
      end
    rescue
      raise(SGFError,"error in coords: " + self.inspect)
    end
  end

  def Coords.to_a(x,y)
    #    col= ?a + x
    #  row= ?a + y
    '' << ?a + x << ?a + y

  end

  def each_coords

    @values.each { |str|
      if  str =~ /(..):(..)/
        c1= a_to_i( $1[0])
        r1= a_to_i( $1[1])
        c2= a_to_i( $2[0])
        r2= a_to_i( $2[1])

       #   puts "#{c1}+#{c2}"

        c=r=0

        for c in c1..c2
          for r in r1..r2
    #   puts "#{c}x#{r}"
            yield(c,r)
          end
        end

      else
        yield(a_to_i(str[0]),a_to_i(str[1]))
      end
    }
  end


  def each_zone
    @values.each { |str|
      if  str =~ /(..):(..)/
        c1= a_to_i( $1[0])
        r1= a_to_i( $1[1])
        c2= a_to_i( $2[0])
        r2= a_to_i( $2[1])

      else
        c1= a_to_i(str[0])
        r1= a_to_i(str[1])
        c2= c1
        r2= r1
      end
      yield(c1,r1,c2,r2)
    }
  end

  def Coords.simple_to_ary(str)
#   str= @values[0]
    if  str =~ /(..):(..)/
      c1= Coords.a_to_i( $1[0])
      r1= Coords.a_to_i( $1[1])
      c2= Coords.a_to_i( $2[0])
      r2= Coords.a_to_i( $2[1])

    else
      c1= Coords.a_to_i(str[0])
      r1= Coords.a_to_i(str[1])
      c2= nil
      r2= nil
    end
    return [c1,r1,c2,r2]
  end

  def put_2p(gowin,klass)
    each_zone { |c1,r1,c2,r2| gowin.goban.add_2p(klass,c1,r1,c2,r2) }
  end


  end

  class PMove < Property
    include Coords

 def print_game(game,dimx,dimy)
    puts
      for j in -1..dimy
    for i in -1..dimx
        putc game[i][j]
      end
      puts
    end

  end

    def col
      @col= a_to_i(@values[0][0]) if @col.nil?
      @col
    end

    def row
      @row= a_to_i(@values[0][1]) if @row.nil?
      @row
    end

    def move_pass
      @values[0].empty? or @values[0] == 'tt'
    end

    def action( gowin)

      if @ident == 'MN'
        @node.move_nb= @values[0].to_i
        gowin.set_move_nb(@node.move_nb)
        return
      end

#debug @node
#debug_caller
      unless @node.move_nb
        n = @node
        n = n.prev until n.move_nb
        @node.move_nb= n.move_nb+1
      end
      gowin.set_move_nb(@node.move_nb)
      if    move_pass
        gowin.put_stone(?P,nil,nil,@node.move_nb)
      else
#    debug "action: #@values, #@ident, #{col} #{row}"
        gowin.put_stone((@ident == 'W' ? ?W : ?B),col,row,@node.move_nb)

        if  @pris.nil?
          @pris = Array.new
          opponent =(@ident == 'W' ? ?B : ?W)

=begin
          debug opponent
          debug gowin.game[ col-1][ row]
          debug gowin.game[ col][ row-1]
          debug gowin.game[ col+1][ row]
          debug gowin.game[ col][ row+1]
=end
    game=gowin.copy_game
          try(game,gowin,col-1,row) if game[ col-1][ row]==  opponent
          try(game,gowin,col,row-1) if game[ col][ row-1]==  opponent
          try(game,gowin,col+1,row) if game[ col+1][ row]==  opponent
          try(game,gowin,col,row+1) if game[ col][ row+1]==  opponent

      end

      @pris.each { |color,col,row,nb | gowin.add_pris(color,col,row,nb) }
      unless @node.w_pris
        @node.w_pris= gowin.w_pris
        @node.b_pris= gowin.b_pris
        if @ident == 'W'
          @node.w_pris += @pris.length
        else
          @node.b_pris += @pris.length
        end
      end
      gowin.set_pris(@node.w_pris, @node.b_pris)

    #TODO: handle suicide
    end
  end

  def try(game,gowin,col,row)
	
    unless have_lib(gowin.copy_game,col,row)
      add_pris_list(gowin,game,col,row)
    end
  end

  def add_pris_list(gowin,game,col,row)

    newcolor = game[col][row]
    #debug newcolor
    nb=gowin.moves[col][row]
    @pris.push( [newcolor,col,row,nb])
    game[col][row]= ?.

    add_pris_list(gowin,game,col-1,row) if game[col-1][row]==  newcolor
    add_pris_list(gowin,game,col,row-1) if game[col][row-1]==  newcolor
    add_pris_list(gowin,game,col+1,row) if game[col+1][row]==  newcolor
    add_pris_list(gowin,game,col,row+1) if game[col][row+1]==  newcolor

  end

  def have_lib(game,col,row)

 #debug "have_lib #{col} #{row}"

    color= game[col][row]
    newcolor=(color == ?W ? ?w : ?b)
    game[col][row] = newcolor

    return true if game[col+1][row] == ?E
    return true if game[col][row+1] == ?E
    return true if game[col-1][row] == ?E
    return true if game[col][row-1] == ?E

    return true if  game[col+1][row] == color and  have_lib(game,col+1,row)
    return true if  game[col][row+1] == color and  have_lib(game,col,row+1)
    return true if  game[col-1][row] == color and  have_lib(game,col-1,row)
    return true if  game[col][row-1] == color and  have_lib(game,col,row-1)

    return false

  end

  def undo(gowin)


    if @ident == 'MN'
      return
    end

    #  debug "undo : #@values, #@ident, #{@col} #{@row}"
    unless move_pass
      gowin.delete_stone(col,row)

      if @pris
        @pris.each { |color, col,row, nb | gowin.del_pris(color, col,row, nb ) }

      end
    end
    gowin.set_move_nb(@node.move_nb-1) # sinon fast_undo marche pas
  end

end

class PRoot < Property

  def size_to_i
    return unless @ident == 'SZ'
    return [ @values[0],  @values[0]] if @values[0].kind_of? Integer
    m = /(\d+):?(\d+)?/.match @values[0]
    w= m[1].to_i
    h =(m[2].nil? ? w : m[2].to_i)
    return [w,h]
  end

  def action(gowin)
    case @ident
    when 'FF'
    when 'GM'
      raise SGFError.new( "bad game : #{@values[0]}") if @values[0] != '1'
    when 'ST'
      gowin.set_show_vars(@values[0].to_i)
    end
  end

  def content
    @values[0]
  end

end

class PInfo_TO < Property


  def action(gowin)

    unless @fields
      @fields = Hash.new
      @values.each { |value|
        if value =~ /(.*):(.*):(.*)/
# new FF[5] note: $2 should be 'A'
      case $1
      when 'TM' # basic thinking time
          @fields['TM']= $3.to_i
        when 'OP' # byo-yomi
          @fields['OP']=  $3.to_i
        when 'OM' # moves per overtime
          @fields['OM']=($3.empty? ? 1 : $3.to_i)
        when 'NO' # number of overtime periods
          @fields['NO']=($3.empty? ? -1 : $3.to_i)
        when 'OG' # TODO
          @fields['OG']=  $3.to_i
        when 'TG' # TODO
          @fields['TG']=  $3.to_i
        end
    else

    end
      }

  end

  @fields.each { |key,val|
    case key
    when 'TM'
      gowin.set_time(val)
    when 'OP'
      gowin.set_byo_time(val)
    when 'OM'
      gowin.set_byo_stones(val)
    when 'NO'
      gowin.set_byo_periods(val)
    end
  }


end

end

class PInfo < Property


  def title
    case @ident
    when 'AN'
      'Annotations'
    when 'BR'
      'Black rank'
    when 'BT'
      'Black team'
    when 'CP'
      'Copyright'
    when 'DT'
      'Date'
    when 'EV'
      'Event'
    when 'GN'
      'Name'
    when 'GC'
      'Comment'
    when 'ON'
      'Opening'
    when 'OT'
      'Byo-yomi'
    when 'PB'
      'Black'
    when 'PC'
      'Place'
    when 'PW'
      'White'
    when 'RE'
      'Result'
    when 'RO'
      'Round-number'
    when 'RU'
      'Rules'
    when 'SO'
      'Source'
    when 'TM'
      'Time'
    when 'US'
      'User'
    when 'WR'
      'White rank'
    when 'WT'
      'White'
    when 'ID'
      'Game Number'
    when 'HA'
      'Handicap'
    when 'KM'
      'Komi'
    end
  end

  def content
    @values[0]
  end

  def action(gowin)
    case @ident
    when 'HA'
      gowin.set_handi(@values[0].to_i)
    when 'KM'
      gowin.set_komi(@values[0].to_f)
    when 'BR'
      gowin.set_rank(?B,@values[0])
    when 'WR'
      gowin.set_rank(?W,@values[0])
    when 'PB'
      gowin.set_name(?B,@values[0])
    when 'PW'
      gowin.set_name(?W,@values[0])
    when 'OT'
      gowin.set_byo_time(@values[0].to_i)
    when 'TM'
      gowin.set_time(@values[0].to_i)
    when 'ID'
      gowin.set_number(@values[0].to_i)
    when 'GC'
      gowin.set_type(@values[0])
    when 'GN'
      gowin.set_title(@values[0])
    when 'RE'
      gowin.set_result(@values[0])
    else
	  #TODO: DT
   #  $stderr.puts("unknown property: "+self.to_s)
    end

  end

  def undo(gowin)
    case @ident
    when 'BR'
      gowin.set_rank('B','')
    when 'WR'
      gowin.set_rank('W','')
    when 'PB'
      gowin.set_name('B','')
    when 'PW'
      gowin.set_name('W','')
    when 'OT'
      gowin.set_byo_time('')
      gowin.set_byo_stones('')
      gowin.set_byo_periods('')
    when 'TM'
      gowin.set_time('')
    when 'ID'
      gowin.set_number(nil)
    when 'GN'
      gowin.set_title('')
    when 'RE'
      gowin.set_result('')
    when 'HA'
      gowin.set_handi(0)
    when 'KM'
      gowin.set_komi(0.0)
    end

  end

end

class PNode < Property

  def set_node_annot(gowin,msg)
    prep=(@values[0]== '1' ? '' : 'Very ')
    gowin.set_node_annot(prep+msg)
  end

  def action(gowin)
    case @ident
    when 'N'
      gowin.set_node_name(@values[0])
    when 'C'
      gowin.add_comment(@values[0])
    when 'DM'
      set_node_annot(gowin,'even position')
    when 'GB'
      set_node_annot(gowin,'good for black')
    when 'GW'
      set_node_annot(gowin,'good for white')
    when 'HO'
      set_node_annot(gowin,'interesting move')
    when 'UC'
      set_node_annot(gowin,'unclear position')
    when 'V'
      score= @values[0].to_f
      if score < 0
        player = 'white'
        score = -score
      else
        player = 'black'
      end
      gowin.set_node_annot("Score estimation: #{player} +#{score}")

    end
  end

  def undo(gowin)
    case @ident
    when 'N'
      gowin.set_node_name('')
    end

  end
  def to_s
    if @ident == 'C'
      buf =  ''
      buf << @ident << '['
      @values[0].each_byte { |c| buf << ?\\ if c == ?\\ or c == ?] or c== ?:
        buf << c }
      buf << ']'
    else
      super
    end
  end

end

class PAnnot < Property

  def action(gowin)
    case @ident
    when 'BM'
      gowin.set_annot 'Bad move.'
    when 'DO'
      gowin.set_annot 'Doubtful move.'
    when 'IT'
      gowin.set_annot 'Interesting move.'
    when 'TE'
      gowin.set_annot 'Tesuji.'
    end
  end

end

class PSetup < Property

  include Coords

  def set_stone(gowin, color)
    each_coords {|c,r| gowin.set_stone(color,c,r) }
  end

  def action(gowin)

    if  @ident == 'PL'
      gowin.set_turn( @values[0])
      return
    end


    if  @list.nil?
      @list = Array.new
      each_coords {|c,r|  @list.push([gowin.game[c][r],c,r])  }
    end

    case @ident
    when 'AW'
      set_stone(gowin, ?W )
    when 'AB'
      set_stone(gowin, ?B )
    when 'AE'
      set_stone(gowin, ?E )
    end

  end

  def undo(gowin)
    case @ident
    when 'PL'
      gowin.clear_turn( @values[0])
      return
    end

    @list.each { |cl,c,r|
      gowin.set_stone(cl,c,r) }

  end
end

class PMark < Property

  include Coords

  def put_items(gowin,klass)
    each_coords {|c,r| gowin.goban.add_mark(klass,c,r) }
  end
=begin
  def delete_items(gowin)
    each_coords {|c,r| gowin.goban.delete_mark_at(c,r) }
  end
=end

  def action(gowin)

    case @ident
    when 'CR'
      put_items(gowin, Circle )
    when 'TR'
      put_items(gowin, Triangle )
    when 'SQ'
      put_items(gowin, Rectangle )
    when 'MA','M'
      put_items(gowin, Cross )
    when 'SL'
      put_2p(gowin, Select )
    when 'DD'
      if @values[0].empty?
        gowin.goban.remove_tag('Dimmed')
      else
        put_2p(gowin, Dim )
      end

    when 'LB'
      @values.each { |str|
        str =~ /(..):(.*)/
        gowin.goban.add_text($2, a_to_i($1[0]), a_to_i($1[1]))
      }

    when 'L'
      c = ?a
      @values.each { |str|
        gowin.goban.add_text(c.chr, a_to_i(str[0]), a_to_i(str[1]))
        c += 1
      }

    when 'LN'
      @values.each { |str|
        if  str =~ /(..):(..)/
          c1= a_to_i( $1[0])
          r1= a_to_i( $1[1])
          c2= a_to_i( $2[0])
          r2= a_to_i( $2[1])
          gowin.goban.add_2p(Line,c1,r1,c2,r2)
        end
      }
    when 'AR'
      @values.each { |str|
        if  str =~ /(..):(..)/
          c1= a_to_i( $1[0])
          r1= a_to_i( $1[1])
          c2= a_to_i( $2[0])
          r2= a_to_i( $2[1])
          gowin.goban.add_2p(Arrow,c1,r1,c2,r2)
        end
      }

    end
  end

  def undo(gowin)
    case @ident
    when 'DD'
#TODO: if @values[0].empty?
      gowin.goban.remove_tag('Dimmed')
    end

  end
end

class PGo < Property
  include Coords

  def action(gowin)
    case @ident
    when 'TW'
      each_coords {|c,r| gowin.goban.set_point(Point,'W',c,r) }
    when 'TB'
      each_coords {|c,r| gowin.goban.set_point(Point,'B',c,r) }
    when 'DA'
      each_coords {|c,r| gowin.goban.set_point(Dame,'',c,r) }
=begin
    when 'WP'
      @old_pris=gowin.w_p_inf.pris if  @old_pris.nil?
      gowin.w_p_inf.set_pris(@values[0].to_i)
    when 'BP'
      @old_pris=gowin.b_p_inf.pris if  @old_pris.nil?
      gowin.b_p_inf.set_pris(@values[0].to_i)
=end
    end
  end

  def undo(gowin)
=begin
  case @ident
  when 'WP'
    gowin.w_p_inf.set_pris(@old_pris)
  when 'BP'
    gowin.b_p_inf.set_pris(@old_pris)
  end
=end
  end
end

class PTime < Property

  include Coords

  def action(gowin)
    case @ident
    when 'BL'
      gowin.set_clock('B',@values[0].to_i)
    when 'WL'
      gowin.set_clock('W',@values[0].to_i)
    when 'OW'
      gowin.set_byo('W',@values[0].to_i)
    when 'OB'
      gowin.set_byo('B',@values[0].to_i)
    end
  end

end

class PPrint < Property

  include Coords
  def action(gowin)
    case @ident
    when 'VW'
      if @values[0].empty?
        gowin.goban.remove_tag('View')
      else
        put_2p(gowin, View )
      end
    when 'PM'
      @previous_PM = gowin.show_move_nb.value if  @previous_PM.nil?
      @values[0]='1' if @values[0].empty? # default value for FF4
      gowin.set_show_move_nb( @values[0])
    when 'FG'
      if @flags.nil?
        @values[0] =~ /(\d+)(:(.*))?/
        @flags = $1.to_i
        @diagram_name= $3
      end
      unless  @flags & 32768 == 1
        if @flags & 1 == 1
          gowin.set_show_coords('0')
        else
          gowin.set_show_coords('1')
        end
        if @flags & 2 == 1
          gowin.set_show_diagram_name('0')
        else
          gowin.set_show_diagram_name('1')
        end
        if @flags & 4 == 1
          gowin.set_show_list_moves('0')
        else
          gowin.set_show_list_moves('1')
        end
        if @flags & 256 == 1
          gowin.set_remove_captured('0')
        else
          gowin.set_remove_captured('1')
        end
        if @flags & 512 == 1
          gowin.set_show_hoshis('0')
        else
          gowin.set_show_hoshis('1')
        end
      end
      gowin.set_diagram_name(@diagram_name) if @diagram_name

    end
  end

  def undo(gowin)
    case @ident
    when 'VW'
#TODO: if @values[0].empty?
      gowin.goban.remove_tag('View')
    when 'PM'
      gowin.set_show_move_nb(@previous_PM)
    end

  end
end

class Property


  Klass = { 'W'=> PMove, 'B'=> PMove ,'MN'=> PMove, 'KO'=> PMove ,
      'SZ'=> PRoot, 'AP'=> PRoot , 'CA'=> PRoot , 'FF'=> PRoot , 'GM'=> PRoot , 'ST'=> PRoot,
'SY'=>PRoot, # SY=AP by Cgoban 1.9.2
      'AN'=> PInfo, 'BR'=> PInfo, 'BT'=> PInfo, 'CP'=> PInfo, 'DT'=> PInfo,
      'EV'=> PInfo, 'GN'=> PInfo, 'GC'=> PInfo, 'ON'=> PInfo, 'OT'=> PInfo,
      'TO'=> PInfo_TO, # private prop for OT in sgf5 -> discarded
      'PB'=> PInfo, 'PC'=> PInfo, 'PW'=> PInfo, 'RE'=> PInfo, 'RO'=> PInfo,
      'HA'=> PInfo, 'KM'=> PInfo,'ID'=> PInfo, #not documented. used for igs game number
      'RU'=> PInfo, 'SO'=> PInfo, 'TM'=> PInfo, 'US'=> PInfo, 'WR'=> PInfo, 'WT'=> PInfo,
      'BM'=> PAnnot, 'DO'=> PAnnot, 'IT'=> PAnnot, 'TE'=> PAnnot,
      'N' => PNode, 'C'=> PNode, 'V'=> PNode, 'DM'=> PNode, 'GB'=> PNode,
      'GW'=> PNode, 'HO'=> PNode, 'UC'=> PNode,
      'AB' => PSetup, 'AE' => PSetup, 'AW' => PSetup, 'PL' => PSetup ,
      'AR'=> PMark, 'CR'=> PMark, 'DD'=> PMark, 'LB'=> PMark, 'LN'=> PMark,
      'MA'=> PMark, 'SL'=> PMark, 'SQ'=> PMark, 'TR' => PMark,
      'M'=> PMark, 'L' => PMark, # old sgf format Mark and Letters
      'TB'=> PGo, 'TW'=> PGo, 'DA'=> PGo, # new: Dame
      'WP'=> PGo, 'BP'=> PGo, # not standard: prisoners -> discarded
      'BL'=> PTime, 'OB'=> PTime, 'OW'=> PTime, 'WL'=> PTime,
      'VW'=> PPrint, 'PM'=> PPrint, 'FG'=> PPrint ,
      'NW'=>PUnknown,'NB'=>PUnknown,'LT'=>PUnknown,
      'PT'=>PUnknown,'RN'=>PUnknown,'RD'=>PUnknown,
      'LC'=>PUnknown
  }

  def Property.create(ident)
    klass = Klass[ident]
    if klass.nil?
      klass = Property
      debug "unknown property #{ident}"
      debug_caller

    end
    klass.new(ident)

  end

  def push(val)
    @values.push(val)
  end


end



