package sso;

import java.sql.*;
import java.util.*;
import sso.event.*;

public class GameObject extends Persistant implements ActionEventListener
{
	protected String	name;
	protected String	description;
	protected int		flags;
	protected GameObject	location;
	protected Graphic	graphic;
	protected int		facing;
	protected Vector	actions;
	protected Vector	contents;
	protected Vector	effects;
	protected Vector	resistances;
	protected Vector	moveListeners;
	protected Vector	propertyChangeListeners;

	// facing should probably NOT be a bitfield, but this works for now
	// FACING
	public static final int	NORTH 	= 0x00;
	public static final int	EAST 	= 0x01;
	public static final int	SOUTH 	= 0x02;
	public static final int	WEST 	= 0x04;

	// FACING MODIFIER
	public static final int	STAND	= 0x0100;
	public static final int	SIT		= 0x0200;
	public static final int	LIE		= 0x0400;
	public static final int	DEAD	= 0x0800;	// for graphics
	public static final int	WALK	= 0x1000;
	public static final int	RUN		= 0x2000;

	// COUNTS
	public static final int	FACING_DIR_COUNT	= 4;
	public static final int FACING_MOD_COUNT	= 6;

	/**
     *  Constructor. Never called directly.
	 *  Use a factory method instead.
	 */
	protected GameObject()
	{
		// TEST TO MAKE SURE THIS *IS* BEING CALLED
		// And that Persistant isn't the only constructor being called.
		super();
		moveListeners = new Vector();
		propertyChangeListeners = new Vector();
System.out.println("GameObject() constructor called");
	}

	/**
	 *  Factory method. Intended for new object only.
	 *  For loading objects from the database, use the factory method load().
	 */
	public static GameObject createGameObject()
	{
		GameObject go = new GameObject();

		go.init();

		return go;
	}

	/**
	 *  For creating new instances of this object only.
     *  Called by create().
	 */
	protected void init()
	{
		super.init();

		name = "(no name)";
		description = "(no description)";
		flags = 0;
		graphic = Graphic.getDefaultGraphic();
		facing = 0;
		actions = new Vector();
		contents = new Vector();
		effects = new Vector();
		resistances = new Vector();
	}

	/**
 	 *  Get the name of the GameObject.
	 */
	public String getName()
	{
		return name;
	}

	/**
	 *  Set the name of the GameObject.
	 */
	public void setName(String name)
	{
		this.name = name;
		propChanged("name");
	}

	/**
	 *  Get the description of the GameObject.
	 */
	public String getDescription()
	{
		return description;
	}

	/**
	 *  Set the description of the GameObject.
	 */
	public void setDescription(String desc)
	{
		description = desc;
		propChanged("desc");
	}

	/**
	 *  Get the int representing the flags.
	 */
	public int getFlags()
	{
		return flags;
	}

	/**
	 *  Toggle a flag on.
	 */
	public void setFlag(int newFlag)
	{
		flags |= newFlag;
		propChanged("flags");
	}

	/**
	 *  Toggle a flag off.
	 */
	public void unsetFlag(int newFlag)
	{
		flags ^= newFlag;
		propChanged("flags");
	}

	/**
	 *  Get the current location.
	 */
	public GameObject getLocation()
	{
		return location;
	}

	// how to move objects:
	// calling .move(dest) will return true or false, indicating if the move
	// was successful. Calling dest.accept(obj) will tell whether the move will
	// probably be successful, but isn't necessary as this check is built into
	// the .move(dest) method.

	/**
	 *  Move to another location.
	 *  Returns true on success.
	 *  This method should not be called directly. Rather, move(dest) should be
	 *  the call for general movement of objects from one place to another.
	 *  Hence, this method is protected.
	 */
	protected boolean setLocation(GameObject destination)
	{
		boolean success = true;
		GameObject from;
	
		if (destination != null)
		{
			if (success = destination.accept(this))
			{
				from = this.location;

				// add this object to the destination's contents (after all, it
				// already accepted this)
				destination.give(this);
	
				// fire a new move event
				moved(from);

			}
		}
		else
		{
			location = destination;
		}

		return success;
	}

	/**
	 *  Move to another location. Calls setLocation().
	 *  This is the publically accesible method for moving objects and should
	 *  be the one used in most cases. It will tend to be overridden by
	 *  children to specialize movement rules, while setLocation() will be left
	 *  alone to perform the actual movement.
	 */
	public boolean move(GameObject destination)
	{
		return setLocation(destination);
	}

	/**
	 *  Return true if this object can accept the spec.
	 */
	protected boolean accept(GameObject spec)
	{
		return true;
	}

	/**
	 *  Actually place the spec in this object's inventory. Change the spec's
	 *  location property.
	 *
	 *  This method should never be called directly because it bypasses the
	 *  checks that may be put in place by accept(). This method should only be
	 *  called by setLocation().
	 */
	protected void give(GameObject spec)
	{
		if (spec.location != null)
		{
			spec.location.contents.removeElement(spec);
		}

		contents.addElement(spec);

		spec.location = this;
	}

	/**
	 *  Get the Graphic for this GameObject.
	 */
	public Graphic getGraphic()
	{
		return graphic;
	}

	/**
	 *  Set the Graphic for this GameObject.
	 */
	public void setGraphic(Graphic g)
	{
		graphic = g;
		propChanged("graphic");
	}

	/**
	 *  Get the Facing for this GameObject.
	 */
	public int getFacing()
	{
		return facing;
	}

	/**
	 *  Set the Facing for this GameObject.
	 */
	public void setFacing(int facing)
	{
		this.facing = facing;
		propChanged("facing");
	}

	/**
	 *  Get the X location of this object.
	 *  Should be overriden by certain children (Tile).
	 */
	public int getX()
	{
		if (location != null)
		{
			return location.getX();
		}

		return Registry.ID_INVALID;
	}

	/**
	 *  Get the Y location of this object.
	 *  Should be overriden by certain children (Tile).
	 */
	public int getY()
	{
		if (location != null)
		{
			return location.getY();
		}

		return Registry.ID_INVALID;
	}

	/**
	 *  Get the Z location of this object.
	 *  Should be overriden by certain children (Tile, PC if flying).
	 */
	public int getZ()
	{
		if (location != null)
		{
			return location.getZ();
		}

		return Registry.ID_INVALID;
	}
	

	/**
	 *  Get the list of actions this Object can handle.
	 */
	public Vector getActions()
	{
		return actions;
	}

	/**
	 * Add an action.
	 */
	public void addAction(String action)
	{
		actions.addElement(action);
		propChanged("actions");
	}

	/**
	 *  Remove an action.
	 */
	public void removeAction(String action)
	{
		actions.removeElement(action);
		propChanged("actions");
	}

	/**
	 *  Handle an action.
	 */
	public void handleAction(String action)
	{
		// use reflection to get the method to call
	}

	/**
	 *  Get the contents of the GameObject.
	 */
	public Vector getContents()
	{
		return contents;
	}

	/**
	 *  Get the list of effects in place on the GameObject.
	 */
	public Vector getEffects()
	{
		return effects;
	}

	/**
	 *  Get a list of effects of a particular effect type.
	 */
	public Vector getEffectsByType(String effectClass)
	{
		Vector eff = new Vector();
	
		try
		{
			Class cl = Class.forName(effectClass);

			Vector effs = (Vector)effects.clone();

			for (int i = 0; i < effs.size(); i++)
			{
				if (cl.isInstance(effs.elementAt(i)))
				{
					eff.addElement(effs.elementAt(i));
				}
			}
		}
		catch (Exception e)
		{
			System.err.println("Trying to do Class.forName(" + effectClass + ") for getEffectsByType(): " + e.getMessage());
		}
	
		return eff;
	}

	/**
	 *  Add an effect.
	 */
	public void addEffect(Effect ef)
	{
		effects.addElement(ef);
		propChanged("effects");
	}

	/**
	 *  Remove an effect. Intended to be called by Effect.
	 */
	public void removeEffect(Effect ef)
	{
		effects.removeElement(ef);
		propChanged("effects");
	}

	/**
	 *  Get resistances.
	 */
	public Vector getResistances()
	{
		return resistances;
	}

	/**
	 *  Get a resistance (total) for a given resistance type.
	 */
	public int getResistance(int resistance)
	{
		int res = 0;

		Vector rests = (Vector)resistances.clone();
		Resistance r;

		for (int i = 0; i < rests.size(); i++)
		{
			r = (Resistance)rests.elementAt(i);

			if ((r.getResistanceType() & resistance) == resistance)
			{
				res += r.getResistancePercentage();
			}
		}

		return res;
	}

	/**
	 *  Add a resistance to this.
	 */
	public void addResistance(Resistance rest)
	{
		resistances.addElement(rest);
		propChanged("resistances");
	}

	/**
	 *  Remove a resistance from this.
	 */
	public void removeResistance(Resistance rest)
	{
		resistances.removeElement(rest);
		propChanged("resistances");
	}

	/**
	 *  Returns true is GameObject in this or any of contents.
	 */
	public boolean has(GameObject target)
	{
		boolean found = false;

		if (target != null)
		{
			Vector vc = (Vector)contents.clone();
			GameObject go;
	
			for (int i = 0; i < vc.size(); i++)
			{
				go = (GameObject)vc.elementAt(i);
	
				if (target.getID() == go.getID())
				{
					found = true;
				}
				else
				{
					found = go.has(target);
				}
	
				if (found)
				{
					break;
				}
			}
		}

		return found;
	}
				

	/**
	 *  Create the rows needed to store this object.
	 */
	protected void createRows()
	{
System.out.println("GameObject.createRows() created.");
		super.createRows();
	
		Statement stmt = null;

		try
		{
			stmt = Persistant.getStatement();

			stmt.executeUpdate("INSERT INTO GameObject (id, name, description, flags, location_id, graphic_id, facing) VALUES (" + id + ", '', '', 0, -1, -1, 0)");

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to insert GameObject row to store this (" + id + "): " + e.getMessage());
		}

		if (stmt != null)
		{
			try
			{
				stmt.close();
			}
			catch (SQLException e)
			{
				//
			}

			stmt = null;
		}
	}

	/**
	 *  Store the object in the database.
	 */
	public void store()
	{
		Statement stmt = null;

		try
		{
			// store each aggregate element
			Vector vAct = (Vector)actions.clone();
			Vector vCon = (Vector)contents.clone();
			Vector vEff = (Vector)effects.clone();
			Vector vRes = (Vector)resistances.clone();
			
			int i;
			
			stmt = Persistant.getStatement();

			stmt.executeUpdate("DELETE FROM GameObject_Actions WHERE gameobject_id=" + id);
			
			for (i = 0; i < vAct.size(); i++)
			{
				stmt.executeUpdate("INSERT INTO GameObject_Actions (gameobject_id, action) VALUES (" + id + ", '" + (String)vAct.elementAt(i) + "')");
			}

			stmt.executeUpdate("DELETE FROM GameObject_Contents WHERE container_id=" + id);

			for (i = 0; i < vCon.size(); i++)
			{
				((GameObject)vCon.elementAt(i)).store();
				stmt.executeUpdate("INSERT INTO GameObject_Contents (container_id, item_id) VALUES (" + id + ", " + ((GameObject)vCon.elementAt(i)).getID() + ")");
			}

			stmt.executeUpdate("DELETE FROM GameObject_Effects WHERE victim_id=" + id);

			for (i = 0; i < vEff.size(); i++)
			{
				((Effect)vEff.elementAt(i)).store();
				stmt.executeUpdate("INSERT INTO GameObject_Effects (victim_id, effect_id) VALUES (" + id + ", " + ((Effect)vEff.elementAt(i)).getID() + ")");
			}

			stmt.executeUpdate("DELETE FROM GameObject_Resistances WHERE gameobject_id=" + id);
			
			for (i = 0; i < vRes.size(); i++)
			{
				((Resistance)vRes.elementAt(i)).store();
				stmt.executeUpdate("INSERT INTO GameObject_Resistances (gameobject_id, resistance_id) VALUES (" + id + ", " + ((Resistance)vRes.elementAt(i)).getID() + ")");
			}

			// store this
			stmt.executeUpdate("UPDATE GameObject SET name='" + name + "', description='" + description + "', flags=" + flags + ", location_id=" + (location == null ? -1 : location.getID()) + ", graphic_id=" + (graphic == null ? -1 : graphic.getID()) + ", facing=" + facing + " WHERE id=" + id);

			// DON'T STORE HOLDER ANY MORE
/*
			if (location != null)
			{
				location.store();
			}

			if (graphic != null)
			{
				graphic.store();
			}
 */

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to store this GameObject (" + id + "): " + e.getMessage());
		}

		if (stmt != null)
		{
			try
			{
				stmt.close();
			}
			catch (SQLException e)
			{
				//
			}

			stmt = null;
		}

		super.store();
	}

	/**
	 *  Load the object from the database.
	 */
	public static GameObject loadGameObject(int id)
	{
		GameObject g = null;

		if (Registry.isLoaded(id))
		{
			g = (GameObject)Registry.get(id);
		}
		else
		{
			g = new GameObject();
			g.id = id;
			g.load();
		}

		return g;
	}

	/**
	 *  Loads the object from the database. Internal method called by
	 *  loadGameObject().
	 */
	protected void load()
	{
		super.load();

		Statement stmt = null;
		ResultSet rs = null;

		try
		{
			stmt = Persistant.getStatement();
			rs = stmt.executeQuery("SELECT * FROM GameObject WHERE id=" + getID());

			while (rs.next())
			{
				name = rs.getString("name");
				description = rs.getString("description");
				flags = rs.getInt("flags");
				location = (GameObject)Registry.load(rs.getInt("location_id"));
				graphic = Graphic.loadGraphic(rs.getInt("graphic_id"));
				facing = rs.getInt("facing");
			}
			
			rs.close();
			rs = null;

			// load aggregate members
			actions = new Vector();
			contents = new Vector();
			effects = new Vector();
			resistances = new Vector();

			rs = stmt.executeQuery("SELECT * FROM GameObject_Contents WHERE container_id=" + getID());

			while (rs.next())
			{
				contents.addElement((GameObject)Registry.load(rs.getInt("item_id")));
			}

			rs.close();

			rs = stmt.executeQuery("SELECT * FROM GameObject_Effects WHERE victim_id=" + getID());

			while (rs.next())
			{
				effects.addElement((Effect)Registry.load(rs.getInt("effect_id")));
			}

			rs.close();

			rs = stmt.executeQuery("SELECT * FROM GameObject_Actions WHERE gameobject_id=" + getID());

			while (rs.next())
			{
				actions.addElement(rs.getString("action"));
			}

			rs.close();

			rs = stmt.executeQuery("SELECT * FROM GameObject_Resistances WHERE gameobject_id=" + getID());

			while (rs.next())
			{
				resistances.addElement((Resistance)Registry.load(rs.getInt("resistance_id")));
			}

			rs.close();

			rs = null;
			
			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to load GameObject ("+getID()+"): " + e.getMessage());
		}

		if (rs != null)
		{
			try
			{
				rs.close();
			}
			catch (SQLException e)
			{
				//
			}

			rs = null;
		}

		if (stmt != null)
		{
			try
			{
				stmt.close();
			}
			catch (SQLException e)
			{
				//
			}

			stmt = null;
		}
	}

	/**
	 *  Unregister the GameObject from the database.
	 */
	public void unregister()
	{
		Statement stmt = null;
		ResultSet rs = null, rs2 = null;

		try
		{
			stmt = Persistant.getStatement();

			// remove components
			Enumeration enC;

			// actions
			stmt.executeUpdate("DELETE FROM GameObject_Actions WHERE gameobject_id=" + id);

			actions.removeAllElements();
			actions = null;

			// contents -- get moved to this object's container
			enC = contents.elements();

			for ( ; enC.hasMoreElements(); )
			{
				GameObject go = (GameObject)enC.nextElement();
			
				if (!go.setLocation(location))
				{
					// ideally, I would traverse the container tree here, but
					// it's just as easy (for now) to send things to nowhere
					go.setLocation(null);
				}

				stmt.executeUpdate("DELETE FROM GameObject_Contents WHERE container_id=" + id);
			}	

			// effects
			enC = effects.elements();
			for ( ; enC.hasMoreElements(); )
			{
				// effect manages itself--don't need to tell it to remove
				// itself unlike resistance
				((Effect)enC.nextElement()).removeVictim(this);
			}

			stmt.executeUpdate("DELETE FROM GameObject_Effects WHERE victim_id=" + id);

			effects.removeAllElements();
			effects = null;

			// resistances
			enC = resistances.elements();
		
			Resistance resistance;
		
			while (enC.hasMoreElements())
			{
				resistance = (Resistance)enC.nextElement();
				
				// get the next result set--see if other GOs are part of this
				// resistance
				rs = stmt.executeQuery("SELECT gameobject_id FROM GameObjectResistances WHERE resistance_id=" + resistance.getID());

				int rc = 0;

				while (rs.next());
				{
					rc++;
				}

				// get the result set meta data--if the rows are > 0, morefound
				// = true, so we can't delete this resistance
				if (rc < 1)
				{
					resistance.unregister();
				}

				resistance = null;

				rs.close();
				rs = null;
			}

			// wipe the vector
			resistances.removeAllElements();
			resistances = null;
				
			stmt.executeUpdate("DELETE FROM GameObject_Resistances WHERE gameobject_id=" + id);
		
			// FOR NOW, don't remove graphic or location
			
			// now, remove thyself
			stmt.executeUpdate("DELETE FROM GameObject WHERE id=" + id);

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to remove GameObject (" + id + "): " +
			e.getMessage());
		}

		if (rs != null)
		{
			try
			{
				rs.close();
			}
			catch (SQLException e)
			{
				//
			}

			rs = null;
		}

		if (stmt != null)
		{
			try
			{
				stmt.close();
			}
			catch (SQLException e)
			{
				//
			}

			stmt = null;
		}

		super.unregister();
	}
	
			
	// EVENT HANDLERS
	/**
	 *  Add move event listener.
	 */
	public void addMoveListener(MoveListener ml)
	{
		moveListeners.addElement(ml);
	}

	/**
 	 *  Remove move event listener.
	 */
	public void removeMoveListener(MoveListener ml)
	{
		moveListeners.removeElement(ml);
	}

	/**
 	 *  Fire move event.
	 */
	protected void moved(GameObject from)
	{
		MoveEvent me = new MoveEvent(from, this, this.location);
		
		Vector list = (Vector)moveListeners.clone();
		MoveListener ml;

		for (int i = 0; i < list.size(); i++)
		{
			ml = (MoveListener)list.elementAt(i);

			ml.notifyMove(me);
		}

		ml = null;
		list = null;
		me = null;
	}

	/**
	 *  Add a property changed event listener.
	 */
	public void addPropertyChangeListener(PropertyChangeListener pl)
	{
		propertyChangeListeners.addElement(pl);
	}

	/**
	 *  Remove a property change event listener.
	 */
	public void removePropertyChangeListener(PropertyChangeListener pl)
	{
		propertyChangeListeners.removeElement(pl);
	}

	/**
	 *  Fire a property change event.
	 */
	protected void propChanged(String propertyName)
	{
		PropertyChangeEvent pe = new PropertyChangeEvent(this, propertyName);

		Vector vpl = (Vector)propertyChangeListeners.clone();

		for (int i = 0; i < vpl.size(); i++)
		{
			((PropertyChangeListener)vpl.elementAt(i)).notifyPropertyChange(pe);
		}
	}

	/**
	 *  Handle action events
	 *  Subclasses provide more specialized behavior.
	 */
	public void handleActionEvent(ActionEvent evt)
	{
		// do nothing with it
	}

	/**
	 *  Tester
	 */
	public static void main(String args [])
	{
		GameObject go = GameObject.createGameObject();

		go.store();

		int id = go.getID();

		try
		{
			Thread.sleep(30000);
		}
		catch (InterruptedException e)
		{
			//
		}

		go = null;
		
		go = GameObject.loadGameObject(id);

		System.out.println(go.getID());

		go.unregister();

		go = null;

		try
		{
			Thread.sleep(30000);
		}
		catch (InterruptedException e)
		{
			//
		}

		System.out.println("done");
	}
}

