/*
 Tiamat - a role-playing game framework
 AdventureStudioFrame.java - main frame for creating adventure games
 
 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.tools.adventureStudio;

import java.awt.Dimension;
import java.io.File;
import java.util.ArrayList;

import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JDesktopPane;
import javax.swing.JFileChooser;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;

import com.huguesjohnson.FileCopy;
import com.huguesjohnson.fileFilter.ImageFileFilter;
import com.huguesjohnson.fileFilter.MidiFileFilter;
import com.huguesjohnson.fileFilter.TrueTypeFontFileFilter;
import com.huguesjohnson.fileFilter.XmlFileFilter;
import com.huguesjohnson.serialization.StorageException;
import com.huguesjohnson.serialization.XmlFormatter;
import com.huguesjohnson.tiamat.BaseAdventureGame;
import com.huguesjohnson.tiamat.GameScript;
import com.huguesjohnson.tiamat.StringTable;
import com.huguesjohnson.tiamat.BaseAdventureGame.ConfigKeys;
import com.huguesjohnson.tiamat.graphics.Sequence;
import com.huguesjohnson.tiamat.tools.adventureStudio.NavigationPanel;
import com.huguesjohnson.tiamat.tools.adventureStudio.NavigationTreeNode;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.CharacterNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.ConfigurationNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.EventUtilities;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.GameScriptNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.ItemNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.LocationNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.RootNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.FileNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.SequenceNavigationEventHandler;
import com.huguesjohnson.tiamat.tools.adventureStudio.event.ThemeNavigationEventHandler;

import com.huguesjohnson.tiamat.tools.BaseEditorFrame;

/** AdventureStudioFrame - main frame for creating adventure games
 * @author Hugues Johnson
 */
public class AdventureStudioFrame extends BaseEditorFrame{
	/* Determines if a de-serialized file is compatible with this class. See <a href="http://java.sun.com/j2se/1.5.0/docs/guide/serialization/spec/class.html">Java Object Serialization Specifications</a>. */
	private static final long serialVersionUID=-7948277754991014994L;

	//path to the project directory
	private String projectDir;
	//the name of the project (which will be the name of the config file for the game too)
	private String projectName;
	//the title of the project (the name minus the extention information)
	private String projectTitle;
	
	//ui components
	private NavigationPanel navigationPanel;
	private JDesktopPane desktop;

	public AdventureStudioFrame(){
		super("Adventure");
		this.projectDir=null;
		this.projectName=null;
		this.projectTitle=null;
		//prompt to load project or create new one
		Object[] options={
			"Create a new adventure project",
			"Open an existing project", 
			"Quit this program"};
		int result=JOptionPane.showOptionDialog(
			this,
			"Please select one of the following options: ",
		    "Tiamat Adventure Studio",
		    JOptionPane.YES_NO_CANCEL_OPTION,
		    JOptionPane.QUESTION_MESSAGE,
		    new ImageIcon(this.getIconImage()),
		    options,
		    options[0]);
		if(result==JOptionPane.YES_OPTION){
			this.onNew();
		} else if(result==JOptionPane.NO_OPTION){
			this.onOpen();
		} else{
			this.exitEvent();
		}
	}

	public AdventureStudioFrame(String filePath){
		super("Adventure");
		this.openFile(filePath);
	}
	
	@SuppressWarnings("serial")
	protected void initComponents(){
		//override some stuff from BaseEditorFrame
		this.setTitle("Tiamat Adventure Studio");
		this.menuFileExit.setToolTipText("Exit Adventure Studio");
		this.menuHelpEditors.setText("Adventure Studio..");
		this.setResizable(true);
		
		//create and add the desktop
		this.panelContent.setLayout(new BoxLayout(this.panelContent,BoxLayout.Y_AXIS));
		this.desktop=new JDesktopPane();
		this.panelContent.add(this.desktop);
		
		//create and add the navigation panel
	    this.navigationPanel=new NavigationPanel(){
	    	public void editorOpenedEvent(BaseEditorFrame editor,String title){
	    		onNewEditorOpened(editor,title);
	    	}
	    };
		JInternalFrame navigationFrame=new JInternalFrame("Navigation");
		navigationFrame.getContentPane().add(this.navigationPanel);
		navigationFrame.setClosable(false);
		navigationFrame.setIconifiable(true);
		navigationFrame.setResizable(true);
		navigationFrame.pack();
		this.desktop.add(navigationFrame);
		navigationFrame.setVisible(true);
		navigationFrame.setLocation(1,1);
		
		//create and add the welcome pane
		JInternalFrame welcomeFrame=new JInternalFrame("Welcome");
		welcomeFrame.getContentPane().add(new WelcomePanel(){
			public void onNewButtonPressed(){ onNew(); }
			public void onOpenButtonPressed(){ onOpen(); }
		});
		welcomeFrame.setClosable(true);
		welcomeFrame.setIconifiable(true);
		welcomeFrame.setResizable(true);
		welcomeFrame.pack();
		this.desktop.add(welcomeFrame);
		welcomeFrame.setVisible(true);
		welcomeFrame.setLocation(navigationFrame.getWidth()+(int)navigationFrame.getLocation().getX()+1,1);
		int width=navigationFrame.getWidth()*3;
		int height=navigationFrame.getHeight();
		welcomeFrame.setSize(new Dimension(width,height));
		
		//make everything visible
		this.pack();
		width=navigationFrame.getWidth()+welcomeFrame.getWidth()+10;
		height=this.getHeight()+navigationFrame.getHeight()+10;
		this.setSize(new Dimension(width,height));
	}
	
	public void onSave(){
		//save all open files
		JInternalFrame[] frames=this.desktop.getAllFrames();
		int length=frames.length; //the navigation pane isn't closable so this array will never be null
		for(int index=0;index<length;index++){
			if(frames[index] instanceof EditorInternalFrame){
				((EditorInternalFrame)frames[index]).save();
			}
		}
		//save the tree view
		XmlFormatter formatter=new XmlFormatter();
		try{
			NavigationTreeModel treeModel=(NavigationTreeModel)this.navigationPanel.getTreeModel();
			formatter.saveTo(treeModel,this.projectDir+"treeModel");
		} catch(Exception x){
			x.printStackTrace();
			EventUtilities.showErrorDialog(x);
		}
	}

	protected void onSaveAs(){
		boolean cancel=!this.browseForProject(true);
		if(!cancel){
			this.addRecentFile(this.projectDir+this.projectName);
			this.onSave();
		}
	}

	protected void onNew(){
		//flag to check if the onNew operation should be stopped
		boolean cancel=false;	
		
		//check if there is no project currently open		
		if(this.projectDir!=null){
			//prompt to save the open project
			int result=this.promptSave();
			if(result==JOptionPane.YES_OPTION){
				this.onSave();
			} else if(result==JOptionPane.CANCEL_OPTION){
				cancel=true;
			}
		}
		
		//browse for a place to save the project
		cancel=!this.browseForProject(true);
		
		//create the project
		if(!cancel){
			//create navigation tree
			this.createNavigationTree();
			//generate source code
			CodeUtilities util=new CodeUtilities();
			util.buildFromTemplate("Frame",this.projectTitle,this.projectDir);
			util.buildFromTemplate("GameState",this.projectTitle,this.projectDir);
			util.buildFromTemplate("AdventureGame",this.projectTitle,this.projectDir);
			//copy depenencies
			String libPath=this.projectDir+"lib";
			(new File(libPath)).mkdir();
			ArrayList<String> classPaths=CodeUtilities.getClassPaths();
			int length=classPaths.size();
			for(int index=0;index<length;index++){
				String sourceFile=classPaths.get(index);
				String destinationFile=libPath+File.separator+sourceFile.substring(sourceFile.lastIndexOf(File.separator)+1);
				FileCopy.copyFile(sourceFile,destinationFile);
			}
			//create a configuration file
			StringTable config=new StringTable();
			ConfigKeys[] keys=BaseAdventureGame.ConfigKeys.values();
			length=keys.length;
			for(int index=0;index<length;index++){
				String keyName=keys[index].keyName();
				String defaultValue=keys[index].defaultValue();
				if(defaultValue!=null){ //null default values are possible
					config.set(keyName,defaultValue);
				}
			}
			try{
				(new XmlFormatter()).saveTo(config,this.projectDir+this.projectName);
			} catch(StorageException x){
				x.printStackTrace();
			}
			//call save to build code and stuff
			this.onSave();
		}
	}

	protected void onOpen(){
		boolean success=this.browseForProject(false);
		if(success){
			this.openFile();
			this.addRecentFile(this.projectDir+this.projectName);
		}
	}
	
	//assumes projectDir and projectName have been set
	private void openFile(){
		XmlFormatter formatter=new XmlFormatter();
		//load tree model
		try{
			NavigationTreeModel treeModel=(NavigationTreeModel)formatter.loadFrom(this.projectDir+"treeModel");
			this.navigationPanel.loadTree(treeModel);
		} catch(Exception x){
			x.printStackTrace();
			EventUtilities.showErrorDialog(x);
		}
	}

	private void openFile(String filePath){
		int lastIndexOf=filePath.lastIndexOf(File.separator);
		if(lastIndexOf>0){
			this.projectDir=new String(filePath.substring(0,lastIndexOf-1));
			this.projectName=new String(filePath.substring(lastIndexOf));
			this.projectTitle=this.projectName.substring(0,this.projectName.indexOf("."));
			this.openFile();
		}
	}
	
	protected void onRecentFile(String filePath){
		this.openFile(filePath);
	}

	public void onViewXml(){
		//look for the active frame
		boolean foundActive=false;
		JInternalFrame[] frames=this.desktop.getAllFrames();
		int length=frames.length; //the navigation pane isn't closable so this array will never be null
		int index=0;
		while((!foundActive)&&(index<length)){
			if(frames[index] instanceof EditorInternalFrame){
				if(frames[index].hasFocus()){
					((EditorInternalFrame)frames[index]).viewXml();
					foundActive=true;
				}
			}
			index++;
		}
		//if no editors were active, show the configuration file
		if(!foundActive){
			this.viewXml(this.projectDir+this.projectName);
		}
	}
	
	public void onNewEditorOpened(BaseEditorFrame editor,String title){
		EditorInternalFrame frame=new EditorInternalFrame(editor);
		//set attributes for the new frame
		frame.setTitle(title);
		frame.setIconifiable(true);
		frame.setClosable(true);
		frame.setResizable(true);
		//add the frame to the desktop
		this.desktop.add(frame);
		frame.pack();
		//make sure the frame isn't larger than the desktop
		int frameWidth,newFrameWidth,frameHeight,newFrameHeight,desktopWidth,desktopHeight;
		frameWidth=newFrameWidth=frame.getWidth();
		frameHeight=newFrameHeight=frame.getHeight();
		desktopWidth=this.desktop.getWidth();
		desktopHeight=this.desktop.getHeight();
		if(frameHeight>desktopHeight){ newFrameHeight=desktopHeight-1; }		
		if(frameWidth>desktopWidth){ newFrameWidth=desktopWidth-1; }
		if((newFrameHeight!=frameHeight)||(newFrameWidth!=frameWidth)){
			frame.setLocation(1,1);
			frame.setSize(new Dimension(newFrameWidth,newFrameHeight));
		}
		//show the frame
		frame.setVisible(true);
	}
	
	//returns a JOptionPane constant
	private int promptSave(){
		String message="Save changes to "+this.projectName+"?";
		String title="Save changes?";
		int result=JOptionPane.showConfirmDialog(this,message,title,JOptionPane.YES_NO_CANCEL_OPTION,JOptionPane.QUESTION_MESSAGE);
		return(result);
	}
	
	//returns a JOptionPane constant
	private int promptOverwriteDirectory(String dirPath){
		String message="Existing contents of "+dirPath+" may be overwritten. Continue?";
		String title="Overwrite?";
		int result=JOptionPane.showConfirmDialog(this,message,title,JOptionPane.YES_NO_OPTION,JOptionPane.QUESTION_MESSAGE);
		return(result);
	}	
	
	//returns true if the project name & directory changed from this function
	private boolean browseForProject(boolean newFile){
        JFileChooser fileChooser=new JFileChooser();
        //set the title
        if(newFile){
        	if((this.projectName==null)||(this.projectName.length()<1)){
        		fileChooser.setDialogTitle("Create New Project");
        	} else{
        		fileChooser.setDialogTitle("Save Project As");
        	}
        } else{
        	fileChooser.setDialogTitle("Open Project");
        }
        //do not allow multiple selections
        fileChooser.setMultiSelectionEnabled(false);
        //set file filters
        fileChooser.setAcceptAllFileFilterUsed(false);
        fileChooser.setFileFilter(new XmlFileFilter());
        //set the current directory
        if(this.projectDir!=null){
        	fileChooser.setCurrentDirectory(new File(this.projectDir));
        }
        //show the file chooser
        int returnValue=fileChooser.showOpenDialog(this);
        //was 'OK' selected?
        if(returnValue==JFileChooser.APPROVE_OPTION){
        	String dir=fileChooser.getCurrentDirectory().getPath();
        	if(newFile){
        		File d=new File(dir);
        		File[] files=d.listFiles();
        		if((files!=null)&&(files.length>0)){
        			if(this.promptOverwriteDirectory(dir)==JOptionPane.NO_OPTION){
        				return(false);
        			}
        		}
        	}
        	if(!dir.endsWith(File.separator)){
        		dir=dir+File.separator;
        	}
        	this.projectDir=new String(dir);
        	String name=fileChooser.getSelectedFile().getName();
        	if(!name.endsWith(".adventure.xml")){
        		name=name+".adventure.xml";
        	}
        	this.projectName=new String(name);
			this.projectTitle=new String(this.projectName.substring(0,this.projectName.indexOf(".")));
        	return(true);
        } 
        return(false);
	}

	private void createNavigationTree(){
		// TODO externalize tree structure
		NavigationTreeNode rootNode=new NavigationTreeNode(this.projectName,this.projectDir,new RootNavigationEventHandler());
		//game data root node
		String dirPath=this.projectDir+"data"+File.separator;
		(new File(dirPath)).mkdir();
		NavigationTreeNode dataRootNode=new NavigationTreeNode("Game Data",dirPath,new RootNavigationEventHandler());
		rootNode.add(dataRootNode);
		//game data-locations
		dirPath=this.projectDir+"data"+File.separator+"locations"+File.separator;
		(new File(dirPath)).mkdir();
		NavigationTreeNode locationItemRootNode=new NavigationTreeNode("Locations",dirPath,new LocationNavigationEventHandler(dirPath));
		dataRootNode.add(locationItemRootNode);
		//game data-characters
		dirPath=this.projectDir+"data"+File.separator+"characters"+File.separator;
		(new File(dirPath)).mkdir();
		NavigationTreeNode dataCharactersRootNode=new NavigationTreeNode("Characters",dirPath,new CharacterNavigationEventHandler(dirPath));
		dataRootNode.add(dataCharactersRootNode);
		//game data-items
		dirPath=this.projectDir+"data"+File.separator+"items"+File.separator;
		(new File(dirPath)).mkdir();
		NavigationTreeNode dataItemRootNode=new NavigationTreeNode("Items",dirPath,new ItemNavigationEventHandler(dirPath));
		dataRootNode.add(dataItemRootNode);
		//game data-sequences
		dirPath=this.projectDir+"data"+File.separator+"sequences"+File.separator;
		(new File(dirPath)).mkdir();
		NavigationTreeNode dataSequencesRootNode=new NavigationTreeNode("Sequences",dirPath,new SequenceNavigationEventHandler(dirPath));
		//create default sequences 
		//TODO move to a utility class
		XmlFormatter formatter=new XmlFormatter();
		try{
			//game over
			String sequenceName=BaseAdventureGame.ConfigKeys.SEQUENCE_GAMEOVER.defaultValue();
			Sequence sequence=CodeUtilities.buildDefaultSequence(sequenceName,dirPath);
			String sequencePath=dirPath+sequenceName+".sequence.xml";
			NavigationTreeNode gameOverNode=new NavigationTreeNode(sequenceName,sequencePath,new SequenceNavigationEventHandler(dirPath,sequenceName+".sequence.xml"));
			formatter.saveTo(sequence,sequencePath);
			dataSequencesRootNode.add(gameOverNode);
			//introduction
			sequenceName=BaseAdventureGame.ConfigKeys.SEQUENCE_INTRODUCTION.defaultValue();
			sequence=CodeUtilities.buildDefaultSequence(sequenceName,dirPath);
			sequencePath=dirPath+sequenceName+".sequence.xml";
			NavigationTreeNode introNode=new NavigationTreeNode(sequenceName,sequencePath,new SequenceNavigationEventHandler(dirPath,sequenceName+".sequence.xml"));
			formatter.saveTo(sequence,sequencePath);
			dataSequencesRootNode.add(introNode);
			//title
			sequenceName=BaseAdventureGame.ConfigKeys.SEQUENCE_TITLE.defaultValue();
			sequence=CodeUtilities.buildDefaultSequence(sequenceName,dirPath);
			sequencePath=dirPath+sequenceName+".sequence.xml";
			NavigationTreeNode titleNode=new NavigationTreeNode(sequenceName,sequencePath,new SequenceNavigationEventHandler(dirPath,sequenceName+".sequence.xml"));
			formatter.saveTo(sequence,sequencePath);
			dataSequencesRootNode.add(titleNode);
		} catch(Exception x){
			x.printStackTrace();
			EventUtilities.showErrorDialog(x);
		}
		dataRootNode.add(dataSequencesRootNode);
		//images root node
		dirPath=this.projectDir+"images"+File.separator;
		(new File(dirPath)).mkdir();
		NavigationTreeNode imagesRootNode=new NavigationTreeNode("Images",dirPath,new RootNavigationEventHandler());
		rootNode.add(imagesRootNode);
		//images-characters
		dirPath=this.projectDir+"images"+File.separator+"characters"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode imagesCharactersRootNode=new NavigationTreeNode("Characters",dirPath,new FileNavigationEventHandler(new ImageFileFilter(),dirPath));
		imagesRootNode.add(imagesCharactersRootNode);
		//images-items
		dirPath=this.projectDir+"images"+File.separator+"items"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode imagesItemsRootNode=new NavigationTreeNode("Items",dirPath,new FileNavigationEventHandler(new ImageFileFilter(),dirPath));
		imagesRootNode.add(imagesItemsRootNode);
		//images-locations
		dirPath=this.projectDir+"images"+File.separator+"locations"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode imagesLocationsRootNode=new NavigationTreeNode("Locations",dirPath,new FileNavigationEventHandler(new ImageFileFilter(),dirPath));
		imagesRootNode.add(imagesLocationsRootNode);
		//images-sequences
		dirPath=this.projectDir+"images"+File.separator+"sequences"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode imagesSequencesRootNode=new NavigationTreeNode("Sequences",dirPath,new FileNavigationEventHandler(new ImageFileFilter(),dirPath));
		imagesRootNode.add(imagesSequencesRootNode);
		//images-ui
		dirPath=this.projectDir+"images"+File.separator+"ui"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode imagesUiRootNode=new NavigationTreeNode("User Interface",dirPath,new FileNavigationEventHandler(new ImageFileFilter(),dirPath));
		imagesRootNode.add(imagesUiRootNode);
		//themes
		dirPath=this.projectDir+"themes"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode themesRootNode=new NavigationTreeNode("Themes",dirPath,new ThemeNavigationEventHandler(dirPath));
		rootNode.add(themesRootNode);
		//fonts
		dirPath=this.projectDir+"fonts"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode fontsRootNode=new NavigationTreeNode("Fonts",dirPath,new FileNavigationEventHandler(new TrueTypeFontFileFilter(),dirPath));
		rootNode.add(fontsRootNode);
		//midi
		dirPath=this.projectDir+"midi"+File.separator;
		(new File(dirPath)).mkdir(); 
		NavigationTreeNode midiRootNode=new NavigationTreeNode("Midi",dirPath,new FileNavigationEventHandler(new MidiFileFilter(),dirPath));
		rootNode.add(midiRootNode);
		//configuration file
		NavigationTreeNode configurationNode=new NavigationTreeNode(this.projectName,this.projectDir+this.projectName,new ConfigurationNavigationEventHandler(this.projectDir,this.projectName));
		rootNode.add(configurationNode);
		//game script
		String gameScriptFileName=this.projectTitle+".script.xml";
		String gameScriptPath=this.projectDir+gameScriptFileName;
		try{
			formatter.saveTo(new GameScript(),gameScriptPath);
		} catch(Exception x){
			x.printStackTrace();
			EventUtilities.showErrorDialog(x);
		}
		NavigationTreeNode scriptNode=new NavigationTreeNode(gameScriptFileName,gameScriptPath,new GameScriptNavigationEventHandler(this.projectDir,gameScriptFileName));
		rootNode.add(scriptNode);
		//set the tree model
		NavigationTreeModel nodes=new NavigationTreeModel(rootNode);
		this.navigationPanel.loadTree(nodes);
	}
}