################################################################
#
#   Bell Runner - Level
#
################################################################

import os
from pygame import Rect
from albow.resource import get_image
from albow.controls import Label, Grid, Row, Column, Button
from albow.dialogs import Dialog

cell_width = 16
cell_height = 16

cells_wide = 50
cells_high = 36

level_width = cells_wide * cell_width
level_height = cells_high * cell_height

image_cache = {}

#------------------------------------------------------------------------

class Blob(object):
	pass

#------------------------------------------------------------------------

class Item(object):
	#  rect         Rect
	#  image_name   string
	
	deletable = True
	editable = False
	is_exit = False
	filename_suffix = ".png"
	
	def __init__(self, rect, image_name):
		self.rect = rect
		self.image_name = image_name
	
	def reset(self):
		pass
	
	def offset(self, delta):
		dx, dy = delta
		self.rect.move_ip(dx, dy)
	
	def draw(self, surf):
		name = self.current_image_name()
		image = get_item_image(self.__class__, name)
		surf.blit(image, self.rect)
	
	def current_image_name(self):
		return self.image_name
	
	def editor_handle_rects(self):
		return ()
	
	def edit(self):
		field_specs = []
		self.create_edit_fields(field_specs)
		if field_specs:
			fields = Blob()
			controls = []
			for name, title, field in field_specs:
				setattr(fields, name, field)
				controls.append((Label(title), field))
			grid = Grid(controls)
			dlog = Dialog(client = grid, responses = ["OK", "Cancel"])
			self.load_edit_fields(fields)
			resp = dlog.present()
			if resp == "OK":
				self.unload_edit_fields(fields)
				return True
		return False
	
	def create_edit_fields(self, field_specs):
		pass
	
	def load_edit_fields(self, fields):
		pass
	
	def unload_edit_fields(self, fields):
		pass
	
	def bell_rung(self, pitch):
		pass
	
	def actor_nearby(self, level, actor):
		pass
	
	def level_completed(self):
		game.level_completed = True

#------------------------------------------------------------------------

class Background(Item):
	resource_prefix = "decorations"
	layer_name = 'background'

Decoration = Background

class Foreground(Item):
	resource_prefix = "decorations"
	layer_name = 'foreground'

class Platform(Item):
	resource_prefix = "platforms"
	layer_name = 'platforms'
	is_wall = False

class Wall(Platform):
	resource_prefix = "walls"
	is_wall = True

#------------------------------------------------------------------------

class Level(object):
	#  background    [Background]
	#  gadgets       [Item]
	#  platforms     [Platform]
	#  bells         [Bell]
	#  actors        [Player]
	#  foreground    [Foreground]
	#  player        Player
	#  is_completed  boolean
	#  has_exit      boolean

	def __init__(self):
		from player import Player
		self.player = Player((512, 512))
		self.background = []
		self.gadgets = []
		self.platforms = []
		self.bells = []
		self.actors = [self.player]
		self.foreground = []
		self.reset()
	
	def __setstate__(self, state):
		self.__dict__.update(state)
		if 'foreground' not in state:
			self.foreground = []
	
	def reset(self):
		has_exit = False
		for layer in self.layers():
			for item in layer:
				item.reset()
				if item.is_exit:
					has_exit = True
		self.is_completed = False
		self.has_exit = has_exit

	def add_item(self, item):
		getattr(self, item.layer_name).append(item)
	
	def draw(self, surf):
		for layer in self.layers():
			for item in layer:
				item.draw(surf)
	
	def layers(self):
		return [self.background, self.gadgets, self.platforms,
			self.bells, self.actors, self.foreground]
	
	def find_layer(self, item):
		return getattr(self, item.layer_name)
	
	def begin_frame(self):
		for item in self.actors:
			self.actor_begin_frame(item)
		for item in self.bells:
			item.begin_frame(self)
		if not self.has_exit:
			x = self.player.position[0]
			if x < 0 or x > level_width:
				self.level_completed()
	
	def actor_begin_frame(self, actor):
		level_bottom = cells_high * cell_height
		g = actor.get_grab_position()
		actor.nearby_rope = self.rope_near_point(g)
		p = actor.position
		#platform = self.item_at_point('platforms', p)
		platform = self.find_supporting_platform(actor)
		if platform:
			#print "Level.actor_begin_frame: on platform with top", platform.rect.top ###
			actor.supported = True
			actor.support_height = platform.rect.top
		elif p[1] >= level_bottom:
			#print "Level.actor_begin_frame: at bottom of level" ###
			actor.supported = True
			actor.support_height = level_bottom
		else:
			actor.supported = False
		for gadget in self.gadgets_near_actor(actor):
			gadget.actor_nearby(self, actor)
		actor.begin_frame(self)
	
	def find_supporting_platform(self, actor):
		#  Find a platform at or immediately underneath actor's foot position
		#  whose top is no higher than the actor can step up. Also find out
		#  whether the actor is standing in front of a platform.
		x, y = actor.position
		t1 = Rect(x, y - 1, 1, 1)
		t2 = Rect(x, y, 1, 1)
		ymin = y - actor.step_height
		highest = None
		platform_is_behind = False
		platform_below = None
		for item in self.platforms:
			r = item.rect
			c1 = t1.colliderect(r)
			c2 = t2.colliderect(r)
			if c1:
				platform_is_behind = True
			if c2:
				platform_below = item
			if (c1 or c2) and r.top >= ymin:
				if not highest or r.top < highest.rect.top:
					highest = item
		#print "Level.find_supporting_platform: platform_was_behind =", actor.platform_was_behind ###
		#print "Level.find_supporting_platform: platform_is_behind =", platform_is_behind ###
		#print "Level.find_supporting_platform: highest =", highest ###
		#print "Level.find_supporting_platform: platform_below =", platform_below ###
		#  Work out whether the actor is walking towards a step. If not, and
		#  there is a platform immediately below, remain standing on that
		#  platform instead of stepping up.
		#if highest:
		#		print "Level.find_supporting_platform: potential supporting platform with top", highest.rect.top ###
		if y >= level_height or platform_below:
			#if platform_below:
			#	print "Level.find_supporting_platform: standing on platform with top", platform_below.rect.top ###
			#else:
			#	print "Level.find_supporting_platform: at bottom of level" ###
			if actor.platform_was_behind:
				#print "...platform was behind" ###
				highest = platform_below
				#print "...highest =", highest ###
		#  Make sure there isn't another platform sitting right on top of a
		#  platform that we're considering climbing onto.
		if highest and highest is not platform_below:
			#print "Level.find_supporting_platform: Checking for space above highest" ###
			r = highest.rect
			t = Rect(x, r.top - 1, 1, 1)
			for item in self.platforms:
				if t.colliderect(item.rect):
					#print "Level.find_supporting_platform: No space above highest" ###
					highest = platform_below
					break
		actor.platform_was_behind = platform_is_behind
		#if highest:
			#print "Level.find_supporting_platform: Returning", highest.rect ###
		#else:
			#print "Level.find_supporting_platform: Returning None" ###
		return highest
	
	def actor_can_walk_to(self, actor, p):
		x, y = p
		if self.has_exit:
			if x < 0 or x > level_width:
				return False
		r = actor.rect
		h = r.height
		t = Rect(x, y - h, 1, h - actor.step_height)
		#if actor.action == 1: ###
		#	print "actor_can_walk_to: test rect =", t ###
		for item in self.platforms:
			if item.is_wall and t.colliderect(item.rect):
				#print "...collide with platform at", item.rect ###
				return False
		return True
	
	def item_at_point(self, layer_name, p):
		for item in getattr(self, layer_name):
			if item.rect.collidepoint(p):
				return item
	
	def rope_near_point(self, p):
		for item in self.bells:
			r = item.get_rope_rect()
			r.inflate_ip((40, 0))
			if r.collidepoint(p):
				return item
	
	def gadgets_near_actor(self, actor):
		r = actor.rect
		for item in self.gadgets:
			if item.rect.colliderect(r):
				yield item 
	
	def bell_rung(self, pitch):
		for item in self.gadgets:
			item.bell_rung(pitch)
	
	def level_completed(self):
		self.is_completed = True

#------------------------------------------------------------------------

def get_item_image(kind, name, border = None):
	image = image_cache.get((kind, name))
	if not image:
		rsrc = "%s/%s" % (kind.resource_prefix, name)
		if os.path.splitext(name)[0].endswith("+"):
			border = 2
		else:
			border = None
		image = get_image(rsrc, border = border)
		image_cache[kind, name] = image
	return image

def round_to_grid(p):
	cw = cell_width
	ch = cell_height
	x, y = p
	x = (x + cw // 2) // cw * cw
	y = (y + ch // 2) // ch * ch
	return x, y
