package com.hammurapi.reasoning.impl;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;

import com.hammurapi.reasoning.Derivation;
import com.hammurapi.reasoning.Handle;
import com.hammurapi.reasoning.ReasoningException;

class DerivationImpl<F> implements Derivation<F> {
	
	private Method method;
	private KnowledgeBase<F> knowledgeBase;	
	private List<Handle<F>> handleList;		
	private String ruleDescription;
	private Object rule;
	private String ruleName;

	DerivationImpl(Object rule, String ruleName, String ruleDescription, Method method, List<Handle<F>> handleList, KnowledgeBase<F> knowledgeBase) {
		this.rule = rule;
		this.ruleName = ruleName;
		this.ruleDescription = ruleDescription;
		this.method = method;
		this.handleList = handleList;
		this.knowledgeBase = knowledgeBase;
	}

	@Override
	public Method getInferenceMethod() {
		return method;
	}

	@Override
	public List<Handle<F>> getInputHandles() {
		return handleList;
	}

	@Override
	public List<F> getInputs() {
		List<F> ret = new ArrayList<F>();
		for (Handle<F> h: handleList) {
			try {
				ret.add(knowledgeBase.get(h));
			} catch (ReasoningException e) {
				throw new RuntimeReasoningException(e);
			}
		}
		return ret;
	}

	@Override
	public String getRuleDescription() {
		return ruleDescription;
	}

	@Override
	public Object getRuleInstance() {
		return rule;
	}

	@Override
	public String getRuleName() {
		return ruleName;
	}

	@Override
	public boolean isDerivedFrom(F fact) {
//		Superseder superseder = ConvertingService.convert(fact, Superseder.class);
		for (Handle<F> h: handleList) {
			HandleImpl<F> hImpl = (HandleImpl<F>) h;
			F sFact = hImpl.get();
			if (sFact!=null) {
				if (sFact.equals(fact)) {
					return true;
				}	
				
//Commented out - shall be controlled by a config parameter							
//				// Try supersession
//				if (superseder!=null) {
//					if (superseder.supersedes(sFact)) {
//						// This fact supersedes source face, so we can assume that 
//						// derivation from subfact is also true for superfact
//						return true;
//					}
//				}
				
				// Go through derivations.
				Lock lock = hImpl.derivationsLock.readLock();
				lock.lock();
				try {
					for (Derivation<F> d: hImpl.derivations) {
						if (d.isDerivedFrom(fact)) {
							return true;
						}
					}
				} finally {
					lock.unlock();
				}
			}
		}
		
		return false;
	}

	@Override
	public boolean isValid() {
		// All input handles return not null.
		for (Handle<F> h: handleList) {
			if (((HandleImpl<?>) h).get()==null) {
				return false;
			}
		}
		return true;
	}
	
	@Override
	public String toString() {
		return toString("  ", -1);
	}

	String toString(String indent, int derivationNumber) {
		StringBuilder ret = new StringBuilder();
		ret.append(indent);
		if (derivationNumber!=-1) {
			ret.append(derivationNumber+": ");
		}
		ret.append("Derivation "+Integer.toString(System.identityHashCode(this), Character.MAX_RADIX)+" (depth="+getDepth()+", cardinality="+getCardinality()+") "+getInferenceMethod());
		for (Handle<F> h: getInputHandles()) {
			try {
				ret.append(Constants.LINE_SEPARATOR+indent+"  "+knowledgeBase.get(h));
				int dc = 0;
				for (Derivation<F> d: knowledgeBase.getDerivations(h)) {
					if (d instanceof DerivationImpl) {
						ret.append(Constants.LINE_SEPARATOR+((DerivationImpl<F>) d).toString(indent+"    ", ++dc));
					} else {
						ret.append(Constants.LINE_SEPARATOR+indent+"    "+d);
					}
				}
			} catch (ReasoningException e) {
				ret.append(Constants.LINE_SEPARATOR+indent+"  Exception: "+e);
			}
		}
		return ret.toString();
	}

			
	private Integer depth;

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result	+ ((handleList == null) ? 0 : handleList.hashCode());
		result = prime * result	+ ((knowledgeBase == null) ? 0 : knowledgeBase.hashCode());
		result = prime * result + ((method == null) ? 0 : method.hashCode());
		result = prime * result + ((rule == null) ? 0 : rule.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		DerivationImpl<F> other = (DerivationImpl<F>) obj;
		if (handleList == null) {
			if (other.handleList != null) {
				return false;
			}
		} else if (!handleList.equals(other.handleList)) {
			return false;
		}
		if (knowledgeBase == null) {
			if (other.knowledgeBase != null) {
				return false;
			}
		} else if (!knowledgeBase.equals(other.knowledgeBase)) {
			return false;
		}
		if (method == null) {
			if (other.method != null) {
				return false;
			}
		} else if (!method.equals(other.method)) {
			return false;
		}
		if (rule == null) {
			if (other.rule != null) {
				return false;
			}
		} else if (!rule.equals(other.rule)) {
			return false;
		}
		// Deep comparison
//		Iterator<Handle<F>> it = handleList.iterator();
//		Iterator<Handle<F>> otherIt = other.handleList.iterator();
//		while (it.hasNext()) {
//			try {
//				Collection<Derivation<F>> myDerivations = knowledgeBase.getDerivations(it.next());
//				Collection<Derivation<F>> otherDerivations = knowledgeBase.getDerivations(otherIt.next());
//				Iterator<Derivation<F>> dit = myDerivations.iterator();
//				Iterator<Derivation<F>> otherDit = otherDerivations.iterator();
//				while (dit.hasNext()) {
//					Derivation<F> myD = dit.next();
//					Derivation<F> otherD = otherDit.next();
//					if (!myD.equals(otherD)) {
//						return false;
//					}
//				}
//			} catch (ReasoningException e) {
//				logger.log(Level.WARNING, "Could not deep compare derivations: "+e, e);
//			}
//		}
		return true;
	}

	@Override
	public synchronized int getDepth() {
		if (depth==null) {
			depth = 0;
			for (Handle<F> h: getInputHandles()) {
				try {
					for (Derivation<F> d: knowledgeBase.getDerivations(h)) {
						int dd = d.getDepth();
						if (dd>depth) {
							depth = dd;
						}
					}
				} catch (ReasoningException e) {
					throw new RuntimeReasoningException(e);
				}
			}
			++depth;
		} 
		return depth;
	}

	private Integer cardinality;

	@Override
	public synchronized int getCardinality() {
		if (cardinality==null) {
			cardinality = 1;
			for (Handle<F> h: getInputHandles()) {
				try {
					for (Derivation<F> d: knowledgeBase.getDerivations(h)) {
						cardinality+=d.getCardinality();
					}
				} catch (ReasoningException e) {
					throw new RuntimeReasoningException(e);
				}
			}
		} 
		return cardinality;
	}				
					
} 	
