/*
Tiamat - a role-playing game framework
BaseAdventureGame.java - abstract class containing functions used by an adventure style game
    
 Copyright  (C) 2006 Hugues Johnson
 
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 2
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, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

package com.huguesjohnson.tiamat;

import java.awt.Font;
import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;

import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import com.huguesjohnson.FileDelete;
import com.huguesjohnson.PathResolver;
import com.huguesjohnson.fileFilter.MultiExtensionFileFilter;
import com.huguesjohnson.serialization.StorageException;
import com.huguesjohnson.serialization.XmlFormatter;
import com.huguesjohnson.tiamat.audio.AudioPlayer;
import com.huguesjohnson.tiamat.audio.AudioPlayerFactory;
import com.huguesjohnson.tiamat.audio.UnsupportedAudioFormatException;
import com.huguesjohnson.tiamat.graphics.AdventureFrame;
import com.huguesjohnson.tiamat.graphics.ImageData;
import com.huguesjohnson.tiamat.graphics.Selection;
import com.huguesjohnson.tiamat.graphics.Sequence;
import com.huguesjohnson.tiamat.graphics.SequenceFrame;
import com.huguesjohnson.tiamat.graphics.Theme;

/** BaseAdventureGame - abstract class containing functions used by an adventure style game
 * @author Hugues Johnson
 */
public abstract class BaseAdventureGame implements Runnable,IAdventureCommands{
	/**
	 * enum containing all supported configuration keys.
	 */
	public enum ConfigKeys{
		FILE_DATA("file.data",".\\dataManager.xml"),
		FILE_SCRIPT("file.script",".\\script.xml"),
		GAME_MAINCHARACTER("game.mainCharacter",null),
		GAME_TITLE("game.title","Adventure Game"),
		PATH_BASEDATA("path.baseData",".\\data\\"),
		SEQUENCE_GAMEOVER("sequenceName.gameOver","game-over"),
		SEQUENCE_INTRODUCTION("sequenceName.introduction","introduction"),
		SEQUENCE_TITLE("sequenceName.title","title"),
		UI_FONTPATH("ui.fontpath",".\\fonts\\VeraBold.ttf"),
		UI_FONTSIZE("ui.fontsize","14"),
		UI_THEMEPATH("ui.theme",".\\main.theme.xml");

		ConfigKeys(String keyName,String defaultValue){ 
			this.keyName=keyName;
			this.defaultValue=defaultValue;
		}
		private final String keyName;
		private final String defaultValue;
		public String keyName(){ return (this.keyName); }
		public String defaultValue(){ return (this.defaultValue); }
	}
	
//begin protected members
	/* path to the configuration file */
	protected String configFilePath;
	/* the configuration */
	protected StringTable config;
    /* the frame */
	protected AdventureFrame frame;
    /* the game state */
	protected BaseGameState gameState;
    /* the main character */
    protected PlayerCharacter player;
	/* path to the audio file that's currently playing */
	protected String currentAudioFile;
	/* the audio player */
	protected AudioPlayer audioPlayer;
	/* path to the game in progress data directory */
	protected String gameInProgressDataPath;
	/* path to the base data directory */
	protected String baseDataPath;
	/* the current location */
	protected Location currentLocation;
	/* the current location name */
	protected String currentLocationName;
	/* the game data manager */
	protected GameDataManager gameDataManager;
	/* the game script */
	protected GameScript script;
	/* reference to the active sequence if one is running */
	protected Sequence activeSequence;
	/* the last path used for a saving or loading a file */
	protected String lastOpenPath;
	/* default string used event and config lookups */
	protected final static String NULLSTRING="\rnU11$tR|Ng\f";
//end protected members
    
//begin constructors	
	/** Create a new instance of BaseAdventureGame.
	 * @param configFilePath The path to the configuration file.
	 * @param frame The frame for the game.
	 */
	public BaseAdventureGame(String configFilePath,AdventureFrame frame){
		this.frame=frame;
		this.configFilePath=new String(configFilePath);
		this.activeSequence=null;
		Thread thread=new Thread(this);
		thread.start();
	}
//end constructors	

//begin public methods

    /** Starts the thread. */
    @SuppressWarnings("serial")
	public void run(){
        //load the configuration
        this.loadConfiguration(); 
        if(this.config==null){
            JOptionPane.showMessageDialog(this.frame,"Unable to load configuration file: "+this.configFilePath,"Error",JOptionPane.ERROR_MESSAGE);
            //TODO browse for configuration file
        } else{
            //set the value for absolute path that will be used throughout this method
            String absolutePath=System.getProperty("user.dir");
            int indexOf=this.configFilePath.lastIndexOf(File.separator);
            if(indexOf>0){
                absolutePath=this.configFilePath.substring(0,indexOf+File.separator.length());
            }
            //set lastOpenPath to absolutePath
            this.lastOpenPath=new String(absolutePath);
            //create the game in progress data directory
            this.baseDataPath=new String(PathResolver.getAbsolutePath(absolutePath,this.config.get(ConfigKeys.PATH_BASEDATA.keyName(),ConfigKeys.PATH_BASEDATA.defaultValue())));
            this.gameInProgressDataPath=AdventureGameUtilities.createGameInProgressDataDirectory(this.baseDataPath);
            //load the game data & script
            String gameDataPath=PathResolver.getAbsolutePath(this.gameInProgressDataPath,this.config.get(ConfigKeys.FILE_DATA.keyName(),ConfigKeys.FILE_DATA.defaultValue()));
            Theme theme=new Theme(); //needs to be declared outside of try-catch so it can be used to create the frame
            XmlFormatter formatter=new XmlFormatter();
            try{
            	//load the data manager
            	this.gameDataManager=(GameDataManager)formatter.loadFrom(gameDataPath);
                //load the script
                String scriptPath=PathResolver.getAbsolutePath(this.baseDataPath,this.config.get(ConfigKeys.FILE_SCRIPT.keyName(),ConfigKeys.FILE_SCRIPT.defaultValue()));
                this.script=(GameScript)formatter.loadFrom(scriptPath);
                //load the theme
                String themePath=PathResolver.getAbsolutePath(absolutePath,this.config.get(ConfigKeys.UI_THEMEPATH.keyName(),ConfigKeys.UI_THEMEPATH.defaultValue()));
                theme=(Theme)formatter.loadFrom(themePath);
            } catch(StorageException x){
            	x.printStackTrace();
            }
            //load the main character
            String mainCharacterName=this.config.get(ConfigKeys.GAME_MAINCHARACTER.keyName(),ConfigKeys.GAME_MAINCHARACTER.defaultValue());
            if(mainCharacterName.equals(ConfigKeys.GAME_MAINCHARACTER.defaultValue())){
            	this.player=null;
            } else{
            	this.player=(PlayerCharacter)this.gameDataManager.getCharacter(mainCharacterName,formatter);
            }
            //load the font
            int fontSize=Integer.parseInt(this.config.get(ConfigKeys.UI_FONTSIZE.keyName(),ConfigKeys.UI_FONTSIZE.defaultValue()));
            if(fontSize<2){ fontSize=Integer.parseInt(ConfigKeys.UI_FONTSIZE.defaultValue()); }
            Font font=new Font(null,Font.PLAIN,fontSize);
            try{
                String fontPath=PathResolver.getAbsolutePath(absolutePath,this.config.get(ConfigKeys.UI_FONTPATH.keyName(),ConfigKeys.UI_FONTPATH.defaultValue()));
                FileInputStream in=new FileInputStream(fontPath);
                font=Font.createFont(Font.TRUETYPE_FONT,in);
                font=font.deriveFont(Font.PLAIN,fontSize);
                in.close();
            } catch(Exception x){
                x.printStackTrace();
            }
            //load the window
            this.frame=new AdventureFrame(this.config.get(ConfigKeys.GAME_TITLE.keyName(),ConfigKeys.GAME_TITLE.defaultValue()),theme,font){
                @SuppressWarnings("synthetic-access")
				public void onFrameClose(){
                    exitHandler();
                }
                @SuppressWarnings("synthetic-access")
				public void postEvent(Event event){
                    processGameEvent(event); 
                }
            };
            this.frame.setVisible(true);
            //load title sequence
            Event e=new Event(Actions.RUNSEQUENCE.value());
            String titleSequence=this.config.get(ConfigKeys.SEQUENCE_TITLE.keyName(),ConfigKeys.SEQUENCE_TITLE.defaultValue());
            e.getParameters().set(Parameters.SEQUENCE.value(),titleSequence);
            this.processGameEvent(e);
        }
    }
	
//end public methods
	
//begin private methods

    public void saveGame(){
    	JFileChooser fileChooser=new JFileChooser();
        //do not allow multiple selections
        fileChooser.setMultiSelectionEnabled(false);
        //only accept specific filters
        fileChooser.setAcceptAllFileFilterUsed(false);
        //set the current directory
        fileChooser.setCurrentDirectory(new File(this.lastOpenPath));
        //add file filters
        String[] extensionList=new String[1];
        extensionList[0]=new String("save");
        fileChooser.setFileFilter(new MultiExtensionFileFilter(extensionList));
        //show the dialog
        int returnValue=fileChooser.showSaveDialog(this.frame);
        //was 'OK' selected?
        if(returnValue==JFileChooser.APPROVE_OPTION){
            String saveFile=fileChooser.getSelectedFile().getPath();
            if(!saveFile.endsWith(".save")){
                saveFile=saveFile+".save";
            }
            this.saveGame(saveFile);
        }
    }
    
    public void loadGame(){
        JFileChooser fileChooser=new JFileChooser();
        //do not allow multiple selections
        fileChooser.setMultiSelectionEnabled(false);
        //only accept specific filters
        fileChooser.setAcceptAllFileFilterUsed(false);
        //set the current directory
        fileChooser.setCurrentDirectory(new File(this.lastOpenPath));
        //add file filters
        String[] extensionList=new String[1];
        extensionList[0]=new String("save");
        fileChooser.setFileFilter(new MultiExtensionFileFilter(extensionList));
        //show the dialog
        int returnValue=fileChooser.showOpenDialog(this.frame);
        //was 'OK' selected?
        if(returnValue==JFileChooser.APPROVE_OPTION){
            String saveFile=fileChooser.getSelectedFile().getPath();
            this.loadGame(saveFile);
        } 
    }	
	
	/** Clean up game in progress data. 
	 */
	private void exitHandler(){
		this.destroyGameInProgressDataDirectory();
		this.onExit();
	}	
	
	/** Plays the specified audio file unless it's already playing.
	 * @param audioFilePath Full path to the audio file to play. 
	 */
	private void playAudioFile(String audioFilePath){
		if(!audioFilePath.equals(this.currentAudioFile)){
			try{
				if(this.audioPlayer!=null){
					this.audioPlayer.stop();
					this.audioPlayer=null;
				}
				this.audioPlayer=AudioPlayerFactory.getAudioPlayerFor(audioFilePath);
				this.audioPlayer.play(audioFilePath,AudioPlayer.INFINITE_LOOP);
				this.currentAudioFile=new String(audioFilePath);
			} catch(UnsupportedAudioFormatException uafx){
				uafx.printStackTrace();
			}
		}
	}	
	
    private void saveGame(String filePath){
        XmlFormatter formatter=new XmlFormatter();
        //save the current game state
        try{
            formatter.saveTo(this.gameState,this.gameInProgressDataPath+"state.xml");
            //save the last open path
            this.lastOpenPath=new String(filePath.substring(0,filePath.lastIndexOf(File.separator)+1));
        } catch(StorageException x){
            x.printStackTrace();
        }
        //save the main character
        if(this.player!=null){
	        String mainCharacterName=this.config.get(ConfigKeys.GAME_MAINCHARACTER.keyName(),ConfigKeys.GAME_MAINCHARACTER.defaultValue());
	        if(!mainCharacterName.equals(ConfigKeys.GAME_MAINCHARACTER.defaultValue())){ //probably not possible at this point so this is just a sanity check
	        	this.gameDataManager.saveCharacter(mainCharacterName,this.player,formatter);
	        }
        }
        AdventureGameUtilities.saveGame(this.gameInProgressDataPath,filePath);
    }	
	
    private void loadGame(String filePath){
        //delete the current game state
        this.destroyGameInProgressDataDirectory();
        //create a new directory
        if(this.baseDataPath.endsWith(File.separator)){
            this.gameInProgressDataPath=new String(this.baseDataPath.substring(0,this.baseDataPath.length()-File.separator.length()));
        } else{
            this.gameInProgressDataPath=new String(this.baseDataPath);
        }
        String timeStamp=String.valueOf(System.currentTimeMillis());
        this.gameInProgressDataPath=this.gameInProgressDataPath.concat(timeStamp).concat(File.separator);
        (new File(this.gameInProgressDataPath)).mkdir();
        //unzip the save file
        AdventureGameUtilities.unpackSavedGame(filePath,this.gameInProgressDataPath);
        //reload the game state
        XmlFormatter formatter=new XmlFormatter();
        try{
            this.gameState=(BaseGameState)formatter.loadFrom(this.gameInProgressDataPath+"state.xml");
            //load the location
            this.loadLocation(this.currentLocationName);
            //save the last open path
            this.lastOpenPath=new String(filePath.substring(0,filePath.lastIndexOf(File.separator)+1));
        } catch(StorageException x){
            x.printStackTrace();
        }
        //reload the main character
        String mainCharacterName=this.config.get(ConfigKeys.GAME_MAINCHARACTER.keyName(),ConfigKeys.GAME_MAINCHARACTER.defaultValue());
        if(mainCharacterName.equals(ConfigKeys.GAME_MAINCHARACTER.defaultValue())){
        	this.player=null;
        } else{
        	this.player=(PlayerCharacter)this.gameDataManager.getCharacter(mainCharacterName,formatter);
        }       
    }
    
    /* delete the game in progress data directory */
    private void destroyGameInProgressDataDirectory(){
        FileDelete.deleteDirectory(this.gameInProgressDataPath);
    }
    
    /* load the configuration file */
    private void loadConfiguration(){
    	XmlFormatter formatter=new XmlFormatter();
    	try{
            this.config=(StringTable)formatter.loadFrom(this.configFilePath);
        } catch(StorageException x){
            x.printStackTrace();
        }
    }
	
    /* load the default menu */
    private void loadDefaultMenu(){
        //TODO this check is a workaround to a bug, need to figure out a better way to do this
        if(this.activeSequence==null){
            int length=0;
            Selection[] customSelections=this.getCustomDefaultMenuSelections();
            if(customSelections!=null){ length=customSelections.length; }
            Selection[] selections=new Selection[8+length];
            Event event=new Event(Actions.LOOK.value());
            selections[0]=new Selection("Look",event);
            event=new Event(Actions.MOVE.value());
            selections[1]=new Selection("Move",event);
            event=new Event(Actions.TALK.value());
            selections[2]=new Selection("Talk",event);
            event=new Event(Actions.TAKE.value());
            selections[3]=new Selection("Take",event);
            event=new Event(Actions.INVENTORY.value());
            selections[4]=new Selection("Inventory",event);
            //add custom menu selections
           	for(int index=0;index<length;index++){
           		selections[index+5]=customSelections[index];
           	}
            //save, load, and help should be the last selections
            event=new Event(Actions.SAVEGAME.value());
            selections[length+5]=new Selection("Save",event);
            event=new Event(Actions.LOADGAME.value());
            selections[length+6]=new Selection("Load",event);
            event=new Event(Actions.HELP.value());
            selections[length+7]=new Selection("Help",event);
            this.frame.setSelections(selections);
        }
    }    
    
    /* run an event against the game script */
    private void runScriptEvent(Event event){
    	Event[] returnEvents=this.script.processEvent(event,this.gameState.getConditions());
        if((returnEvents==null)||(returnEvents.length<1)){
        	this.frame.setText("Nothing happens");
        	this.loadDefaultMenu();
        } else{
            int length=returnEvents.length;
            for(int index=0;index<length;index++){
                this.processGameEvent(returnEvents[index]);
            }
        }
    }
    
    private void processGameEvent(Event event){
        String actionText=new String(event.getAction());
        if(actionText.equals(Actions.LOOK.value())){
            String target=event.getParameters().get(Parameters.TARGET.value(),NULLSTRING);
            if(target.equals(NULLSTRING)){
                ArrayList<Selection> selections=new ArrayList<Selection>();
                Event e=new Event(Actions.LOOK.value());
                e.getParameters().set(Parameters.TARGET.value(),Parameters.LOCATION.value());
                e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                selections.add(new Selection("Area",e)); //TODO - is "Area" the best caption?
                //add a list of exits
                LocationExit[] exits=this.currentLocation.getExits();
                if(exits!=null){
                    int length=exits.length;
                    for(int index=0;index<length;index++){
                        e=new Event(Actions.LOOK.value());
                        e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                        String exitName=exits[index].getName();
                        String exitDir=exits[index].getDirection();
                        e.getParameters().set(Parameters.TARGET.value(),Parameters.EXIT.value());
                        e.getParameters().set(Parameters.EXIT.value(),exitDir);
                        selections.add(new Selection(exitDir,e));
                    }
                }
                //add characters in the area
                LocationObjectData[] characters=this.currentLocation.getCharacters();
                if(characters!=null){
                	int length=characters.length;
                	for(int index=0;index<length;index++){
                		String characterName=characters[index].getName();
                		e=new Event(Actions.LOOK.value());
                		e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                		e.getParameters().set(Parameters.TARGET.value(),Parameters.CHARACTER.value());
                		e.getParameters().set(Parameters.CHARACTER.value(),characterName);
                		selections.add(new Selection(characterName,e));
                	}
                }
                //add items in the area
                LocationObjectData[] items=this.currentLocation.getItems();
                if(items!=null){
                    int length=items.length;
                    for(int index=0;index<length;index++){
                        String itemName=items[index].getName();
                		e=new Event(Actions.LOOK.value());
                        e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                        e.getParameters().set(Parameters.TARGET.value(),Parameters.ITEM.value());
                        e.getParameters().set(Parameters.ITEM.value(),itemName);
                        selections.add(new Selection(itemName,e));
                    }
                }
                this.frame.setSelections(selections.toArray(new Selection[0]));
            } else{
                this.runScriptEvent(event);
            }
        } else if(actionText.equals(Actions.MOVE.value())){
            String target=event.getParameters().get(Parameters.TARGET.value(),NULLSTRING);
            if(target.equals(NULLSTRING)){
                //add a list of exits
                LocationExit[] exits=this.currentLocation.getExits();
                if(exits!=null){
                    int length=exits.length;
                    Selection[] selections=new Selection[length];
                    for(int index=0;index<length;index++){
                        Event e=new Event(Actions.MOVE.value());
                        e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                        String exitName=exits[index].getName();
                        String exitDir=exits[index].getDirection();
                        e.getParameters().set(Parameters.TARGET.value(),Parameters.LOCATION.value());
                        e.getParameters().set(Parameters.LOCATION.value(),exitName);
                        selections[index]=new Selection(exitDir,e);
                    }
                    this.frame.setSelections(selections);
                }
            } else{
                this.runScriptEvent(event);
            }
        } else if(actionText.equals(Actions.TALK.value())){
            String target=event.getParameters().get(Parameters.TARGET.value(),NULLSTRING);
            if(target.equals(NULLSTRING)){
                LocationObjectData[] characters=this.currentLocation.getCharacters();
                int length=characters.length;
                if(length<1){
                    Event e=new Event(Actions.DRAWTEXT.value());
                    e.getParameters().set(Parameters.TEXT.value(),"There's no one here to talk to.");
                    this.processGameEvent(e);
                } else{
                    Selection[] selections=new Selection[length];
                    for(int index=0;index<length;index++){
                        Event e=new Event(Actions.TALK.value());
                        e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                        String name=characters[index].getName();
                        e.getParameters().set(Parameters.TARGET.value(),Parameters.CHARACTER.value());
                        e.getParameters().set(Parameters.CHARACTER.value(),name);
                        selections[index]=new Selection(name,e);
                    }
                    this.frame.setSelections(selections);
                }
            } else{
                this.runScriptEvent(event);
                this.loadDefaultMenu();
            }
        } else if(actionText.equals(Actions.TAKE.value())){
            String target=event.getParameters().get(Parameters.TARGET.value(),NULLSTRING);
            if(target.equals(NULLSTRING)){
            	LocationObjectData[] items=this.currentLocation.getItems();
                int length=items.length;
                if(length<1){
                    Event e=new Event(Actions.DRAWTEXT.value());
                    e.getParameters().set(Parameters.TEXT.value(),"There are no items here you can take.");
                    this.processGameEvent(e);
                } else{
                    ArrayList<Selection> selections=new ArrayList<Selection>();
                    for(int index=0;index<length;index++){
                    	Event e=new Event(Actions.TAKE.value());
                    	String name=items[index].getName();
                    	e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                    	e.getParameters().set(Parameters.TARGET.value(),name);
                    	selections.add(new Selection(name,e));
                    }
                    if(selections.size()>0){
                        this.frame.setSelections(selections.toArray(new Selection[0]));
                    } else{
                        Event e=new Event(Actions.DRAWTEXT.value());
                        e.getParameters().set(Parameters.TEXT.value(),"There are no items here you can take.");
                        this.processGameEvent(e);
                    }
                }
            } else{
                this.runScriptEvent(event);
                this.loadDefaultMenu();
            }
        } else if(actionText.equals(Actions.USE.value())){
            String target=event.getParameters().get(Actions.USE.value(),NULLSTRING);
            if(!target.equals(NULLSTRING)){
                this.runScriptEvent(event);
            }
            this.loadDefaultMenu();
        } else if(actionText.equals(Actions.SAVEGAME.value())){
            this.saveGame();
        } else if(actionText.equals(Actions.LOADGAME.value())){
            this.loadGame();
        } else if(actionText.equals(Actions.HELP.value())){
        	this.runScriptEvent(event);
        } else if(actionText.equals(Actions.INVENTORY.value())){
        	Item[] items=null;
        	if(this.player!=null){
        		items=this.player.getItems().getAll();
        	}
            int length;
            if(items!=null){ 
                length=items.length; 
            } else{
                length=0;
            }
            if(length<1){
                Event e=new Event(Actions.DRAWTEXT.value());
                e.getParameters().set(Parameters.TEXT.value(),"You are not carrying any items.");
                this.processGameEvent(e);
            } else{
                ArrayList<Selection> selections=new ArrayList<Selection>(length);
                for(int index=0;index<length;index++){
                    Event e=new Event(Actions.USE.value());
                    e.getParameters().set(Parameters.LOCATION.value(),this.currentLocationName);
                    String itemName=items[index].getName();
                    e.getParameters().set(Parameters.ITEM.value(),itemName);
                    selections.add(new Selection(itemName,e));
                }
                this.frame.setSelections(selections.toArray(new Selection[0]));
            }
        } else if(actionText.equals(Actions.MOVEPLAYER.value())){
        	String location=event.getParameters().get(Parameters.LOCATION.value(),NULLSTRING);
            if(!location.equals(NULLSTRING)){
                this.loadLocation(location);
            }
        } else if(actionText.equals(Actions.DRAWTEXT.value())){
            String text=event.getParameters().get(Parameters.TEXT.value(),NULLSTRING);
            if(!text.equals(NULLSTRING)){
                this.frame.setText(text);
                this.loadDefaultMenu();
            }
        } else if(actionText.equals(Actions.ADDITEM.value())){
            String itemName=event.getParameters().get(Parameters.ITEM.value(),NULLSTRING);
            if(!itemName.equals(NULLSTRING)){
                Item item=this.gameDataManager.getItem(itemName,new XmlFormatter());
                if(item!=null){
                    this.player.getItems().setItem(itemName,item);
                }
            }
        } else if(actionText.equals(Actions.MOVEITEM.value())){
        	String itemName=event.getParameters().get(Parameters.ITEM.value(),NULLSTRING);
            if(!itemName.equals(NULLSTRING)){
            	XmlFormatter formatter=new XmlFormatter();
                //check if there is an old location to remove the item from
                String oldLocationName=event.getParameters().get(Parameters.OLDLOCATION.value(),NULLSTRING);
                if(!oldLocationName.equals(NULLSTRING)){
                	Location oldLocation=this.gameDataManager.getLocation(oldLocationName,formatter);
                	if(oldLocation!=null){
                		oldLocation.removeItem(itemName);
                		this.gameDataManager.saveLocation(oldLocationName,oldLocation,formatter);
                	}
                }
                //check if there is a new location to add the item to
                String newLocationName=event.getParameters().get(Parameters.NEWLOCATION.value(),NULLSTRING);
                if(!newLocationName.equals(NULLSTRING)){
                	Location newLocation=this.gameDataManager.getLocation(newLocationName,formatter);
                	if(newLocation!=null){
                		newLocation.addItem(itemName);
                		this.gameDataManager.saveLocation(newLocationName,newLocation,formatter);
                	}
                }
            }
        } else if(actionText.equals(Actions.MOVECHARACTER.value())){
            String characterName=event.getParameters().get(Parameters.CHARACTER.value(),NULLSTRING);
            if(!characterName.equals(NULLSTRING)){
            	XmlFormatter formatter=new XmlFormatter();
                //check if there is an old location to remove the character from
                String oldLocationName=event.getParameters().get(Parameters.OLDLOCATION.value(),NULLSTRING);
                if(!oldLocationName.equals(NULLSTRING)){
                	Location oldLocation=this.gameDataManager.getLocation(oldLocationName,formatter);
                	if(oldLocation!=null){
	                	oldLocation.removeCharacter(characterName);
	            		this.gameDataManager.saveLocation(oldLocationName,oldLocation,formatter);
	                	if(oldLocationName.equals(this.currentLocationName)){
	                		this.loadLocation(this.currentLocationName);
	                	}
	                }
                }
                //check if there is a new location to add the character to
                String newLocationName=event.getParameters().get(Parameters.NEWLOCATION.value(),NULLSTRING);
                if(!newLocationName.equals(NULLSTRING)){
                	Location newLocation=this.gameDataManager.getLocation(newLocationName,formatter);
                	if(newLocation!=null){
                		newLocation.addCharacter(characterName);
	            		this.gameDataManager.saveLocation(newLocationName,newLocation,formatter);
	                	if(newLocationName.equals(this.currentLocationName)){
	                		this.loadLocation(this.currentLocationName);
	                	}
	                }                	
                }
            }
        } else if(actionText.equals(Actions.DROPITEM.value())){
        	String itemName=event.getParameters().get(Parameters.ITEM.value(),NULLSTRING);
            if(!itemName.equals(NULLSTRING)){
                this.player.getItems().removeItem(itemName);
            }
        } else if(actionText.equals(Actions.ADDCONDITION.value())){
        	String condition=event.getParameters().get(Parameters.CONDITION.value(),NULLSTRING);
            if(!condition.equals(NULLSTRING)){
                this.gameState.addCondition(condition);
            }
        } else if(actionText.equals(Actions.NEWGAME.value())){
            Event e=new Event(Actions.RUNSEQUENCE.value());
            e.getParameters().set(Parameters.SEQUENCE.value(),Actions.NEWGAME.value());
            this.processGameEvent(e);
        } else if(actionText.equals(Actions.NEXTSEQUENCEFRAME.value())){
        	int frame=Integer.parseInt(event.getParameters().get(Parameters.FRAME.value(),NULLSTRING));
        	if(frame<this.activeSequence.getFrameCount()){
        		//set the menu
        		Selection[] selections=new Selection[1];
        		StringTable parameters=new StringTable();
        		parameters.set(Parameters.FRAME.value(),String.valueOf(frame+1)); 
        		Event menuEvent=new Event(Parameters.NEXTFRAME.value(),parameters);
        		selections[0]=new Selection("Next",menuEvent);
        		this.frame.setSelections(selections);
        		//display the sequence
        		SequenceFrame currentFrame=this.activeSequence.getFrame(frame);
        		this.playAudioFile(currentFrame.getBgMusicPathAbsolute());
        		this.frame.setText(currentFrame.getText());
        		this.frame.setBgImagePath(currentFrame.getBgImagePathAbsolute(),true);
        	} else{ //run the exit events
        		Event[] exitEvents=this.activeSequence.getExitEvents();
        		int length=exitEvents.length;
        		for(int index=0;index<length;index++){
        			this.processGameEvent(exitEvents[index]);
        		}
        		this.activeSequence=null;
        		//TODO - this is a really tacky fix to a bug
        		if(exitEvents[length-1].getAction().equals(Actions.GAMEOVER.value())){
        			this.loadGameOverMenu();
        		} else{
        			this.loadDefaultMenu();
        		}
        	}
        } else if(actionText.equals(Actions.RUNSEQUENCE.value())){
        	String sequenceName=event.getParameters().get(Parameters.SEQUENCE.value(),NULLSTRING);
        	//load the sequence
        	this.activeSequence=this.gameDataManager.getSequence(sequenceName,new XmlFormatter());
        	//set the menu
        	Selection[] selections=new Selection[1];
        	StringTable parameters=new StringTable();
        	parameters.set(Parameters.FRAME.value(),"1"); 
        	Event menuEvent=new Event(Parameters.NEXTFRAME.value(),parameters);
        	selections[0]=new Selection("Next",menuEvent);
        	this.frame.setSelections(selections);
        	//display the sequence
        	SequenceFrame currentFrame=this.activeSequence.getFrame(0);
        	this.playAudioFile(currentFrame.getBgMusicPathAbsolute());
    		this.frame.setText(currentFrame.getText());
    		this.frame.setBgImagePath(currentFrame.getBgImagePathAbsolute(),true);
        } else if(actionText.equals(Actions.QUIT.value())){
            this.onExit();
        } else if(actionText.equals(Actions.GAMEOVER.value())){
            this.loadGameOverMenu();
        } else if(actionText.equals(Actions.TITLEMENU.value())){
            this.loadTitleMenu();
        } else{ //see if a custom event has been defined for this event
        	this.processCustomGameEvent(event);
        }
    }
    
    /* load the game over menu */
    private void loadGameOverMenu(){
        Selection[] selections=new Selection[2];
        Event event=new Event(Actions.NEWGAME.value());
        selections[0]=new Selection("New Game",event);
        event=new Event(Actions.QUIT.value());
        selections[1]=new Selection("Quit",event);
        this.frame.setSelections(selections);
    }
    
    /* load the game over menu */
    private void loadTitleMenu(){
        Selection[] selections=new Selection[2];
        Event event=new Event(Actions.NEWGAME.value());
        selections[0]=new Selection("New Game",event);
        event=new Event(Actions.LOADGAME.value());
        selections[1]=new Selection("Load",event);
        this.frame.setSelections(selections);
        //show help text
        this.frame.setText(this.getTitleScreenText());
    }    
    
    private void loadLocation(String locationName){
        Location location=this.gameDataManager.getLocation(locationName,new XmlFormatter());
        if(location==null){
            JOptionPane.showMessageDialog(this.frame,"Unable to load location file: "+locationName,"Error",JOptionPane.ERROR_MESSAGE);
        } else{
	        //add background image
	        this.frame.setBgImagePath(location.getBgImagePathAbsolute(),false);
	        //add overlay image
	        if((location.getOverlayImagePath()==null)||(location.getOverlayImagePath().length()<1)){
	        	this.frame.setOverlays(null,false);
	        } else{
	            ImageData[] overlayData=new ImageData[1];
	            overlayData[0]=new ImageData(location.getOverlayImagePathAbsolute(),0,0);
	            this.frame.setOverlays(overlayData,false);
	        }
	        //add characters
	        LocationObjectData[] characterData=location.getCharacters();
	        if((characterData==null)||(characterData.length<1)){
	        	this.frame.setObjects(null,false);
	        } else{
	        	int length=characterData.length;
	        	ImageData[] imageData=new ImageData[length];
	        	for(int index=0;index<length;index++){
	        		String filePath=this.gameInProgressDataPath+"characters\\"+characterData[index].getName()+".character.xml";
	        		imageData[index]=new ImageData(filePath,characterData[index].getX(),characterData[index].getY());
	        	}
	        	this.frame.setObjects(imageData,false);
	        }
	        //redraw
	        this.frame.redrawImages();
	        //load the menu
	        this.loadDefaultMenu();
	        //if everything was successful, save a reference to the location
	        this.currentLocation=location;
	        this.currentLocationName=new String(locationName);
	        //start the audio
	        this.playAudioFile(location.getBgMusicPathAbsolute());
	        //fire a look event in the new area
	        Event e=new Event(Actions.LOOK.value());
	        e.getParameters().set(Parameters.TARGET.value(),Parameters.LOCATION.value());
	        e.getParameters().set(Parameters.LOCATION.value(),locationName);
	        this.runScriptEvent(e);
        }
    }    
    
//end private methods    
    
//begin abstract methods

    /** 
     * Returns an array of type Selection containing the custom menu entries.
     * @return An array of type Selection containing the custom menu entries.
     */
    protected abstract Selection[] getCustomDefaultMenuSelections();
    
    /** 
     * Process a game event not handled in BaseAdventureGame.
     * @param event The event to process.
     */
    protected abstract void processCustomGameEvent(Event event);    
    
	/** 
	 * Event that fires when exit is complete.
	 */
	public abstract void onExit();    
    
	/**
	 * Returns the text to display on the title screen.
	 * @return The text to display on the title screen.
	 */
	public abstract String getTitleScreenText();
	
//end abstract methods 
}