package daruma.server;

import daruma.util.PortNumberValidator;
import daruma.util.LogWriter;
import daruma.util.PropertyReader;
import daruma.util.Itk;
import daruma.sql.DatabaseConnectionException;
import daruma.sql.MySQLDatabaseConnectionFactory;

import java.net.*;
import java.io.*;

public class DarumaDaemon
{
	static	final	public	int	DEFAULT_PORT = PropertyReader.getProperty("daruma.serv.port", 5050);
	static	final	public	String	DEFAULT_DATABASE_NAME = "daruma$devel";
	static	final	public	String	DEFAULT_USER = PropertyReader.getProperty("daruma.db.uid", "daruma");
	static	final	public	String	DEFAULT_PASSWORD = PropertyReader.getProperty("daruma.db.pass", "");
	static	final	public	String	DEFAULT_CONNECTION_LOG_DIRECTORY = ".";

	private	int	port;
	private	String	databaseName;
	private	String	user;
	private	String	password;
	private	boolean	debug;
	private	boolean	connectionLog;
	private	File	connectionLogDirectory;

	private	long	connectionID;

	public	DarumaDaemon( int  port ,
			      String  databaseName ,
			      String  user ,
			      String  password )
	{
		this.port = port;
		this.debug = false;
		this.connectionLog = false;
		this.connectionLogDirectory = null;
		this.databaseName = databaseName;
		this.user = user;
		this.password = password;
		this.connectionID = 0;
	}

	public	boolean	run()
	{
		try
		{
			if ( ! PortNumberValidator.check( port ) )
			{
				LogWriter.qwrite("ERROR",
						 "configuration error: invalid port number: ",
						 port );

				System.err.println( "invalid port number: "
						    + port );
				return( false );
			}


			ServerSocket	socket = new ServerSocket( port );

			LogWriter.qwrite("INFO",
					 "[" + Itk.getCurrentTimeStr() + "] ",
					 "Listen:", socket) ;

			for(;;)
			{
				Socket	s = socket.accept();

				ConnectionHandler	c;

				try
				{
					c = new ConnectionHandler
						( s.getInputStream() ,
						  s.getOutputStream() ,
						  this.databaseName ,
						  this.user ,
						  this.password ,
						  s.toString() ,
						  this.debug ,
						  this.connectionLog ,
						  this.connectionLogDirectory ,
						  this.connectionID );

					this.connectionID ++;
				}
				catch( DatabaseConnectionException  e )
				{
					LogWriter.qwrite("ERROR",  e.getMessage() );
					continue;
				}


				Thread	th = new Thread( c );
				th.start();
			}
		}
		catch( java.net.BindException  e )
		{
			LogWriter.qwrite("ERROR",  "Can't bind server socket: ",
					 e.getMessage() );

			System.err.println( "Can't bind server socket: "
					    + e.getMessage() );
			return( false );
		}
		catch( IOException  e )
		{
			e.printStackTrace();
			return( false );
		}
	}


	public void setDebug( boolean  debug )
	{
		this.debug = debug;
	}

	public void setConnectionLog( boolean  connectionLog ,
				      File  connectionLogDirectory )
	{
		this.connectionLog = connectionLog;
		this.connectionLogDirectory = connectionLogDirectory;
	}


	public static void main( String  args[] )
	{
		DarumaDaemonOptionAnalyzer	opt;
		opt = new DarumaDaemonOptionAnalyzer
			( args , "DarumaDaemon" ,
			  DarumaDaemon.DEFAULT_PORT ,
			  DarumaDaemon.DEFAULT_DATABASE_NAME ,
			  DarumaDaemon.DEFAULT_USER ,
			  DarumaDaemon.DEFAULT_PASSWORD ,
			  DarumaDaemon.DEFAULT_CONNECTION_LOG_DIRECTORY );

		if ( ! opt.analyze() ) {
			opt.printHelp( System.err );

			System.exit( 1 );
		} else if ( opt.getHelp() ) {
			opt.printHelp( System.out );

			System.exit( 0 );
		}


		File	connectionLogDirectory
			  = new File( opt.getConnectionLogDirectory() );

		if ( opt.getConnectionLog() )
		{
			if ( connectionLogDirectory.exists() )
			{
				if ( ! connectionLogDirectory.isDirectory() )
				{
					String	err;
					err = "connection log directory ["
						+ connectionLogDirectory
						+ "] is not a directory";

					System.err.println( err );
					LogWriter.qwrite( "FATAL" , err );

					System.exit( 1 );
				}
			}
			else
			{
				if ( ! connectionLogDirectory.mkdirs() )
				{
					String	err;
					err = "Can't create"
						+ " connection log directory ["
						+ connectionLogDirectory
						+ "]";

					System.err.println( err );
					LogWriter.qwrite( "FATAL" , err );

					System.exit( 1 );
				}
			}
		}

		try {
			new MySQLDatabaseConnectionFactory().loadClass();
		} catch( DatabaseConnectionException
			 .ClassLoadFailedDatabaseConnectionException  e ) {
			LogWriter.qwrite("FATAL",  "Can't load class: "
					 + e.getFailedClassName() );

			throw new RuntimeException("Can't load class: "
						   + e.getFailedClassName());
		} catch(DatabaseConnectionException dce) {
			LogWriter.qwrite("FATAL", "Can't load DB class");
			throw new RuntimeException("Can't load DB class: "
						   + dce.getMessage());
		}


		DarumaDaemon daemon = new DarumaDaemon( opt.getPortNumber() ,
							opt.getDatabaseName() ,
							opt.getUser() ,
							opt.getPassword() );

		daemon.setDebug( opt.getDebug() );
		daemon.setConnectionLog( opt.getConnectionLog() ,
					 connectionLogDirectory );

		if ( ! daemon.run() ) {
			System.exit( 1 );
		}

	}


	public static class  DarumaDaemonOptionAnalyzer
	{
		private	String[]	args;
		private	String		programName;
		private	int		portNumber;
		private	String		databaseName;
		private	String		user;
		private	String		password;

		private	boolean	helpFlag  = false;
		private	boolean	debugFlag = false;
		private	boolean	connectionLogFlag = false;
		private	String	connectionLogDirectory = null;


		public	DarumaDaemonOptionAnalyzer
				( final String[]  args ,
				  final String  programName ,
				  final int     defaultPortNumber ,
				  final String  defaultDatabaseName ,
				  final String  defaultUser ,
				  final String  defaultPassword ,
				  final String  defaultConnectionLogDirectory )
		{
			this.args = args.clone();
			this.programName = programName;
			this.portNumber = defaultPortNumber;
			this.databaseName = defaultDatabaseName;
			this.user = defaultUser;
			this.password = defaultPassword;
			this.connectionLogFlag = false;
			this.connectionLogDirectory
				= defaultConnectionLogDirectory;
		}

		public	boolean	analyze()
		{
			int	index = 0;
			while( index < args.length )
			{
				final	String	arg = this.args[index];
				int	numArgs = args.length - index - 1;

				if ( arg.equals( "--help" ) )
				{
					this.helpFlag = true;
				}
				else if ( arg.equals( "--port" ) )
				{
					if ( numArgs < 1 )
					{
						return( false );
					}

					try
					{
						this.portNumber
						  = Integer.parseInt
						    ( this.args[index + 1] );

					}
					catch( NumberFormatException  e )
					{
						LogWriter.qwrite("ERROR",
								 "invalid port number: ",
								 this.args[index + 1] );

						System.err.println
						  ( "invalid port number: "
						    + this.args[index + 1] );

						return( false );
					}

					index ++;
				}
				else if ( arg.equals( "--database" ) )
				{
					if ( numArgs < 1 )
					{
						return( false );
					}

					this.databaseName
						= this.args[index + 1];

					index ++;
				}
				else if ( arg.equals( "--user" ) )
				{
					if ( numArgs < 1 )
					{
						return( false );
					}

					this.user = this.args[index + 1];

					index ++;
				}
				else if ( arg.equals( "--password" ) )
				{
					if ( numArgs < 1 )
					{
						return( false );
					}

					this.password = this.args[index + 1];

					index ++;
				}
				else if ( arg.equals( "--debug" ) )
				{
					this.debugFlag = true;
				}
				else if ( arg.equals( "--connection-log" ) )
				{
					this.connectionLogFlag = true;
				}
				else if ( arg.equals
					  ( "--connection-log-directory" ) )
				{
					if ( numArgs < 1 )
					{
						return( false );
					}

					index ++;

					this.connectionLogDirectory
						= this.args[index];
				}
				else
				{
					LogWriter.qwrite
						( "invalid option: " + arg );

					System.err.println
						( "invalid option: " + arg );

					return( false );
				}

				index ++;
			}

			return( true );
		}

		public	void	printHelp( OutputStream  out )
		{
			String	mes = "";
			mes += "Usage: "
				   + this.programName + " [options]" + "\n";
			mes += "Possible options are:"               + "\n";
			mes += "      --help"                        + "\n";
			mes += "      --port PORT"                   + "\n";
			mes += "      --database DATABASE_NAME"      + "\n";
			mes += "      --user USER_NAME"              + "\n";
			mes += "      --password PASSWORD"           + "\n";
			mes += "      --debug"                       + "\n";
			mes += "      --connection-log"              + "\n";
			mes += "      --connection-log-directory DIRECTORY"
								     + "\n";

			PrintStream	p = new PrintStream( out );
			p.print( mes );
		}

		public	boolean	getHelp()
		{
			return( this.helpFlag );
		}

		public	int	getPortNumber()
		{
			return( this.portNumber );
		}

		public	String	getDatabaseName()
		{
			return( this.databaseName );
		}

		public	String	getUser()
		{
			return( this.user );
		}

		public	String	getPassword()
		{
			return( this.password );
		}

		public	boolean	getDebug()
		{
			return( this.debugFlag );
		}

		public	boolean	getConnectionLog()
		{
			return( this.connectionLogFlag );
		}

		public	String	getConnectionLogDirectory()
		{
			return( this.connectionLogDirectory );
		}
	}
}
