package com.planet_ink.coffee_mud.core.intermud.i3.server;
import com.planet_ink.coffee_mud.core.intermud.imc2.*;
import com.planet_ink.coffee_mud.core.intermud.i3.packets.*;
import com.planet_ink.coffee_mud.core.intermud.i3.persist.*;
import com.planet_ink.coffee_mud.core.intermud.i3.server.*;
import com.planet_ink.coffee_mud.core.intermud.i3.net.*;
import com.planet_ink.coffee_mud.core.intermud.*;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
import com.planet_ink.coffee_mud.core.CMSecurity.DisFlag;
import com.planet_ink.coffee_mud.core.collections.*;
import com.planet_ink.coffee_mud.Abilities.interfaces.*;
import com.planet_ink.coffee_mud.Areas.interfaces.*;
import com.planet_ink.coffee_mud.Behaviors.interfaces.*;
import com.planet_ink.coffee_mud.CharClasses.interfaces.*;
import com.planet_ink.coffee_mud.Commands.interfaces.*;
import com.planet_ink.coffee_mud.Common.interfaces.*;
import com.planet_ink.coffee_mud.Exits.interfaces.*;
import com.planet_ink.coffee_mud.Items.interfaces.*;
import com.planet_ink.coffee_mud.Libraries.interfaces.*;
import com.planet_ink.coffee_mud.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;

import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;

/**
 * The Server class uses exactly one thread ServerThread object
 * during the course of program execution.  This thread loops
 * until the Server class tells it to shut down.  The loop is
 * executed in the thread's run() method.
 * @author George Reese (borg@imaginary.com), Bo Zimmerman
 * @version 1.1
 * @see com.planet_ink.coffee_mud.core.intermud.i3.server.I3Server
 *
 * Modified in 2013 to cut down on thread use
 */
public class ServerThread implements Tickable
{
	private java.util.Date				boot_time		= null;
	private int							count			= 1;
	private final String				mud_name;
	private final int					port;
	private boolean						running;
	private final ImudServices			intermuds;
	private ListenThread				listen_thread	= null;
	private volatile int				tickStatus		= Tickable.STATUS_NOT;
	private Map<String, ServerObject>	objects;
	private Map<String, ServerUser>		interactives;

	protected ServerThread(final String mname,
						   final int mport,
						   final ImudServices imud)
						   {
		mud_name = mname;
		port = mport;
		intermuds=imud;
	}

	@Override
	public String ID()
	{
		return "I3ServerThread";
	}

	@Override
	public CMObject newInstance()
	{
		return this;
	}

	@Override
	public CMObject copyOf()
	{
		return this;
	}

	@Override
	public void initializeClass()
	{
	}

	@Override
	public int compareTo(final CMObject o)
	{
		return (o==this)?0:1;
	}

	@Override
	public String name()
	{
		return "I3UserThread"+Thread.currentThread().getThreadGroup().getName().charAt(0);
	}

	@Override
	public int getTickStatus()
	{
		return tickStatus;
	}

	protected synchronized ServerObject copyObject(String str) throws ObjectLoadException {
		ServerObject ob;

		try
		{
			ob = (ServerObject)Class.forName(str).newInstance();
			count++;
			str = str + "#" + count;
			ob.setObjectId(str);
			objects.put(str, ob);
		}
		catch( final Exception e )
		{
			throw new ObjectLoadException("Failed to load object: " + e.getMessage());
		}
		return ob;
	}

	protected synchronized ServerObject findObject(final String str) throws ObjectLoadException {
		ServerObject ob;

		if( objects.containsKey(str) )
		{
			ob = objects.get(str);
			if( ob.getDestructed() )
			{
				ob = null;
			}
		}
		else
		{
			ob = null;
		}
		if( ob == null )
		{
			try
			{
				ob = (ServerObject)Class.forName(str).newInstance();
				ob.setObjectId(str);
				objects.put(str, ob);
			}
			catch( final Exception e )
			{
				throw new ObjectLoadException("Failed to load object: " + e.getMessage());
			}
		}
		return ob;
	}

	protected synchronized void removeObject(final ServerObject ob)
	{
		final String id = ob.getObjectId();

		if( !objects.containsKey(id) )
		{
			return;
		}
		objects.remove(id);
		if( interactives.containsKey(id) )
		{
			interactives.remove(id);
		}
	}

	/**
	 * While the mud is running, this method repeats the following
	 * steps over and over:
	 *
	 * Check for pending user input and trigger user commands
	 * Check for pending object events and execute them
	 * Check for incoming user connections and create an
	 *  	interactive object for each.
	 *
	 */
	public void start()
	{
		if( boot_time != null )
		{
			Log.errOut("I3Server","Illegal attempt to invoke run().");
			return;
		}

		try
		{
			listen_thread = new ListenThread(port);
		}
		catch( final java.io.IOException e )
		{
			Log.errOut("I3Server",e);
			return;
		}

		try
		{
			Intermud.setup(intermuds,
						   (PersistentPeer)Class.forName("com.planet_ink.coffee_mud.core.intermud.i3.IMudPeer").newInstance());
		}
		catch( final Exception e )
		{
			Log.errOut("I3Server",e);
			return;
		}

		boot_time = new java.util.Date();
		Log.sysOut("I3Server", "InterMud3 Server started on port "+port);

		synchronized( this )
		{
			objects = new Hashtable<String,ServerObject>(1000, 100);
			interactives = new Hashtable<String,ServerUser>(50, 20);
		}

		running = true;
		CMLib.threads().deleteTick(this, Tickable.TICKID_SUPPORT);
		CMLib.threads().startTickDown(this, Tickable.TICKID_SUPPORT, 250, 1);
	}

	@Override
	public boolean tick(final Tickable ticking, final int tickID)
	{
		if(CMSecurity.isDisabled(DisFlag.I3))
			return running;

		tickStatus=Tickable.STATUS_ALIVE;
		ServerObject[] things;
		ServerUser[] users;
		synchronized( this )
		{
			things = getObjects();
			users = getInteractives();
		}
		{// Process all input
			int i;

			for(i=0; i<users.length; i++)
			{
				final ServerUser interactive = users[i];

				if( interactive.getDestructed() )
				{
					continue;
				}
				try
				{
					interactive.processInput();
				}
				catch( final Exception e )
				{
					Log.errOut("IMServerThread",e);
				}
			}
		}
		{// Check for pending object events
			int i;

			for(i=0; i<things.length; i++)
			{
				final ServerObject thing = things[i];

				if( !thing.getDestructed() )
				{
					try
					{
						thing.processEvent();
					}
					catch( final Exception e )
					{
						Log.errOut("IMServerThread",e);
					}
				}
			}
		}
		{// Get new connections
			int i;

			for(i=0; i<5; i++)
			{
				java.net.Socket s;
				ServerUser new_user;

				if(listen_thread!=null)
					s = listen_thread.nextSocket();
				else
					s=null;
				if( s == null )
				{
					break;
				}
				try
				{
					new_user = (ServerUser)copyObject("com.planet_ink.coffee_mud.core.intermud.i3.IMudUser");
				}
				catch( final ObjectLoadException e )
				{
					continue;
				}
				try
				{
					new_user.setSocket(s);
					synchronized( this )
					{
						interactives.put(new_user.getObjectId(), new_user);
						new_user.connect();
					}
				}
				catch( final java.io.IOException e )
				{
					new_user.destruct();
				}
			}
		}
		tickStatus=Tickable.STATUS_NOT;
		return running;
	}

	protected Date getBootTime()
	{
		return boot_time;
	}

	protected synchronized ServerUser[] getInteractives()
	{
		final ServerUser[] tmp = new ServerUser[interactives.size()];
		int i = 0;

		for(final ServerUser U : interactives.values())
		{
			tmp[i++] = U;
		}
		return tmp;
	}

	protected String getMudName()
	{
		return mud_name;
	}

	protected int getPort()
	{
		return port;
	}

	public void shutdown()
	{
		running=false;
		Intermud.shutdown();
		if(listen_thread!=null)
		{
			listen_thread.close();
			CMLib.killThread(listen_thread,500,1);
			listen_thread=null;
		}
		boot_time = null;
		CMLib.threads().deleteTick(this, Tickable.TICKID_SUPPORT);
	}

	protected synchronized ServerObject[] getObjects()
	{
		final ServerObject[] tmp = new ServerObject[objects.size()];
		int i = 0;
		for(final ServerObject O : objects.values())
		{
			tmp[i++] = O;
		}
		return tmp;
	}
}
