package it.unibz.inf.ontop.query.impl;

import it.unibz.inf.ontop.query.ConstructTemplate;
import it.unibz.inf.ontop.query.RDF4JConstructQuery;
import it.unibz.inf.ontop.query.resultset.GraphResultSet;
import it.unibz.inf.ontop.exception.MinorOntopInternalBugException;
import org.eclipse.rdf4j.query.BindingSet;
import org.eclipse.rdf4j.query.algebra.*;
import org.eclipse.rdf4j.query.parser.ParsedQuery;
import org.eclipse.rdf4j.query.parser.ParsedTupleQuery;

import java.util.Optional;


class RDF4JConstructQueryImpl extends RegularRDF4JKGQueryImpl<GraphResultSet> implements RDF4JConstructQuery {
    private final ConstructTemplate template;

    RDF4JConstructQueryImpl(String queryString, ParsedQuery parsedQuery, BindingSet bindings) {
        this(split(parsedQuery), queryString, bindings);
    }

    RDF4JConstructQueryImpl(ConstructQuerySplit split, String queryString, BindingSet bindings) {
        this(split.getConstructTemplate(), split.getSelectParsedQuery(), queryString, bindings);
    }

    private RDF4JConstructQueryImpl(ConstructTemplate template, ParsedQuery selectParsedQuery, String queryString,
                                    BindingSet bindings) {
        super(selectParsedQuery, queryString, bindings);
        this.template = template;
    }

    @Override
    public ConstructTemplate getConstructTemplate() {
        return template;
    }

    @Override
    public RDF4JConstructQuery newBindings(BindingSet newBindings) {
        return new RDF4JConstructQueryImpl(template, parsedQuery, getOriginalString(), newBindings);
    }

    private static ConstructQuerySplit split(ParsedQuery parsedQuery) {
        TupleExpr root = parsedQuery.getTupleExpr();
        TupleExpr topNonSliceExpression = getFirstNonSliceExpression(root);
        UnaryTupleOperator constructionProjection = getFirstProjection(topNonSliceExpression);
        Optional<Extension> constructionExtension = getExtension(constructionProjection);

        ConstructTemplate constructTemplate = constructionExtension
                .map(e -> new RDF4JConstructTemplate(constructionProjection, e))
                .orElseGet(() -> new RDF4JConstructTemplate(constructionProjection, null));

        TupleExpr selectSubTree = constructionExtension
                .map(Extension::getArg)
                .orElseGet(constructionProjection::getArg);

        ParsedTupleQuery selectQuery = computeSelectQuery(root, selectSubTree);

        return new ConstructQuerySplit(constructTemplate, selectQuery);
    }

    private static TupleExpr getFirstNonSliceExpression(TupleExpr expr) {
        // NB: the slice is not relevant for the construct template
        // (will be taken into account in the SELECT query fragment)
        return expr instanceof Slice ?
                getFirstNonSliceExpression(((Slice) expr).getArg()):
                expr;
    }

    private static Optional<Extension> getExtension(UnaryTupleOperator proj) {
        return Optional.of(proj.getArg())
                .filter(t -> t instanceof Extension)
                .map(t -> (Extension) t);
    }

    private static UnaryTupleOperator getFirstProjection(TupleExpr expr) {
        if (expr instanceof QueryRoot)
            return getFirstProjection(((QueryRoot) expr).getArg());
        if(expr instanceof Projection || expr instanceof MultiProjection)
            return (UnaryTupleOperator) expr;
        if (expr instanceof Reduced)
            return getFirstProjection(((Reduced) expr).getArg());

        throw new MinorOntopInternalBugException("Unexpected SPARQL query (after parsing): "+
                "an instance of "+
                Projection.class+
                " or "+
                Reduced.class+
                " is expected, instead of\n"+
                expr
        );
    }

    private static ParsedTupleQuery computeSelectQuery(TupleExpr root, TupleExpr selectSubTree) {
        TupleExpr selectTree;
        if (root instanceof Slice) {
            Slice newSliceTree = (Slice) root.clone();
            newSliceTree.setArg(selectSubTree.clone());
            selectTree = newSliceTree;
        }
        else
            selectTree = selectSubTree;

        return new ParsedTupleQuery(selectTree);
    }

}
