/*
Tiamat - a role-playing game framework
ScriptTable.java - table for editing a GameScript
 
 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.scriptTable;

import java.awt.Point;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Vector;

import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

import com.huguesjohnson.tiamat.Event;
import com.huguesjohnson.tiamat.GameScript;
import com.huguesjohnson.tiamat.ScriptTableKey;
import com.huguesjohnson.tiamat.ScriptTableValue;
import com.huguesjohnson.tiamat.StringTable;
import com.huguesjohnson.ui.TableSorter;

/**
 * ScriptTable - table for editing a GameScript
 * @author Hugues Johnson
 */
public class ScriptTable extends JTable{
	/* 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=3354112715044751277L;
	
	private ScriptTablePopupMenu popupMenu;
	private ScriptTableEntryDialog entryDialog;
	private int entryDialogRow;
	private int defaultRowHeight;
	public int getDefaultRowHeight(){ return(this.defaultRowHeight); }
	
	public ScriptTable(){
		super();
		this.init();
	}
	
	public ScriptTable(GameScript script){
		super();
		this.init();
	    this.loadScript(script);
	}
	
	//called by constructors
	@SuppressWarnings("serial")
	private void init(){
		//setup the table
		this.setAutoCreateColumnsFromModel(false);
	    TableSorter sorter=new TableSorter(new DefaultTableModel(){
	    	public boolean isCellEditable(int row,int column){ return(false); }
	    });
	    this.setModel(sorter);
	    sorter.setTableHeader(this.getTableHeader());
	    this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 	    //add columns and data
	    this.buildColumns();
	    this.defaultRowHeight=this.getRowHeight();
	    //add mouselistener for double-click event
	    this.addMouseListener(new ScriptTableMouseAdapter());
	    //construct the popup menu
	    this.popupMenu=new ScriptTablePopupMenu(this);
	}
		
	@SuppressWarnings("serial")
	public void loadScript(GameScript script){
		//recreate the model - it still baffles me that there's no removeAll method in DefaultTableModel
		TableSorter sorter=(TableSorter)this.getModel();
		DefaultTableModel model=(DefaultTableModel)sorter.getTableModel();
		int rowCount=model.getRowCount()-1;
		for(int index=rowCount;index>=0;index--){
			model.removeRow(rowCount);			
		}
	    //load the script
		Hashtable<ScriptTableKey,ScriptTableValue> scriptTable=script.getScriptTable();
		ScriptTableKey[] keys=scriptTable.keySet().toArray(new ScriptTableKey[0]);
		int length=keys.length;
		for(int index=0;index<length;index++){
			this.addRow(keys[index],scriptTable.get(keys[index]));
		}		
	}
	
	private void buildColumns(){
		this.appendColumn("Action");
		this.getColumnModel().getColumn(0).setCellRenderer(new ScriptTableCellRenderer());
		this.appendColumn("Parameters");
		this.getColumnModel().getColumn(1).setCellRenderer(new ScriptTableCellRenderer());
		this.appendColumn("Conditions");
		this.getColumnModel().getColumn(2).setCellRenderer(new ScriptTableCellRenderer());
		this.appendColumn("Value");
		this.getColumnModel().getColumn(3).setCellRenderer(new ScriptTableCellRenderer());
	}
	
	public GameScript getScript(){ 
		GameScript modifiedScript=new GameScript();
		//loop through rows
		int rowCount=this.getRowCount();
		for(int rowIndex=0;rowIndex<rowCount;rowIndex++){
			modifiedScript.add(this.getEvent(rowIndex),this.getConditions(rowIndex),this.getReturnEvents(rowIndex));
		}
		return(modifiedScript); 
	}
	
	public Event getEvent(int row){
		TableColumnModel columnModel=this.getColumnModel();
		int actionColumn=columnModel.getColumnIndex("Action");
		int parametersColumn=columnModel.getColumnIndex("Parameters");
		//get the event and parameters
		Event event=new Event((String)this.getValueAt(row,actionColumn));
		String parametersString=(String)this.getValueAt(row,parametersColumn);
		if((parametersString!=null)&&(parametersString.length()>0)){
			StringTable parameterTable=new StringTable();
			String[] parameters=parametersString.split("\n");
			int parameterCount=parameters.length;
			for(int parameterIndex=0;parameterIndex<parameterCount;parameterIndex++){
				String[] parameter=parameters[parameterIndex].split("=");
				//shouldn't be possible for a different value unless I screwed something up
				if(parameter.length==2){
					parameterTable.set(parameter[0],parameter[1]);
				}
			}
			event.setParameters(parameterTable);
		}	
		return(event);
	}

	public void onEntryDialogClose(){
		if(!this.entryDialog.getCancel()){
			if(this.entryDialogRow<0){
				this.addRow(this.entryDialog.getKey(),this.entryDialog.getValue());
			} else{
				this.updateRow(this.entryDialogRow,this.entryDialog.getKey(),this.entryDialog.getValue());
			}
		}
	}
	
	public String[] getConditions(int row){
		TableColumnModel columnModel=this.getColumnModel();
		int conditionsColumn=columnModel.getColumnIndex("Conditions");
		//get the conditions
		String[] conditions=null;
		String conditionsString=(String)this.getValueAt(row,conditionsColumn);
		if((conditionsString!=null)&&(conditionsString.length()>0)){
			conditions=conditionsString.split("\n");
		}
		return(conditions);
	}
	
	public Event[] getReturnEvents(int row){
		TableColumnModel columnModel=this.getColumnModel();
		int valueColumn=columnModel.getColumnIndex("Value");
		ArrayList<Event> events=new ArrayList<Event>();
		String valueString=(String)this.getValueAt(row,valueColumn);
		if((valueString!=null)&&(valueString.length()>0)){
			String[] values=valueString.split("\n");
			if(values!=null){
				int length=values.length;
				Event event=new Event("_N_u_l_L_!_");
				StringTable parameters=new StringTable();
				for(int index=0;index<length;index++){
					if(values[index].length()>1){
						if(values[index].indexOf('=')>0){
							//parameter
							String[] params=values[index].split("=");
							if((params!=null)&&(params.length==2)){
								parameters.set(params[0],params[1]);
							}
						} else{
							//add current event unless it's the first one
							if(!event.getAction().equals("_N_u_l_L_!_")){
								event.setParameters(parameters.clone());
								events.add(event.clone());							
							}
							//create new event
							event=new Event(values[index]);
						}
					}
				}
				//add the last event
				event.setParameters(parameters.clone());
				events.add(event.clone());
			}
		}
		return(events.toArray(new Event[0]));
	}
	
	private void appendColumn(String caption){
    	DefaultTableModel model=(DefaultTableModel)((TableSorter)this.getModel()).getTableModel();
    	TableColumn column=new TableColumn(model.getColumnCount());
    	column.setHeaderValue(caption);
    	this.addColumn(column);
    	model.addColumn(caption);
    }	
	
	private void addRow(ScriptTableKey key,ScriptTableValue value){
		int rowSize=this.getColumnCount();
		Vector<String> rowData=new Vector<String>(rowSize);
		//this next bit really shouldn't be necessary but the setElementAt calls throw an index out of range exception without it
		for(int index=0;index<rowSize;index++){ rowData.add(new String()); }
    	DefaultTableModel model=(DefaultTableModel)((TableSorter)this.getModel()).getTableModel();
		TableColumnModel columnModel=this.getColumnModel();
		//Action
		int columnIndex=columnModel.getColumnIndex("Action");
		rowData.setElementAt(key.event.getAction(),columnIndex);
		//Conditions
		columnIndex=columnModel.getColumnIndex("Conditions");
		rowData.setElementAt(ScriptTableUtilities.getConditionsString(key.conditions),columnIndex);
		//Parameters
		columnIndex=columnModel.getColumnIndex("Parameters");
		rowData.setElementAt(ScriptTableUtilities.getParametersString(key.event.getParameters()),columnIndex);
		//Value
		columnIndex=columnModel.getColumnIndex("Value");
		rowData.setElementAt(ScriptTableUtilities.getValueString(value),columnIndex);
		//add the row to the table
		model.addRow(rowData);
	}
	
	private void updateRow(int rowIndex,ScriptTableKey key,ScriptTableValue value){
		int rowSize=this.getColumnCount();
    	DefaultTableModel model=(DefaultTableModel)((TableSorter)this.getModel()).getTableModel();
		TableColumnModel columnModel=this.getColumnModel();
		//Action
		int columnIndex=columnModel.getColumnIndex("Action");
		model.setValueAt(key.event.getAction(),rowIndex,columnIndex);
		//Conditions
		columnIndex=columnModel.getColumnIndex("Conditions");
		model.setValueAt(ScriptTableUtilities.getConditionsString(key.conditions),rowIndex,columnIndex);
		//Parameters
		columnIndex=columnModel.getColumnIndex("Parameters");
		model.setValueAt(ScriptTableUtilities.getParametersString(key.event.getParameters()),rowIndex,columnIndex);
		//Value
		columnIndex=columnModel.getColumnIndex("Value");
		model.setValueAt(ScriptTableUtilities.getValueString(value),rowIndex,columnIndex);
	}	
	
	public void showPopupMenu(Point point){
		this.popupMenu.show(point);
	}
	
//begin events
	@SuppressWarnings("serial")
	public void onAdd(){
		this.entryDialog=new ScriptTableEntryDialog(){ public void onHide(){ onEntryDialogClose(); } };
		this.entryDialogRow=-1;
		this.entryDialog.setVisible(true);
	}
	
	@SuppressWarnings("serial")
	public void onEdit(int row){
		ScriptTableKey key=new ScriptTableKey(this.getEvent(row),this.getConditions(row));
		ScriptTableValue value=new ScriptTableValue(this.getReturnEvents(row));
		this.entryDialog=new ScriptTableEntryDialog(){ public void onHide(){ onEntryDialogClose(); } };
		this.entryDialog.setEntry(key,value);
		this.entryDialogRow=row;
		this.entryDialog.setVisible(true);
	}
	
	public void onDelete(int row){
		if(ScriptTableUtilities.confirmDelete()==true){
			((DefaultTableModel)((TableSorter)(this.getModel())).getTableModel()).removeRow(row);
		}
	}
	
	public void onExpandRow(int row){
		//find the preferred height for this row
		int rowHeight=this.getRowHeight(row);
		int preferredHeight=rowHeight;
		int columnCount=this.getColumnCount();
		for(int column=0;column<columnCount;column++){
			ScriptTableCellRenderer cellRenderer=(ScriptTableCellRenderer)this.getCellRenderer(row,column);
			int rendererPreferredHeight=(int)cellRenderer.getPreferredSize().getHeight();
			if(rendererPreferredHeight>preferredHeight){
				preferredHeight=rendererPreferredHeight;
			}
		}
		if(preferredHeight>rowHeight){
			this.setRowHeight(row,preferredHeight);
		}
	}
	
	public void onCollapseRow(int row){
		this.setRowHeight(row,this.defaultRowHeight);
	}
//end events
}