package org.optaplanner.core.api.score.stream;

import java.util.stream.Stream;

import org.optaplanner.core.api.domain.constraintweight.ConstraintConfiguration;
import org.optaplanner.core.api.domain.constraintweight.ConstraintWeight;
import org.optaplanner.core.api.domain.entity.PlanningEntity;
import org.optaplanner.core.api.domain.solution.PlanningSolution;
import org.optaplanner.core.api.domain.solution.ProblemFactCollectionProperty;
import org.optaplanner.core.api.domain.solution.ProblemFactProperty;
import org.optaplanner.core.api.score.Score;
import org.optaplanner.core.api.score.ScoreManager;
import org.optaplanner.core.api.score.constraint.ConstraintMatchTotal;
import org.optaplanner.core.api.score.stream.bi.BiConstraintStream;
import org.optaplanner.core.api.score.stream.bi.BiJoiner;
import org.optaplanner.core.api.score.stream.uni.UniConstraintStream;

/**
 * A constraint stream is a declaration on how to match {@link UniConstraintStream one}, {@link BiConstraintStream two}
 * or more objects.
 * Constraint streams are similar to a declaration of a JDK {@link Stream} or an SQL query,
 * but they support incremental score calculation and {@link ScoreManager#explainScore(Object)} score justification}.
 * <p>
 * An object that passes through constraint streams is called a fact.
 * It's either a {@link ProblemFactCollectionProperty problem fact} or a {@link PlanningEntity planning entity}.
 * <p>
 * A constraint stream is typically created with {@link ConstraintFactory#forEach(Class)}
 * or {@link UniConstraintStream#join(UniConstraintStream, BiJoiner)} by joining another constraint stream}.
 * Constraint streams form a directed, non-cyclic graph, with multiple start nodes (which listen to fact changes)
 * and one end node per {@link Constraint} (which affect the {@link Score}).
 * <p>
 * Throughout this documentation, we will be using the following terminology:
 *
 * <dl>
 * <dt>Constraint Stream</dt>
 * <dd>A chain of different operations, originated by {@link ConstraintFactory#forEach(Class)} (or similar
 * methods) and terminated by a penalization or reward operation.</dd>
 * <dt>Operation</dt>
 * <dd>Operations (implementations of {@link ConstraintStream}) are parts of a constraint stream which mutate
 * it.
 * They may remove tuples from further evaluation, expand or contract streams. Every constraint stream has
 * a terminal operation, which is either a penalization or a reward.</dd>
 * <dt>Fact</dt>
 * <dd>Object instance entering the constraint stream.</dd>
 * <dt>Genuine Fact</dt>
 * <dd>Fact that enters the constraint stream either through a from(...) call or through a join(...) call.
 * Genuine facts are either planning entities (see {@link PlanningEntity}) or problem facts (see
 * {@link ProblemFactProperty} or {@link ProblemFactCollectionProperty}).</dd>
 * <dt>Inferred Fact</dt>
 * <dd>Fact that enters the constraint stream through a computation.
 * This would typically happen through an operation such as groupBy(...).</dd>
 * <dt>Tuple</dt>
 * <dd>A collection of facts that the constraint stream operates on, propagating them from operation to
 * operation.
 * For example, {@link UniConstraintStream} operates on single-fact tuples {A} and {@link BiConstraintStream}
 * operates on two-fact tuples {A, B}.
 * Putting facts into a tuple implies a relationship exists between these facts.</dd>
 * <dt>Match</dt>
 * <dd>Match is a tuple that reached the terminal operation of a constraint stream and is therefore either
 * penalized or rewarded.</dd>
 * <dt>Cardinality</dt>
 * <dd>The number of facts in a tuple. Uni constraint streams have a cardinality of 1, bi constraint streams
 * have a cardinality of 2, etc.</dd>
 * <dt>Conversion</dt>
 * <dd>An operation that changes the cardinality of a constraint stream.
 * This typically happens through join(...) or a groupBy(...) operations.</dd>
 * </dl>
 */
public interface ConstraintStream {

    /**
     * The {@link ConstraintFactory} that build this.
     *
     * @return never null
     */
    ConstraintFactory getConstraintFactory();

    // ************************************************************************
    // Penalize/reward
    // ************************************************************************

    /**
     * Negatively impact the {@link Score}: subtract the constraintWeight for each match.
     * <p>
     * To avoid hard-coding the constraintWeight, to allow end-users to tweak it,
     * use {@link #penalizeConfigurable(String)} and a {@link ConstraintConfiguration} instead.
     * <p>
     * The {@link Constraint#getConstraintPackage()} defaults to the package of the {@link PlanningSolution} class.
     *
     * @deprecated Prefer {@link UniConstraintStream#penalize(Score)} and equivalent bi/tri/... overloads.
     * @param constraintName never null, shows up in {@link ConstraintMatchTotal} during score justification
     * @param constraintWeight never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint penalize(String constraintName, Score<?> constraintWeight);

    /**
     * As defined by {@link #penalize(String, Score)}.
     *
     * @deprecated Prefer {@link UniConstraintStream#penalize(Score)} and equivalent bi/tri/... overloads.
     * @param constraintPackage never null
     * @param constraintName never null
     * @param constraintWeight never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint penalize(String constraintPackage, String constraintName, Score<?> constraintWeight);

    /**
     * Negatively impact the {@link Score}: subtract the {@link ConstraintWeight} for each match.
     * <p>
     * The constraintWeight comes from an {@link ConstraintWeight} annotated member on the {@link ConstraintConfiguration},
     * so end users can change the constraint weights dynamically.
     * This constraint may be deactivated if the {@link ConstraintWeight} is zero.
     * If there is no {@link ConstraintConfiguration}, use {@link #penalize(String, Score)} instead.
     * <p>
     * The {@link Constraint#getConstraintPackage()} defaults to {@link ConstraintConfiguration#constraintPackage()}.
     *
     * @deprecated Prefer {@link UniConstraintStream#penalizeConfigurable()} and equivalent bi/tri/... overloads.
     * @param constraintName never null, shows up in {@link ConstraintMatchTotal} during score justification
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint penalizeConfigurable(String constraintName);

    /**
     * As defined by {@link #penalizeConfigurable(String)}.
     *
     * @deprecated Prefer {@link UniConstraintStream#penalizeConfigurable()} and equivalent bi/tri/... overloads.
     * @param constraintPackage never null
     * @param constraintName never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint penalizeConfigurable(String constraintPackage, String constraintName);

    /**
     * Positively impact the {@link Score}: add the constraintWeight for each match.
     * <p>
     * To avoid hard-coding the constraintWeight, to allow end-users to tweak it,
     * use {@link #penalizeConfigurable(String)} and a {@link ConstraintConfiguration} instead.
     * <p>
     * The {@link Constraint#getConstraintPackage()} defaults to the package of the {@link PlanningSolution} class.
     *
     * @deprecated Prefer {@link UniConstraintStream#reward(Score)} and equivalent bi/tri/... overloads.
     * @param constraintName never null, shows up in {@link ConstraintMatchTotal} during score justification
     * @param constraintWeight never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint reward(String constraintName, Score<?> constraintWeight);

    /**
     * As defined by {@link #reward(String, Score)}.
     *
     * @deprecated Prefer {@link UniConstraintStream#reward(Score)} and equivalent bi/tri/... overloads.
     * @param constraintPackage never null
     * @param constraintName never null
     * @param constraintWeight never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint reward(String constraintPackage, String constraintName, Score<?> constraintWeight);

    /**
     * Positively impact the {@link Score}: add the {@link ConstraintWeight} for each match.
     * <p>
     * The constraintWeight comes from an {@link ConstraintWeight} annotated member on the {@link ConstraintConfiguration},
     * so end users can change the constraint weights dynamically.
     * This constraint may be deactivated if the {@link ConstraintWeight} is zero.
     * If there is no {@link ConstraintConfiguration}, use {@link #reward(String, Score)} instead.
     * <p>
     * The {@link Constraint#getConstraintPackage()} defaults to {@link ConstraintConfiguration#constraintPackage()}.
     *
     * @deprecated Prefer {@link UniConstraintStream#rewardConfigurable()} and equivalent bi/tri/... overloads.
     * @param constraintName never null, shows up in {@link ConstraintMatchTotal} during score justification
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint rewardConfigurable(String constraintName);

    /**
     * As defined by {@link #rewardConfigurable(String)}.
     *
     * @deprecated Prefer {@link UniConstraintStream#rewardConfigurable()} and equivalent bi/tri/... overloads.
     * @param constraintPackage never null
     * @param constraintName never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint rewardConfigurable(String constraintPackage, String constraintName);

    /**
     * Positively or negatively impact the {@link Score} by the constraintWeight for each match.
     * <p>
     * Use {@code penalize(...)} or {@code reward(...)} instead, unless this constraint can both have positive and
     * negative weights.
     * <p>
     * The {@link Constraint#getConstraintPackage()} defaults to the package of the {@link PlanningSolution} class.
     *
     * @deprecated Prefer {@link UniConstraintStream#impact(Score)} and equivalent bi/tri/... overloads.
     * @param constraintName never null, shows up in {@link ConstraintMatchTotal} during score justification
     * @param constraintWeight never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint impact(String constraintName, Score<?> constraintWeight);

    /**
     * As defined by {@link #impact(String, Score)}.
     *
     * @deprecated Prefer {@link UniConstraintStream#impact(Score)} and equivalent bi/tri/... overloads.
     * @param constraintPackage never null
     * @param constraintName never null
     * @param constraintWeight never null
     * @return never null
     */
    @Deprecated(forRemoval = true)
    Constraint impact(String constraintPackage, String constraintName, Score<?> constraintWeight);

}
