package sso;

import java.sql.*;
import java.util.*;

public final class Registry
{
	private static Connection dbc;
	private static Hashtable htLoaded;
	private static Hashtable htTile;

	public static final int	ID_INVALID	 = -1;
	public static final int ID_DEFAULT_GRAPHIC = -2;

	/**
	 *  Register a new object with the database.
	 *  Return the next unique object id.
	 *  Synchronized, mostly because I don't have a lot of choice.
	 */
	public static synchronized int register(Persistant po)
	{
System.out.println("Registry.register called");

		checkDBC();
		checkLoaded();

		Statement stmt = null;
		ResultSet rs = null;
	
		// get the next unused id
		int nextId = ID_INVALID;
		
		try
		{
			stmt = dbc.createStatement();
			rs = stmt.executeQuery("SELECT MAX(id) FROM Registry");

			while (rs.next())
			{
				// max should always be in column 1
				nextId = rs.getInt(1) + 1;
			}

			// assume that the next entry was added successfully
			stmt.executeQuery("INSERT INTO Registry SET id=" + nextId + ", class='" + po.getClass().getName() + "'");

			rs.close();
			rs = null;

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Trying to get max id from registry table: " + e.getMessage());
		}

		// ensure that the db objects are closed and nulled for the gc
		if (rs != null)
		{
			try
			{
				rs.close();
			}
			catch (SQLException e)
			{
				//
			}

			rs = null;
		}

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

			stmt = null;
		}

System.out.println("Registry:  register " + nextId);

		return nextId;
	}

	/**
	 *  Unregister the given object with the database.
	 *  The caller should have removed their type rows from the database already.
	 */
	public static void unregister(Persistant po)
	{
		checkDBC();
		checkLoaded();

		Statement stmt = null;
		
		try
		{
			// remove from the Registry table
			stmt = dbc.createStatement();
			stmt.executeUpdate("DELETE FROM Registry WHERE id=" + po.getID());

			// mark it as unloaded, even if something still has a refernce to
			// it
			unload(po);
		}
		catch (SQLException e)
		{
			System.err.println("Attempting to unregister (" + po.getID() + "): " + e.getMessage());
		}
	}

	/**
     *  Load the given object by id.
	 *  This will ensure the object get loaded completely...however all objects
	 *  returned are Persistant object type.
	 */
	public static Persistant load(int id)
	{
		Statement stmt = null;
		ResultSet rs = null;
		Persistant po = null;
		String cl;

		try
		{
			stmt = dbc.createStatement();
			rs = stmt.executeQuery("SELECT class FROM Registry WHERE id=" + id);

			while (rs.next())
			{
				cl = rs.getString("class");

				// UNFORTUNATELY, I have to add each class to here as I develop
				// them. It's still easier than reflection, believe it or not.
				// And I'm good at using reflection, too.
				if (cl.equals("sso.Persistant"))
				{
					po = Persistant.loadPersistant(id);
				}
				else if (cl.equals("sso.GameObject"))
				{
					po = (Persistant)GameObject.loadGameObject(id);
				}
				else if (cl.equals("sso.Resistance"))
				{
					po = (Persistant)Resistance.loadResistance(id);
				}
				else if (cl.equals("sso.Effect"))
				{
					po = (Persistant)Effect.loadEffect(id);
				}
				else if (cl.equals("sso.Graphic"))
				{
					po = (Persistant)Graphic.loadGraphic(id);
				}
				else if (cl.equals("sso.Group"))
				{
					po = (Persistant)Group.loadGroup(id);
				}
				else if (cl.equals("sso.Thing"))
				{
					po = (Persistant)Thing.loadThing(id);
				}
				else if (cl.equals("sso.Skill"))
				{
					po = (Persistant)Skill.loadSkill(id);
				}
				else if (cl.equals("sso.PC"))
				{
					po = (Persistant)PC.loadPC(id);
				}
				else if (cl.equals("sso.Armor"))
				{
					po = (Persistant)Armor.loadArmor(id);
				}
				else if (cl.equals("sso.Tile"))
				{
					po = (Persistant)Tile.loadTile(id);
					
					// add the tile to the hashtable
					htTile.put(new String(((Tile)po).getX() + "+" + ((Tile)po).getY()), (Tile)po);
				}
				else if (cl.equals("sso.Arms"))
				{
					po = (Persistant)Arms.loadArms(id);
				}
				else if (cl.equals("sso.Wall"))
				{
					po = (Persistant)Wall.loadWall(id);
				}
				else if (cl.equals("sso.Window"))
				{
					po = (Persistant)Window.loadWindow(id);
				}
				else if (cl.equals("sso.Trap"))
				{
					po = (Persistant)Trap.loadTrap(id);
				}
				else if (cl.equals("sso.Lock"))
				{
					po = (Persistant)Lock.loadLock(id);
				}
			}

			rs.close();
			rs = null;

			stmt.close();
			stmt = null;
		}
		catch (SQLException e)
		{
			System.err.println("Loading object by id reference (" + 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;
		}

		return po;
	}

	/**
	 *  Get the database connection.
	 */
	public static Connection getConnection()
	{
		checkDBC();
		checkLoaded();
		return dbc;
	}

	/**
	 *  Check the see if a database connection has been created yet.
	 *  If not, create it.
	 */
	private static void checkDBC()
	{
		if (dbc == null)
		{
			// load the database driver
			try
			{
				Class.forName("org.gjt.mm.mysql.Driver").newInstance();
			}
			catch (Exception e)
			{
				System.err.println("Unable to load database driver. " + e.getMessage());
				e.printStackTrace();
			}

			// create a connection to reuse
			try
			{
				dbc = DriverManager.getConnection("jdbc:mysql://localhost/sso?user=root&password=&autoReconnect=true");
			}
			catch (SQLException e)
			{
				System.err.println("Unable to get initial db connection. " + e.getMessage());
			}
		}
	}

	/**
	 *  Set an object as loaded.
	 */
	public static void markLoaded(Persistant po)
	{
		if (!isLoaded(po.getID()))
		{
			htLoaded.put(new Integer(po.getID()), po);
		}
	}

	/**
     *  Returns true if an object is loaded.
     */
	public static boolean isLoaded(int id)
	{
		return htLoaded.containsKey(new Integer(id));
	}

	/**
	 *  Unload an object.
	 */
	public static void unload(Persistant po)
	{
		if (isLoaded(po.getID()))
		{
			htLoaded.remove(new Integer(po.getID()));
		}
	}

	/**
	 *  Get the reference to this object if loaded.
	 */
	public static Persistant get(int id)
	{
		return (Persistant)htLoaded.get(new Integer(id));
	}

	/**
	 *  Make the loaded object registry if it doesn't exist.
	 */
	private static void checkLoaded()
	{
		if (htLoaded == null)
		{
			htLoaded = new Hashtable();
			htTile = new Hashtable();
		}
	}

	/**
	 *  Get the tile at the given location
	 */
	public static Tile getXY(int x, int y)
	{
		// I'm not particularly worried about calling this before the hashtable
		// is created, so I'm not adding checkLoaded() here.

		Tile tile = null;
		
		if ((tile = (Tile)htTile.get(new String(x + "+" + y))) == null)
		{
			tile = Tile.getTile(x, y);
		}

		return tile;
	}

	/**
     *  Tester.
     */
	public static void main(String [] args)
	{
		Persistant po = Persistant.createPersistant();
		try
		{
			Thread.sleep(60000);
		}
		catch (InterruptedException e)
		{
			//
		}
		po.unregister();
	}
}

