#!/usr/bin/python

# usage:
# 	newstosgf < article
# -or-
#	newstosgf -v sgf-viewer < article
#
# newstosgf defaults to using cgoban with particular options.  If you
# want to change the default, search for the string "default viewer here",
# and change the value of the "viewer" variable.

# read in a news article and spawn an sgf viewer for each diagram in the
# article, one viewer at a time (as long as your sgf viewer doesn't background
# itself).

# this is version 0.96.

# Mon Nov 22 18:02:37 PST 1999  greatly improved left, right, bottom, top
#                               positioning.  Also made it so a board
#                               would be shown even if there was no nonboard
#                               line following it
# Mon Nov 22 20:07:42 PST 1999  Eliminated one-line diagrams that were getting
#                               prepended to real diagrams
# Sun Nov 28 11:24:25 PST 1999  Expanded letter options from a-g to a-j.
# Sun Nov 28 18:23:28 PST 1999  Added '+' as ignored; many people use it
#                               for hoshi.
# Mon Nov 29 19:42:07 PST 1999  Added max_width and min_width variables.
#                               Added some lines of all letters as top or
#                               bottom, instead of a line of labels
# Sun Dec  5 19:17:13 PST 1999  Rewritten in large part, in order to reduce
#                               the amount of code that relies on regex's.
#                               Also decomposed the problem into subprocedures
#                               much better.

# Please consider this software under the Gnu General Public License version
# 2.  There are copies of the license all over the internet; you should
# have no trouble finding one.  It should be at
# http://www.gnu.org/copyleft/gpl.html.


import string
import sys
import regex
import os

#def count(line,ch):
#	v = 0
#	for c in line:
#		if c == ch:
#			v = v + 1
#	return v

def even(number):
	n=string.atoi(number)
	if int(n/2)*2 == n:
		return 1
	else:
		return 0

def escaped(line):
	result=''
	for ch in line:
		if ch == ']':
			result=result+('\\]')
		else:
			result=result+(ch)
	return result
	sys.stderr.write('result is '+result+'\n')

# change row,col to sgf coordinates, taking into account which corner
# of the board we're in and the width of the board (assumed to be square)
def pos(top,left,row,col,width,height):
	#0..7 is normal, 8 is skipped - not.  In sgf, it's not skipped. On the
	# board, it's skipped
	size=max(width,height)
	lrow=row
	lcol=col
	if not top:
		lrow=size-height+lrow
	if not left:
		lcol=size-width+lcol
	lrow=chr(ord('a')+lrow)
	lcol=chr(ord('a')+lcol)
	return lcol+lrow

def show_board(board):
	print
	for row in range(0,len(board)):
		print board[row]
	print

def refine_board_1(raw,board_regex,left_edge_regex,right_edge_regex,\
	max_width,min_width):
	######################################################################
	# just pull out some raw data, don't worry too much about getting
	# it completely right yet.
	#
	# this handles left and right edges based on |'s
	######################################################################
	board=[]
	height = len(raw)
	top = 0
	left = 0
	for row in range(0,height):
		line = raw[row]
		for width in range(max_width,min_width-1,-1):
			matched = board_regex[width].match(line)
			if matched <> -1:
				break
		# got the right width now, and regs are set
		cooked_line=''
		for col in range(0,width):
			lpos=board_regex[width].regs[1+col][0]
			rpos=board_regex[width].regs[1+col][1]
			stone=line[lpos:rpos]
			cooked_line = cooked_line+stone
		board.append(cooked_line)
		for width in range(max_width,min_width-1,-1):
			matched = left_edge_regex[width].match(line)
			# if both, fine, just do whatever. If the board is square, we're
			# fine, and if the board is retangular, it doesn't matter much which
			# side suffers
			if matched <> -1:
				left = 1
			matched = right_edge_regex[width].match(line)
			if matched <> -1:
				left = 0
	return board,top,left

def sequential_letters(str):
	s = string.lower(str)
	yup = 1
	# only check up through H; if it matches that closely, assume the
	# whole thing is matching.  Things get slightly more complicated at I
	for i in range(0,min(len(s),8)):
		if s[i] <> chr(ord('a') + i):
			yup = 0
			break
	return yup

def horiz_edge(str):
	yup = 1
	for ch in str:
		if not ch in '-_+':
			yup = 0
			break
	return yup

def refine_board_2(board,top,left):
	######################################################################
	# handle rows of letters and rows of dashes
	######################################################################
	if sequential_letters(board[0]) or horiz_edge(board[0]):
		board = board[1:]
		top = 1
	height = len(board)
	if sequential_letters(board[height-1]) or horiz_edge(board[height-1]):
		board = board[:-1]
		top = 0
	# again, if we're both top and bottom, so what. If we're square, we'll
	# just fit, and if we're rectangular it doesn't matter which edge
	# suffers
	return board,top,left

def refine_board_3(board,top,left):
	######################################################################
	# handle columns of digits along the edges, identifying board
	# coordinates (as opposed to numbers identifying a sequence of moves)
	######################################################################
	height = len(board)
	left_is = 1
	right_is = 1
	for row in range(0,height):
		inv=height-row
		n = str(inv)
		left_num = n[-1:]
		right_num = n[0:1]
		if board[row][0] <> left_num:
			left_is = 0
		if board[row][len(board[row])-1] <> right_num:
			right_is = 0
	if left_is:
		# left is all coordinates, so strip it
		left = 1
		for row in range(0,height):
			board[row] = board[row][1:]
	if right_is:
		# right is all coordinates, so strip it
		left = 0
		for row in range(0,height):
			board[row] = board[row][:-1]
	# again, if both left_is and right_is, and we're square, then we'll
	# "just fit", and if we're rectangular, then it doesn't matter which
	# edge suffers
	return board,top,left

def refine_board_4(board,top,left):
	######################################################################
	# handle columns of |'s
	######################################################################
	height = len(board)
	left_is = 1
	right_is = 1
	for row in range(0,height):
		if board[row][0] <> '|':
			left_is = 0
		if board[row][-1:] <> '|':
			right_is = 0
	if left_is:
		# left is all -'s, so strip it
		left = 1
		for row in range(0,height):
			board[row] = board[row][1:]
	if right_is:
		# right is all -'s, so strip it
		left = 0
		for row in range(0,height):
			board[row] = board[row][:-1]
	# again, if both left_is and right_is, and we're square, then we'll
	# "just fit", and if we're rectangular, then it doesn't matter which
	# edge suffers
	return board,top,left

def refine_board(raw,board_regex,left_edge_regex,right_edge_regex,\
	max_width,min_width):
	board,top,left=refine_board_1(raw,board_regex,\
		left_edge_regex,right_edge_regex,max_width,min_width)
	# rows of letters and rows of dashes
	board,top,left=refine_board_2(board,top,left)
	# columns of digits
	board,top,left=refine_board_3(board,top,left)
	# columns of |'s
	board,top,left=refine_board_4(board,top,left)
	return board,top,left

def board_width(board):
	height=len(board)
	max_width=-1
	for row in range(0,height):
		l = len(board[row])
		if l > max_width:
			max_width = l
	return max_width
	
def convert(board,black_to_play,white_to_play,top,left,comment):
	height = len(board)
	width=board_width(board)
	sgf = '(\n;GM[1]FF[3]SZ['+str(max(width,height))+'];\n'
	for row in range(0,height):
		line=board[row]
		width = len(line)
		for col in range(0,len(line)):
			stone = string.upper(line[col])
			if stone in 'X#':
				sgf=sgf+'AB['+pos(top,left,row,col,width,height)+']\n'
			elif stone == 'O':
				sgf=sgf+'AW['+pos(top,left,row,col,width,height)+']\n'
			elif stone in '.,+-_*':
				# do nothing!  comma, plus and dash get used for hoshi sometimes
				pass
			elif stone >= 'A' and stone <= 'T':
				# output a letter
				sgf=sgf+'LB['+pos(top,left,row,col,width,height)+':'+stone+']\n'
			elif stone >= '0' and stone <= '9':
				# output a stone of the appropriate color, if any,
				# and always output a label
				if black_to_play:
					if white_to_play:
						# do nothing, we don't want both colors!
						pass
					else:
						# just black to play, do a stone
						if even(stone):
							sgf=sgf+'AW['+pos(top,left,row,col,width,height)+']\n'
						else:
							sgf=sgf+'AB['+pos(top,left,row,col,width,height)+']\n'
				else:
					if white_to_play:
						# just white to play, do a stone

						if even(stone):
							sgf=sgf+'AB['+pos(top,left,row,col,width,height)+']\n'
						else:
							sgf=sgf+'AW['+pos(top,left,row,col,width,height)+']\n'
				sgf=sgf+'LB['+pos(top,left,row,col,width,height)+':'+stone+']\n'
			else:
				sys.stderr.write('internal error, bad character found:'+\
					stone+'\n')
	sgf=sgf+'C[\n'
	for i in range(0,len(comment)):
		sgf=sgf+comment[i]
	sgf=sgf+']\n)\n'
	return sgf

def display(sgf,viewer,tempfile):
	file = open(tempfile,'w')
	file.write(sgf)
	file.close()
	os.system(viewer+' '+tempfile)
	os.remove(tempfile)

def who_to_play(line,black_regex1,black_regex2,white_regex1,white_regex2):
	lower_line=string.lower(line)
	black_to_play = 0
	white_to_play = 0
	if black_regex1.match(lower_line) != -1:
		black_to_play=1
	if black_regex2.match(lower_line) != -1:
		black_to_play=1
	if white_regex1.match(lower_line) != -1:
		white_to_play=1
	if white_regex2.match(lower_line) != -1:
		white_to_play=1
	return black_to_play,white_to_play

def process_input_stream(input,viewer,tempfile):
	# we actually want 19x19 max, but the regex's like to match the rightmost
	# part of a line, which sometimes bumps the left edge of the board when
	# there are numbers down the right edge.  So we just match 20.
	max_width=20
	min_width=4
	# some people use -'s for hoshi's, and we need dashes for edge detection
	# and some people use *'s for hoshi's too. Criminy, there's a lot
	# of variation in what gets used for hoshis
	s='-\*_xX#oO\\.,+0-9a-tA-T'
	bs='\\(['+s+']\\)'
	board_regex={}
	left_edge_regex={}
	right_edge_regex={}
	for i in range(min_width-1,max_width):
		pattern=''+i*(bs+'[-_ ]')+bs+''
		board_regex[i+1]=regex.compile('^.*'+pattern+'.*$')
		left_edge_regex[i+1]=regex.compile('^.*| *'+pattern+'.*$')
		right_edge_regex[i+1]=regex.compile('^.*'+pattern+' *|.*$')
	black_regex1=regex.compile('^.*black to play.*$')
	white_regex1=regex.compile('^.*white to play.*$')
	black_regex2=regex.compile('^.*black 1\>.*$')
	white_regex2=regex.compile('^.*white 1\>.*$')
	row = 0
	comment=[]
	raw_board=[]
	commenting=0
	black_to_play=0
	white_to_play=0
	done = 0
	while 1:
		if done:
			break
		line = input.readline()
		if not line:
			# generate one fake line at the end, to force the last board
			# to be shown even if there isn't a line of nonboard stuff
			# after it
			line=''
			done = 1
		if len(line) == 1:
			# we do this so the news headers don't end up in the comments
			commenting = 1
		matched = 0
		if commenting == 1:
			comment.append(escaped(line))
		bt,wt = who_to_play(line,
			black_regex1,black_regex2,white_regex1,white_regex2)
		black_to_play = bt or black_to_play
		white_to_play = wt or white_to_play
		matched = 0
		for width in range(max_width,min_width-1,-1):
			if board_regex[width].match(line) <> -1:
				raw_board.append(line)
				row = row + 1
				matched = 1
				break
		if not matched:
			if row >= 2:
				# we have a "board" that's at least 2 lines tall.  Refine it,
				# convert it to SGF, and display it
				board,top,left = refine_board(raw_board,board_regex,\
					left_edge_regex,right_edge_regex,max_width,min_width)
				sgf = convert(board,black_to_play,white_to_play,top,left,comment)
				display(sgf,viewer,tempfile)
				comment=[]
			raw_board = []
			row = 0

def main():
	if sys.argv[2:] and sys.argv[1] == '-v':
		viewer = sys.argv[2]
	else:
		# default viewer here
		viewer = 'cgoban-font -nostealth -edit'
	tempfile="newstosgf.temp"
	process_input_stream(sys.stdin,viewer,tempfile)

main()