package fireTester.communicator.server;

import java.util.ArrayList;

import org.mr.api.blocks.ScalableDispatcher;
import org.mr.api.blocks.ScalableFactory;
import org.mr.api.blocks.ScalableHandler;
import org.mr.api.blocks.ScalableStage;

import fireTester.interfaces.ResultsObserver;
import fireTester.interfaces.ServerController;
import fireTester.interfaces.SubmissionTest;
import fireTester.messages.AcknowledgeMessage;
import fireTester.messages.KeepAliveMessage;
import fireTester.messages.TestUnitResults;
import fireTester.messages.TesterException;



/**
 * This class keeps tabs on the clients and handles all communication for the server.
 */
public class Server implements ScalableHandler, ServerController {	
	private ArrayList observers;
	
	/**
	 * Stores a copy of every test currently in queue or currently
	 * being processed by a client.
	 */
	public ArrayList pendingSubmissionCache;
	
	/**
	 * SubmissionTests that are resubmits of submissions still in progress.
	 * They are queued up when the first submit completes.
	 */
	public ArrayList waitingSubmissions;
	
	/**
	 *  the out going stage, queues messages for first avaiable client
	 */
	private ScalableStage clientStage;
	
	/**
	 *  the inbound dispatcher, receives messages from clients
	 */
	private ScalableDispatcher serverDispatcher;
	
	/**
	 * Init network.
	 */
	public Server() {
		this.observers = new ArrayList();
		this.pendingSubmissionCache = new ArrayList();
		this.waitingSubmissions = new ArrayList();
		new ServerTimeoutMonitor(this);
		////////////////// FREEZES ON INTEGRATION HERE
		this.clientStage = ScalableFactory.getStage("FireByDragonsoft_Clients", true);
		this.serverDispatcher = ScalableFactory.getDispatcher("FireByDragonsoft_Server", true);
		this.serverDispatcher.addHandler(this);
	}
	
	/**
	 * A non-blocking call to queue a SubmissionTest for execution.
	 * 
	 * @param t the test to forward to the first avaliable client.
	 */
	public void queueTest(SubmissionTest t) {
		assert t != null;
		

		CachedSubmission cache = new CachedSubmission(t);
		synchronized(this.pendingSubmissionCache) {
			for(int i = 0; i < this.pendingSubmissionCache.size(); i++) {
				CachedSubmission checkSub = (CachedSubmission) this.pendingSubmissionCache.get(i);
				if(checkSub.submission.get_submission().getActivityID().equals(t.get_submission().getActivityID())
						&& checkSub.submission.get_submission().getStudentUsername().equals(t.get_submission().getStudentUsername()) ) {
					if(!checkSub.submission.get_submission().getSubmissionID().equals(t.get_submission().getSubmissionID())) {
						// hold submission till first one completes or timesout.
						for(int wi = 0; wi < this.waitingSubmissions.size(); wi++) {
							SubmissionTest waitTest = (SubmissionTest)this.waitingSubmissions.get(wi);
							if(waitTest.get_submission().getActivityID().equals(t.get_submission().getActivityID())
									&& waitTest.get_submission().getStudentUsername().equals(t.get_submission().getStudentUsername())) {
								// Remove any queued submissions for same activity by same student
								// this ensures at most 1 in queue
								this.waitingSubmissions.remove(wi);
							}
						}
						this.waitingSubmissions.add(t);
						System.out.println(t.get_submission().toString() + " waitlisted.");
						return;
					} else {
						// ignore request to queue, submission already in queue
						System.out.println(t.get_submission().toString() + " dropped.");
						return;
					}
				} 
			}

			// new submission, queue it up.
			this.pendingSubmissionCache.add(cache);
			this.clientStage.queue(t);
			System.out.println(t.get_submission().toString() + " queued.");
		}
	}
	
	/**
	 * This is an implementation method of ScalableHandler interface
	 * Called by the dispatcher when an event (TestUnitResults) was
	 * received from a client. 
	 * 
	 * May be a TesterException in the event of an error.
	 * 
	 * @param event KeepAliveMessage | AcknowledgeMessage | TestUnitResults | TesterException
	 */
	public void handle(Object event) {
		assert event != null;

    	KeepAliveMessage alive = null;
    	AcknowledgeMessage ack = null;
    	TestUnitResults r = null;
    	TesterException e = null;
    	
    	if(event instanceof KeepAliveMessage) {
    		alive = (KeepAliveMessage)event;
    	}
    	else if(event instanceof AcknowledgeMessage) {
    		ack = (AcknowledgeMessage)event;
    	}
    	else if(event instanceof TestUnitResults) {
    		r = (TestUnitResults)event;
    	}
    	else if(event instanceof TesterException) {
    		e = (TesterException)event;
    	} else {
    		System.err.println("ERROR: Unknown command! (ignored) " + e.toString());
    	}
    	
    	for(int i = 0; i < this.observers.size(); i++) {
    		ResultsObserver o = (ResultsObserver)this.observers.get(i);
    		if(alive != null)
    			o.receive_keep_alive(alive);
    		else if(ack != null)
    			o.receive_acknowledge(ack);
    		else if(r != null)
    			o.receive_unit_results(r);
    		else if(e != null)
    			o.receive_test_failed(e);
    	}
	}
	
	
	/**
	 * Register a new ResultsObserver.
	 * 
	 * @param o the object to receive results as they arrive.
	 */
	public void registerObserver(ResultsObserver o) {
		System.out.println(o.toString() + " is observing test results.");
		
		if(o != null)
			this.observers.add(o);
	}
	
	/**
	 * Stops sending results to the specified object.
	 * 
	 * @param o the object no longer interested in updates.
	 */
	public void detachObserver(ResultsObserver o) {
		System.out.println(o.toString() + " dropped");
		
		if(o != null)
			this.observers.remove(o);
	}
}
