package models.terms.meta;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import lombok.Getter;
import models.Position;
import models.algebra.Variable;
import models.terms.Dependency;
import models.terms.DependencyTerm;
import models.terms.EvaluatableTerm;
import models.terms.LinearRightNormalizedType;
import models.terms.RDLTerm;
import models.terms.Resource;
import models.terms.ResourceConstant;
@Getter
public abstract class MetaRDLTerm extends RDLTerm {

	protected TermType termType;
	protected LinearRightNormalizedType linearRightNormalizedType = LinearRightNormalizedType.UNDEFINED;
	
	protected MetaRDLTerm(TermType termType, int size) {
		super(-1, size);
		this.termType = termType;
		if (isResource()) {
			linearRightNormalizedType = LinearRightNormalizedType.LINEAR_RIGHT_NORMALIZED;
		}
	}
	
	
	public Set<MatchConstraint> isMatchedBy(RDLTerm another) {
		Set<MatchConstraint> result = isMatchedBy(another, MatchConstraint.createDefault(), new Position());
		for (MatchConstraint constraint: result) {
			constraint.getContext().put("isDynamicStarted", false);
		}
		return result;
	}
	
	public Set<MatchConstraint> isMatchedBy(RDLTerm another, Set<MatchConstraint> constraints) {
		Set<MatchConstraint> result = new HashSet<>();
		for (MatchConstraint constraint : constraints) {
			result.addAll(isMatchedBy(another, constraint, new Position()));
		}
		for (MatchConstraint constraint: result) {
			constraint.getContext().put("isDynamicStarted", false);
		}
		return result;
	}
	
	public Set<MatchConstraint> isMatchedBy(RDLTerm another, MatchConstraint constraint) {
		Set<MatchConstraint> result =  isMatchedBy(another, constraint, new Position());
		for (MatchConstraint res: result) {
			res.getContext().put("isDynamicStarted", false);
		}
		return result;
	}
	
	protected Set<MatchConstraint> isMatchedBy(RDLTerm another, Set<MatchConstraint> constraints, Position position) {
		Set<MatchConstraint> result = new HashSet<>();
		for (MatchConstraint constraint : constraints) {
			result.addAll(isMatchedBy(another, constraint, position));
		}
		return result;
	}
	
	protected abstract Set<MatchConstraint> isMatchedBy(RDLTerm another, MatchConstraint constraint, Position position);
	
	
	public RDLTerm substitute(Map<Variable, RDLTerm> binding) {
		return substitute(binding, new HashMap<>(), new Position());
	}
	
	public RDLTerm substitute(Map<Variable, RDLTerm> binding, Map<Object, Object> context) {
		return substitute(binding, context, new Position());
	}
	
	abstract public RDLTerm substitute(Map<Variable, RDLTerm> binding, Map<Object, Object> context, Position position); 
	
	
	public boolean checkTermType(Class<? extends RDLTerm> clazz) {
		return clazz.isAssignableFrom(termType.getBaseTermClass());
	}
	
	public boolean isVariable() {
		return false;
	}
	
	public boolean isDependency() {
		return checkTermType(Dependency.class);
	}
	
	public boolean isEvaluatableTerm() {
		return checkTermType(EvaluatableTerm.class);
	}
	
	public boolean isDependencyTerm() {
		return checkTermType(DependencyTerm.class);
	}
	
	public boolean isResource() {
		return checkTermType(Resource.class);
	}
	
	public void setLinearRightNormalizedType(LinearRightNormalizedType next) {
		if (isDependency()) {
			return;
		}
		this.linearRightNormalizedType = next;
	}
	
	public Set<MetaVariable> getAllVariables() {
		return new HashSet<>(getSubTerms(MetaVariable.class).values());
	}
	
	protected boolean islinearRightNormalizedMatchedBy(RDLTerm another) {
		if (linearRightNormalizedType != LinearRightNormalizedType.UNDEFINED) {
			if (another instanceof DependencyTerm) {
				if (((DependencyTerm) another).getLinearRightNormalizedType() != linearRightNormalizedType) {
					return false;
				}
			}
			else if (another instanceof MetaRDLTerm) {
				if (((MetaRDLTerm) another).getLinearRightNormalizedType() != linearRightNormalizedType) {
					return false;
				}
			}
		}
		return true;
	}
	
	
	@Override
	public int getMaxIndex() {
		return -1;
	}
	
	@Override
	public int getMaxDepth() {
		return -1;
	}
	
	public abstract RDLTerm replace(Map<? extends MetaRDLTerm, ? extends RDLTerm> mapping);
	
	@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() + toString()).hashCode();
	}

	@Override
	public Object clone() {

		return null;
	}

	protected static enum TermType {
		META_RDL_TERM(RDLTerm.class),
		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(Resource.class),
		MEAT_CONSTANT_VARIABLE(ResourceConstant.class);
		
		@Getter
		private Class<?> baseTermClass;
		
		private TermType(Class<?> clazz) {
			this.baseTermClass = clazz;
		}
		
	}
	
}
