/* gtkconstraintexpression.c: Constraint expressions and variables
 * Copyright 2019  GNOME Foundation
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Author: Emmanuele Bassi
 */

#include "config.h"

#include "gtkconstraintexpressionprivate.h"
#include "gtkconstraintsolverprivate.h"

/* {{{ Variables */

typedef enum {
  GTK_CONSTRAINT_SYMBOL_DUMMY         = 'd',
  GTK_CONSTRAINT_SYMBOL_OBJECTIVE     = 'o',
  GTK_CONSTRAINT_SYMBOL_SLACK         = 'S',
  GTK_CONSTRAINT_SYMBOL_REGULAR       = 'v'
} GtkConstraintSymbolType;

struct _GtkConstraintVariable
{
  guint64 _id;

  GtkConstraintSymbolType _type;

  /* Interned strings */
  const char *name;
  const char *prefix;

  double value;

  guint is_external : 1;
  guint is_pivotable : 1;
  guint is_restricted : 1;
};

/* Variables are sorted by a monotonic id */
static guint64 gtk_constraint_variable_next_id;

static void
gtk_constraint_variable_init (GtkConstraintVariable *variable,
                              const char *prefix,
                              const char *name)
{
  variable->_id = gtk_constraint_variable_next_id++;

  variable->prefix = g_intern_string (prefix);
  variable->name = g_intern_string (name);
  variable->prefix = NULL;
  variable->value = 0.0;
}

/*< private >
 * gtk_constraint_variable_new_dummy:
 * @name: the name of the variable
 *
 * Allocates and initializes a new #GtkConstraintVariable for a "dummy"
 * symbol. Dummy symbols are typically used as markers inside a solver,
 * and will not be factored in the solution when pivoting the tableau
 * of the constraint equations.
 *
 * Only #GtkConstraintSolver should use this function.
 *
 * Returns: a newly allocated #GtkConstraintVariable
 */
GtkConstraintVariable *
gtk_constraint_variable_new_dummy (const char *name)
{
  GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);

  gtk_constraint_variable_init (res, NULL, name);

  res->_type = GTK_CONSTRAINT_SYMBOL_DUMMY;
  res->is_external = FALSE;
  res->is_pivotable = FALSE;
  res->is_restricted = TRUE;

  return res;
}

/*< private >
 * gtk_constraint_variable_new_objective:
 * @name: the name of the variable
 *
 * Allocates and initializes a new #GtkConstraintVariable for an objective
 * symbol. This is the constant value we wish to find as the result of the
 * simplex optimization.
 *
 * Only #GtkConstraintSolver should use this function.
 *
 * Returns: a newly allocated #GtkConstraintVariable
 */
GtkConstraintVariable *
gtk_constraint_variable_new_objective (const char *name)
{
  GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);

  gtk_constraint_variable_init (res, NULL, name);

  res->_type = GTK_CONSTRAINT_SYMBOL_OBJECTIVE;
  res->is_external = FALSE;
  res->is_pivotable = FALSE;
  res->is_restricted = FALSE;

  return res;
}

/*< private >
 * gtk_constraint_variable_new_slack:
 * @name: the name of the variable
 *
 * Allocates and initializes a new #GtkConstraintVariable for a "slack"
 * symbol. Slack variables are introduced inside the tableau to turn
 * inequalities, like:
 *
 * |[
 *   expr ≥ 0
 * ]|
 *
 * Into equalities, like:
 *
 * |[
 *   expr - slack = 0
 * ]|
 *
 * Only #GtkConstraintSolver should use this function.
 *
 * Returns: a newly allocated #GtkConstraintVariable
 */
GtkConstraintVariable *
gtk_constraint_variable_new_slack (const char *name)
{
  GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);

  gtk_constraint_variable_init (res, NULL, name);

  res->_type = GTK_CONSTRAINT_SYMBOL_SLACK;
  res->is_external = FALSE;
  res->is_pivotable = TRUE;
  res->is_restricted = TRUE;

  return res;
}

/*< private >
 * gtk_constraint_variable_new:
 * @prefix: (nullable): an optional prefix string for @name
 * @name: (nullable): an optional name for the variable
 *
 * Allocates and initializes a new #GtkConstraintVariable for a regular
 * symbol. All variables introduced by constraints are regular variables.
 *
 * Only #GtkConstraintSolver should use this function; a constraint layout
 * manager should ask the #GtkConstraintSolver to create a variable, using
 * gtk_constraint_solver_create_variable(), which will insert the variable
 * in the solver's tableau.
 *
 * Returns: a newly allocated #GtkConstraintVariable
 */
GtkConstraintVariable *
gtk_constraint_variable_new (const char *prefix,
                             const char *name)
{
  GtkConstraintVariable *res = g_rc_box_new (GtkConstraintVariable);

  gtk_constraint_variable_init (res, prefix, name);

  res->_type = GTK_CONSTRAINT_SYMBOL_REGULAR;
  res->is_external = TRUE;
  res->is_pivotable = FALSE;
  res->is_restricted = FALSE;

  return res;
}

/*< private >
 * gtk_constraint_variable_ref:
 * @variable: a #GtkConstraintVariable
 *
 * Acquires a reference to @variable.
 *
 * Returns: (transfer full): the given #GtkConstraintVariable, with its reference
 *   count increased
 */
GtkConstraintVariable *
gtk_constraint_variable_ref (GtkConstraintVariable *variable)
{
  g_return_val_if_fail (variable != NULL, NULL);

  return g_rc_box_acquire (variable);
}

/*< private >
 * gtk_constraint_variable_unref:
 * @variable: (transfer full): a #GtkConstraintVariable
 *
 * Releases a reference to @variable.
 */
void
gtk_constraint_variable_unref (GtkConstraintVariable *variable)
{
  g_return_if_fail (variable != NULL);

  g_rc_box_release (variable);
}

/*< private >
 * gtk_constraint_variable_set_value:
 * @variable: a #GtkConstraintVariable
 *
 * Sets the current value of a #GtkConstraintVariable.
 */
void
gtk_constraint_variable_set_value (GtkConstraintVariable *variable,
                                   double value)
{
  variable->value = value;
}

/*< private >
 * gtk_constraint_variable_get_value:
 * @variable: a #GtkConstraintVariable
 *
 * Retrieves the current value of a #GtkConstraintVariable
 *
 * Returns: the value of the variable
 */
double
gtk_constraint_variable_get_value (const GtkConstraintVariable *variable)
{
  return variable->value;
}

/*< private >
 * gtk_constraint_variable_to_string:
 * @variable: a #GtkConstraintVariable
 *
 * Turns @variable into a string, for debugging purposes.
 *
 * Returns: (transfer full): a string with the contents of @variable
 */
char *
gtk_constraint_variable_to_string (const GtkConstraintVariable *variable)
{
  GString *buf = g_string_new (NULL);

  if (variable == NULL)
    g_string_append (buf, "<null>");
  else
    {
      switch (variable->_type)
        {
        case GTK_CONSTRAINT_SYMBOL_DUMMY:
          g_string_append (buf, "(d)");
          break;
        case GTK_CONSTRAINT_SYMBOL_OBJECTIVE:
          g_string_append (buf, "(O)");
          break;
        case GTK_CONSTRAINT_SYMBOL_SLACK:
          g_string_append (buf, "(S)");
          break;
        case GTK_CONSTRAINT_SYMBOL_REGULAR:
          break;

        default:
          g_assert_not_reached ();
        }

      g_string_append_c (buf, '[');

      if (variable->prefix != NULL)
        {
          g_string_append (buf, variable->prefix);
          g_string_append_c (buf, '.');
        }

      if (variable->name != NULL)
        g_string_append (buf, variable->name);

      if (variable->_type == GTK_CONSTRAINT_SYMBOL_REGULAR)
        {
          char dbl_buf[G_ASCII_DTOSTR_BUF_SIZE];

          g_ascii_dtostr (dbl_buf, G_ASCII_DTOSTR_BUF_SIZE, variable->value);

          g_string_append_c (buf, ':');
          g_string_append (buf, dbl_buf);
        }

      g_string_append_c (buf, ']');
    }

  return g_string_free (buf, FALSE);
}

/*< private >
 * gtk_constraint_variable_is_external:
 * @variable: a #GtkConstraintVariable
 *
 * Checks whether the @variable was introduced from outside the solver.
 *
 * Returns: %TRUE if the variable is external
 */
gboolean
gtk_constraint_variable_is_external (const GtkConstraintVariable *variable)
{
  return variable->is_external;
}

/*< private >
 * gtk_constraint_variable_is_pivotable:
 * @variable: a #GtkConstraintVariable
 *
 * Checks whether the @variable can be used as a pivot.
 *
 * Returns: %TRUE if the variable is pivotable
 */
gboolean
gtk_constraint_variable_is_pivotable (const GtkConstraintVariable *variable)
{
  return variable->is_pivotable;
}

/*< private >
 * gtk_constraint_variable_is_restricted:
 * @variable: a #GtkConstraintVariable
 *
 * Checks whether the @variable's use is restricted.
 *
 * Returns: %TRUE if the variable is restricted
 */
gboolean
gtk_constraint_variable_is_restricted (const GtkConstraintVariable *variable)
{
  return variable->is_restricted;
}

/*< private >
 * gtk_constraint_variable_is_dummy:
 * @variable: a #GtkConstraintVariable
 *
 * Checks whether the @variable is a dummy symbol.
 *
 * Returns: %TRUE if the variable is a dummy symbol
 */
gboolean
gtk_constraint_variable_is_dummy (const GtkConstraintVariable *variable)
{
  return variable->_type == GTK_CONSTRAINT_SYMBOL_DUMMY;
}

/*< private >
 * GtkConstraintVariableSet:
 *
 * A set of variables.
 */
struct _GtkConstraintVariableSet {
  /* List<Variable>, owns a reference */
  GSequence *set;

  /* Age of the set, to guard against mutations while iterating */
  gint64 age;
};

/*< private >
 * gtk_constraint_variable_set_free:
 * @set: a #GtkConstraintVariableSet
 *
 * Frees the resources associated to a #GtkConstraintVariableSet/
 */
void
gtk_constraint_variable_set_free (GtkConstraintVariableSet *set)
{
  g_return_if_fail (set != NULL);

  g_sequence_free (set->set);

  g_free (set);
}

/*< private >
 * gtk_constraint_variable_set_new:
 *
 * Creates a new #GtkConstraintVariableSet.
 *
 * Returns: the newly created variable set
 */
GtkConstraintVariableSet *
gtk_constraint_variable_set_new (void)
{
  GtkConstraintVariableSet *res = g_new (GtkConstraintVariableSet, 1);

  res->set = g_sequence_new ((GDestroyNotify) gtk_constraint_variable_unref);

  res->age = 0;

  return res;
}

static int
sort_by_variable_id (gconstpointer a,
                     gconstpointer b,
                     gpointer      data)
{
  const GtkConstraintVariable *va = a, *vb = b;

  if (va == vb)
    return 0;

  return va->_id - vb->_id;
}

/*< private >
 * gtk_constraint_variable_set_add:
 * @set: a #GtkConstraintVariableSet
 * @variable: a #GtkConstraintVariable
 *
 * Adds @variable to the given @set, if the @variable is not already
 * in it.
 *
 * The @set will acquire a reference on the @variable, and will release
 * it after calling gtk_constraint_variable_set_remove(), or when the @set
 * is freed.
 *
 * Returns: %TRUE if the variable was added to the set, and %FALSE otherwise
 */
gboolean
gtk_constraint_variable_set_add (GtkConstraintVariableSet *set,
                                 GtkConstraintVariable *variable)
{
  GSequenceIter *iter;

  iter = g_sequence_search (set->set, variable, sort_by_variable_id, NULL);
  if (!g_sequence_iter_is_end (iter))
    {
      GtkConstraintVariable *v = g_sequence_get (iter);
      if (v->_id == variable->_id)
        return FALSE;
    }

  g_sequence_insert_before (iter, gtk_constraint_variable_ref (variable));

  set->age += 1;

  return TRUE;
}

/*< private >
 * gtk_constraint_variable_set_remove:
 * @set: a #GtkConstraintVariableSet
 * @variable: a #GtkConstraintVariable
 *
 * Removes @variable from the @set.
 *
 * This function will release the reference on @variable held by the @set.
 *
 * Returns: %TRUE if the variable was removed from the set, and %FALSE
 *   otherwise
 */
gboolean
gtk_constraint_variable_set_remove (GtkConstraintVariableSet *set,
                                    GtkConstraintVariable *variable)
{
  GSequenceIter *iter;

  iter = g_sequence_lookup (set->set, variable, sort_by_variable_id, NULL);
  if (iter != NULL)
    {
      g_sequence_remove (iter);
      set->age += 1;

      return TRUE;
    }

  return FALSE;
}

/*< private >
 * gtk_constraint_variable_set_size:
 * @set: a #GtkConstraintVariableSet
 *
 * Retrieves the size of the @set.
 *
 * Returns: the number of variables in the set
 */
int
gtk_constraint_variable_set_size (GtkConstraintVariableSet *set)
{
  return g_sequence_get_length (set->set);
}

gboolean
gtk_constraint_variable_set_is_empty (GtkConstraintVariableSet *set)
{
  return g_sequence_is_empty (set->set);
}

gboolean
gtk_constraint_variable_set_is_singleton (GtkConstraintVariableSet *set)
{
  return g_sequence_iter_next (g_sequence_get_begin_iter (set->set)) == g_sequence_get_end_iter (set->set);
}

/*< private >
 * GtkConstraintVariableSetIter:
 *
 * An iterator type for #GtkConstraintVariableSet.
 */
/* Keep in sync with GtkConstraintVariableSetIter */
typedef struct {
  GtkConstraintVariableSet *set;
  GSequenceIter *iter;
  gint64 age;
} RealVariableSetIter;

#define REAL_VARIABLE_SET_ITER(i)       ((RealVariableSetIter *) (i))

/*< private >
 * gtk_constraint_variable_set_iter_init:
 * @iter: a #GtkConstraintVariableSetIter
 * @set: the #GtkConstraintVariableSet to iterate
 *
 * Initializes @iter for iterating over @set.
 */
void
gtk_constraint_variable_set_iter_init (GtkConstraintVariableSetIter *iter,
                                       GtkConstraintVariableSet *set)
{
  RealVariableSetIter *riter = REAL_VARIABLE_SET_ITER (iter);

  g_return_if_fail (iter != NULL);
  g_return_if_fail (set != NULL);

  riter->set = set;
  riter->iter = g_sequence_get_begin_iter (set->set);
  riter->age = set->age;
}

/*< private >
 * gtk_constraint_variable_set_iter_next:
 * @iter: a #GtkConstraintVariableSetIter
 * @variable_p: (out): the next variable in the set
 *
 * Advances the @iter to the next variable in the #GtkConstraintVariableSet.
 *
 * Returns: %TRUE if the iterator was advanced, and %FALSE otherwise
 */
gboolean
gtk_constraint_variable_set_iter_next (GtkConstraintVariableSetIter *iter,
                                       GtkConstraintVariable **variable_p)
{
  RealVariableSetIter *riter = REAL_VARIABLE_SET_ITER (iter);

  g_return_val_if_fail (iter != NULL, FALSE);
  g_return_val_if_fail (variable_p != NULL, FALSE);

  g_assert (riter->age == riter->set->age);

  if (g_sequence_iter_is_end (riter->iter))
    return FALSE;

  *variable_p = g_sequence_get (riter->iter);
  riter->iter = g_sequence_iter_next (riter->iter);

  return TRUE;
}

/*< private >
 * gtk_constraint_variable_pair_new:
 * @first: a #GtkConstraintVariable
 * @second: a #GtkConstraintVariable
 *
 * Creates a new #GtkConstraintVariablePair, containing @first and @second.
 *
 * The #GtkConstraintVariablePair acquires a reference over the two
 * given #GtkConstraintVariables.
 *
 * Returns: a new #GtkConstraintVariablePair
 */
GtkConstraintVariablePair *
gtk_constraint_variable_pair_new (GtkConstraintVariable *first,
                                  GtkConstraintVariable *second)
{
  GtkConstraintVariablePair *res = g_new (GtkConstraintVariablePair, 1);

  res->first = gtk_constraint_variable_ref (first);
  res->second = gtk_constraint_variable_ref (second);

  return res;
}

/*< private >
 * gtk_constraint_variable_pair_free:
 * @pair: a #GtkConstraintVariablePair
 *
 * Frees the resources associated by @pair.
 */
void
gtk_constraint_variable_pair_free (GtkConstraintVariablePair *pair)
{
  g_clear_pointer (&pair->first, gtk_constraint_variable_unref);
  g_clear_pointer (&pair->second, gtk_constraint_variable_unref);

  g_free (pair);
}

/* }}} */

/* {{{ Expressions */

/*< private >
 * Term:
 * @variable: a #GtkConstraintVariable
 * @coefficient: the coefficient applied to the @variable
 * @next: the next term in the expression
 * @prev: the previous term in the expression;
 *
 * A tuple of (@variable, @coefficient) in an equation.
 *
 * The term acquires a reference on the variable.
 */
typedef struct _Term Term;

struct _Term {
  GtkConstraintVariable *variable;
  double coefficient;

  Term *next;
  Term *prev;
};

static Term *
term_new (GtkConstraintVariable *variable,
          double coefficient)
{
  Term *res = g_new (Term, 1);

  res->variable = gtk_constraint_variable_ref (variable);
  res->coefficient = coefficient;
  res->next = res->prev = NULL;

  return res;
}

static void
term_free (gpointer data)
{
  Term *term = data;

  if (term == NULL)
    return;

  gtk_constraint_variable_unref (term->variable);

  g_free (term);
}

struct _GtkConstraintExpression
{
  double constant;

  /* HashTable<Variable, Term>; the key is the term's variable,
   * and the value is owned by the hash table
   */
  GHashTable *terms;

  /* List of terms, in insertion order */
  Term *first_term;
  Term *last_term;

  /* Used by GtkConstraintExpressionIter to guard against changes
   * in the expression while iterating
   */
  gint64 age;
};

/*< private >
 * gtk_constraint_expression_add_term:
 * @self: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable
 * @coefficient: a coefficient for @variable
 *
 * Adds a new term formed by (@variable, @coefficient) into a
 * #GtkConstraintExpression.
 *
 * The @expression acquires a reference on @variable.
 */
static void
gtk_constraint_expression_add_term (GtkConstraintExpression *self,
                                    GtkConstraintVariable *variable,
                                    double coefficient)
{
  Term *term;

  if (self->terms == NULL)
    {
      g_assert (self->first_term == NULL && self->last_term == NULL);
      self->terms = g_hash_table_new_full (NULL, NULL,
                                           NULL,
                                           term_free);
    }

  term = term_new (variable, coefficient);

  g_hash_table_insert (self->terms, term->variable, term);

  if (self->first_term == NULL)
    self->first_term = term;

  term->prev = self->last_term;

  if (self->last_term != NULL)
    self->last_term->next = term;

  self->last_term = term;

  /* Increase the age of the expression, so that we can catch
   * mutations from within an iteration over the terms
   */
  self->age += 1;
}

static void
gtk_constraint_expression_remove_term (GtkConstraintExpression *self,
                                       GtkConstraintVariable *variable)
{
  Term *term, *iter;

  if (self->terms == NULL)
    return;

  term = g_hash_table_lookup (self->terms, variable);
  if (term == NULL)
    return;

  /* Keep the variable alive for the duration of the function */
  gtk_constraint_variable_ref (variable);

  iter = self->first_term;
  while (iter != NULL)
    {
      Term *next = iter->next;
      Term *prev = iter->prev;

      if (iter == term)
        {
          if (prev != NULL)
            prev->next = next;
          if (next != NULL)
            next->prev = prev;

          if (iter == self->first_term)
            self->first_term = next;
          if (iter == self->last_term)
            self->last_term = prev;

          iter->next = NULL;
          iter->prev = NULL;

          break;
        }

      iter = next;
    }

  g_hash_table_remove (self->terms, variable);

  gtk_constraint_variable_unref (variable);

  self->age += 1;
}

/*< private >
 * gtk_constraint_expression_new:
 * @constant: a constant for the expression
 *
 * Creates a new #GtkConstraintExpression with the given @constant.
 *
 * Returns: (transfer full): the newly created expression
 */
GtkConstraintExpression *
gtk_constraint_expression_new (double constant)
{
  GtkConstraintExpression *res = g_rc_box_new (GtkConstraintExpression);

  res->age = 0;
  res->terms = NULL;
  res->first_term = NULL;
  res->last_term = NULL;
  res->constant = constant;

  return res;
}

/*< private >
 * gtk_constraint_expression_new_from_variable:
 * @variable: a #GtkConstraintVariable
 *
 * Creates a new #GtkConstraintExpression with the given @variable.
 *
 * Returns: (transfer full): the newly created expression
 */
GtkConstraintExpression *
gtk_constraint_expression_new_from_variable (GtkConstraintVariable *variable)
{
  GtkConstraintExpression *res = gtk_constraint_expression_new (0.0);

  gtk_constraint_expression_add_term (res, variable, 1.0);

  return res;
}

/*< private >
 * gtk_constraint_expression_ref:
 * @expression: a #GtkConstraintExpression
 *
 * Acquires a reference on @expression.
 *
 * Returns: (transfer full): the @expression, with its reference
 *   count increased
 */
GtkConstraintExpression *
gtk_constraint_expression_ref (GtkConstraintExpression *expression)
{
  g_return_val_if_fail (expression != NULL, NULL);

  return g_rc_box_acquire (expression);
}

static void
gtk_constraint_expression_clear (gpointer data)
{
  GtkConstraintExpression *self = data;

  g_clear_pointer (&self->terms, g_hash_table_unref);

  self->age = 0;
  self->constant = 0.0;
  self->first_term = NULL;
  self->last_term = NULL;
}

/*< private >
 * gtk_constraint_expression_unref:
 * @expression: (transfer full): a #GtkConstraintExpression
 *
 * Releases a reference on @expression.
 */
void
gtk_constraint_expression_unref (GtkConstraintExpression *expression)
{
  g_rc_box_release_full (expression, gtk_constraint_expression_clear);
}

/*< private >
 * gtk_constraint_expression_is_constant:
 * @expression: a #GtkConstraintExpression
 *
 * Checks whether @expression is a constant value, with no variable terms.
 *
 * Returns: %TRUE if the @expression is a constant
 */
gboolean
gtk_constraint_expression_is_constant (const GtkConstraintExpression *expression)
{
  return expression->terms == NULL;
}

/*< private >
 * gtk_constraint_expression_set_constant:
 * @expression: a #GtkConstraintExpression
 * @constant: the value of the constant
 *
 * Sets the value of the constant part of @expression.
 */
void
gtk_constraint_expression_set_constant (GtkConstraintExpression *expression,
                                        double constant)
{
  g_return_if_fail (expression != NULL);

  expression->constant = constant;
}

/*< private >
 * gtk_constraint_expression_get_constant:
 * @expression: a #GtkConstraintExpression
 *
 * Retrieves the constant value of @expression.
 *
 * Returns: the constant of @expression
 */
double
gtk_constraint_expression_get_constant (const GtkConstraintExpression *expression)
{
  g_return_val_if_fail (expression != NULL, 0.0);

  return expression->constant;
}

GtkConstraintExpression *
gtk_constraint_expression_clone (GtkConstraintExpression *expression)
{
  GtkConstraintExpression *res;
  Term *iter;

  res = gtk_constraint_expression_new (expression->constant);

  iter = expression->first_term;
  while (iter != NULL)
    {
      gtk_constraint_expression_add_term (res, iter->variable, iter->coefficient);

      iter = iter->next;
    }

  return res;
}

/*< private >
 * gtk_constraint_expression_add_variable:
 * @expression: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable to add to @expression
 * @coefficient: the coefficient of @variable
 * @subject: (nullable): a #GtkConstraintVariable
 * @solver: (nullable): a #GtkConstraintSolver
 *
 * Adds a `(@coefficient × @variable)` term to @expression.
 *
 * If @expression already contains a term for @variable, this function will
 * update its coefficient.
 *
 * If @coefficient is 0 and @expression already contains a term for @variable,
 * the term for @variable will be removed.
 *
 * This function will notify @solver if @variable is added or removed from
 * the @expression.
 */
void
gtk_constraint_expression_add_variable (GtkConstraintExpression *expression,
                                        GtkConstraintVariable *variable,
                                        double coefficient,
                                        GtkConstraintVariable *subject,
                                        GtkConstraintSolver *solver)
{
  /* If the expression already contains the variable, update the coefficient */
  if (expression->terms != NULL)
    {
      Term *t = g_hash_table_lookup (expression->terms, variable);

      if (t != NULL)
        {
          double new_coefficient = t->coefficient + coefficient;

          /* Setting the coefficient to 0 will remove the variable */
          if (G_APPROX_VALUE (new_coefficient, 0.0, 0.001))
            {
              /* Update the tableau if needed */
              if (solver != NULL)
                gtk_constraint_solver_note_removed_variable (solver, variable, subject);

              gtk_constraint_expression_remove_term (expression, variable);
            }
          else
            {
              t->coefficient = new_coefficient;
            }

          return;
        }
    }

  /* Otherwise, add the variable if the coefficient is non-zero */
  if (!G_APPROX_VALUE (coefficient, 0.0, 0.001))
    {
      gtk_constraint_expression_add_term (expression, variable, coefficient);

      if (solver != NULL)
        gtk_constraint_solver_note_added_variable (solver, variable, subject);
    }
}

/*< private >
 * gtk_constraint_expression_remove_variable:
 * @expression: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable
 *
 * Removes @variable from @expression.
 */
void
gtk_constraint_expression_remove_variable (GtkConstraintExpression *expression,
                                           GtkConstraintVariable *variable)
{
  g_return_if_fail (expression != NULL);
  g_return_if_fail (variable != NULL);

  gtk_constraint_expression_remove_term (expression, variable);
}

/*< private >
 * gtk_constraint_expression_set_variable:
 * @expression: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable
 * @coefficient: a coefficient for @variable
 *
 * Sets the @coefficient for @variable inside an @expression.
 *
 * If the @expression does not contain a term for @variable, a new
 * one will be added.
 */
void
gtk_constraint_expression_set_variable (GtkConstraintExpression *expression,
                                        GtkConstraintVariable *variable,
                                        double coefficient)
{
  if (expression->terms != NULL)
    {
      Term *t = g_hash_table_lookup (expression->terms, variable);

      if (t != NULL)
        {
          t->coefficient = coefficient;
          return;
        }
    }

  gtk_constraint_expression_add_term (expression, variable, coefficient);
}

/*< private >
 * gtk_constraint_expression_add_expression:
 * @a_expr: first operand
 * @b_expr: second operand
 * @n: the multiplication factor for @b_expr
 * @subject: (nullable): a #GtkConstraintVariable
 * @solver: (nullable): a #GtkConstraintSolver
 *
 * Adds `(@n × @b_expr)` to @a_expr.
 *
 * Typically, this function is used to turn two expressions in the
 * form:
 *
 * |[
 *   a.x + a.width = b.x + b.width
 * ]|
 *
 * into a single expression:
 *
 * |[
 *   a.x + a.width - b.x - b.width = 0
 * ]|
 *
 * If @solver is not %NULL, this function will notify a #GtkConstraintSolver
 * of every variable that was added or removed from @a_expr.
 */
void
gtk_constraint_expression_add_expression (GtkConstraintExpression *a_expr,
                                          GtkConstraintExpression *b_expr,
                                          double n,
                                          GtkConstraintVariable *subject,
                                          GtkConstraintSolver *solver)
{
  Term *iter;

  a_expr->constant += (n * b_expr->constant);

  iter = b_expr->last_term;
  while (iter != NULL)
    {
      Term *next = iter->prev;

      gtk_constraint_expression_add_variable (a_expr,
                                              iter->variable, n * iter->coefficient,
                                              subject,
                                              solver);

      iter = next;
    }
}

/*< private >
 * gtk_constraint_expression_plus_constant:
 * @expression: a #GtkConstraintExpression
 * @constant: a constant value
 *
 * Adds a @constant value to the @expression.
 *
 * This is the equivalent of creating a new #GtkConstraintExpression for
 * the @constant and calling gtk_constraint_expression_add_expression().
 *
 * Returns: the @expression
 */
GtkConstraintExpression *
gtk_constraint_expression_plus_constant (GtkConstraintExpression *expression,
                                         double constant)
{
  GtkConstraintExpression *e;

  e = gtk_constraint_expression_new (constant);
  gtk_constraint_expression_add_expression (expression, e, 1.0, NULL, NULL);
  gtk_constraint_expression_unref (e);

  return expression;
}

/*< private >
 * gtk_constraint_expression_minus_constant:
 * @expression: a #GtkConstraintExpression
 * @constant: a constant value
 *
 * Removes a @constant value from the @expression.
 *
 * This is the equivalent of creating a new #GtkConstraintExpression for
 * the inverse of @constant and calling gtk_constraint_expression_add_expression().
 *
 * Returns: the @expression
 */
GtkConstraintExpression *
gtk_constraint_expression_minus_constant (GtkConstraintExpression *expression,
                                          double constant)
{
  return gtk_constraint_expression_plus_constant (expression, constant * -1.0);
}

/*< private >
 * gtk_constraint_expression_plus_variable:
 * @expression: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable
 *
 * Adds a @variable to the @expression.
 *
 * Returns: the @expression
 */
GtkConstraintExpression *
gtk_constraint_expression_plus_variable (GtkConstraintExpression *expression,
                                         GtkConstraintVariable *variable)
{
  GtkConstraintExpression *e;

  e = gtk_constraint_expression_new_from_variable (variable);
  gtk_constraint_expression_add_expression (expression, e, 1.0, NULL, NULL);
  gtk_constraint_expression_unref (e);

  return expression;
}

/*< private >
 * gtk_constraint_expression_minus_variable:
 * @expression: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable
 *
 * Subtracts a @variable from the @expression.
 *
 * Returns: the @expression
 */
GtkConstraintExpression *
gtk_constraint_expression_minus_variable (GtkConstraintExpression *expression,
                                          GtkConstraintVariable *variable)
{
  GtkConstraintExpression *e;

  e = gtk_constraint_expression_new_from_variable (variable);
  gtk_constraint_expression_add_expression (expression, e, -1.0, NULL, NULL);
  gtk_constraint_expression_unref (e);

  return expression;
}

/*< private >
 * gtk_constraint_expression_multiply_by:
 * @expression: a #GtkConstraintExpression
 * @factor: the multiplication factor
 *
 * Multiplies the constant part and the coefficient of all terms
 * in @expression with the given @factor.
 *
 * Returns: the @expression
 */
GtkConstraintExpression *
gtk_constraint_expression_multiply_by (GtkConstraintExpression *expression,
                                       double factor)
{
  GHashTableIter iter;
  gpointer value_p;

  expression->constant *= factor;

  if (expression->terms == NULL)
    return expression;

  g_hash_table_iter_init (&iter, expression->terms);
  while (g_hash_table_iter_next (&iter, NULL, &value_p))
    {
      Term *t = value_p;

      t->coefficient *= factor;
    }

  return expression;
}

/*< private >
 * gtk_constraint_expression_divide_by:
 * @expression: a #GtkConstraintExpression
 * @factor: the division factor
 *
 * Divides the constant part and the coefficient of all terms
 * in @expression by the given @factor.
 *
 * Returns: the @expression
 */
GtkConstraintExpression *
gtk_constraint_expression_divide_by (GtkConstraintExpression *expression,
                                     double factor)
{
  if (G_APPROX_VALUE (factor, 0.0, 0.001))
    return expression;

  return gtk_constraint_expression_multiply_by (expression, 1.0 / factor);
}

/*< private >
 * gtk_constraint_expression_new_subject:
 * @expression: a #GtkConstraintExpression
 * @subject: a #GtkConstraintVariable part of @expression
 *
 * Modifies @expression to have a new @subject.
 *
 * A #GtkConstraintExpression is a linear expression in the form of
 * `@expression = 0`. If @expression contains @subject, for instance:
 *
 * |[
 *   c + (a × @subject) + (a1 × v1) + … + (an × vn) = 0
 * ]|
 *
 * this function will make @subject the new subject of the expression:
 *
 * |[
 *   subject = - (c / a) - ((a1 / a) × v1) - … - ((an / a) × vn) = 0
 * ]|
 *
 * The term @subject is removed from the @expression.
 *
 * Returns: the reciprocal of the coefficient of @subject, so we
 *   can use this function in gtk_constraint_expression_change_subject()
 */
double
gtk_constraint_expression_new_subject (GtkConstraintExpression *expression,
                                       GtkConstraintVariable *subject)
{
  double reciprocal = 1.0;
  Term *term;

  g_assert (!gtk_constraint_expression_is_constant (expression));

  term = g_hash_table_lookup (expression->terms, subject);
  g_assert (term != NULL);
  g_assert (!G_APPROX_VALUE (term->coefficient, 0.0, 0.001));

  reciprocal = 1.0 / term->coefficient;

  gtk_constraint_expression_remove_term (expression, subject);
  gtk_constraint_expression_multiply_by (expression, -reciprocal);

  return reciprocal;
}

/*< private >
 * gtk_constraint_expression_change_subject:
 * @expression: a #GtkConstraintExpression
 * @old_subject: the old subject #GtkConstraintVariable of @expression
 * @new_subject: the new subject #GtkConstraintVariable of @expression
 *
 * Turns an @expression in the form of:
 *
 * |[
 *   old_subject = c + (a × new_subject) + (a1 × v1) + … + (an × vn)
 * ]|
 *
 * into the form of:
 *
 * |[
 *   new_subject = -c / a + old_subject / a - ((a1 / a) × v1) - … - ((an / a) × vn)
 * ]|
 *
 * Which means resolving @expression for @new_subject.
 */
void
gtk_constraint_expression_change_subject (GtkConstraintExpression *expression,
                                          GtkConstraintVariable *old_subject,
                                          GtkConstraintVariable *new_subject)
{
  double reciprocal;

  g_return_if_fail (expression != NULL);
  g_return_if_fail (old_subject != NULL);
  g_return_if_fail (new_subject != NULL);

  reciprocal = gtk_constraint_expression_new_subject (expression, new_subject);
  gtk_constraint_expression_set_variable (expression, old_subject, reciprocal);
}

/*< private >
 * gtk_constraint_expression_get_coefficient:
 * @expression: a #GtkConstraintExpression
 * @variable: a #GtkConstraintVariable
 *
 * Retrieves the coefficient of the term for @variable inside @expression.
 *
 * Returns: the coefficient of @variable
 */
double
gtk_constraint_expression_get_coefficient (GtkConstraintExpression *expression,
                                           GtkConstraintVariable *variable)
{
  const Term *term;

  g_return_val_if_fail (expression != NULL, 0.0);
  g_return_val_if_fail (variable != NULL, 0.0);

  if (expression->terms == NULL)
    return 0.0;

  term = g_hash_table_lookup (expression->terms, variable);
  if (term == NULL)
    return 0.0;

  return term->coefficient;
}

/*< private >
 * gtk_constraint_expression_substitute_out:
 * @expression: a #GtkConstraintExpression
 * @out_var: the variable to replace
 * @expr: the expression used to replace @out_var
 * @subject: (nullable): a #GtkConstraintVariable
 * @solver: (nullable): a #GtkConstraintSolver
 *
 * Replaces every term containing @out_var inside @expression with @expr.
 *
 * If @solver is not %NULL, this function will notify the #GtkConstraintSolver
 * for every variable added to or removed from @expression.
 */
void
gtk_constraint_expression_substitute_out (GtkConstraintExpression *expression,
                                          GtkConstraintVariable *out_var,
                                          GtkConstraintExpression *expr,
                                          GtkConstraintVariable *subject,
                                          GtkConstraintSolver *solver)
{
  double multiplier;
  Term *iter;

  if (expression->terms == NULL)
    return;

  multiplier = gtk_constraint_expression_get_coefficient (expression, out_var);
  gtk_constraint_expression_remove_term (expression, out_var);

  expression->constant = expression->constant + multiplier * expr->constant;

  iter = expr->first_term;
  while (iter != NULL)
    {
      GtkConstraintVariable *clv = iter->variable;
      double coeff = iter->coefficient;
      Term *next = iter->next;

      if (expression->terms != NULL &&
          g_hash_table_contains (expression->terms, clv))
        {
          double old_coefficient = gtk_constraint_expression_get_coefficient (expression, clv);
          double new_coefficient = old_coefficient + multiplier * coeff;

          if (G_APPROX_VALUE (new_coefficient, 0.0, 0.001))
            {
              if (solver != NULL)
                gtk_constraint_solver_note_removed_variable (solver, clv, subject);

              gtk_constraint_expression_remove_term (expression, clv);
            }
          else
            gtk_constraint_expression_set_variable (expression, clv, new_coefficient);
        }
      else
        {
          gtk_constraint_expression_set_variable (expression, clv, multiplier * coeff);

          if (solver != NULL)
            gtk_constraint_solver_note_added_variable (solver, clv, subject);
        }

      iter = next;
    }
}

/*< private >
 * gtk_constraint_expression_get_pivotable_variable:
 * @expression: a #GtkConstraintExpression
 *
 * Retrieves the first #GtkConstraintVariable in @expression that
 * is marked as pivotable.
 *
 * Returns: (transfer none) (nullable): a #GtkConstraintVariable
 */
GtkConstraintVariable *
gtk_constraint_expression_get_pivotable_variable (GtkConstraintExpression *expression)
{
  Term *iter;

  if (expression->terms == NULL)
    {
      g_critical ("Expression %p is a constant", expression);
      return NULL;
    }

  iter = expression->first_term;
  while (iter != NULL)
    {
      Term *next = iter->next;

      if (gtk_constraint_variable_is_pivotable (iter->variable))
        return iter->variable;

      iter = next;
    }

  return NULL;
}

/*< private >
 * gtk_constraint_expression_to_string:
 * @expression: a #GtkConstraintExpression
 *
 * Creates a string containing @expression.
 *
 * This function is only useful for debugging.
 *
 * Returns: (transfer full): a string containing the given expression
 */
char *
gtk_constraint_expression_to_string (const GtkConstraintExpression *expression)
{
  gboolean needs_plus = FALSE;
  GString *buf;
  Term *iter;

  if (expression == NULL)
    return g_strdup ("<null>");

  buf = g_string_new (NULL);

  if (!G_APPROX_VALUE (expression->constant, 0.0, 0.001))
    {
      g_string_append_printf (buf, "%g", expression->constant);

      if (expression->terms != NULL)
        needs_plus = TRUE;
    }

  if (expression->terms == NULL)
    return g_string_free (buf, FALSE);

  iter = expression->first_term;
  while (iter != NULL)
    {
      char *str = gtk_constraint_variable_to_string (iter->variable);
      Term *next = iter->next;

      if (needs_plus)
        g_string_append (buf, " + ");

      if (G_APPROX_VALUE (iter->coefficient, 1.0, 0.001))
        g_string_append_printf (buf, "%s", str);
      else
        g_string_append_printf (buf, "(%g * %s)", iter->coefficient, str);

      g_free (str);

      if (!needs_plus)
        needs_plus = TRUE;

      iter = next;
    }

  return g_string_free (buf, FALSE);
}

/* Keep in sync with GtkConstraintExpressionIter */
typedef struct {
  GtkConstraintExpression *expression;
  Term *current;
  gint64 age;
} RealExpressionIter;

#define REAL_EXPRESSION_ITER(i) ((RealExpressionIter *) (i))

/*< private >
 * gtk_constraint_expression_iter_init:
 * @iter: a #GtkConstraintExpressionIter
 * @expression: a #GtkConstraintExpression
 *
 * Initializes an iterator over @expression.
 */
void
gtk_constraint_expression_iter_init (GtkConstraintExpressionIter *iter,
                                     GtkConstraintExpression *expression)
{
  RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter);

  riter->expression = expression;
  riter->current = NULL;
  riter->age = expression->age;
}

/*< private >
 * gtk_constraint_expression_iter_next:
 * @iter: a valid #GtkConstraintExpressionIter
 * @variable: (out): the variable of the next term
 * @coefficient: (out): the coefficient of the next term
 *
 * Moves the given #GtkConstraintExpressionIter forwards to the next
 * term in the expression, starting from the first term.
 *
 * Returns: %TRUE if the iterator was moved, and %FALSE if the iterator
 *   has reached the end of the terms of the expression
 */
gboolean
gtk_constraint_expression_iter_next (GtkConstraintExpressionIter *iter,
                                     GtkConstraintVariable **variable,
                                     double *coefficient)
{
  RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter);

  g_assert (riter->age == riter->expression->age);

  if (riter->current == NULL)
    riter->current = riter->expression->first_term;
  else
    riter->current = riter->current->next;

  if (riter->current != NULL)
    {
      *coefficient = riter->current->coefficient;
      *variable = riter->current->variable;
    }

  return riter->current != NULL;
}

/*< private >
 * gtk_constraint_expression_iter_prev:
 * @iter: a valid #GtkConstraintExpressionIter
 * @variable: (out): the variable of the previous term
 * @coefficient: (out): the coefficient of the previous term
 *
 * Moves the given #GtkConstraintExpressionIter backwards to the previous
 * term in the expression, starting from the last term.
 *
 * Returns: %TRUE if the iterator was moved, and %FALSE if the iterator
 *   has reached the beginning of the terms of the expression
 */
gboolean
gtk_constraint_expression_iter_prev (GtkConstraintExpressionIter *iter,
                                     GtkConstraintVariable **variable,
                                     double *coefficient)
{
  RealExpressionIter *riter = REAL_EXPRESSION_ITER (iter);

  g_assert (riter->age == riter->expression->age);

  if (riter->current == NULL)
    riter->current = riter->expression->last_term;
  else
    riter->current = riter->current->prev;

  if (riter->current != NULL)
    {
      *coefficient = riter->current->coefficient;
      *variable = riter->current->variable;
    }

  return riter->current != NULL;
}

typedef enum {
  BUILDER_OP_NONE,
  BUILDER_OP_PLUS,
  BUILDER_OP_MINUS,
  BUILDER_OP_MULTIPLY,
  BUILDER_OP_DIVIDE
} BuilderOpType;

typedef struct
{
  GtkConstraintExpression *expression;
  GtkConstraintSolver *solver;
  int op;
} RealExpressionBuilder;

#define REAL_EXPRESSION_BUILDER(b) ((RealExpressionBuilder *) (b))

/*< private >
 * gtk_constraint_expression_builder_init:
 * @builder: a #GtkConstraintExpressionBuilder
 * @solver: a #GtkConstraintSolver
 *
 * Initializes the given #GtkConstraintExpressionBuilder for the
 * given #GtkConstraintSolver.
 *
 * You can use the @builder to construct expressions to be added to the
 * @solver, in the form of constraints.
 *
 * A typical use is:
 *
 * |[<!-- language="C" -->
 *   GtkConstraintExpressionBuilder builder;
 *
 *   // "solver" is set in another part of the code
 *   gtk_constraint_expression_builder_init (&builder, solver);
 *
 *   // "width" is set in another part of the code
 *   gtk_constraint_expression_builder_term (&builder, width);
 *   gtk_constraint_expression_builder_divide_by (&builder);
 *   gtk_constraint_expression_builder_constant (&builder, 2.0);
 *
 *   // "left" is set in another part of the code
 *   gtk_constraint_expression_builder_plus (&builder);
 *   gtk_constraint_expression_builder_term (&builder, left);
 *
 *   // "expr" now contains the following expression:
 *   //     width / 2.0 + left
 *   GtkConstraintExpression *expr =
 *     gtk_constraint_expression_builder_finish (&builder);
 *
 *   // The builder is inert, and can be re-used by calling
 *   // gtk_constraint_expression_builder_init() again.
 * ]|
 */
void
gtk_constraint_expression_builder_init (GtkConstraintExpressionBuilder *builder,
                                        GtkConstraintSolver *solver)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  rbuilder->solver = solver;
  rbuilder->expression = gtk_constraint_expression_new (0);
  rbuilder->op = BUILDER_OP_NONE;
}

/*< private >
 * gtk_constraint_expression_builder_term:
 * @builder: a #GtkConstraintExpressionBuilder
 * @term: a #GtkConstraintVariable
 *
 * Adds a variable @term to the @builder.
 */
void
gtk_constraint_expression_builder_term (GtkConstraintExpressionBuilder *builder,
                                        GtkConstraintVariable *term)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);
  GtkConstraintExpression *expr;

  expr = gtk_constraint_expression_new_from_variable (term);

  switch (rbuilder->op)
    {
    case BUILDER_OP_NONE:
      g_clear_pointer (&rbuilder->expression, gtk_constraint_expression_unref);
      rbuilder->expression = g_steal_pointer (&expr);
      break;

    case BUILDER_OP_PLUS:
      gtk_constraint_expression_add_expression (rbuilder->expression,
                                                expr, 1.0,
                                                NULL,
                                                NULL);
      gtk_constraint_expression_unref (expr);
      break;

    case BUILDER_OP_MINUS:
      gtk_constraint_expression_add_expression (rbuilder->expression,
                                                expr, -1.0,
                                                NULL,
                                                NULL);
      gtk_constraint_expression_unref (expr);
      break;

    default:
      break;
    }

  rbuilder->op = BUILDER_OP_NONE;
}

/*< private >
 * gtk_constraint_expression_builder_plus:
 * @builder: a #GtkConstraintExpressionBuilder
 *
 * Adds a plus operator to the @builder.
 */
void
gtk_constraint_expression_builder_plus (GtkConstraintExpressionBuilder *builder)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  rbuilder->op = BUILDER_OP_PLUS;
}

/*< private >
 * gtk_constraint_expression_builder_minus:
 * @builder: a #GtkConstraintExpressionBuilder
 *
 * Adds a minus operator to the @builder.
 */
void
gtk_constraint_expression_builder_minus (GtkConstraintExpressionBuilder *builder)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  rbuilder->op = BUILDER_OP_MINUS;
}

/*< private >
 * gtk_constraint_expression_builder_divide_by:
 * @builder: a #GtkConstraintExpressionBuilder
 *
 * Adds a division operator to the @builder.
 */
void
gtk_constraint_expression_builder_divide_by (GtkConstraintExpressionBuilder *builder)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  rbuilder->op = BUILDER_OP_DIVIDE;
}

/*< private >
 * gtk_constraint_expression_builder_multiply_by:
 * @builder: a #GtkConstraintExpressionBuilder
 *
 * Adds a multiplication operator to the @builder.
 */
void
gtk_constraint_expression_builder_multiply_by (GtkConstraintExpressionBuilder *builder)

{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  rbuilder->op = BUILDER_OP_MULTIPLY;
}

/*< private >
 * gtk_constraint_expression_builder_constant:
 * @builder: a #GtkConstraintExpressionBuilder
 * @value: a constant value
 *
 * Adds a constant value to the @builder.
 */
void
gtk_constraint_expression_builder_constant (GtkConstraintExpressionBuilder *builder,
                                            double value)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  switch (rbuilder->op)
    {
    case BUILDER_OP_NONE:
      gtk_constraint_expression_set_constant (rbuilder->expression, value);
      break;

    case BUILDER_OP_PLUS:
      gtk_constraint_expression_plus_constant (rbuilder->expression, value);
      break;

    case BUILDER_OP_MINUS:
      gtk_constraint_expression_minus_constant (rbuilder->expression, value);
      break;

    case BUILDER_OP_MULTIPLY:
      gtk_constraint_expression_multiply_by (rbuilder->expression, value);
      break;

    case BUILDER_OP_DIVIDE:
      gtk_constraint_expression_divide_by (rbuilder->expression, value);
      break;

    default:
      break;
    }

  rbuilder->op = BUILDER_OP_NONE;
}

/*< private >
 * gtk_constraint_expression_builder_finish:
 * @builder: a #GtkConstraintExpressionBuilder
 *
 * Closes the given expression builder, and returns the expression.
 *
 * You can only call this function once.
 *
 * Returns: (transfer full): the built expression
 */
GtkConstraintExpression *
gtk_constraint_expression_builder_finish (GtkConstraintExpressionBuilder *builder)
{
  RealExpressionBuilder *rbuilder = REAL_EXPRESSION_BUILDER (builder);

  rbuilder->solver = NULL;
  rbuilder->op = BUILDER_OP_NONE;

  return g_steal_pointer (&rbuilder->expression);
}

/* }}} */