package com.hammurapi.reasoning.impl;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.hammurapi.reasoning.Handle;

/**
 * Wrapper around Handle with consumed flag.
 * @author Pavel Vlasov
 */
class InferenceToken<F> {

	private Handle<F> handle;
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	
	InferenceToken(Handle<F> handle) {
		this.handle = handle;
	}
	
	Handle<F> getHandle() {
		return handle;		
	}
	
	private boolean consumed;
	
	boolean isConsumed() {
		lock.readLock().lock();
		try {
			return consumed;
		} finally {
			lock.readLock().unlock();
		}
	}
	
	/**
	 * Consumes token, so it cannot be used for further inference.
	 */
	void consume() {
		lock.writeLock().lock();
		try {
			consumed = true;
		} finally {
			lock.writeLock().unlock();
		}
	}
	
	private class PriorityEntry {
		int priority;
		int tasks;
//		List<Thread> waiters = new ArrayList<Thread>();
		Condition condition;
	}
	
	private List<PriorityEntry> priorityEntries = new ArrayList<PriorityEntry>();
	
	private Lock taskLock = new ReentrantLock();
		
	/**
	 * This method shall be invoked before execution of inference task.
	 * The method blocks, if there are tasks with higher priority working on the 
	 * token, until higher priority tasks finish execution.
	 * @param priority Rule priority.
	 * @throws InterruptedException 
	 * @throws IllegalArgumentException If this method is invoked with priority higher than
	 * priority of already running tasks.
	 */
	void taskCreated(int priority) throws InterruptedException {
		taskLock.lock();
		try {
			for (PriorityEntry pe: priorityEntries) {
				if (pe.tasks>0 && pe.priority<priority) {
					throw new IllegalArgumentException("Tasks with higher priority ("+priority+") shall be created before tasks with lower priority ("+pe.priority+")");
				}
			}
			
			boolean hasHigherPriority = false;
			PriorityEntry thisPriorityEntry = null;
			for (PriorityEntry pe: priorityEntries) {
				if (pe.tasks>0 && pe.priority>priority) {
					hasHigherPriority = true;
				}
				
				if (pe.priority==priority) {
					thisPriorityEntry = pe;
				}
			}
			
			if (thisPriorityEntry==null) {
				thisPriorityEntry = new PriorityEntry();
				thisPriorityEntry.condition = taskLock.newCondition();
				thisPriorityEntry.priority = priority;
				priorityEntries.add(thisPriorityEntry);
			}
			
			++thisPriorityEntry.tasks;
			
			if (hasHigherPriority) {
				thisPriorityEntry.condition.await();
			}
			
		} finally {
			taskLock.unlock();
		}		
	}
	
	/**
	 * This method shall be invoked when inference task finished working on the token.
	 * When all tasks with higher priority finish execution, tasks with lower priority
	 * are executed.
	 * @param priority Rule priority.
	 */
	void taskFinished(int priority) {
		taskLock.lock();
		try {
			Iterator<PriorityEntry> it = priorityEntries.iterator();
			while (it.hasNext()) {
				PriorityEntry pe = it.next();
				if (pe.priority==priority) {
					--pe.tasks;
					if (pe.tasks==0) {
						PriorityEntry candidate = null;
						for (PriorityEntry cpe: priorityEntries) {
							if (cpe.priority<pe.priority && (candidate==null || candidate.priority<cpe.priority)) {
								candidate = cpe;
							}
						}
						it.remove();
						if (candidate!=null) {
							candidate.condition.signalAll();
						}
					}
					break;
				}
			}			
		} finally {
			taskLock.unlock();
		}		
	}
	
	// TODO Remember properties and context so they can propagate though joins.
	
	@Override
	public String toString() {
		return getClass().getName()+" ("+getHandle()+", consumed="+isConsumed()+")";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + (consumed ? 1231 : 1237);
		result = prime * result + ((handle == null) ? 0 : handle.hashCode());
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		InferenceToken<F> other = (InferenceToken<F>) obj;
		if (consumed != other.consumed) {
			return false;
		}
		
		if (handle == null) {
			return other.handle == null;
		} 
		
		return handle.equals(other.handle);
	}
	
	
}
