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

/**
 *
 * Client/Server with object streams. The server
 * is a thread that accepts requests from clients.
 * Each request creates a new Threaded handler to
 * service the client --- this means there's one
 * thread per client on the server side, though all
 * handler threads share some common resources such
 * as the database of information.
 * 
 *@author Owen Astrachan
 *
 */


public class EchoServer extends Thread
{
    private int myRequestCount;             // # requests handled
    private ServerSocket myServerSocket;    // client request source
    
    private static Database ourData = new Database();
    
    public EchoServer(int port) 
    {
        myRequestCount = 0;
        try{
            myServerSocket = new ServerSocket(port);
            Debug.println("server started");        
        }
        catch(Exception e){
            Debug.println("Server error "+e);
        }       
    }

    public void run()
    {
        while (true) {

            try {
                // accept blocks until request comes over socket
                Socket intoServer = myServerSocket.accept();
            
                Thread t = new Handler(intoServer,ourData);
                t.start();
                Debug.println("Serviced client "+myRequestCount);
                myRequestCount++;
            }
            catch (Exception e){
                Debug.println("trouble with server socket "+e);
            }
        }       
    }
    
    public static void main(String args[])
    {
        EchoServer server = new EchoServer(Globals.PORT);
        server.start();
    }    
}

/**
 * The Handler class interacts with clients and process
 * commands. In this version command processing is decidedly
 * non-OO, but rather uses a chain of if/else statements to
 * decide what action to take
 */

class Handler extends Thread
{
    private Socket mySocket;
    private Database myData;
    private static int ourTotal = 0;

    private ObjectInputStream myInput;
    private ObjectOutputStream myOutput;
    
    /**
     * Construct an echo handler.
     * @param sock is the socket for the connection
     * @param db is where data is stored for this handler
     */
    
    public Handler(Socket sock, Database db)
    {
        mySocket = sock;
        myData = db;
    }

    public void run()
    {
        // order of opening input and output here must be
        // different from order at Client side or potential
        // deadlock results. Here we use input/ouput, client
        // therefore must open output/input

        try{
            myInput = new ObjectInputStream(mySocket.getInputStream());
            myOutput = new ObjectOutputStream(mySocket.getOutputStream());
        }
        catch (Exception e){
            Debug.println("Server error on streams "+e);
            return;
        }
        Debug.println("handler loop starting");
        
        while (true) {
            
            String s = null;
            try{
                s = (String) myInput.readObject();
            }
            catch (IOException ioe){
                Debug.println("error server read, shut down: "+ioe);
                break;
            }
            catch (ClassNotFoundException cnfe){
                Debug.println("error class read, shut down "+cnfe);
                break;
            }
            
            Debug.println("Serviced Request "+ourTotal+" on "+s);
            ourTotal++;         

            if (s == null) {
                Debug.println("null string error on server");
                break;
            }
            else if (doRequest(s)){
                break; // out of loop
            }
        }
        try{
            myInput.close();
            myOutput.close();
        }
        catch (IOException e){
            Debug.println("error closing object streams "+e);
            e.printStackTrace();
        }
    }

    /**
     * Handles request and returns true if we should quit
     * otherwise returns false
     */
    public boolean doRequest(String s)
    {
        if (s.equals(Globals.PUT)){
            String key = null;
            Person p = null;
            try{
                key = (String) myInput.readObject();
                p = (Person) myInput.readObject();
                myData.put(key,p);                  
            }
            catch (Exception e){
                Debug.println("error reading key/value "+e);
                e.printStackTrace();
            }
            return false;
        }
        else if (s.equals(Globals.GET)) {
            String key = null;
            try{
                key = (String) myInput.readObject();                
            }
            catch (Exception e){
                Debug.println("error reading key/value "+e);
                e.printStackTrace();
            }
            try{
                myOutput.writeObject(myData.get(key));              
            }
            catch (IOException e){
                Debug.println("error writing value "+e);
                e.printStackTrace();
            }
            return false;
        }
        else if (s.equals(Globals.QUIT)) {
            return true;
        }
        else {
            Debug.println("unknown command " + s);
            return false;
        }       
    }
}
