package sso;

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

public class Tile extends Immobile
{
	protected int x_c;		// x-coordinate
	protected int y_c;		// y-coordinate
	protected int z_c;		// base height (z-coordinate)
	protected Vector enterEffects;	// effects that happen when PC enters

	protected Vector handledEvents;	// ActionEvents that have been handled by
									// this

	// Tile flags
	public final static int		FLAT		= 0x00;
	public final static int		ANGLE		= 0x01;
	public final static int		STEP		= 0x02;
	public final static int		IMPASSABLE	= 0x04;
	public final static int		VERTICAL	= 0x08;
	public final static int		ROOF		= 0x10;	// roof or floor (second story)
	public final static int		WET			= 0x20;	// rivers, lakes, oceans, puddles


	/**
	 *  Constructor. Call a factory method instead.
	 */
	protected Tile()
	{
		super();
		enterEffects = new Vector();
	}

	/**
	 *  Factory method. Creates a new tile.
	 */
	public static Tile createTile()
	{
		Tile t = new Tile();
		t.init();
		return t;
	}

	/**
	 *  Initializers for new tiles.
	 */
	protected void init()
	{
		super.init();
		
		x_c = 0;
		y_c = 0;
		z_c = 0;
	}

	/**
	 *  Return the x location of this.
	 *  OVERRIDE.
	 */
	public int getX()
	{
		return x_c;
	}

	/**
	 *  Set the X coordinate
	 */
	public void setX(int x)
	{
		x_c = x;

		propChanged("x_c");
	}

	/**
	 *  Return the y location of this.
	 *  OVERRIDE.
	 */
	public int getY()
	{
		return y_c;
	}

	/**
	 *  Set the Y coordinate
	 */
	public void setY(int y)
	{
		y_c = y;

		propChanged("y_c");
	}

	/**
	 *  Return the z location of this.
	 *  OVERRIDE.
	 */
	public int getZ()
	{
		return z_c;
	}

	/**
	 *  Set the Z coordinate
	 */
	public void setZ(int z)
	{
		z_c = z;

		propChanged("z_c");
	}

	/**
	 *  Get the enter effects
	 */
	public Vector getEnterEffects()
	{
		return enterEffects;
	}

	/**
	 *  Add an enter effect (String classname)
	 */
	public void addEnterEffect(String effectClass)
	{
		enterEffects.addElement(effectClass);

		propChanged("enterEffects");
	}

	/**
	 *  Remove an enter effect (String classname)
	 */
	public void removeEnterEffect(String effectClass)
	{
		enterEffects.removeElement(effectClass);

		propChanged("enterEffects");
	}
	
	/**
	 *  Accept objects on this tile.
	 *  Don't allow anything in this tile if an immobile is present
	 *  Only allow ONE PC in this tile at a time.
	 *  Allow many non-PCs in this tile at one time.
	 *  Override method.
	 */
	public boolean accept(GameObject spec)
	{
		// easy checks first
		// I'm assuming that there may instances where you want a vertical tile
		// to be passable so I'm only checking for the impassable condition
		if ((getFlags() & IMPASSABLE) == IMPASSABLE)
		{
			System.err.println("Can't accept " + spec.getID() + " because Tile " + getID() + " is IMPASSABLE");

			return false;
		}
		
		boolean acc = true;
	
		Vector vc = (Vector)contents.clone();
		
		GameObject go;
		
		for (int i = 0; i < vc.size(); i++)
		{
			go = (GameObject)vc.elementAt(i);

			if (spec instanceof PC)
			{
				if (go instanceof PC)
				{
					// I definitely KNOW that I can't have two PCs in the same
					// tile
					System.out.println("Can't have two PCs in the same Tile (" + id + ")");

					return false;
				}
			}
			
			if (go instanceof Immobile)
			{
				if (go instanceof Climbable)
				{
					// NO, I'm not letting you put things on the stairs
					if (!(spec instanceof PC))
					{
						// so unless something else causes this to definitely
						// turn false, the PC will be able to enter
						acc = false;
					}
				}
			}
		}

		return super.accept(spec) && acc;
	}

	/**
	 *  Put the object in the inventory. Start any effects.
	 *  OVERRIDE.
	 */
	protected void give(GameObject spec)
	{
		super.give(spec);

		// start effects
		Effect eff;

		Vector ve = (Vector)enterEffects.clone();

		for (int i = 0; i < ve.size(); i++)
		{
			try
			{
				eff = (Effect)Class.forName((String)ve.elementAt(i)).newInstance();

				// Effects are little different than most objects. Yes, they
				// should be created with a Factory method, but that's not
				// supported well by Reflection, so I'm going to call the init
				// myself.
				eff.init();

				eff.addVictim(spec);
				eff.start();
			}
			catch (Exception e)
			{
				System.err.println("While give(" + spec.getID() + ") to " +
				getID() + " creating effect exception: " + e.getMessage());
			}
		}
	}


	/**
	 *  Get a tile from the database by x,y
	 */
	public static Tile getTile(int x, int y)
	{
		Statement stmt = null;
		ResultSet rs = null;
		Tile tile = null;

		try
		{
			stmt = Persistant.getStatement();

			rs = stmt.executeQuery("SELECT tile_id FROM Tile WHERE x_coord=" +
			x + " AND y_coord=" + y);

			while (rs.next())
			{
				tile = Tile.loadTile(rs.getInt("tile_id"));
			}

			rs.close();
			rs = null;

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to get a tile by x,y (" + x + ", " + y +
			"): " + e.getMessage());
		}

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

			rs = null;
		}

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

			stmt = null;
		}

		return tile;
	}


	// EVENT HANDLERS

	/**
	 *  Handle Action events.
	 */
	public void handleActionEvent(ActionEvent evt)
	{	
		synchronized (handledEvents)
		{
			if (!handledEvents.contains(evt.getID()))
			{
				// noted now that this has been handled
				handledEvents.addElement(evt.getID());
				
				// check to see if this contains an immobile
				Vector cont = (Vector)contents.clone();
	
				boolean prop = true;

				Scenery scene;

				int i;

				for (i = 0; i < cont.size(); i++)
				{
					if (cont.elementAt(i) instanceof Immobile)
					{
						if (cont.elementAt(i) instanceof Scenery)
						{
							scene = (Scenery)cont.elementAt(i);
							
							if (!scene.doesPropagate())
							{
								// contains scenery that will not propagate
								// events
								prop = false;
							}
						}
					}
				}

				if (prop)
				{
					// this will reduce the ttl by 1
					evt = (ActionEvent)evt.clone();
				
					for (i = 0; i < cont.size(); i++)
					{
						// tell each contained object
						((GameObject)cont.elementAt(i)).handleActionEvent(evt); 
					}

					// get neighboring rooms, tell them about this
					for (int x = -1; x < 2; x++)
					{
						for (int y = -1; y < 2; y++)
						{
							// tell each neighbor
							if (!(x == 0 && y == 0))
							{
								Registry.getXY(getX() + x, getY() +	y).handleActionEvent(evt);
							}
						}
					}
				}

			}
			else
			{
				// handled before
							
				// check for compaction (threshold 1.5:1.0)
				// *** TODO *** What would be better is to timestamp these
				// events so that events greater than a few minutes old are
				// purged automatically.
				
				if (handledEvents.size() > 1500)
				{
					// too many events
					// create a new vector to hold the list of events
					Vector h2 = new Vector();
		
					// get the end index of the events
					int end = handledEvents.size();

					// iterate from the end, adding events to the new vector
					for (int i = end; i > end - 1000; i--)
					{
						h2.addElement(handledEvents.elementAt(i));
					}

					handledEvents = null;
					handledEvents = h2;
					h2 = null;
				}
			}
		} // end synchronized
	}

			
	// DATABASE METHODS

	/**
	 *  Create the rows needed to store this object.
	 */
	protected void createRows()
	{
		super.createRows();

		Statement stmt = null;

		try
		{
			stmt = Persistant.getStatement();
			
			stmt.executeUpdate("INSERT INTO Tile (tile_id, x_coord, y_coord, z_coord) VALUES (" + id + ", 0, 0, 0)");

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

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

			stmt = null;
		}
	}

	/**
	 *  Store the Tile.
	 */
	public void store()
	{
		super.store();

		Statement stmt = null;

		try
		{
			stmt = Persistant.getStatement();

			// first, delete the old effects
			stmt.executeUpdate("DELETE FROM Tile_Effects WHERE tile_id=" + id);

			// now, store the new effects
			Vector ve = (Vector)enterEffects.clone();

			for (int i = 0; i < ve.size(); i++)
			{
				stmt.executeUpdate("INSERT INTO Tile_Effects (tile_id, effect_class) VALUES (" + id + ", " + (String)ve.elementAt(i) + ")");
			}

			ve = null;
			
			// finally, store the Tile itself
			stmt.executeUpdate("UPDATE Tile SET x_coord=" + x_c + ", y_coord=" + y_c + ", z_coord=" + z_c + " WHERE tile_id=" + id);

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

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

	/**
	 *  Load the tile. Factory method.
	 */
	public static Tile loadTile(int id)
	{
		Tile t = null;

		if (Registry.isLoaded(id))
		{
			t = (Tile)Registry.get(id);
		}
		else
		{
			t = new Tile();
			t.id = id;
			t.load();
		}

		return t;
	}

	/**
	 *  Load the tile from the database
	 */
	protected void load()
	{
		super.load();

		Statement stmt = null;
		ResultSet rs = null;

		try
		{
			stmt = Persistant.getStatement();

			rs = stmt.executeQuery("SELECT * FROM Tile WHERE tile_id=" + id);

			while (rs.next())
			{
				x_c = rs.getInt("x_coord");
				y_c = rs.getInt("y_coord");
				z_c = rs.getInt("z_coord");
			}

			rs.close();
			rs = null;

			rs = stmt.executeQuery("SELECT * FROM Tile_Effects WHERE tile_id="
			+ id);

			while (rs.next())
			{
				enterEffects.addElement(rs.getString("effect_class"));
			}
			
			rs.close();
			rs = null;

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to load Tile (" + 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;
		}
	}

	/**
	 *  Unregister the tile.
	 */
	public void unregister()
	{
		Statement stmt = null;

		try
		{
			stmt = Persistant.getStatement();

			stmt.executeUpdate("DELETE FROM Tile_Effects WHERE tile_id=" + id);
			stmt.executeUpdate("DELETE FROM Tile WHERE tile_id=" + id);

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

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

			stmt = null;
		}

		super.unregister();
	}

	/**
	 *  Tester
	 */
	public static void main(String [] args)
	{
		Tile t = Tile.createTile();
		GameObject g = GameObject.createGameObject();

		System.out.println(g.move(t));


		//System.out.println("g contents " + ((GameObject)g.contents.elementAt(0)).getID());
		System.out.println("t location " + t.getLocation());

		t.store();
		// should cause g to store
		// but doesn't, so, try this...
//		g.store();

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

		t.setX(100);
		t.setY(105);

		t.store();

		t = null;

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

		t = Tile.getTile(100, 105);

		System.out.println("Tile at : " + t.getX() + ", " + t.getY());
	
		g.unregister();
		t.unregister();
	}
}	

