package it.unibz.inf.ontop.model.term.functionsymbol;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import it.unibz.inf.ontop.iq.node.VariableNullability;
import it.unibz.inf.ontop.model.term.*;
import it.unibz.inf.ontop.model.type.TermType;
import it.unibz.inf.ontop.model.type.TermTypeInference;
import it.unibz.inf.ontop.utils.VariableGenerator;

import java.util.Optional;
import java.util.stream.Stream;

/**
 * FunctionSymbols are the functors needed to build ImmutableFunctionalTerms
 */
public interface FunctionSymbol extends Predicate {

    Optional<ImmutableFunctionalTerm.FunctionalTermDecomposition> analyzeInjectivity(
            ImmutableList<? extends ImmutableTerm> arguments,
            ImmutableSet<Variable> nonFreeVariables,
            VariableNullability variableNullability,
            VariableGenerator variableGenerator, TermFactory termFactory);

    FunctionalTermNullability evaluateNullability(ImmutableList<? extends NonFunctionalTerm> arguments,
                               VariableNullability childNullability, TermFactory termFactory);


    Optional<TermTypeInference> inferType(ImmutableList<? extends ImmutableTerm> terms);

    ImmutableTerm simplify(ImmutableList<? extends ImmutableTerm> terms,
                           TermFactory termFactory, VariableNullability variableNullability);

    TermType getExpectedBaseType(int index);

    IncrementalEvaluation evaluateStrictEq(ImmutableList<? extends ImmutableTerm> terms, ImmutableTerm otherTerm,
                                           TermFactory termFactory, VariableNullability variableNullability);

    IncrementalEvaluation evaluateIsNotNull(ImmutableList<? extends ImmutableTerm> terms, TermFactory termFactory,
                                            VariableNullability variableNullability);

    /**
     * 1. When a functional term simplifies itself in a BOTTOM-UP manner:
     *     Returns true if is guaranteed to "simplify itself" as a Constant when receiving Constants as arguments
     *     (outside the optimization phase) .
     *
     * 2.  When a functional term simplifies itself in a TOP-DOWN manner (e.g. IF-THEN-ELSE functional terms):
     *     The permission for post-processing may depend on the ability of sub-functional terms to be post-processed
     *      or safely evaluated by the DB engine.
     *  (Recall that top-down evaluation allows some arguments not to be evaluated when their pre-conditions are not met.
     *   This is particularly valuable for preventing fatal errors).
     *
     */
    boolean canBePostProcessed(ImmutableList<? extends ImmutableTerm> arguments);

    boolean isDeterministic();

    /**
     * Returns true for SUM, AVG, etc.
     */
    boolean isAggregation();

    boolean isNullable(ImmutableSet<Integer> nullableIndexes);

    /**
     * Returns some variables are required to non-null for the functional term to be non-null.
     *
     * The stream is NOT guaranteed to be COMPLETE
     *
     * TODO: find a better name
     */
    Stream<Variable> proposeProvenanceVariables(ImmutableList<? extends ImmutableTerm> terms);

    /**
     * See ImmutableFunctionalTerm.simplifyAsGuaranteedToBeNonNull() for an explanation
     *
     */
    FunctionalTermSimplification simplifyAsGuaranteedToBeNonNull(ImmutableList<? extends ImmutableTerm> terms,
                                                                 TermFactory termFactory);

    /**
     * Returns true if it can be decomposed to be lifted above a UNION.
     *
     * One good reason for not decomposed is when some arguments may have very different types
     * (avoiding strong-typing issues).
     */
    boolean shouldBeDecomposedInUnion();

    /**
     * Informs if injecting input types by casting its arguments with their types can help inferring the datatype.
     * Useful for not running unnecessary inferences.
     */
    default boolean canDeriveTypeFromInputTypes() {
        return false;
    }

    interface FunctionalTermNullability {

        boolean isNullable();

        /**
         * When the nullability of a functional term is bound to the nullability
         * of a variable
         */
        Optional<Variable> getBoundVariable();
    }

}
