

class SGFError < StandardError
	attr :retr
	def initialize(msg,retr=false)
		super(msg)
		@retr= retr
	end
end

class PropertyError <  SGFError
end

class Node  
	attr_accessor :prev, :next, :var_next, :var_prev, :c_var, :props
	attr_accessor :move_nb, :w_pris, :b_pris


	def initialize
		@props = Hash.new
	end
	def to_s
		str= ';'
		str << @prop_MN.to_s if @prop_MN
		each_value { |prop| str << prop.to_s }
		str << "\n"
	end
	def [] (key)
		case key
		when 'MN'
			return @prop_MN
		when 'SZ'
			if   @props['SZ'].nil?
				return [19,19]
			else
				return   @props['SZ'].size_to_i
			end
		when 'ST','HA'
			if   @props[key].nil?
				return 0
			else
				return   @props[key].to_i
			end
		when 'KM'
			if   @props['KM'].nil?
				return 0.0
			else
				return   @props['KM'].values[0].to_f
			end
		else
			return   @props[key]
		end
	end
	def get_prop(key)
		@props[key]
	end
	def has_key?(key)
		case key
		when 'MN'
			return @prop_MN
		else
			return @props.has_key?(key)
		end
	end
	def []= (key,value='')

		#debug "#{key}=> #{value}"

		if key == 'MN'
			@prop_MN= Property.create(key) if @prop_MN.nil?
			@prop_MN.set_node(self)
			@prop_MN.push(value) 
		else
			store(key,Property.create(key)) unless @props.key?(key)
			@props[key].push(value) 
		end
	end
	def add_comment(value)
#must be only one value in Comment property
		store('C',Property.create('C')) unless @props.key?('C')
		p= @props['C']
		if p.values[0]
			p.values[0] += value
		else
			p.push(value)
		end
	end
	def action(gowin,key=nil)
		if key
			if key == 'MN'
				@prop_MN.action(gowin) 
			else
				@props[key].action(gowin)
			end
		else
			@prop_MN.action(gowin) if @prop_MN
			@props.each_value { | prop|  prop.action(gowin) }
		end
	end
	def undo(gowin)
		@prop_MN.undo(gowin) if @prop_MN
		@props.each_value { | prop| 
			prop.undo(gowin) }
		end
		def fast_action(gowin)

			p.action(gowin) if p=@props['AE']
			p.action(gowin) if p=@props['AB']
			p.action(gowin) if p=@props['AW']
			p.action(gowin) if p=@props['B']
			p.action(gowin) if p=@props['W']
		end
		def fast_undo(gowin)

			p.undo(gowin) if p=@props['AE']
			p.undo(gowin) if p=@props['AB']
			p.undo(gowin) if p=@props['AW']
			p.undo(gowin) if p=@props['B']
			p.undo(gowin) if p=@props['W']
		end
		def each_value
			@props.each_value { | val| yield(val) }
		end
		def store(key, prop)
			#  debug "#{key}=> #{prop}"
			prop.set_node(self)
			@props.store(key,prop)
		end
		def delete (key)
			@props.delete(key)
		end
		def set(key,val)
			#debug "#{key}=> #{val}"
			if @props.key?(key)
				@props[key].values[0]=val
			else
				self[key]= val
			end

		end
		def empty?
			@props.empty?
		end 
		def push(prop)
			# for Kogo
			# no problem with that
			#    if  (prop.ident== 'AB' or prop.ident=='AW' or prop.ident=='AW') and (has_key?('W') or has_key?('B'))
			#      $stderr.puts ("warning: move and setup mixed in the same node")
			#     end
			if prop.ident == 'AE'
				for  p in   [@props['AB'],  @props['AW']]
					if p
						list= prop.values.clone
						list.each { |v|
							if p.values.include?(v)
								p.values.delete(v)
								prop.values.delete(v)
							end 
						}
						if p.values.empty? 
							@props.delete(p.ident)
							$stderr.puts("Illegal properties AE and #{p.ident} with identical values #{list.inspect} - deleted")
						end
					end
				end
			end

			#for cgoban
			if prop.ident == 'CR'
				for  p in   [@props['B'],  @props['W']]
					if p and p.values[0] == prop.values[0]
						return
					end
				end
			end

			# for gifus
			if @props.has_key?(prop.ident)
				p = @props[prop.ident]
				if prop.ident == 'C'
					prop.values.each {  |v|  p.values[0] << "\n"<< v}
				else
					#$stderr.puts ("warning: multiple definition of #{prop.ident}")
					prop.values.each {  |v| p.push(v) }
					#debug p.values
				end
				return
			end



			store(prop.ident,prop) unless prop.values.empty?
		end
		def merge(node)
			node.props.each{|k,v| self[k]=v }

		end
	end # class Node

	class IO_String

		def initialize(data)
			@data= data
			@index=0
		end

		def getc
			c= @data[@index]
			@index += 1
			c
		end

		def ungetc(c)
			@index -=1
		end

		def eof?
			@index >= @data.length
		end
		def rewind
			@index=0
		end

	end 

	class SGF < Array

		def parse_data(data)
			@inp= IO_String.new(data)
			node= Node.new
			while not @inp.eof?
				prop=read_prop
				node.push(prop)
			end
			return node
		end

		def read_file(file)
			File.open(file) { |@inp| parse }
		end

		def read_data(data)
			@inp= IO_String.new(data)
			parse
		end

		def write_file(file)
			begin
				File.open(file, "w+") { |f|
					f.puts self.to_s
				}
			rescue
				$stderr.puts "Error writing file: #{file}"
			end
		end
		def write_var(file,end_node)
			begin
				File.open(file, "w+") { |f|
					f.puts print_var(end_node)
				}
			rescue
				$stderr.puts "Error writing file: #{file}"
			end
		end

		def parse
			@old_format= false
			@read_mode='normal'

			begin
				while not @inp.eof?
					c=getchar
					if c == ?(
						push(read_tree)
					end
				end
			rescue SGFError=>e
				$stderr.puts e.message
				if e.retr
					puts("Trying to import from other format")
					@read_mode='gifus'
					@inp.rewind
					c=getchar
					c=getchar while c != ?(
					push(read_gifus_tree)
				else
					raise
				end
			end


			$stderr.puts "Warning: old SGF format detected" if @old_format
			#puts to_s
		end

		def to_s
			str= ''
			each { | root| 
				str << print_tree( root)
			}
			str << "\n"
		end

		def getchar

			c=@inp.getc
			c=@inp.getc while c== ?\n or c==?\r or c ==?\t or c==32

#			putc c
			return c
		end

		def print_tree(root)
			str = ''
			c = root
			str << "\n("
			str << c.to_s
			c=c.next
			while   c and c.var_next.nil?
				str << c.to_s
				c=c.next
			end
			while not c.nil?
				str << print_tree(c)
				c= c.var_next
			end
			str << ")"

		end

		def print_var(end_node)

			stack = []
			c= end_node
			while c
				stack.push(c)
				c= c.prev
			end


			str = "\n("
			while c= stack.pop
				str << c.to_s
			end

			str << ")"
		end


		def add_var(current,root_node)
			if current.next.nil?
				current.next= root_node
				root_node.c_var= ?A
				root_node.prev= current
			else
				c=current.next
				c=c.var_next while c.var_next
				c.var_next= root_node
				root_node.c_var = c.c_var + 1
				root_node.var_prev = c
				root_node.prev= current
			end
		end

		def read_gifus_tree
			vars= []
			root= Node.new
			root.c_var= ?A
			current= root
			while not @inp.eof?
				c=getchar
				if c == ?)
					return root
				elsif c == ?(
					vars.push(read_gifus_var)
				elsif c== ?;
					unless current.empty?
						n= Node.new
						current.next= n
						n.prev= current
						n.c_var= current.c_var
						unless vars.empty?
							vars.each { |v| add_var(current,v) }
							vars= []
						end
						current= n
					end
				else
					@inp.ungetc(c)
					begin
						p= read_prop
						if p.ident == 'TE'
							prop= Property.create('GN')
							prop.push(p.values[0])
						elsif p.ident == 'RD'
							prop= Property.create('DT')
							prop.push(p.values[0])
						elsif p.ident == 'KO'
							prop= Property.create('KM')
							prop.push(p.values[0])
						else
							prop=p
						end

						current.push(prop)
					rescue
						# bad property: try to skip 
						debug "skip: "+$!
						c= @inp.getc while c != ?\n and c != ?\;
						@inp.ungetc(c)
					end

				end
			end

		end

		#pt18 == ref 17-x
		def read_gifus_var

			root=Node.new
			current=root

			while not @inp.eof?
				c=getchar
				if c == ?)
					return root
				elsif c== ?;
					n=Node.new
					current.next= n
					n.prev= current
					n.c_var= current.c_var
					current = n
				else
					@inp.ungetc(c)
					begin
						p= read_prop
						# 	 debug p
						if p.ident == 'RN' 
							#         puts("gifus detected")
						elsif p.ident == 'PT'
							i=p.values[0].to_i 
							i.times { c= getchar;
							while c != ?;
								c= getchar 
								return root if c == ?)
							end
						}
					else    
						current.push(p)
					end
				rescue
					# bad property: try to skip 
					debug "skip: "+$!
					putc c=@inp.getc while c != ?\n and c != ?\;
					puts
					@inp.ungetc(c)
				end

			end

		end
	end

	def  read_tree
		bogus= false
		root= Node.new
		root.c_var= ?A
		current= root
		while not @inp.eof?
			c=getchar
			if c == ?)
				return root
			elsif c == ?(
				add_var(current,read_tree) 
				# should not be another node after a variation
				bogus=true
			elsif c== ?;
				unless current.empty?
					n= Node.new
					current.next= n
					n.prev= current
					n.c_var= current.c_var
					current= n
				end
			else
				if bogus
					raise SGFError.new('Node after tree',true)
					# current.next= nil
				end
				@inp.ungetc(c)
				begin 
					p= read_prop
					raise SGFError.new('gifus file detected',true) if p.ident=='TE' and current==root
					# puts "\n#{p}"
					# missing ";" in a node
					if (p.ident == 'W' or  p.ident == 'B') and (current.props.has_key?('B') or current.props.has_key?('W') )
						$stderr.puts("malformed node: #{p.to_s}\n")
						#	debug "current: "+current.to_s
						#debug "current_props: "+current.props.inspect
						#debug "prev: "+current.prev.to_s
						n= Node.new
						current.next= n
						n.prev= current
						n.c_var= current.c_var
						current= n
					end
					current.push(p)
				rescue PropertyError
					# bad property: try to skip 
					$stderr.puts("skip: "+$!)
					c= @inp.getc while c != ?\n and c != ?\;

					@inp.ungetc(c)
				end
				# raise  SGFError.new("unknown char: #{c}")
			end
		end

		raise(SGFError.new,'end of file reached after: '+buf)
	end

	def read_prop
		name= read_prop_name
		raise  PropertyError.new('bad property') if name.empty?
		prop= Property.create(name)
		read_contents(prop)
		return prop
	end

	def read_prop_name
		buf=''
		while not @inp.eof?
			c=getchar
			if c.between?(?A,?Z)
				buf<< c
			elsif c.between?(?a, ?z)
				@old_format=true
			else
				@inp.ungetc(c)
				return buf
			end
		end 

		raise(SGFError.new,'end of file reached after: '+buf)
	end

	def read_contents(prop)

		while not @inp.eof?
			c=getchar
			if c == ?[
				if prop.ident == 'C'  or prop.ident == 'GN'
					prop.push(read_text)
				else
					prop.push(read_value)
				end
			else
				@inp.ungetc(c)
				return
			end 
		end
		# raise  SGFError.new('end of file reached')
	end

	def read_value
		str=''
		while not @inp.eof?
			c=getchar
			# skip \
			if c == ?\\
				c = getchar
				str << c
			elsif c == ?]
				return str
			elsif c == ?\r
				#skip \r
			else
				str << c
			end
		end

		raise(SGFError.new,'end of file reached after: '+str)
	end

	def read_text
		cpt= 1
		str=''
		while not @inp.eof?
			c=@inp.getc
			# skip \
			if c == ?\\
				c = getchar
				str << c
			elsif c == ?[
				cpt += 1
				str << c
			elsif c == ?]
				cpt -= 1 
				if cpt == 0 or @read_mode == 'normal'
					return str
				else 
					str << c
				end
			elsif c == ?\r
				#skip \r
			else
				str << c
			end
		end

		raise(SGFError,'end of file reached')
	end

end



