#!/usr/bin/env python
"""
A python module with logging to animate a reversible random chess knight.

Metadata:

  - Author:    W.S.Kendall
  - Copyright: opensource (c) W.S.Kendall 2008
  - Date:      2008-07-04
  - URL:       http://www.wilfridkendall.co.uk
  - Generator: '/home/wilfrid/lib/python/generate.py' version 1.18.


Command-line options::

  usage: %prog [options] arguments
    --logging=LOGGING:	   logging verbosity level.

Notes:
 - values for 'logging' option:
 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
 (decreasing level of verbosity), with 'WARNING' as default.

"""
__RCSID__ = '$Revision: 1.1 $ $Date: 2008/07/05 17:37:05 $'


# \section{Definitions}
# We need the logging module regardless of whether the #logging# option
# is deployed, since #logging.info# calls are scattered through the text.
import logging
from PIL import Image, ImageTk
import Tkinter
from Tkinter import Checkbutton, DoubleVar, IntVar, LAST, Scale, Tk, \
    VERTICAL, X, Y

from knight import Knight

from display2 import ITALICS, BOLD, Display2

#\section{Classes}
class AnimateKnight(Display2):
	"""
	Presents animation of reversible random walk.
	"""
	def __init__(self, root, x0=0, x1=8, y0=0, y1=8, T=20):
		Display2.__init__(self, root, xlo=x0, xhi=x1, ylo=y0, yhi=y1)

		# Buttons for actions.
		self.button(text='SIMULATE', font=BOLD, foreground='darkgreen',\
				    command=self.simulate)
		self.button(text='ANALYZE', font=BOLD, foreground='darkgreen', \
				    command=self.analyze)
		self.button(text='ITERATE', font=BOLD, foreground='darkgreen', \
				    command=self.iterate)
		
		# Scales for adjustments.
		self.prob = DoubleVar()
		self.prob.set(0.5)

		self.batch = IntVar()
		self.batch.set(50)
		Scale(self.buttonframe, from_=1000, to=100, \
			      resolution=100, \
			      orient=VERTICAL, \
			      label='batch', font=BOLD, \
			      variable=self.batch).pack(fill=Y)

		# Buttons for adjustments.

		self.button(text='WIPE', font=BOLD, foreground='firebrick', \
				    command=self.wipe)


		for yinc in range(y0, y1):
			for xinc in range(x0, x1):
				if (xinc + yinc) % 2:
					col = 'skyblue'
				else:
					col = 'white'
				self.box(xinc, yinc, xinc + 1, yinc + 1, \
						 outline='black', fill=col)
		
		self.T, self.x0, self.x1, self.y0, self.y1 = T, x0, x1, y0, y1

		im = Image.open('image/knight.png').convert("RGBA")
		im.thumbnail((64,64))
		self.piece = ImageTk.PhotoImage(im)
		
		self.chain, self.occupied = None, {}

		self.iterations = 1
		
	def simulate(self):
		"""
		Run a single animation.
		"""
		self.canvas.delete('active')

		if self.chain is None:		
			self.chain = Knight()
			loc = self.chain.equilibrium_draw()
			if loc not in self.occupied:
				self.knght(loc, fill='yellow', \
						   width=4, tags='objects')
				self.occupied[loc] = 1
			else:
				self.occupied[loc] += 1
			self.knght(loc, fill='red', width=4, tags='active')

		for t in range(self.T):
			self.chain.update()
			loc = self.chain[-1]
			if loc not in self.occupied:
				self.knght(loc, fill='peachpuff', tags='objects')
				self.occupied[loc] = 1
			else:
				self.occupied[loc] += 1
			self.knght(loc, fill='midnightblue', \
					 tags='active')

		x, y = self.convert(*loc)
		self.canvas.delete('knight')
		self.canvas.create_image(x+32, y-32, image=self.piece, \
						 tags=('knight', 'object'))
		self.iterations += 1
	
	def knght(self, loc, **kw):
		"""
		Draw a knight.
		"""
		x, y = loc
		self.dot((x + 0.5, y + 0.5), **kw)

	def analyze(self):
		"""
		Analyze last animation.
		"""
		if self.chain is None:
			return

		T = len(self.chain)
		self.canvas.delete('transient2')
		self.canvas.addtag_withtag('transient2', 'transient1')
		self.canvas.dtag('transient1', 'transient1')
		self.canvas.addtag_withtag('transient1', 'transient0')
		self.canvas.dtag('transient0', 'transient0')
		self.canvas.itemconfig('transient1', fill='orange', width=2)
		self.canvas.itemconfig('transient2', fill='pink', width=1)

		for t, transition in enumerate(self.chain.reversed_statistics()):
			start, finis = transition
			x0, y0 = start
			x1, y1 = finis
			self.line(x0 + 0.5, y0 + 0.5, x1 + 0.5, y1 + 0.5, \
					  width=4, fill='red', arrow=LAST, \
					  tag='transient0')
		
		self.canvas.delete('prob')

		K = float(sum(x for x in self.occupied.values()))
		for loc in self.occupied:
			self.write('%3d' % int(K/self.occupied[loc]), \
					   loc[0] + 0.5, loc[1] + 0.25, \
					   font=BOLD, tags='prob')

		self.canvas.lift('knight')
		self.chain = None

	def iterate(self):
		m, dm = 10, self.batch.get()
		for i in range(self.iterations, self.iterations + dm):
			self.simulate()
			self.analyze()
			if not i % m:
				self.canvas.delete('record')
				self.write('[%4d]' % i, -2, -0.5, \
						   font=ITALICS, tags='record')
				self.canvas.update_idletasks()
		self.iterations += dm
	
	def togglewipe(self):
		"""
		Wipe all objects and change checkbuttona ppearance.
		"""
		if self.toggle.get():
			self.togglecb['text'] = 'EQUILIBRIUM'
			self.togglecb['foreground'] = 'black'
		else:
			self.togglecb['text'] = '2 PT START '
			self.togglecb['foreground'] = 'red'
		self.wipe()

	def wipe(self):
		"""
		Wipe all objects.
		"""
		self.chain, self.iterations = None, 1
		self.occupied = {}
		
		self.canvas.delete('objects', 'active', 'transient0', \
					   'transient1', 'transient2', \
					   'prob', 'record')
		
	def adjust_prob(self, p):
		"""
		Callback if slider is adjusted.
		"""
		self.wipe()
		self.iterations, self.p = 1, float(p)


#\section{Exceptions}
class Error(Exception):
	"""
	Generic exception to be raised by this module.
	"""

# \section{Functions}
def main(opt, arguments):
	"""
	The action of the module as script should be concentrated here!
	"""
	root = Tk()
	root.title('Reversible random chess knight')
	animation = AnimateKnight(root)
	
	root.mainloop()




# \section{Main script}
if __name__ == '__main__':
	import optionparse
	option, args = optionparse.parse(__doc__, version=__RCSID__)

	if option.logging is not None:
		logging.basicConfig(level=logging.__dict__[option.logging])
		logging.info('Logging level %s' % option.logging)


	# The action of the module as script should be concentrated
	# in the following function, to facilitate profiling.
	main(option, args)


	logging.info('Finishing')
	raise SystemExit


# \newpage \scriptsize
# \begin{multicols}{2}
# \section{History}
# \text{ }
#\\ $Log: animateknight.pyw,v $
#\\ Revision 1.1  2008/07/05 17:37:05  wskendall
#\\ Initial revision
#\\
#\\ Revision 1.1  2008/07/05 10:33:33  wskendall
#\\ Initial revision
#\\
#
# \end{multicols}

'$Id: animateknight.pyw,v 1.1 2008/07/05 17:37:05 wskendall Exp wskendall $'
