package org.aksw.sparqlify.core.algorithms;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.aksw.commons.collections.generator.Generator;
import org.aksw.jena_sparql_api.restriction.RestrictionManagerImpl;
import org.aksw.jena_sparql_api.restriction.UnsatisfiabilityException;
import org.aksw.jena_sparql_api.views.CandidateViewSelectorBase;
import org.aksw.jena_sparql_api.views.E_RdfTerm;
import org.aksw.jena_sparql_api.views.OpViewInstanceJoin;
import org.aksw.jena_sparql_api.views.RecursionResult;
import org.aksw.jena_sparql_api.views.RestrictedExpr;
import org.aksw.jena_sparql_api.views.VarBinding;
import org.aksw.jena_sparql_api.views.VarDefinition;
import org.aksw.jena_sparql_api.views.ViewInstance;
import org.aksw.sparqlify.algebra.sql.nodes.SqlOpEmpty;
import org.aksw.sparqlify.core.domain.input.Mapping;
import org.aksw.sparqlify.core.domain.input.ViewDefinition;
import org.aksw.sparqlify.core.interfaces.MappingOps;
import org.aksw.sparqlify.core.sparql.algebra.transform.FilterPlacementOptimizer2Sparqlify;
import org.aksw.sparqlify.database.GetVarsMentioned;
import org.aksw.sparqlify.sparqlview.ViewInstanceJoin;
import org.apache.jena.sparql.algebra.Op;
import org.apache.jena.sparql.algebra.op.OpDisjunction;
import org.apache.jena.sparql.algebra.op.OpQuadBlock;
import org.apache.jena.sparql.core.QuadPattern;
import org.apache.jena.sparql.core.Var;
import org.apache.jena.sparql.expr.Expr;
import org.apache.jena.sparql.expr.ExprVar;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;




/**
 * The candidate selector for RDB-RDF
 * @author raven
 *
 * @param <T>
 */
public class CandidateViewSelectorSparqlify
    extends CandidateViewSelectorBase<ViewDefinition, Mapping>
{
    private static final Logger logger = LoggerFactory.getLogger(CandidateViewSelectorSparqlify.class);

    //private OpMappingRewriter opMappingRewriter;// = new OpMappingRewriterImpl(new MappingOps)
    private MappingOps mappingOps;

    private ViewDefinitionNormalizer<ViewDefinition> viewDefinitionNormalizer;

    public CandidateViewSelectorSparqlify() {
        this(null);

        logger.warn("No mappingOps provided. This means that view candidates cannot be pruned efficently which is most likely not what you want!!!");
    }

    public CandidateViewSelectorSparqlify(MappingOps mappingOps) {
        this(mappingOps, new ViewDefinitionNormalizerImpl());
    }

    public CandidateViewSelectorSparqlify(MappingOps mappingOps, ViewDefinitionNormalizer<ViewDefinition> viewDefinitionNormalizer) {
        super((op, rm) -> FilterPlacementOptimizer2Sparqlify.optimizeStatic(op, rm));
        //super(viewDefinitionNormalizer);

        this.viewDefinitionNormalizer = viewDefinitionNormalizer;
        this.mappingOps = mappingOps;
    }

    @Override
    public ViewDefinition normalizeView(ViewDefinition view) {
        ViewDefinition normalized = viewDefinitionNormalizer.normalize(view);

        logger.trace("Normalized view:\n" + normalized);

        return normalized;
    }


    /**
     * Create a new context based on the baseContext and the current candidate viewInstance
     *
     * if null is returned, the candidateViewInstance becomes rejected, otherwise, the new context
     * will be passed to this function together with all sub candidate viewInstances.
     *
     * @return
     */
    @Override
    public Mapping createContext(Mapping baseMapping, ViewInstance<ViewDefinition> viewInstance)
        throws UnsatisfiabilityException
    {
        Mapping nextMapping = null;
        boolean enablePruningMappingRewrite = true;
        if(enablePruningMappingRewrite && mappingOps != null) {

            Mapping mapping = mappingOps.createMapping(viewInstance);

            if(baseMapping == null) {
                nextMapping = mapping;
            } else {
                nextMapping = mappingOps.join(baseMapping, mapping);
            }

            if(nextMapping.isEmpty()) {
                throw new UnsatisfiabilityException();
            }
        }

        return nextMapping;
    }

    /*
    @Override
    public ViewInstanceJoin createUnionItem(List<ViewInstance<ViewDefinition>> viewInstances, RestrictionManagerImpl subRestrictions) {
        ViewInstanceJoin viewConjunction = new ViewInstanceJoin(viewInstances, subRestrictions);

        // remove self joins
        SelfJoinEliminator.merge(viewConjunction);

        //result.add(viewConjunction);
        return viewConjunction;
    }
    */

    @Override
    public Op createOp(OpQuadBlock opQuadPattern, List<RecursionResult<ViewDefinition, Mapping>> conjunctions) {

        OpDisjunction result = OpDisjunction.create();

        for(RecursionResult<ViewDefinition, Mapping> entry : conjunctions) {
            Mapping mapping = entry.getFinalContext();
            RestrictionManagerImpl restrictions = entry.getViewInstances().getRestrictions();

            Op tmp = new OpMapping(mapping, restrictions);
            result.add(tmp);
        }


        // If there were no candidates, we fake a view definition where all variables of
        // the quad pattern (of the query) are bound to Node.nvNothing
        if(result.size() == 0) {
            Op tmp = createEmptyViewInstance(opQuadPattern);
            result.add(tmp);
        }

        return result;
    }


    //@Override
    public Op createOpOldButWorking(OpQuadBlock opQuadPattern, List<RecursionResult<ViewDefinition, Mapping>> conjunctions) {

        for(RecursionResult<ViewDefinition, Mapping> tmp : conjunctions) {
            ViewInstanceJoin<ViewDefinition> conjunction = tmp.getViewInstances();
            SelfJoinEliminator.merge(conjunction);
        }


        OpDisjunction result = OpDisjunction.create();

        for(RecursionResult<ViewDefinition, Mapping> entry : conjunctions) {
            ViewInstanceJoin<ViewDefinition> item = entry.getViewInstances();
            Op tmp = new OpViewInstanceJoin(item);
            result.add(tmp);
        }


        // If there were no candidates, we fake a view definition where all variables of
        // the quad pattern (of the query) are bound to Node.nvNothing
        if(result.size() == 0) {
            Op tmp = createEmptyViewInstance(opQuadPattern);
            result.add(tmp);
        }

        return result;
    }

    public static Generator<String> emptyViewNameGenerator = Generator.create("emptyView");

    public static Op createEmptyViewInstance(OpQuadBlock opQuadBlock) {
        Set<Var> vars = GetVarsMentioned.getVarsMentioned(opQuadBlock);


        VarBinding binding = new VarBinding();

        Multimap<Var, RestrictedExpr> varDefMap = HashMultimap.create();

        String colName = "nll";
        SqlOpEmpty sqlOpEmpty = SqlOpEmpty.create(colName);
        //MappingOpsImpl.createEmptyMapping()


        for(Var queryVar : vars) {
            // Bind the query var to a view var with the same name
            Var viewVar = queryVar;
            binding.put(queryVar, viewVar);//NodeValue.TRUE.asNode());//NodeValue.nvNothing.asNode());;

            ExprVar colVar = new ExprVar(colName);
            Expr termCtor = E_RdfTerm.createPlainLiteral(colVar);


            // Bind the view var to a column in the table
            varDefMap.put(viewVar, new RestrictedExpr(termCtor));

            //binding.put(var, null);
            //map.put(var, new RestrictedExpr(NodeValue.nvNothing));
        }

        VarDefinition varDef = new VarDefinition(varDefMap);
        Mapping mapping = new Mapping(varDef, sqlOpEmpty);

        String viewName = emptyViewNameGenerator.next();
        ViewDefinition viewDef = new ViewDefinition(viewName, new QuadPattern(), null, mapping, null);

        ViewInstance<ViewDefinition> viewInstance = new ViewInstance<ViewDefinition>(viewDef, binding);
        List<ViewInstance<ViewDefinition>> tmp = new ArrayList<ViewInstance<ViewDefinition>>();
        tmp.add(viewInstance);

        ViewInstanceJoin<ViewDefinition> join = new ViewInstanceJoin<ViewDefinition>(tmp, new RestrictionManagerImpl());
        Op result = new OpViewInstanceJoin<ViewDefinition>(join);

        return result;
    }


    @Override
    public RestrictionManagerImpl getRestrictions2(Op op) {
        if(op instanceof OpMapping) {
            OpMapping opMapping = (OpMapping)op;
            return opMapping.getRestrictions();
        } else {
            // TODO Auto-generated method stub
            return super.getRestrictions2(op);
        }
    }

    @Override
    public void getRestrictions(Op op,
            Collection<RestrictionManagerImpl> result) {
//         if(op instanceof OpRdfViewPattern) {
//            OpRdfViewPattern o = (OpRdfViewPattern)op;
//            result.add(o.getConjunction().getRestrictions());
//         } else {
             super.getRestrictions(op, result);
//         }
    }

}
