################################################################
#
#   Bell Runner - Game
#
################################################################

import os
from compat import set
from datetime import date, timedelta
from tempfile import mkstemp
from cPickle import Pickler, Unpickler
from albow.dialogs import ask
from albow.file_dialogs import look_for_file_or_directory
import settings
from level import Level

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

class Game(object):
	#  levels_completed     [string]   List of level names
	#  level                Level      Level currently being played
	#  playing_level_dir    string     Dir containing levels being played
	#  level_set_dir        string     Dir containing level set being edited
	#  level_set_name       string     Name of level set being edited
	#  level_name           string     Name of level being played or edited
	#  save_dir             string     Last save file dir
	#  save_name            string     Last save file name
	#  unsaved_progress     boolean    Game progress needs saving
	#  level_needs_saving   boolean    Changes to level need saving
  #  testing              boolean    Test Level command in effect
	#
	#  Editor state variables:
	#
	#  current_tool         string
	#  current_item_kind    string
	#  current_item_name    string
	#  selection            set
	#  drag_pt              point
	#  drag_handle          Item
	
	level = None
	playing_level_dir = None
	level_set_dir = None
	level_set_name = None
	level_name = None
	save_dir = None
	save_name = None
	unsaved_progress = False
	level_needs_saving = False
	
	current_tool = 'select'
	current_item_kind = None
	current_item_name = None
	drag_pt = None
	drag_handle = None

	def __init__(self):
		self.levels_completed = []
		self.selection = set()

	def game_in_progress(self):
		return self.level and self.playing_level_set()
	
	def level_in_progress(self):
		return self.level and not self.level.is_completed
	
	def new_game(self):
		del self.levels_completed[:]
		self.load_next_uncompleted_level()
		self.save_name = None
	
	def load_game(self, path):
		f = open(path, "rb")
		self.check_save_magic(f, settings.app_magic)
		self.check_save_magic(f, settings.save_file_magic)
		p = Unpickler(f)
		file_version = p.load()
		app_version = p.load()
		self.check_file_version(file_version, app_version,
			settings.save_min_version, settings.save_file_version,
			settings.save_version_ranges)
		level_set_path = p.load()
		if not level_set_path:
			level_set_path = settings.get_std_level_dir()
		levels_completed = p.load()
		f.close()
		if not os.path.exists(level_set_path):
			level_set_path = self.look_for_level_set(level_set_path)
			if not level_set_path:
				return
		self.levels_completed = levels_completed
		self.set_level_set_path(level_set_path)
		self.set_save_path(path)
		self.unsaved_progress = False
		self.load_next_uncompleted_level()
	
	def look_for_level_set(self, orig_path):
		name = os.path.basename(orig_path)
		prompt = "Can't find '%s'. Would you like to look for it?" % name
		if ask(prompt, ["Yes", "No"]) == "Yes":
			return look_for_file_or_directory(name)

	def check_save_magic(self, f, expected):
		magic = f.read(len(expected))
		if magic <> expected:
			raise EnvironmentError("Does not seem to be a saved game file.")
	
	def check_file_version(self, file_version, app_version,
			min_version, cur_version, version_ranges):
		if not min_version <= file_version <= cur_version:
			if file_version > cur_version:
				mess = "Can only be opened by version %s.%s.%s or later." % app_version
			else:
				v1, v2 = version_ranges[file_version]
				vs1 = "%s.%s.%s" % v1
				vs2 = "%s.%s.%s" % v2
				mess = "Can only be opened by versions %s to %s." % (vs1, vs2)
			raise EnvironmentError(mess)
	
	def save_game(self, path):
		fd, tmp_path = mkstemp(dir = os.path.dirname(path))
		f = os.fdopen(fd, "wb")
		f.write(settings.app_magic)
		f.write(settings.save_file_magic)
		p = Pickler(f, 2)
		p.dump(settings.save_file_version)
		p.dump(settings.save_app_version)
		level_set_path = self.playing_level_dir
		if level_set_path == settings.get_std_level_dir():
			level_set_path = ""
		#print "Game.save_game: saving level dir as %r" % level_set_path ###
		p.dump(level_set_path)
		p.dump(self.levels_completed)
		f.close()
		os.rename(tmp_path, path)
		self.set_save_path(path)
		self.unsaved_progress = False
	
	def get_save_dir(self):
		return self.save_dir or settings.get_default_save_dir()
	
	def get_save_path(self):
		dir = self.get_save_dir()
		name = self.save_name or settings.save_file_default_name
		return os.path.join(dir, name)

	def set_save_path(self, path):
		self.save_dir, self.save_name = os.path.split(path)
	
	def get_playing_level_dir(self):
		path = self.playing_level_dir
		if not path:
			path = settings.get_std_level_dir()
		return path
	
	def get_level_path(self):
		dir = self.level_set_dir
		setname = self.level_set_name
		filename = self.level_name
		if not (dir and setname and filename):
			return None
		return os.path.join(dir, setname, filename)
	
	def set_level_set_path(self, path):
		self.playing_level_dir = path
		dir, setname = os.path.split(path)
		self.level_set_dir = dir
		self.level_set_name = setname
	
	def set_level_path(self, path):
		dir, filename = os.path.split(path)
		self.set_level_set_path(dir)
		self.level_name = filename
	
	def get_default_level_set_dir(self):
		return self.level_set_dir or settings.get_default_level_set_dir()
	
	def get_default_level_dir(self):
		dir = self.get_default_level_set_dir()
		setname = self.level_set_name
		if setname:
			dir = os.path.join(dir, setname)
		return dir
	
	def mark_current_level_completed(self):
		if not self.testing and self.playing_level_set() and self.level.has_exit:
			name = self.level_name
			if name not in self.levels_completed:
				self.levels_completed.append(name)
				self.unsaved_progress = True
	
	def playing_level_set(self):
		set_name = self.level_set_name
		return set_name and set_name.endswith(settings.level_set_suffix)

	def load_next_uncompleted_level(self):
		path = self.next_uncompleted_level_path()
		if path:
			self.load_level(path)
		else:
			self.level = None
			self.level_needs_saving = False
	
	def next_uncompleted_level_path(self):
		ext = settings.level_file_suffix
		dir = self.get_playing_level_dir()
		names = [name for name in os.listdir(dir) if name.endswith(ext)]
		names.sort()
		for name in names:
			if name not in self.levels_completed:
				return os.path.join(dir, name)
		return None
	
	def load_level(self, path):
		f = open(path, "rb")
		self.check_level_magic(f, settings.app_magic)
		self.check_level_magic(f, settings.level_file_magic)
		p = Unpickler(f)
		file_version = p.load()
		app_version = p.load()
		self.check_file_version(file_version, app_version,
			settings.level_min_version, settings.level_file_version,
			settings.level_version_ranges)
		level = p.load()
		f.close()
		self.level = level
		self.clear_selection()
		self.level_needs_saving = False
		self.set_level_path(path)

	def check_level_magic(self, f, expected):
		magic = f.read(len(expected))
		if magic <> expected:
			raise EnvironmentError("Does not seem to be a level file.")
	
	def write_level(self, path):
		fd, tmp_path = mkstemp(dir = os.path.dirname(path))
		f = os.fdopen(fd, "wb")
		f.write(settings.app_magic)
		f.write(settings.level_file_magic)
		p = Pickler(f, 2)
		p.dump(settings.level_file_version)
		p.dump(settings.level_app_version)
		p.dump(self.level)
		f.close()
		os.rename(tmp_path, path)
		self.set_level_path(path)

	def new_level(self):
		self.level = Level()
		self.level_name = None
		self.level_needs_saving = False
	
	def begin_frame(self):
		level = self.level
		if level:
			level.begin_frame()
			if level.is_completed:
				self.mark_current_level_completed()
	
	def clear_selection(self):
		self.selection.clear()
		
	def level_changed(self):
		self.level_needs_saving = True
	
	def level_is_completed(self):
		return self.level and self.level.is_completed
	
#------------------------------------------------------------------------

game = Game()
