package com.planet_ink.coffee_mud.core.http;
import com.planet_ink.coffee_mud.core.interfaces.*;
import com.planet_ink.coffee_mud.core.*;
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.Locales.interfaces.*;
import com.planet_ink.coffee_mud.MOBS.interfaces.*;
import com.planet_ink.coffee_mud.Races.interfaces.*;
import java.net.*;
import java.io.IOException;
import java.util.*;


/* 
   Portions Copyright 2002 Jeff Kamenek
   Portions Copyright 2002-2006 Bo Zimmerman

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/
public class HTTPserver extends Thread implements MudHost
{
	public CMProps page=null;

	public static final float HOST_VERSION_MAJOR=(float)1.0;
	public static final float HOST_VERSION_MINOR=(float)0.3;
	public static CMProps webCommon=null;

	// this gets sent in HTTP response
	//  also used by @WEBSERVERVERSION@
	public final static String ServerVersionString = "CoffeeMud HTTPserver/" + HOST_VERSION_MAJOR + "." + HOST_VERSION_MINOR;

	public boolean isOK = false;

	public boolean isAdminServer = false;

	public ServerSocket servsock=null;

	private MudHost mud;
	protected String partialName;
    private static final String[] STATUS_STRINGS={"waiting","processing","done"};
    private int state=0;
    private int myPort=27744;
    private int myServerNumber=0;
    private boolean acceptConnections=true;

	String serverDir = null;
	String serverTemplateDir = null;

	public FileGrabber pageGrabber=new FileGrabber(this);
	public FileGrabber templateGrabber=new FileGrabber(this);

	public HTTPserver(MudHost a_mud, String a_name, int num)
	{
		super("HTTP-"+a_name+((num>0)?""+(num+1):""));
		partialName = a_name;		//name without prefix
		mud = a_mud;
		myServerNumber=num;
		setDaemon(true);
		if (!initServer(num))
			isOK = false;
		else
			isOK = true;
	}

	public String getPartialName()	{return partialName;}
	public MudHost getMUD()	{return mud;}
	public String getServerDir() {return serverDir;}
	public String getServerTemplateDir() {return serverTemplateDir;}

	public Properties getCommonPropPage()
	{
		if (webCommon==null || !webCommon.loaded)
		{
			webCommon=new CMProps ("web/common.ini");
			if(!webCommon.loaded)
				Log.errOut("HTTPserver","Unable to load common.ini!");
		}
		return webCommon;
	}

	protected boolean initServer(int which)
	{
		if (!loadPropPage())
		{
			Log.errOut(getName(),"ERROR: HTTPserver unable to read ini file.");
			return false;
		}

		if (page.getStr("PORT").length()==0)
		{
			Log.errOut(getName(),"ERROR: required parameter missing: PORT");
			return false;
		}
		if(which>0)
		{
			Vector V=CMParms.parseCommas(page.getStr("PORT"),true);
			if(which>=V.size())
			{
				Log.errOut(getName(),"ERROR: not enough PORT entries to support #"+(which+1));
				return false;
			}
		}
		if (page.getStr("DEFAULTFILE").length()==0)
		{
			Log.errOut(getName(),"ERROR: required parameter missing: DEFAULTFILE");
			return false;
		}
		if (page.getStr("VIRTUALPAGEEXTENSION").length()==0)
		{
			Log.errOut(getName(),"ERROR: required parameter missing: VIRTUALPAGEEXTENSION");
			return false;
		}

		if (page.getStr("BASEDIRECTORY").length()==0)
		{
			serverDir = new String ("web/"+partialName);
		}
		else
		{
			serverDir = page.getStr("BASEDIRECTORY");
		}

		// don't want any trailing / chars
		serverDir = FileGrabber.fixDirName(serverDir);

		if (!pageGrabber.setBaseDirectory(serverDir))
		{
			Log.errOut(getName(),"Could not set server base directory: "+serverDir);
			return false;
		}

		if (page.getStr("TEMPLATEDIRECTORY").length()==0)
			serverTemplateDir = new String (serverDir + ".templates");
		else
			serverTemplateDir = page.getStr("TEMPLATEDIRECTORY");

		// don't want any trailing / chars
		serverTemplateDir = FileGrabber.fixDirName(serverTemplateDir);

		if (!templateGrabber.setBaseDirectory(serverTemplateDir))
		{
			Log.errOut(getName(),"Could not set server template directory: "+serverTemplateDir);
			return false;
		}

		addVirtualDirectories();


		return true;
	}

	public Hashtable getVirtualDirectories(){return pageGrabber.getVirtualDirectories();}

	private void addVirtualDirectories()
	{
		for (Enumeration e = page.keys() ; e.hasMoreElements() ;)
		{
			String s = (String) e.nextElement();

			// nb: hard-coded!
			if (s.startsWith("MOUNT/"))
			{
				// nb: hard-coded! - leaves in '/'
				String v = s.substring(5);

				pageGrabber.addVirtualDirectory(v,page.getStr(s));
			}
		}
	}

	protected boolean loadPropPage()
	{
		if (page==null || !page.loaded)
		{
			String fn = "web/" + getPartialName() + ".ini";
			page=new CMProps(getCommonPropPage(), fn);
			if(!page.loaded)
			{
				Log.errOut(getName(),"failed to load " + fn);
				return false;
			}
		}

		return true;
	}

	public void run()
	{
		int q_len = 6;
		Socket sock=null;
		boolean serverOK = false;

		if (!isOK)	return;
		if ((page == null) || (!page.loaded))
		{
			Log.errOut(getName(),"ERROR: HTTPserver will not run with no properties. WebServer shutting down.");
			isOK = false;
			return;
		}


		if (page.getInt("BACKLOG") > 0)
			q_len = page.getInt("BACKLOG");

		InetAddress bindAddr = null;


		if (page.getStr("ADMIN") != null && page.getStr("ADMIN").equalsIgnoreCase("true"))
			isAdminServer = true;
		if (page.getStr("BIND") != null && page.getStr("BIND").length() > 0)
		{
			try
			{
				bindAddr = InetAddress.getByName(page.getStr("BIND"));
			}
			catch (UnknownHostException e)
			{
				Log.errOut(getName(),"ERROR: Could not bind to address " + page.getStr("BIND"));
				bindAddr = null;
			}
		}

		try
		{
			Vector allports=CMParms.parseCommas(page.getStr("PORT"),true);
			myPort=CMath.s_int((String)allports.elementAt(myServerNumber));
			servsock=new ServerSocket(myPort, q_len, bindAddr);

			Log.sysOut(getName(),"Started on port: "+myPort);
			if (bindAddr != null)
				Log.sysOut(getName(),"Bound to: "+bindAddr.toString());


			serverOK = true;

			while(true)
			{
                state=0;
				sock=servsock.accept();
                if(acceptConnections)
                {
                    while(CMLib.threads().isAllSuspended())
                        Thread.sleep(1000);
                    state=1;
    				ProcessHTTPrequest W=new ProcessHTTPrequest(sock,this,page,isAdminServer);
    				W.equals(W); // this prevents an initialized by never used error
    				// nb - ProcessHTTPrequest is a Thread, but it .start()s in the constructor
    				//  if succeeds - no need to .start() it here
                }
				sock = null;
			}
		}
		catch(Throwable t)
		{
			// jef: if we've been interrupted, servsock will be null
			//   and serverOK will be true
			if((t!=null)&&(t instanceof Exception))
				Log.errOut(getName(),((Exception)t).getMessage());


			// jef: this prevents initHost() from running if run() has failed (eg socket in use)
			if (!serverOK)
				isOK = false;
		}
        state=2;
		try
		{
			if(servsock!=null)
				servsock.close();
			if(sock!=null)
				sock.close();
		}
		catch(IOException e)
		{
		}

		//Log.sysOut(getName(),"Thread stopped!");
	}


	// sends shutdown message to both log and optional session
	// then just calls interrupt

	public void shutdown(Session S)
	{
		Log.sysOut(getName(),"Shutting down.");
		if (S != null)
			S.println( getName() + " shutting down.");
		CMLib.killThread(this,500,1);
	}

	public void shutdown()	{shutdown(null);}



	// interrupt does NOT interrupt the ServerSocket.accept() call...
	//  override it so it does
	public void interrupt()
	{
		if(servsock!=null)
		{
			try
			{
				servsock.close();
				//jef: we MUST set it to null
				// (so run() can tell it was interrupted & didn't have an error)
				servsock = null;
			}
			catch(IOException e)
			{
			}
		}
		super.interrupt();
	}

	public int totalPorts()
	{
		return CMParms.parseCommas(page.getStr("PORT"),true).size();
	}
	public int getPort()
	{
		return myPort;
	}

	public String getPortStr()
	{
		return page.getStr("PORT");
	}
    public String getHost(){return getName();}
    public void shutdown(Session S, boolean keepItDown, String externalCommand){
        shutdown(S);
    }
    public String getStatus()
    {
        return STATUS_STRINGS[state];
    }
    public void setAcceptConnections(boolean truefalse){ acceptConnections=truefalse;}
    
    public String executeCommand(String cmd)
        throws Exception
    {
        throw new Exception("Not implemented");
    }
}
