package eu.dnetlib.contract.cp;

import org.apache.log4j.Logger;


import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

import eu.dnetlib.contract.conv.IComplexObectToStringConverter;
import eu.dnetlib.contract.cp.comp.ComparatorManager;
import eu.dnetlib.contract.cp.eval.module.IEvaluatorModule;
import eu.dnetlib.contract.event.EntryContractEvent;
import eu.dnetlib.contract.event.IContractEvent;
import eu.dnetlib.contract.node.EvaluationResult;
import eu.dnetlib.contract.node.EvaluationResult.Status;

/**
 * Entry parameters CheckPoint object.
 * Defines field values and rules that should be applied to the checkpoint validity evaluation.
 * 
 * @author mhorst
 *
 */
@XStreamAlias("EntryCheckPoint")
public class EntryCheckPoint extends AbstractCheckPoint 
	implements ICheckPoint<IContractEvent>, IComparatorInjectableCheckPoint, IConverterInjectableCheckPoint {
	
	@XStreamOmitField
	private static final Logger log = Logger.getLogger(EntryCheckPoint.class);
	
	/**
	 * Comparator manager.
	 * Should be set dynamically by CheckPointListImporter.
	 */
	@XStreamOmitField
	private ComparatorManager comparatorManager;
	
	/**
	 * Converts complex object to its proper string representation.
	 * Should be set dynamically by CheckPointListImporter. 
	 */
	@XStreamOmitField
	private IComplexObectToStringConverter converter;
	
	/**
	 * Entry arguments.
	 */
	private Object[] args;
	
	/**
	 * Array of flags describing strict ckecing of given argument.
	 * attrsStrictChecking size needs to be equal to the args size.
	 * 
	 */
	private boolean[] argsStrictChecking;


	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.ICheckPoint#getSupportedEventClass()
	 */
	public Class<? extends IContractEvent> getSupportedEventClass() {
		return EntryContractEvent.class;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.AbstractCheckPoint#check(eu.dnetlib.contract.event.IContractEvent)
	 */
	@SuppressWarnings("unchecked")
	@Override
	public EvaluationResult check(IContractEvent event) throws CheckPointEvaluationException {
		EvaluationResult superResult = super.check(event);
		if (superResult.getStatus()==Status.FAIL ||
				superResult.getStatus()==Status.FATAL) {
			return superResult;
		}
		if (this.getArgs()!=null && this.getArgs().length>0) {
			if (this.getArgsStrictChecking()==null ||
					this.getArgsStrictChecking().length!=this.getArgs().length) {
				String message = "Invalid attrsStrictChecking array value!";
				log.error(message);
				return new EvaluationResult(Status.FATAL, this, event, message);
			}
			
			if (event.getJoinPoint().getArgs()==null || 
					event.getJoinPoint().getArgs().length!=this.getArgs().length) {
				String message = "Invalid joinPoint attrs count: " + 
					(event.getJoinPoint().getArgs()!=null?event.getJoinPoint().getArgs().length:"0") +
				", expected: " + this.getArgs().length;
				log.info(message);
				return new EvaluationResult(Status.FAIL, this, event, message);
			}
			StringBuffer warnMessages = new StringBuffer();
			for (int i=0; i<this.getArgsStrictChecking().length; i++) {
//				added support for IEvaluatorModules among attrs
				if (this.getArgs()[i] instanceof IEvaluatorModule) {
					EvaluationResult evalResult = ((IEvaluatorModule)this.getArgs()[i]).evaluate(
							event.getJoinPoint().getArgs()[i], event, this);
					switch (evalResult.getStatus()) {
					case OK: {
						log.debug("got OK attr status for evaluator module " +
								this.getArgs()[i].getClass().getName());
						break;
					}
					case WARN: {
						log.warn("got warn attr status when evaluating module " +
								this.getArgs()[i].getClass().getName() + ". Message: " + 
								evalResult.getMessage());
						if (evalResult.getMessage()!=null) {
							if (warnMessages.length()>0) {
								warnMessages.append("; ");
							}
							warnMessages.append(evalResult.getMessage());
						}
						break;
					}
					case FAIL: {
						log.warn("Evaluator module " + this.getArgs()[i].getClass().getName() + 
								" returned status: "+evalResult.getStatus() + 
								" interrupting evaluation chain");
						return evalResult;
					}
					case FATAL: {
						log.error("Evaluator module " + this.getArgs()[i].getClass().getName() + 
								" returned status: "+evalResult.getStatus() + 
								" interrupting evaluation chain");
						return evalResult;
					}
					default: {
						throw new CheckPointEvaluationException("unsupported result status:" + evalResult.getStatus() +
								"quitting...");
					}
					}
				} else if (this.getArgsStrictChecking()[i]) {
					if (!comparatorManager.equals(this.getArgs()[i], event.getJoinPoint().getArgs()[i])) {
						String message = "Invalid joinPoint attr value: " + converter.toString(event.getJoinPoint().getArgs()[i]) +
						" ["+(event.getJoinPoint().getArgs()[i]!=null?event.getJoinPoint().getArgs()[i].getClass().getName():"null")+"]" +
						", expected: " + converter.toString(this.getArgs()[i]) +
						" ["+(this.getArgs()[i]!=null?this.getArgs()[i].getClass().getName():"null")+"]";
						log.info(message);
						return new EvaluationResult(Status.FAIL, this, event, message);
					}
				}
			}
			if (warnMessages.length()>0) {
				return new EvaluationResult(Status.WARN, this, 
						event, warnMessages.toString());
			} else {
				return new EvaluationResult(Status.OK, this, event);
			}
		} else {
//				when no entry attrs provided
			if (event.getJoinPoint().getArgs()!=null ||
					event.getJoinPoint().getArgs().length>0) {
//				FIXME should we return true instead?
				String message = "No entry attributes provided in CheckPoint, got: " +
					event.getJoinPoint().getArgs().length + " in JoinPoint";
				log.info(message);
				return new EvaluationResult(Status.FAIL, this, event, message);
			} else {
				return new EvaluationResult(Status.OK, this, event);
			}
		}
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.ICheckPoint#identify()
	 */
	@Override
	public String identify() {
		StringBuffer strBuff = new StringBuffer("class: " + this.getClass().getName());
		String superIdentify = super.identify();
		if (superIdentify!=null && superIdentify.length()>0) {
			strBuff.append(ICheckPoint.IDENTIFY_DELIMITER);
			strBuff.append(superIdentify);
		}
		if (strBuff.length()>0) {
			strBuff.append(ICheckPoint.IDENTIFY_DELIMITER);
		}
		strBuff.append("attrs: ");
		if (args!=null) {
			int count = 0;
			for (Object attr : args) {
				if (count>0) {
					strBuff.append(", ");
				}
				strBuff.append(attr);
				count++;
			}
		} else {
			strBuff.append("null");
		}
		return strBuff.toString();
	}
	
	/**
	 * Returns entry arguments.
	 * @return entry arguments
	 */
	public Object[] getArgs() {
		return args;
	}

	/**
	 * Sets entry arguments.
	 * @param args
	 */
	public void setArgs(Object[] args) {
//		currently setting strict checking for all parameters to true by default
		setAttrsWithStrictFlag(args, true);
	}

	/**
	 * Sets entry attributes.
	 * @param attrs
	 */
	public void setAttrsWithStrictFlag(Object[] attrs, boolean flag) {
		this.args = attrs;
		if (attrs!=null) {
			argsStrictChecking = new boolean[attrs.length];
			for (int i=0; i < argsStrictChecking.length; i++) {
				argsStrictChecking[i] = flag;
			}
		}
	}
	
	/**
	 * Returns strict checking flags.
	 * @return strict checking flags
	 */
	public boolean[] getArgsStrictChecking() {
		return argsStrictChecking;
	}

	/**
	 * Sets strict checking flags.
	 * @param argsStrictChecking
	 */
	public void setArgsStrictChecking(boolean[] argsStrictChecking) {
		this.argsStrictChecking = argsStrictChecking;
	}
	
	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.IComparatorInjectableCheckPoint#getComparatorManager()
	 */
	public ComparatorManager getComparatorManager() {
		return comparatorManager;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.IComparatorInjectableCheckPoint#setComparatorManager(eu.dnetlib.contract.cp.comp.ComparatorManager)
	 */
	public void setComparatorManager(ComparatorManager comparatorManager) {
		this.comparatorManager = comparatorManager;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.IConverterInjectableCheckPoint#getConverter()
	 */
	public IComplexObectToStringConverter getConverter() {
		return converter;
	}

	/* (non-Javadoc)
	 * @see eu.dnetlib.contract.cp.IConverterInjectableCheckPoint#setConverter(eu.dnetlib.contract.conv.IComplexObectToStringConverter)
	 */
	public void setConverter(IComplexObectToStringConverter converter) {
		this.converter = converter;
	}
	
}
