package org.wikiwebserver.distribute.server;

import java.text.DateFormat;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.wikiwebserver.distribute.interfaces.Task;
import org.wikiwebserver.distribute.interfaces.WorkerNode;

public class RemoteWorkerNode implements WorkerNode {
	
	public static final int TASK_EXPIRE_DELAY = 20000;
	public static final int SYSTEM_TIMER_LATENCY = 250;
	public static final int MAX_WORKER_CHECK_IN_DELAY = 60 * 1000;
	
	private String nodeId;
	private int taskId = 0;
	private String configId;
	
	private List<TaskStub> stubs = new LinkedList<TaskStub>();
	
	private final static int[] delayPattern = { 
           0, 100, 500,
           1000, 1000, 1000, 1000,       // max delay of 1 sec if idle < 4 secs  
           2000, 2000, 2000,  		     // max delay of 2 sec if idle < 10 secs  
           4000, 4000, 4000, 4000, 4000, // max delay of 4 sec if idle < 30 secs 
           10000, 10000, 10000,          // max delay of 10 sec if idle < 1 minute
           20000, 20000, 20000, 	
           20000, 20000, 20000,            
           20000, 20000, 20000,                
           20000, 20000, 20000,          // max delay of 20 sec if idle < 5 minutes           
           60000                         // max delay of 1 minute if idle > 5 minutes
	};

	private volatile int delayPatternIdx;
    private volatile long expectedTaskCheckTime;	
	
	public RemoteWorkerNode(String nodeId) {
		this.nodeId = nodeId;
	}
	
	public String getNodeId() {
		return this.nodeId;
	}

	public void setConfigurationId(String configId) {
	    if (!configId.equals(this.configId)) {
    		this.configId = configId;
    		// A new configuration invalidates pending tasks
    		stubs = new LinkedList<TaskStub>();
            reduceWaitTime();    		
	    }
	}	
	
	public String getConfigurationId() {
		return this.configId;
	}	
	
	public synchronized int getWaitTime() throws NodeOfflineException {
			
		long time = System.currentTimeMillis();			
		if (time > expectedTaskCheckTime + MAX_WORKER_CHECK_IN_DELAY) {
			DateFormat df = DateFormat.getInstance();
			String dateString = df.format(expectedTaskCheckTime);
			throw new NodeOfflineException("Node communication expected at: " + dateString);
		}
		int waitTime = (int) (expectedTaskCheckTime - time);
		if (waitTime < 0) waitTime = 0;
		return waitTime;
	}	
	
    public synchronized void reduceWaitTime() {
        delayPatternIdx = 0;        
    }	
	
	/**
	 * @param taskStub The task the remote node is required to run.
	 * 
	 * @return The number of milliseconds until the task will be started.
	 */
	public synchronized TaskStub addNewTask(Task task) throws NodeOfflineException {
		
		long time = System.currentTimeMillis();	
		long delay = getWaitTime();
		if (task.getExpireTime() == 0) {
			task.setExpireTime(time + delay + TASK_EXPIRE_DELAY);
		}		
		
		TaskStub stub = new TaskStub(task);
		stub.setTaskId(nextTaskId());
		
		stubs.add(stub);
		
		return stub;
	}
	
	public TaskStub addNewTaskAndWait(Task task) throws NodeCommunicationException, Exception {
	    
        long time = System.currentTimeMillis();
        long expireTime = task.getExpireTime();
	    if (expireTime != 0 && time > expireTime) {
	        throw new NodeCommunicationException("Task expired before it was executed");
	    }
		
		TaskStub stub = addNewTask(task);
		
		// Wait for the task to complete
		synchronized (stub) {
		    try {
		        int timeout = (int) (task.getExpireTime() - time);
		        stub.wait(timeout);
		    } catch (InterruptedException ex) {
		        throw new NodeCommunicationException("Waiting for task was interrupted");
		    }
		}
		
		if (stub.getOutput() == null) {
			throw new NodeCommunicationException("Task expired or did not return output");
		}
		
        if (stub.getOutput() instanceof Exception) {
            throw (Exception) stub.getOutput();
        }		
		
		return stub;
	}	
	
	public synchronized TaskStub getTaskStub(String taskId) {
		for (TaskStub stub : stubs) {
			if (stub.getTaskId().equals(taskId)) return stub;
		}
		return null;
	}
	
	public synchronized int updateTaskCheckDelay() {
	    
        if (delayPatternIdx + 1 < delayPattern.length) {
            delayPatternIdx++;
        }
        
        int delay = delayPattern[delayPatternIdx];    
        
        expectedTaskCheckTime = System.currentTimeMillis() + delay;
        
        return delay;    
    }
	
	public synchronized TaskStub getNextTaskStubForStarting() {
	
		long time = System.currentTimeMillis();		
		
		Iterator<TaskStub> taskIterator = stubs.iterator();
		while (taskIterator.hasNext()) {
		    TaskStub stub = taskIterator.next();
			if (!stub.isIssued()) {
				if (time > stub.getExpireTime()) {
					taskIterator.remove(); // Expired
				} else {
					stub.setIssued(true);
					// Expect another task soon
					reduceWaitTime();				
					return stub;
				}
			}
		}

		return null;
	}
	
	private synchronized String nextTaskId() {
		return String.valueOf(taskId++);
	}
}

