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

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

import org.aksw.jena_sparql_api.views.E_RdfTerm;
import org.aksw.jena_sparql_api.views.SparqlifyConstants;
import org.aksw.jenax.arq.util.expr.ExprUtils;
import org.aksw.sparqlify.type_system.CandidateMethod;
import org.aksw.sparqlify.type_system.FunctionModel;
import org.aksw.sparqlify.type_system.MethodDeclaration;
import org.aksw.sparqlify.type_system.TypeSystemUtils;
import org.apache.jena.sparql.expr.E_Function;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprFunction;
import org.apache.jena.sparql.expr.ExprList;
import org.apache.jena.sparql.expr.NodeValue;
import org.apache.jena.vocabulary.XSD;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class ExprTransformerSparqlFunctionModel
    implements ExprTransformer
{

    private static final Logger logger = LoggerFactory.getLogger(ExprTransformerSparqlFunctionModel.class);


    // TODO Move to appropriate place
    private static E_RdfTerm typeError = E_RdfTerm.createTypedLiteral(SparqlifyConstants.nvTypeError, SparqlifyConstants.nvTypeError);

    private FunctionModel<String> sparqlModel;


    public ExprTransformerSparqlFunctionModel(FunctionModel<String> sparqlModel) {
        this.sparqlModel = sparqlModel;
    }


    public static List<Expr> getTermValues(List<E_RdfTerm> rdfTerms) {
        List<Expr> result = new ArrayList<Expr>(rdfTerms.size());
        for(E_RdfTerm rdfTerm : rdfTerms) {
            Expr item = rdfTerm.getLexicalValue();

            result.add(item);
        }

        return result;
    }

    @Override
    public E_RdfTerm transform(Expr orig, List<E_RdfTerm> exprs) {
        ExprFunction fn = orig.getFunction();

        String fnName = ExprUtils.getFunctionId(fn);

        // Get the argument type list
        // All of the arguments must be typed literals (maybe also plain literal???)
        // otherwise its an type error
        List<String> argTypes = new ArrayList<String>(exprs.size());
        for(E_RdfTerm rdfTerm : exprs) {
            Expr termType = rdfTerm.getType();
            Expr datatype = rdfTerm.getDatatype();

            if(!termType.isConstant()) {
                logger.debug("Yielding type error because termType is not a contant in: " + rdfTerm + " from " + orig + " with " + exprs);
                return typeError;
            }


            int termTypeVal = termType.getConstant().getDecimal().intValue();

            if(!datatype.isConstant()) {
                logger.debug("Yielding type error because datatype is not a contant in: " + rdfTerm + " from " + orig + " with " + exprs);
                return typeError;
            }

            NodeValue nodeValue = datatype.getConstant();


            if(nodeValue.equals(SparqlifyConstants.nvTypeError)) { //SqlValue.TYPE_ERROR)) {
                logger.debug("Passing on type error for: " + fnName + " " + exprs);
                return typeError;
            }

            if(!nodeValue.isString()) {
                logger.debug("Yielding type error because datatype is not a string for: " + fnName + " " + exprs);
                return typeError;
            }

            String datatypeStr = nodeValue.asUnquotedString();

            // Convert a plain literal to a typed literal with xsd:string
            if(termTypeVal == 2) {
                if(!datatypeStr.trim().isEmpty()) {
                    logger.debug("Yielding type error because termType has a language tag in : " + rdfTerm + " from " + orig + " with " + exprs);
                    return typeError;
                }

                datatypeStr = XSD.xstring.toString();

            } else {

                if(termTypeVal != 3) {
                    logger.debug("Yielding type error because termType is not 3 (typed literal) in: " + rdfTerm + " from " + orig + " with " + exprs);
                    return typeError;
                }
            }

            argTypes.add(datatypeStr);
        }


        //FunctionModel
        CandidateMethod<String> candidate = TypeSystemUtils.lookupCandidate(sparqlModel, fnName, argTypes);

        E_RdfTerm result;
        if(candidate == null) {
            logger.debug("Yielding type error because no suitable candidate found for " + fnName + " with " + argTypes);
            return typeError;
        }

        MethodDeclaration<String> dec = candidate.getMethod().getDeclaration();

        String name = dec.getName();
        String returnType = dec.getSignature().getReturnType();

        NodeValue nvReturnType = NodeValue.makeString(returnType);

        ExprList newArgs = new ExprList(getTermValues(exprs));
        ExprFunction exprFn = new E_Function(name, newArgs);

        result = E_RdfTerm.createTypedLiteral(exprFn, nvReturnType);

        return result;
    }

}