package models.meta;

import java.util.Map;

import exceptions.IllegalTypeException;
import lombok.Getter;
import models.Dependency;
import models.DependencyTerm;
import models.EvaluatableTerm;
import models.RDLTerm;
import models.ResourceVariable;
import models.algebra.Symbol;
import models.algebra.Variable;

@Getter
public class MetaRDLTerm extends RDLTerm {

	protected static enum TermType {
		META_DEPENDENCY(Dependency.class),
		META_DEPENDENCY_LIST(Dependency.class),
		META_DEPENDENCY_VARIABLE(Dependency.class),
		META_DEPENDENCY_TERM(DependencyTerm.class),
		META_DEPENDENCY_TERM_VARIABLE(DependencyTerm.class),
		META_EVALUATABLE_TERM_VARIABLE(EvaluatableTerm.class),
		META_RESOURCE_VARIABLE(ResourceVariable.class);
		
		private Class<?> clazz;
		
		TermType(Class<?> clazz) {
			this.clazz = clazz;
		}
		
		public Class<?> getClazz() {
			return this.clazz;
		}
		
	}
	
	protected TermType termType;
	
	protected MetaRDLTerm(Symbol symbol, TermType termType) {
		super(symbol, -1);
		this.termType = termType;
	}
	
	//dependency
	public MetaRDLTerm(RDLTerm dependingTerm, MetaResourceVariable dependedVariable) {
		super(new Symbol(":", 2), dependedVariable.getOrder());
		addChild(dependingTerm);
		addChild(dependedVariable);
		this.termType = TermType.META_DEPENDENCY;
	}
	
	public MetaRDLTerm(MetaRDLTerm dependingTerm, ResourceVariable dependedVariable) {
		super(new Symbol(":", 2), dependedVariable.getOrder());
		addChild(dependingTerm);
		addChild(dependedVariable);
		this.termType = TermType.META_DEPENDENCY;
	}
	
	public MetaRDLTerm(MetaRDLTerm dependingTerm, MetaResourceVariable dependedVariable) {
		super(new Symbol(":", 2), dependedVariable.getOrder());
		addChild(dependingTerm);
		addChild(dependedVariable);
		this.termType = TermType.META_DEPENDENCY;
	}
	
	//list type dependency
	public MetaRDLTerm(MetaRDLTerm dependency) {
		super(new Symbol(":", 1), dependency.getOrder() - 1);
		addChild(dependency);
		this.termType = TermType.META_DEPENDENCY_LIST;
	}
	
	//dependency term
	public MetaRDLTerm(Dependency dependency, MetaRDLTerm argumentTerm) {
		super(
			new Symbol(":", 3), 
			dependency.getDependedVariable().getOrder() == argumentTerm.getOrder() 
				? dependency.getDependedVariable().getOrder() 
				: dependency.getDependedVariable().getOrder() - 1
		);
		if(! dependency.isDependeingTermEvaluatable()) {
			throw new IllegalTypeException();
		}
		addChild(dependency.getDependingTerm());
		addChild(dependency.getDependedVariable());
		addChild(argumentTerm);
		this.termType = TermType.META_DEPENDENCY_TERM;
	}
	
	public MetaRDLTerm(MetaRDLTerm dependency, EvaluatableTerm argumentTerm) {
		super(
			new Symbol(":", 3), 
			((RDLTerm) dependency.getChild(1)).getOrder() == argumentTerm.getOrder() 
				? ((RDLTerm) dependency.getChild(1)).getOrder()
				: ((RDLTerm) dependency.getChild(1)).getOrder() - 1
		);
		var firstTerm = (RDLTerm) dependency.getChild(0);
		if(firstTerm instanceof MetaRDLTerm) {
			var firtTerm2 = (MetaRDLTerm) firstTerm;
			if(firtTerm2.getTermType() == TermType.META_DEPENDENCY || firtTerm2.getTermType() == TermType.META_DEPENDENCY_TERM) {
				throw new IllegalTypeException();
			}
		} else {
			if(! (firstTerm instanceof EvaluatableTerm)) {
				throw new IllegalTypeException();
			}
		}
		
		addChild(dependency.getChild(0));
		addChild(dependency.getChild(1));
		addChild(argumentTerm);
		this.termType = TermType.META_DEPENDENCY_TERM;
	}

	public MetaRDLTerm(MetaRDLTerm dependency, MetaRDLTerm argumentTerm) {
		super(
				new Symbol(":", 3), 
				((RDLTerm) dependency.getChild(1)).getOrder() == argumentTerm.getOrder() 
					? ((RDLTerm) dependency.getChild(1)).getOrder()
					: ((RDLTerm) dependency.getChild(1)).getOrder() - 1
			);
			var firstTerm = (RDLTerm) dependency.getChild(0);
			if(firstTerm instanceof MetaRDLTerm) {
				var firtTerm2 = (MetaRDLTerm) firstTerm;
				if(firtTerm2.getTermType() == TermType.META_DEPENDENCY || firtTerm2.getTermType() == TermType.META_DEPENDENCY_TERM) {
					throw new IllegalTypeException();
				}
			} else {
				if(! (firstTerm instanceof EvaluatableTerm)) {
					throw new IllegalTypeException();
				}
			}
			
			addChild(dependency.getChild(0));
			addChild(dependency.getChild(1));
			addChild(argumentTerm);
			this.termType = TermType.META_DEPENDENCY_TERM;
	}
	
	public boolean isVariable() {
		return false;
	}
	
	public boolean isMatchedBy(RDLTerm another, Map<Variable, RDLTerm> binding, Map<Variable, OrderVariableConstraint> orderConstraint) {
		if (! another.getClass().isAssignableFrom(this.termType.getClazz())) {
			return false;
		}
		if (this.getChildren().size() != another.getChildren().size()) {
			return false;
		}
		for (int i = 0; i < this.getChildren().size(); i++) {
			RDLTerm child = (RDLTerm) this.getChild(i);
			RDLTerm anotherChild = (RDLTerm) another.getChild(i);
			if (child instanceof MetaRDLTerm) {
				MetaRDLTerm metaChild = (MetaRDLTerm) child;
				if (! metaChild.isMatchedBy(anotherChild, binding, orderConstraint)) {
					return false;
				}
			} else {
				if (!(child.equals(anotherChild))) {
					return false;
				}
			}
		}
		return true;
	}
	
	
	@Override
	public String toString() {
		switch(termType) {
		case META_DEPENDENCY:
			return "[" + getChild(0).toString() + " : " + getChild(1).toString() + "]";
		case META_DEPENDENCY_LIST:
			return "[" + getChild(0).toString() + "]";
		case META_DEPENDENCY_TERM:
			return "[" + getChild(0).toString() + " : " + getChild(1).toString() + " -> " + getChild(2).toString() + "]";
		default:
			return "";
		}
	}

	@Override
	public String toStringWithOrder() {
		switch(termType) {
		case META_DEPENDENCY:
			return "[" + ((RDLTerm) getChild(0)).toStringWithOrder() + " : " + ((RDLTerm) getChild(1)).toStringWithOrder() + "]";
		case META_DEPENDENCY_LIST:
			return "[" + ((RDLTerm) getChild(0)).toStringWithOrder() + "]";
		case META_DEPENDENCY_TERM:
			return "[" + ((RDLTerm) getChild(0)).toStringWithOrder() + " : "
					+ ((RDLTerm) getChild(1)).toStringWithOrder() + " -> " + ((RDLTerm) getChild(2)).toStringWithOrder() + "]";
		default:
			return "";
		}
	}

	@Override
	public boolean equals(Object another) {
		if(! (another instanceof MetaRDLTerm)) {
			return false;
		}
		MetaRDLTerm anotherTerm = (MetaRDLTerm) another;
		return super.equals(another) && termType == anotherTerm.getTermType();
	}

	@Override
	public int hashCode() {
		return (termType.toString() + toStringWithOrder()).hashCode();
	}

	@Override
	public Object clone() {

		return null;
	}

}
