import java.io.PrintWriter;
import java.io.FileWriter;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.ServerSocket;
import java.util.Date;


public class tbServer {
    private static final int        maxque = 1;     // but we allow one more person to wait
    private static final int        port   = 8051;  // listen to this port

    private static ServerSocket     SERV=null;
            static tbRobotInterface SEND=null;

    public static void main(String A[]) {
        try {
            // starting a network server
            SERV= new ServerSocket(port, maxque);

            // starting a command processor
            SEND=new tbRobot();
            SEND.start();

            System.out.println("TELEBOT SERVICE STARTED!");
            Log("server started");
            // server main loop
            while (true) {
                // The variable with thread is created
                // when a new connection is made
                new tbConnection(SERV.accept());
            }
        } catch (Exception e) {
            System.out.println("ERROR: (server) "+e);
        }
        System.out.println("TELEBOT SERVICE STOPPED!");
        SEND.stop();
        Log("server stopped");
    }

    public static void terminate(int level) {
        try {
            SERV.close();
        } catch (Exception e) { 
            System.out.println("SHUTDOWN: (server) "+e);
        }
        SEND.stop();
        System.out.println("TELEBOT SERVICE TERMINATED!");
        Log("server terminated");
        System.exit(level);
    }

    public synchronized static void Log(String memo) {
        try {
            PrintWriter LOG=new PrintWriter(new FileWriter("tbLOG.txt", true));
            LOG.println((new Date()).toString()+" : "+memo);
            LOG.flush();
            LOG.close();
        } catch (Exception e) {
            System.out.println("ERROR: (log file) "+e);
        }
    }
}



class tbConnection implements Runnable {

    // array of connected clients
    private static final int        maxcon = 10;
    private static tbConnection     CLI[]=new tbConnection[maxcon+1];
    static {
        int c; for (c=0; c<=maxcon; c++) CLI[c]=null;
    }

    // Send message back to everybody
    private static void SendAll(String msg) {
        int c;
        for (c=1; c<=maxcon; c++)
            if (CLI[c]!=null)
                CLI[c].Send(msg);
    }

    private int             CO=0;
    private Socket          SO=null;
    private BufferedReader  SR=null;
    private PrintWriter     SW=null;
    private String          KTO=""; // client's address

    tbConnection(Socket client) {
        SO=client;
        // searching for an empty entry
        for (CO=1; CO<=maxcon; CO++) if (CLI[CO]==null) break;
        if (CO>maxcon) CO=0; else CLI[CO]=this;
        (new Thread(this, "Serving a Client")).start();
    }

    // Sends message back to current user
    private void Send(String msg) {
        synchronized (SW) {
            try {
                SW.println(msg);
                SW.flush();
            } catch (Exception e) {
                try {
                    SO.close();
                } catch (Exception f) {}
            }
        }
    }
    
    // Interpret Server commands
    private void CO_Server(String cmndline) {
             if (cmndline.equalsIgnoreCase("/HELP"))    CS_Help();
        else if (cmndline.equals("/DATE"))              CS_Date();
        else if (cmndline.startsWith("/ECHO "))         CS_Echo(cmndline);
        else if (cmndline.equals("/EXIT"))              CS_Exit();
        else if (cmndline.equals("/KICK"))              CS_Kick("");
        else if (cmndline.startsWith("/KICK "))         CS_Kick(cmndline);
        else if (cmndline.equals("/KILL for good"))     CS_Kill();
        else if (cmndline.equals("/PING"))              CS_Ping();
        else if (cmndline.equals("/QUIT"))              CS_Exit();
        else if (cmndline.equals("/REST"))              CS_Restart();
        else if (cmndline.equals("/WHO"))               CS_Who();
        else Send("-ERR unrecognized command, type /HELP");
    }

    // Echoes back the current date and time
    private void CS_Date() {
        Send("+DATE "+(new Date()).toString());
    }

    // Echoes a message to all users
    private void CS_Echo(String echo) {
        Send("+OK echo");
        SendAll(CO+">>"+echo.substring(6));
    }

    // Exits the current user
    private void CS_Exit() {
        Send("+OK exit");
        SendAll(CO+"> So long!");
        try {
            SO.close();
        } catch (Exception e) {}
        // an exception will terminate this connection handle
    }

    // Kickes out a specified user
    private void CS_Kick(String cmnd) {
        if (cmnd.equals("")) {
            Send("-ERR Kick who?");
        } else {
            Send("+OK kick");
            SendAll(CO+"> Kick user #"+cmnd.substring(6));
        }
    }

    // Quits current robot server with ERRORLEVEL 10
    private void CS_Kill() {
        tbServer.Log("server killed    by "+KTO);
        System.out.println("killed by "+KTO);
        Send("+OK kill");
        SendAll("SERVER WILL BE SHUT DOWN");
        tbServer.terminate(10);
    }

    // pings back the individual user
    private void CS_Ping() {
        Send("+OK PONG");
    }

    // prints short help file
    private void CS_Help() {
        Send(   "HELP:\n\r"+
                "Available server commands:\n\r"+
                "/DATE /ECHO /EXIT /HELP /KICK /KILL /PING /QUIT /REST /WHO\n\r\n\r"+
                (tbServer.SEND).help()
        );
        Send("+DONE help");
    }

    // Quits current robot server (restarts if looped batch)
    private void CS_Restart() {
        tbServer.Log("reset requested  by "+KTO);
        System.out.println("restarting by "+KTO);
        Send("+OK restart");
        SendAll("SERVER RESTARTING\n\rPLEASE RECONNECT SOON");
        tbServer.terminate(0);
    }

    // List all current users
    private void CS_Who() {
        int c;
        for (c=1; c<=maxcon; c++)
            if (CLI[c]!=null)
                Send(c+"="+CLI[c].KTO);
        Send("+DONE who");
    }

    // Pass the command to the robot
    private void CO_Robot(String cmndline) {
        Send((tbServer.SEND).exec(cmndline));
        SendAll(CO+"> "+cmndline);
    }

    // Send service rejection notice
    private void CU_Rej() {
        tbServer.Log("rejected   (+) from "+KTO);
        System.out.println("rejected   (+) from "+KTO);
        Send("-ERR server reached its capacity");
        SendAll("# Server reached its capacity");
        Send("# MAX NUMBER OF CLIENTS REACHED");
        Send("# PLEASE TRY AGAIN LATER, SORRY");
    }

    private void CU_Bye() {
        int c;
        for (c=1; c<=maxcon; c++)
            if (CLI[c]!=null) return;
        (tbServer.SEND).reset();
    }

    public void run() {
        try {
            // storing the client's IP address
            KTO=(SO.getInetAddress()).toString();

            // create a stream for reading via socket connection
            SR=new BufferedReader(new InputStreamReader(SO.getInputStream()));
            // create a stream for writing via socket connection
            SW=new PrintWriter (SO.getOutputStream());

            Send("+OK connected");
            SendAll(CO+"> Welcome all!");
            if (CO>0) {
                tbServer.Log("connected  ("+CO+") from "+KTO);
                System.out.println("connected  ("+CO+") from "+KTO);
                while (true) {
                    String line=SR.readLine();
                    if (line==null) break;
//                      System.out.println("COMMAND: "+line);
                    if (line.startsWith("/"))       CO_Server(line);
                    else                            CO_Robot(line);
                }
            } else CU_Rej();
            SO.close();
        } catch (Exception e) {
            System.out.println("ERROR: (connection) "+e);
            tbServer.Log("broken con ("+CO+") from "+KTO+" because of "+e);
        }
        tbServer.Log("disconnect ("+CO+") from "+KTO);
        System.out.println("disconnect ("+CO+") from "+KTO);
        CLI[CO]=null;
        SendAll(CO+"> Bye bye!");
        CU_Bye();
    }

}