package org.ggp.base.player.proxy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import org.ggp.base.player.event.PlayerDroppedPacketEvent;
import org.ggp.base.player.event.PlayerReceivedMessageEvent;
import org.ggp.base.player.event.PlayerSentMessageEvent;
import org.ggp.base.player.gamer.Gamer;
import org.ggp.base.player.gamer.statemachine.random.RandomGamer;
import org.ggp.base.player.request.factory.RequestFactory;
import org.ggp.base.player.request.grammar.AbortRequest;
import org.ggp.base.player.request.grammar.Request;
import org.ggp.base.player.request.grammar.StartRequest;
import org.ggp.base.player.request.grammar.StopRequest;
import org.ggp.base.util.logging.GamerLogger;
import org.ggp.base.util.observer.Event;
import org.ggp.base.util.observer.Observer;
import org.ggp.base.util.observer.Subject;
import org.ggp.base.util.reflection.ProjectSearcher;

import com.google.common.collect.Lists;

public final class ProxyGamePlayerClient extends Thread implements Subject, Observer
{
    private final Gamer gamer;
    private final List<Observer> observers;

    private Socket theConnection;
    private BufferedReader theInput;
    private PrintStream theOutput;

    /**
     * @param args
     * Command line arguments:
     *  ProxyGamePlayerClient gamer port
     */
    public static void main(String[] args) {
        GamerLogger.setSpilloverLogfile("spilloverLog");
        GamerLogger.log("Proxy", "Starting the ProxyGamePlayerClient program.");

        if (!(args.length == 2)) {
            GamerLogger.logError("Proxy", "Usage is: \n\tProxyGamePlayerClient gamer port");
            return;
        }

        int port = 9147;
        Gamer gamer = null;
        try {
            port = Integer.valueOf(args[1]);
        } catch(Exception e) {
            GamerLogger.logError("Proxy", args[1]+" is not a valid port.");
            return;
        }

        List<Class<? extends Gamer>> gamers = Lists.newArrayList(ProjectSearcher.GAMERS.getConcreteClasses());
        List<String> gamerNames = new ArrayList<String>();
        if(gamerNames.size()!=gamers.size())
        {
            for(Class<?> c : gamers)
                gamerNames.add(c.getName().replaceAll("^.*\\.",""));
        }

        int idx = gamerNames.indexOf(args[0]);
        if (idx == -1) {
            GamerLogger.logError("Proxy", args[0] + " is not a subclass of gamer.  Valid options are:");
            for(String s : gamerNames)
                GamerLogger.logError("Proxy", "\t"+s);
            return;
        }

        try {
            gamer = gamers.get(idx).newInstance();
        } catch(Exception ex) {
            GamerLogger.logError("Proxy", "Cannot create instance of " + args[0]);
            return;
        }

        try {
            ProxyGamePlayerClient theClient = new ProxyGamePlayerClient(port, gamer);
            theClient.start();
        } catch (IOException e) {
            GamerLogger.logStackTrace("Proxy", e);
        }
    }

    public ProxyGamePlayerClient(int port, Gamer gamer) throws IOException
    {
        observers = new ArrayList<Observer>();

        theConnection = new Socket("127.0.0.1", port);
        theOutput = new PrintStream(theConnection.getOutputStream());
        theInput = new BufferedReader(new InputStreamReader(theConnection.getInputStream()));

        this.gamer = gamer;
        gamer.addObserver(this);
    }

    @Override
    public void addObserver(Observer observer)
    {
        observers.add(observer);
    }

    @Override
    public void notifyObservers(Event event)
    {
        for (Observer observer : observers)
        {
            observer.observe(event);
        }
    }

    private long theCode;

    @Override
    public void run()
    {
        while (!isInterrupted())
        {
            try
            {
                ProxyMessage theMessage = ProxyMessage.readFrom(theInput);
                GamerLogger.log("Proxy", "[ProxyClient] Got message: " + theMessage);
                String in = theMessage.theMessage;
                theCode = theMessage.messageCode;
                long receptionTime = theMessage.receptionTime;
                notifyObservers(new PlayerReceivedMessageEvent(in));

                Request request = new RequestFactory().create(gamer, in);
                if(request instanceof StartRequest) {
                    RandomGamer theDefaultGamer = new RandomGamer();
                    new RequestFactory().create(theDefaultGamer, in).process(1);
                    GamerLogger.startFileLogging(theDefaultGamer.getMatch(), theDefaultGamer.getRoleName().toString());
                    GamerLogger.log("Proxy", "[ProxyClient] Got message: " + theMessage);
                }
                String out = request.process(receptionTime);

                ProxyMessage outMessage = new ProxyMessage("DONE:" + out, theCode, 0L);
                outMessage.writeTo(theOutput);
                GamerLogger.log("Proxy", "[ProxyClient] Sent message: " + outMessage);
                notifyObservers(new PlayerSentMessageEvent(out));

                if(request instanceof StopRequest) {
                    GamerLogger.log("Proxy", "[ProxyClient] Got stop request, shutting down.");
                    System.exit(0);
                }
                if(request instanceof AbortRequest) {
                    GamerLogger.log("Proxy", "[ProxyClient] Got abort request, shutting down.");
                    System.exit(0);
                }
            }
            catch (Exception e)
            {
                GamerLogger.logStackTrace("Proxy", e);
                notifyObservers(new PlayerDroppedPacketEvent());
            }
        }

        GamerLogger.log("Proxy", "[ProxyClient] Got interrupted, shutting down.");
    }

    @Override
    public void observe(Event event) {
        if(event instanceof WorkingResponseSelectedEvent) {
            WorkingResponseSelectedEvent theWorking = (WorkingResponseSelectedEvent)event;
            ProxyMessage theMessage = new ProxyMessage("WORK:" + theWorking.getWorkingResponse(), theCode, 0L);
            theMessage.writeTo(theOutput);
            GamerLogger.log("Proxy", "[ProxyClient] Sent message: " + theMessage);
        }
    }
}
