package org.aksw.sparqlify.core.rewrite.expr.transform;

import org.aksw.jena_sparql_api.views.E_RdfTerm;
import org.aksw.jena_sparql_api.views.ExprCopy;
import org.aksw.jena_sparql_api.views.SparqlifyConstants;
import org.aksw.jena_sparql_api.views.SqlTranslationUtils;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.sparql.expr.E_Conditional;
import org.apache.jena.sparql.expr.E_Equals;
import org.apache.jena.sparql.expr.E_LogicalAnd;
import org.apache.jena.sparql.expr.E_LogicalOr;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprFunction;
import org.apache.jena.sparql.expr.NodeValue;

/**
 * 
 * 
 * 
 * 
 * @author Claus Stadler <cstadler@informatik.uni-leipzig.de>
 *
 */

public class ExprTransformerArithmetic
	extends ExprTransformerBase2
{
	private Resource resultType;
	
	public ExprTransformerArithmetic(Resource resultType) {
		this.resultType = resultType;
	}

	
	public Expr handleConcat(ExprFunction fn) {
		
		// The result is null if it could not be further transformed
		Expr result = SqlTranslationUtils.optimizeOpConcat(fn);
		
		/*
		if(result == null) {
			result = fn;
		}*/
		
		return result;
	}

	
	@Override
	public E_RdfTerm transform(Expr orig, E_RdfTerm a, E_RdfTerm b) {
		
		// Create an expression base on the arguments
		// using the type of the original expression
		
		Expr isCompatibleTypes = ExprTransformerRdfTermComparator.createTypeCheck(a, b);
		
		E_RdfTerm result;
		if(isCompatibleTypes.equals(SparqlifyConstants.nvTypeError)) {
			result = SqlTranslationUtils.expandConstant(SparqlifyConstants.nvTypeError);
		}
		else {
			
			Expr va = a.getLexicalValue();
			Expr vb = b.getLexicalValue();
			Expr ex = ExprCopy.getInstance().copy(orig, va, vb);
			
			if(isCompatibleTypes.equals(NodeValue.TRUE)) {
				result = E_RdfTerm.createTypedLiteral(ex, resultType);
			}
			else {
				Expr cond = new E_Conditional(isCompatibleTypes, ex, SparqlifyConstants.nvTypeError);
				result = E_RdfTerm.createTypedLiteral(cond, resultType);
			}
			
		}

		return result;
	}
	
	public static final NodeValue two = NodeValue.makeDecimal(2);
	public static final NodeValue three = NodeValue.makeDecimal(3);
	
	public static Expr createTypeCheck(E_RdfTerm a, E_RdfTerm b) {
		
		// Condition: Either the types are equal or both are either 2 (plain) or 3 (typed literal)
		
		// TODO Already evaluate here as far as possible - Otherwise the expressions get annoyingly complicated
		Expr at = a.getType();
		Expr bt = b.getType();
		
		
		Expr result;
		
		
		if(at.isConstant() && bt.isConstant()) {
			NodeValue x = at.getConstant();
			NodeValue y = at.getConstant();
			
			boolean isSameValue = NodeValue.sameAs(x, y);

			boolean isBothLiterals = 
					(NodeValue.sameAs(x, two) || NodeValue.sameAs(x, three)) &&
					(NodeValue.sameAs(y, two) || NodeValue.sameAs(y, three));

			boolean isCompatibleTypes = isSameValue || isBothLiterals;
			
			if(isCompatibleTypes) {
				result = NodeValue.TRUE;
			} else {
				result = SparqlifyConstants.nvTypeError;
			}

			
		} else {
		
			Expr eqTmpA = new E_Equals(a.getType(), b.getType());
			
			Expr e =
				new E_LogicalAnd(
					new E_LogicalOr(
						new E_Equals(a.getType(), NodeValue.makeDecimal(2)),
						new E_Equals(a.getType(), NodeValue.makeDecimal(3))
					),
					new E_LogicalOr(
							new E_Equals(b.getType(), NodeValue.makeDecimal(2)),
							new E_Equals(b.getType(), NodeValue.makeDecimal(3))
					)
				);
						
					
			Expr eqTA = new E_LogicalOr(eqTmpA, e);
			result = new E_Conditional(eqTA, NodeValue.TRUE, SparqlifyConstants.nvTypeError);
		
		}
		

		return result;
	}
	
	public static E_RdfTerm processOpRdfTerm(E_RdfTerm a, E_RdfTerm b, Expr innerExpr, Resource resultType) {
		
		
		// Condition: Either the types are equal or both are either 2 (plain) or 3 (typed literal)
		

		Expr eqT = createTypeCheck(a, b);

		
		//Expr eqV = new E_Equals(a.getLexicalValue(), b.getLexicalValue());
		
		Expr eqV = innerExpr;
		
		
		// TODO We need to consider type hierarchies, but for now we just skip on that.
		Expr eqD = NodeValue.TRUE;
		//Expr eqD = new E_Equals(a.getDatatype(), b.getDatatype());
		Expr eqL = new E_Equals(a.getLanguageTag(), b.getLanguageTag());
		
		Expr tmp =
				new E_LogicalAnd(
						new E_LogicalAnd(eqT, eqV),
						new E_LogicalAnd(eqD, eqL)
				);
		
		E_RdfTerm result = E_RdfTerm.createTypedLiteral(tmp, resultType);

		return result;
		
	}
}