package daruma.server;

import daruma.server.DarumaDaemon;
import daruma.server.ConnectionInfoHolder;
import daruma.global_switch.ImplementationSwitches;

import daruma.util.LogWriter;
import daruma.util.PropertyReader;
import daruma.util.ISO8601DateFormat;
import daruma.util.Itk;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.SocketException;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.PrintStream;
import java.io.IOException;
import java.util.Set;

public class DarumaDaemonAdministrator implements Runnable
{
    class AdminClientHandler implements Runnable
    {
	Socket adminSocket;
	DarumaDaemonAdministrator admin;
	DarumaDaemon daemon;
	boolean printGreetingUsage;
	boolean isShuttingDown;

	AdminClientHandler( Socket adminSocket,
			    DarumaDaemonAdministrator admin,
			    DarumaDaemon daemon,
			    boolean printGreetingUsage )
	{
	    this.adminSocket = adminSocket;
	    this.admin = admin;
	    this.daemon = daemon;
	    this.printGreetingUsage = printGreetingUsage;
	    this.isShuttingDown = false;
	}

	public void run()
	{
	    try
	    {
		LogWriter.qwrite( "INFO",
				  "[" + Itk.getCurrentTimeStr() + "] ",
				  "Admin Port Open :", this.adminSocket );

		BufferedReader in = new BufferedReader
				    ( new InputStreamReader
				      ( this.adminSocket.getInputStream() ) );

		PrintStream out = new PrintStream( this.adminSocket
						   .getOutputStream() );

		if ( this.printGreetingUsage )
		{
		    out.println( "type \"help\" to get command list." );
		    out.flush();
		}


		while( true )
		{
		    String command = in.readLine();

		    if ( command == null )
		    {
			break;
		    }

		    try
		    {
			if ( command.equals( "" ) )
			{
			    // blank line, ignore this
			    continue;
			}
			else if ( command.equals( "help" ) )
			{
			    this.printUsage( out );

			    // don't write log, go to next
			    continue;
			}
			else if ( command.equals( "quit" ) )
			{
			    break;
			}

			LogWriter.qwrite( "INFO",
					  "[" + Itk.getCurrentTimeStr() + "] ",
					  "received admin command [",
					  command, "]" );

			if ( command.equals( "list" ) )
			{
			    this.printClientList( out );
			}
			else if ( command.equals( "force-disconnect-client" ) )
			{
			    this.forceDisconnectClient( out );
			}
			else if ( command.equals( "shutdown" ) )
			{
			    this.admin.shutdown();
			    this.daemon.shutdown();
			    break;
			}
			else
			{
			    LogWriter.qwrite( "INFO",
					      "[" + Itk.getCurrentTimeStr()
					      + "] ",
					      "admin: no such command" );

			    out.println( "no such command." );
			    out.println( "type \"help\" "
					 + "to get valid command list." );
			    out.flush();
			}
		    }
		    catch( SocketException e )
		    {
			throw e;
		    }
		    catch( IOException e )
		    {
			e.printStackTrace();
		    }
		}
	    }
	    catch( SocketException e )
	    {
	    }
	    catch( IOException e )
	    {
		e.printStackTrace();
	    }
	    finally
	    {
		this.finalizeConnection();
	    }
	}

	public void finalizeConnection()
	{
	    if ( this.isShuttingDown )
	    {
		return;
	    }

	    this.isShuttingDown = true;

	    LogWriter.qwrite( "INFO",
			      "[" + Itk.getCurrentTimeStr() + "] ",
			      "Admin Port Close :", this.adminSocket );

	    try
	    {
		this.adminSocket.close();
	    }
	    catch( IOException e )
	    {
		e.printStackTrace();
	    }
	}

	public void printUsage( PrintStream out ) throws IOException
	{
	    out.println( "================================="
			 + "===============================" );
	    out.println( " help                     "
			 + ": print this message" );
	    out.println( " list                     "
			 + ": get connecting client list" );
	    out.println( " force-disconnect-client  "
			 + ": disconnect client connection" );
	    out.println( " shutdown                 "
			 + ": shutdown the server" );
	    out.println( " quit                     "
			 + ": close this administrator connection" );
	    out.println( "================================="
			 + "===============================" );
	}

	public ConnectionInfoHolder.DisconnectResult
		  forceDisconnectClient( PrintStream out ) throws IOException
	{
	    ConnectionInfoHolder.DisconnectResult result;
	    result = daemon.getConnectionInfoHolder().forceDisconnectClient();

	    String message;
	    if ( ! result.getSuccess() )
	    {
		message = "failed";
	    }
	    else
	    {
		if ( result.getDone() )
		{
		    message = "ok, disconnected";
		}
		else
		{
		    message = "no client found";
		}
	    }

	    LogWriter.qwrite( "INFO",
			      "[" + Itk.getCurrentTimeStr() + "] ",
			      "admin: ", message );

	    out.println( message );
	    out.flush();

	    return result;
	}

	public void printClientList( PrintStream out )
	{
	    Set<ConnectionInfoHolder.ConnectionInfo> cs;
	    cs = daemon.getConnectionInfoHolder().getClientSocketSet();

	    out.println( "(" + cs.size() + " client"
			 + (cs.size() != 1 ? "s" :"") + ")" );

	    for ( ConnectionInfoHolder.ConnectionInfo c : cs )
	    {
		String statusString;

		switch( c.getStatus() )
		{
		case Running:
		    statusString = "Running";
		    break;
		case Waiting:
		    statusString = "Waiting";
		    break;
		default:
		    statusString = "Unknown";
		}

		String connectedDateString
		    = ISO8601DateFormat.getISO8601Instance()
			.format( c.getConnectedDate() );

		out.print( c.getConnectionID() + " " );
		out.print( statusString + " " );
		out.print( c.getSocket().getInetAddress().getHostAddress()
			   + ":" + c.getSocket().getPort() + " " );
		out.print( connectedDateString );
		out.println();
	    }

	    out.flush();
	}
    }


    private final DarumaDaemon daemon;
    private final int adminPort;
    private final boolean printGreetingUsage;

    private ServerSocket adminSocket;
    private boolean isShuttingDown;

    private static final boolean LOCALHOST_ONLY
	= PropertyReader.getProperty( "daruma.serv.admin_port_localhost_only",
				      true );

    DarumaDaemonAdministrator( DarumaDaemon daemon, int adminPort,
			       boolean printGreetingUsage )
    {
	this.daemon = daemon;
	this.adminPort = adminPort;
	this.isShuttingDown = false;
	this.printGreetingUsage = printGreetingUsage;
    }

    public void run()
    {
	try
	{
	    this.adminSocket = new ServerSocket( this.adminPort );
	    this.adminSocket.setReuseAddress( true );

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

	    while( true )
	    {
		if ( ImplementationSwitches.instance().getDoFullGC() )
		{
		    Runtime.getRuntime().gc();
		}


		if ( this.isShuttingDown )
		{
		    break;
		}

		Socket adminConnection = null;;
		try
		{
		    adminConnection = this.adminSocket.accept();
		}
		catch( SocketException e )
		{
			if ( ! this.isShuttingDown )
			{
				throw e;
			}
		}


		if ( this.isShuttingDown )
		{
		    if ( adminConnection != null )
		    {
			try
			{
			    adminConnection.close();
			}
			catch( IOException e )
			{
			    e.printStackTrace();
			}
		    }
		    break;
		}

		if ( LOCALHOST_ONLY
		     && ! adminConnection.getInetAddress()
					 .isLoopbackAddress() )
		{
		    LogWriter.qwrite( "INFO",
				      "[" + Itk.getCurrentTimeStr() + "] ",
				      "blocked administration connection ",
				      adminConnection, ",",
				      " administration connection",
				      " not allowed except from localhost." );

		    adminConnection.close();
		    continue;
		}


		Thread clientThread
		    = new Thread( new AdminClientHandler
				  ( adminConnection,
				    this,
				    this.daemon,
				    this.printGreetingUsage ) );
		clientThread.start();
	    }
	}
	catch( IOException e )
	{
	    if ( ! this.isShuttingDown )
	    {
		e.printStackTrace();
	    }
	}
	finally
	{
	    try
	    {
		if ( this.adminSocket != null )
		{
		    this.adminSocket.close();
		}
	    }
	    catch( IOException e )
	    {
		e.printStackTrace();
	    }
	}

    }

    void shutdown()
    {
	this.isShuttingDown = true;

	try
	{
	    if ( this.adminSocket != null )
	    {
		this.adminSocket.close();
	    }
	}
	catch( IOException e )
	{
	    e.printStackTrace();
	}
    }
}
