/Richter/Level.py
import sys
import os
import re
import new
import yaml
from Collage import Collage
from Surface import SurfaceSet
import pygame
from OpenGL.GL import *

classpath = ['classes']
sys.path += classpath

function_re = re.compile('^(\w+)\(([^)]*)\)$')


class Layer:
	"""Container for layer data.

	collage - A Collage object defining the visuals of the layer
	scale - A float value for the size of the background layer
	parallax - A float value specifying the relative scroll speed (where
		   1.0 is typically the playfield scroll speed, >1.0 is faster,
		   and <1.0 is slower)"""

	def __init__(self, collage=None, surfaces=None, scale=1.0, parallax=1.0):
		self.collage = collage
		self.surfaces = surfaces
		self.scale = scale
		self.parallax = parallax
		self.position = (0, 0)
		self.things = []

		self.drawsurfaces = False


	def add(self, thing):
		self.things.append(thing)


	def moveTo(self, x, y):
		self.position = (x * self.parallax, y * self.parallax)


	def update(self):
		for t in self.things:
			t.update()
			t.collideCheck(self.things)


	def drawSurfaces(self):
		self.drawsurfaces = True


	def hideSurfaces(self):
		self.drawsurfaces = False


	def toggleDrawSurfaces(self):
		self.drawsurfaces = not self.drawsurfaces


	def draw(self):
		glPushMatrix()
		if self.scale != 1.0:
			glScalef(self.scale, self.scale, self.scale)
		glTranslatef(-self.position[0], -self.position[1], 0)
		self.collage.draw()
		for t in self.things:
			t.draw()
		if self.drawsurfaces and self.surfaces:
			self.surfaces.draw()
		glPopMatrix()


class Level:
	def __init__(self, dir):
		self.layers = []
		self.position = (0, 0)
		self.env = {}

		self.load(dir)


	def load(self, dir):
		f = open(os.path.join(dir, 'level.yaml'), 'r')
		level = yaml.safe_load(f)
		f.close()
		layers = level['layers']

		for layer in layers:
			collage = None
			surfaces = None
			scale = 1.0
			parallax = 1.0

			if layer.has_key('collage'):
				collage = Collage(os.path.join(dir, 'collages', layer['collage']))
			if layer.has_key('surfaces'):
				surfaces = SurfaceSet(os.path.join(dir, 'surfaces', layer['surfaces']))
			if layer.has_key('scale'):
				scale = layer['scale']
			if layer.has_key('parallax'):
				parallax = layer['parallax']

			print 'Creating layer', layer['name'], 'with scale=%f, parallax=%f' % (scale, parallax)
			new_layer = Layer(collage, surfaces, scale, parallax)
			self.addLayerBack(new_layer)

			if layer.has_key('things'):
				self.compileThings(new_layer, layer['things'])

		# Load or compute level bounding box
		if level.has_key('bbox'):
			self.bbox = pygame.Rect(level['bbox'])
		else:
			bboxes = filter(None, [c.bbox for c in filter(None, [l.collage for l in self.layers])])
			if bboxes:
				self.bbox = bboxes[0]
				self.bbox.unionall_ip(bboxes[1:])
			else:
				self.bbox = None
		print "Bounding box:", self.bbox


	def compileThings(self, layer, things):
		for (name, t) in things.iteritems():
			print "Creating", t['class'], name
			self.loadClass(t['class'])
			properties = {}
			if t.has_key('properties'):
				properties = t['properties']
			properties['surfaces'] = layer.surfaces

			self.env[name] = eval('%s(**properties)' % t['class'], self.env, {'properties': properties})

			if t.has_key('events'):
				for (fdef, str) in t['events'].iteritems():
					m = function_re.match(fdef)
					if not m:
						print "Malformed function definition:", fdef
						continue

					funcname = m.group(1)
					code = "def %s:\n" % m.group(0)
					for s in str.split("\n"):
						if not s: continue
						code += "\t" + s + "\n"
					exec code in self.env
					f = new.function(self.env[funcname].func_code, self.env)
					self.env[name].__dict__[funcname] = new.instancemethod(f, self.env[name], self.env[name].__class__)
					del self.env[funcname]
			layer.add(self.env[name])


	def loadClass(self, classname):
		"""A fun bit of metaprogramming. Load a class definition into
		our environment based on its name"""
		exec "from %s import %s" % (classname, classname) in self.env


	def addLayerBack(self, layer):
		self.layers.insert(0, layer)


	def addLayerFront(self, layer):
		self.layers.append(layer)


	def moveTo(self, x, y):
		self.position = [x,y]
		for l in self.layers:
			l.moveTo(x, y)


	def update(self):
		for l in self.layers:
			l.update()


	def drawSurfaces(self):
		for l in self.layers:
			l.drawSurfaces()


	def hideSurfaces(self):
		for l in self.layers:
			l.hideSurfaces()


	def toggleDrawSurfaces(self):
		for l in self.layers:
			l.toggleDrawSurfaces()


	def draw(self):
		for l in self.layers:
			l.draw()