
# Invivo AI challenge simulation environment
# Copyright 2009 Simon Funk
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import pygame
import Model as m

class WorldView(object):

	"""A graphical View of a world model."""

	def __init__(self, model, size=(640,480), sound=False):

		self.model = model
		self.sound = sound

		pygame.init()

		self.resize(size)	# This also creates the window and establishes self.screen, plus sets up model to view transform params.

		self.bodies = [self.makeBody(body) for body in model.bodies]	# Make a view of each model body.

		#
		# All the sound stuff is just a quick hack right now.
		# Enough to see that... the delay makes it useless.
		#
		if sound:
			self.maximp = 0.01
			pygame.mixer.init(44100, 16, 1)
			self.soundBall = pygame.mixer.Sound("ball.wav")

		model.watch(self.update)

	def resize(self, size):
		self.size   = size
		self.screen = pygame.display.set_mode(size, pygame.RESIZABLE)

		range            = m.psub(self.model.bounds[1], self.model.bounds[0])	# Size of the model bounds
		range            = m.padd(range, (1., 1.))								# Margin
		self.scale       = min(size[0]/range[0], size[1]/range[1])				# Scale maps from model coords to view coords; use min to avoid clipping.
		self.xo, self.yo = m.psub(self.model.bounds[0], (0.5, 0.5))				# View origin of the model is the minimum bound less margin.
		self.yflip       = self.size[1] - 1

	def update(self, model):

		#
		# Handle events (key presses, resize, quit, etc):
		#
		for event in pygame.event.get():
			if event.type == pygame.QUIT:
				model.stop = True
			elif event.type == pygame.KEYDOWN:
				if event.unicode == 'q':
					model.stop = True
			elif event.type == pygame.VIDEORESIZE:
				self.resize(event.size)
			else:
				#print "Unhandled event: ", event
				pass

		if self.sound:
			for body in model.bodies:
				if body.contacts:
					if body.name == "Ball":	# Temp hack!
						imp = max(con.impulse for con in body.contacts)
						if imp > self.maximp:
							self.maximp = imp
						self.soundBall.set_volume(imp/self.maximp)
						self.soundBall.play()

		#
		# Redraw the world:
		#
		self.screen.fill(model.bgcolor)

		for body in self.bodies:
			body.draw(self)

		pygame.display.flip()

	def drawPoly(self, points, color, border=0):
		"""This maps points to screen space and draws a polygon of the
		specified color and border thickness (0 = fill).
		"""
		xfm = self.modelToViewXfm
		pygame.draw.polygon(self.screen, color, [xfm(pt) for pt in points], border)

	def drawCircle(self, pos, rad, color, border=0):
		"""Circle at pos with radius, color, border."""
		pygame.draw.circle(self.screen, color, self.modelToViewXfm(pos), int(rad*self.scale), border)

	def drawLine(self, a, b, color, width=1):
		"""Line from a to b with color, width."""
		pygame.draw.line(self.screen, color, self.modelToViewXfm(a), self.modelToViewXfm(b), width)

	def modelToViewXfm(self, (x, y)):
		"Transforms a coordinate from model space to view (integer) space."
		return (int(self.scale * (x-self.xo)), self.yflip - int(self.scale * (y-self.yo)))

	def makeBody(self, mbody):
		"""This creates and returns a View body of model body mbody."""
		return BodyView(mbody)

class BodyView(object):

	def __init__(self, body):
		self.body = body	# Model body

	def draw(self, world):
		body = self.body
		for shape in body.shapes:
			if isinstance(shape, m.Circle):
				center = shape.worldPos(body)
				world.drawCircle(center, shape.radius, shape.color)
				if shape.marker:
					world.drawLine(center, body.toWorld(m.padd(shape.center, (shape.radius, 0.))), (0., 0., 0.))
			elif isinstance(shape, m.Poly):
				world.drawPoly(shape.worldPoints(body), shape.color)
			else:
				raise Exception("Unknown Shape class: %s"%(shape.__class__.__name__,))

