package models.terms.meta;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import exceptions.SubstituteFailedException;
import models.algebra.Expression;
import models.algebra.Variable;
import models.terms.RDLTerm;

public class MetaDynamicDependency extends MetaDependency implements MetaDynamicTerm {

	private final MetaTermGenerator termGenerator;
	
	public MetaDynamicDependency(MetaTermGenerator termGenerator) {
		this.termGenerator = termGenerator;
	}
	
	public MetaDynamicDependency(MetaTermGenerator termGenerator, List<? extends RDLTerm> terms) {
		this.termGenerator = termGenerator;
		this.dependingTerm = terms.size() > 0 ? terms.get(0) : null;
		this.dependedTerms = terms.size() > 1 ? terms.stream().skip(1).toList() : List.of();
		for (RDLTerm term : terms) {
			addChild(term);
		}
	}
	
	public MetaDynamicDependency(MetaTermGenerator termGenerator, RDLTerm ...terms) {
		this(termGenerator, Arrays.asList(terms));
	}
	
	@Override
	public MetaRDLTerm generate(int depth, Map<String, Object> context) {
		return null;
	}
	
	@Override
	public MetaDependency generate(int depth, int maxIndex, int maxDepth, Map<String, Object> context) {
		if (maxIndex < 2) return null;
		RDLTerm dependingTerm = this.dependingTerm == null ? termGenerator.generate(0, depth, maxIndex, maxDepth, context) : this.dependingTerm;
		while (dependingTerm instanceof MetaDynamicTerm dynamicTerm) {
			dependingTerm = dynamicTerm.generate(depth + 1, maxIndex, maxDepth, context);
		}
		List<RDLTerm> dependedTerms = new ArrayList<>();
		int staticSize = this.dependedTerms == null ? 0 : this.dependedTerms.size();
		for (int i = 0; i < Math.min(maxIndex - 1, staticSize); i++) {
			dependedTerms.add(this.dependedTerms.get(i));
		}
		for (int i = staticSize; i < maxIndex - 1; i++) {
			RDLTerm generatedTerm = termGenerator.generate(i + 1, depth, maxIndex, maxDepth, context);
			if (generatedTerm == null) {
				break;
			}
			while (generatedTerm instanceof MetaDynamicTerm dynamicTerm) {
				generatedTerm = dynamicTerm.generate(depth + 1, maxIndex, maxDepth, context);
			}
			dependedTerms.add(generatedTerm);
		}
		return new MetaDependency(dependingTerm, dependedTerms);
	}
	
	
	@Override
	public Set<MatchConstraint> isMatchedBy(RDLTerm another, MatchConstraint constraint) {
		int maxIndex = another.getMaxIndex();
		int maxDepth = another.getMaxDepth();
		Set<MatchConstraint> result = new HashSet<>();
		if (maxDepth == 1) {
			MetaRDLTerm metaDep = generate(maxIndex, maxDepth, constraint.getContext());
			return metaDep.isMatchedBy(another, constraint); 
		}
		for (int i = maxDepth; i > 1; i--) {
			MatchConstraint newConstraint = new MatchConstraint(constraint);
			MetaRDLTerm metaDep = generate(maxIndex, i, newConstraint.getContext());
			Set<MatchConstraint> res = metaDep.isMatchedBy(another, newConstraint);
			result.addAll(res);
		}
		return result;
	}

	@Override
	public RDLTerm substitute(Map<Variable, RDLTerm> binding, Map<String, Object> context) {
		if (! context.containsKey("maxIndex")) throw new SubstituteFailedException();
		if (! context.containsKey("maxDepth")) throw new SubstituteFailedException();
		int maxIndex = (int) context.get("maxIndex");
		int maxDepth = (int) context.get("maxDepth");
		return generate(maxIndex, maxDepth, context).substitute(binding, context);
	}

	
	@Override
	public String toString() {
		if (dependingTerm != null) {
			return "[" + 
				getChild(0).toString() + " : " + 
					getChildren().stream().skip(1).map(Expression::toString).collect(Collectors.joining(",")) + 
					" ...? ]";
		}
		return "[ ...? ]";
	}
}
