package eu.dnetlib.utils.cql;

import static eu.dnetlib.utils.cql.CqlClause.CqlClauseType.BOOLEAN;
import static eu.dnetlib.utils.cql.CqlClause.CqlClauseType.RELATION;
import static eu.dnetlib.utils.cql.CqlClause.CqlClauseType.TERM;

import java.util.ArrayList;
import java.util.List;

/**
 * @author stoumpos
 * 
 */
public class CqlQuery {

	CqlClause root = null;

	CqlQuery() {
		this(null);
	}

	CqlQuery(CqlClause root) {
		this.root = root;
	}

	public CqlClause getRoot() {
		return root;
	}

	public void setRoot(CqlClause root) {
		this.root = root;
	}

	@Override
	public String toString() {
		return (root == null) ? "null" : root.toCqlString();
	}


	/**
	 * Return <code>true</code> if this query is a superset of
	 * <code>query</code>, otherwise <code>false</code>.
	 */
	public boolean contains(CqlQuery query) {
		return contains(root, query.getRoot());
	}
	
	private boolean contains(CqlClause big, CqlClause small) {
		
		if (big.type ==  BOOLEAN && small.type ==  BOOLEAN) {
			return containsBooleans((CqlBoolean) big, (CqlBoolean) small);
		
		} else if (big.type ==  BOOLEAN) {
			// small is either term or relation
			return containsBooleanTerm((CqlBoolean) big, small);
			
		} else if (small.type ==  BOOLEAN) {
			// big is either term or relation
			return containsTermBoolean(big, (CqlBoolean) small);
			
		} else if (big.type == RELATION && small.type == RELATION) {
			CqlRelation bigRelation = (CqlRelation) big;
			CqlRelation smallRelation = (CqlRelation) small;
			return bigRelation.index.equals(smallRelation.index)
				&& bigRelation.operator.equals(smallRelation.operator)
				&& bigRelation.value.equals(smallRelation.value);
			
		} else if (big.type == TERM && small.type == TERM) {
			return ((CqlTerm) big).term.equals(((CqlTerm) small).term);
		}
				
		return false;
	}

	private boolean containsBooleans(CqlBoolean a, CqlBoolean b) {
		if (a.getOperator().equalsIgnoreCase(b.getOperator())) {
			
			ArrayList<CqlClause> aList = unfold(a);
			ArrayList<CqlClause> bList = unfold(b);
			
			if (a.operator.equalsIgnoreCase("and")) {
				return subset(aList, bList);
				
			} else if (a.operator.equalsIgnoreCase("or")) {
				return superset(bList, aList);
				
			} else {
				throw new IllegalArgumentException(
						"Unsupported operator: " + a.operator);
			}
			
		} else {
			return contains(a, b.getLeft()) && contains(a, b.getRight());
				
		}
	}
	
	public ArrayList<CqlClause> unfold(CqlBoolean bool) {
		
		String op = bool.operator;
		ArrayList<CqlClause> siblings = new ArrayList<CqlClause>();
		
		if (bool.left.type == BOOLEAN
				&& ((CqlBoolean) bool.left).operator.equalsIgnoreCase(op)) {
			siblings.addAll(unfold((CqlBoolean) bool.left));
		} else {
			siblings.add(bool.left);
		}
		
		if (bool.right.type == BOOLEAN
				&& ((CqlBoolean) bool.right).operator.equalsIgnoreCase(op)) {
			siblings.addAll(unfold((CqlBoolean) bool.right));
		} else {
			siblings.add(bool.right);
		}
		
		return siblings;
	}

	
	private boolean superset(List<CqlClause> superset, List<CqlClause> subset) {
		if (subset.size() > superset.size()) {
			return false;
		}
		for (CqlClause sub : subset) {
			boolean found = false;
			for (CqlClause sup : superset) {
				if (contains(sup, sub)) {
					found = true;
					break;
				}
			}
			if (!found) {
				return false;
			}
		}
		return true;
	}

	private boolean subset(List<CqlClause> subset, List<CqlClause> superset) {
		if (subset.size() > superset.size()) {
			return false;
		}
		for (CqlClause sub : subset) {
			boolean found = false;
			for (CqlClause sup : superset) {
				if (contains(sub, sup)) {
					found = true;
					break;
				}
			}
			if (!found) {
				return false;
			}
		}
		return true;
	}

	private boolean containsBooleanTerm(CqlBoolean bool, CqlClause clause) {

		if (bool.getOperator().equalsIgnoreCase("and")) {
			// warn: assume clause is either term or relation
			return false;
		}
		
		return contains(bool.getLeft(), clause)
				|| contains(bool.getRight(), clause);
	}
	
	private boolean containsTermBoolean(CqlClause clause, CqlBoolean bool) {
		
		if (bool.getOperator().equalsIgnoreCase("or")) {
			// warn: assume clause is either term or relation
			return false;
		}
		
		return contains(clause, bool.getLeft())
				|| contains(clause, bool.getRight());
	}
}
