package it.unibz.inf.ontop.injection;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.inject.Guice;
import com.google.inject.assistedinject.Assisted;
import it.unibz.inf.ontop.dbschema.QuotedID;
import it.unibz.inf.ontop.dbschema.RelationDefinition;
import it.unibz.inf.ontop.iq.node.*;
import it.unibz.inf.ontop.iq.*;
import it.unibz.inf.ontop.model.atom.AtomPredicate;
import it.unibz.inf.ontop.model.atom.DataAtom;
import it.unibz.inf.ontop.model.atom.DistinctVariableOnlyDataAtom;
import it.unibz.inf.ontop.model.term.*;
import it.unibz.inf.ontop.model.type.DBTermType;
import it.unibz.inf.ontop.substitution.ImmutableSubstitution;

import java.util.Optional;

/**
 * Factory following the Guice AssistedInject pattern.
 *
 * See https://github.com/google/guice/wiki/AssistedInject.
 *
 * Accessible through {@link Guice} (recommended) or through {@link CoreSingletons#getIQFactory()}.
 *
 */
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
public interface IntermediateQueryFactory {

    ConstructionNode createConstructionNode(ImmutableSet<Variable> projectedVariables);

    ConstructionNode createConstructionNode(ImmutableSet<Variable> projectedVariables,
                                            ImmutableSubstitution<ImmutableTerm> substitution);

    UnionNode createUnionNode(ImmutableSet<Variable> projectedVariables);

    InnerJoinNode createInnerJoinNode();
    InnerJoinNode createInnerJoinNode(ImmutableExpression joiningCondition);
    InnerJoinNode createInnerJoinNode(Optional<ImmutableExpression> joiningCondition);

    LeftJoinNode createLeftJoinNode();
    LeftJoinNode createLeftJoinNode(ImmutableExpression joiningCondition);
    LeftJoinNode createLeftJoinNode(Optional<ImmutableExpression> joiningCondition);

    FilterNode createFilterNode(ImmutableExpression filterCondition);

    FlattenNode createFlattenNode(@Assisted("outputVariable") Variable outputVariable,
                                  @Assisted("flattenedVariable") Variable flattenedVariable,
                                  Optional<Variable> indexVariable, DBTermType dbTermType);

    IntensionalDataNode createIntensionalDataNode(DataAtom<AtomPredicate> atom);

    /**
     * NB: keys of the argumentMap are indices. They start at 0.
     */
    ExtensionalDataNode createExtensionalDataNode(RelationDefinition relationDefinition,
                                                  ImmutableMap<Integer, ? extends VariableOrGroundTerm> argumentMap);

    ExtensionalDataNode createExtensionalDataNode(RelationDefinition relationDefinition,
                                                  ImmutableMap<Integer, ? extends VariableOrGroundTerm> argumentMap,
                                                  VariableNullability variableNullability);

    EmptyNode createEmptyNode(ImmutableSet<Variable> projectedVariables);

    ValuesNode createValuesNode(@Assisted("orderedVariables") ImmutableList<Variable> orderedVariables,
                                @Assisted("values") ImmutableList<ImmutableList<Constant>> values);

    NativeNode createNativeNode(ImmutableSortedSet<Variable> variables,
                                @Assisted("variableTypeMap") ImmutableMap<Variable, DBTermType> variableTypeMap,
                                @Assisted("columnNames") ImmutableMap<Variable, QuotedID> columnNames,
                                String nativeQueryString, VariableNullability variableNullability);

    TrueNode createTrueNode();

    DistinctNode createDistinctNode();

    SliceNode createSliceNode(@Assisted("offset") long offset, @Assisted("limit") long limit);
    SliceNode createSliceNode(long offset);

    OrderByNode createOrderByNode(ImmutableList<OrderByNode.OrderComparator> comparators);
    OrderByNode.OrderComparator createOrderComparator(NonGroundTerm term, boolean isAscending);

    AggregationNode createAggregationNode(ImmutableSet<Variable> groupingVariables,
                                          ImmutableSubstitution<ImmutableFunctionalTerm> substitution);

    UnaryIQTree createUnaryIQTree(UnaryOperatorNode rootNode, IQTree child);
    UnaryIQTree createUnaryIQTree(UnaryOperatorNode rootNode, IQTree child, IQTreeCache treeCache);

    BinaryNonCommutativeIQTree createBinaryNonCommutativeIQTree(BinaryNonCommutativeOperatorNode rootNode,
                                                                @Assisted("left") IQTree leftChild,
                                                                @Assisted("right") IQTree rightChild);
    BinaryNonCommutativeIQTree createBinaryNonCommutativeIQTree(BinaryNonCommutativeOperatorNode rootNode,
                                                                @Assisted("left") IQTree leftChild,
                                                                @Assisted("right") IQTree rightChild,
                                                                IQTreeCache treeCache);

    NaryIQTree createNaryIQTree(NaryOperatorNode rootNode, ImmutableList<IQTree> children);
    NaryIQTree createNaryIQTree(NaryOperatorNode rootNode, ImmutableList<IQTree> children, IQTreeCache treeCache);

    IQ createIQ(DistinctVariableOnlyDataAtom projectionAtom, IQTree tree);

    /**
     * Temporary. IQTreeCache are normally not created from scratch but derived from existing IQTreeCache-s
     */
    @Deprecated
    IQTreeCache createIQTreeCache();

    /**
     * Temporary. IQTreeCache are normally not created from scratch but derived from existing IQTreeCache-s
     */
    @Deprecated
    IQTreeCache createIQTreeCache(boolean isNormalizedForOptimization);
}
