/**
 * Copyright 2015, SRI International.
 *
 * This file is part of LibPoly.
 *
 * LibPoly 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 3 of the License, or
 * (at your option) any later version.
 *
 * LibPoly 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 LibPoly.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "poly.h"

#include "variable_order.h"
#include "integer.h"
#include "polynomial_context.h"
#include "assignment.h"
#include "monomial.h"
#include "sign_condition.h"

#ifdef __cplusplus
extern "C" {
#endif

/**
 * Polynomials incorporate the context and coefficient data. The also carry
 * flags so as not to re-do any computation.
 *
 * A polynomial can be marked as *external* with the lp_polynomial_set_external
 * function. This means that the context data will be attached and before every
 * operation, the polynomial will be reordered to in the right order. The non-external
 * polynomials are useful if you are doing lots of intermediate computation to produce a
 * final (external) polynomial. By default polynomials are not external.
 *
 * For non-external polynomials no checks are done during operations, so if the
 * order has changed the operations will fail or produce wrong results.
 *
 * In any case, the polynomial context should not be changed externally during
 * any operations.
 */

/** Construct a zero polynomial (does not attach the context) */
void lp_polynomial_construct(lp_polynomial_t* A, const lp_polynomial_context_t* ctx);

/** Construct a simple polynomial c*x^n */
void lp_polynomial_construct_simple(lp_polynomial_t* A, const lp_polynomial_context_t* ctx, const lp_integer_t* c, lp_variable_t x, unsigned n);

/** Construct a copy of the given polynomial (does not attach the context). */
void lp_polynomial_construct_copy(lp_polynomial_t* A, const lp_polynomial_t* from);

/** Destruct the polynomial. */
void lp_polynomial_destruct(lp_polynomial_t* A);

/** Delete the polynomial (destrcuct and free) */
void lp_polynomial_delete(lp_polynomial_t* A);

/** Allocate a new polynomial (unconstructed) */
lp_polynomial_t* lp_polynomial_alloc(void);

/** Allocate and construct a new polynomial */
lp_polynomial_t* lp_polynomial_new(const lp_polynomial_context_t* ctx);

/** Allocate and construct a copy of the given polynomial */
lp_polynomial_t* lp_polynomial_new_copy(const lp_polynomial_t* A);

/** Make the polynomial as external (automatic reordering) */
void lp_polynomial_set_external(lp_polynomial_t* A);

/** Check if the polynomial is ordered properly according to the context order (useful for non-external polynomials) */
int lp_polynomial_check_order(const lp_polynomial_t* A);

/** Make sure that the polynomial is in the context order (useful for non-external polynomials) */
void lp_polynomial_ensure_order(lp_polynomial_t* A);

/** Swap two polynomials. */
void lp_polynomial_swap(lp_polynomial_t* A1, lp_polynomial_t* A2);

/** Assign the polynomial a given polynomial. */
void lp_polynomial_assign(lp_polynomial_t* A, const lp_polynomial_t* from);

/** Returns the degree of the polynomial (in it's top variable) */
size_t lp_polynomial_degree(const lp_polynomial_t* A);

/** Returns the top variable of the polynomial */
lp_variable_t lp_polynomial_top_variable(const lp_polynomial_t* A);

/** Returns true if the polynomial is linear in all variables */
int lp_polynomial_is_linear(const lp_polynomial_t* A);

/** Returns 1 if the leading coefficient is constant */
int lp_polynomial_lc_is_constant(const lp_polynomial_t* A);

/** In case lc is constant, this returns the sign */
int lp_polynomial_lc_sgn(const lp_polynomial_t* A);

/** Returns the constant part of the leading coefficient */
void lp_polynomial_lc_constant(const lp_polynomial_t* A, lp_integer_t *out);

/** Get the context of the given polynomial */
const lp_polynomial_context_t* lp_polynomial_get_context(const lp_polynomial_t* A);

/**
 * Sets the context of a polynomial.
 * Warning: variable DB indexes are not checked. Use with caution.
 */
void lp_polynomial_set_context(lp_polynomial_t* A, const lp_polynomial_context_t* ctx);

/** Returns all the variables (it will not clear the output list vars) */
void lp_polynomial_get_variables(const lp_polynomial_t* A, lp_variable_list_t* vars);

/** Puts the k-th coefficient of A into C */
void lp_polynomial_get_coefficient(lp_polynomial_t* C, const lp_polynomial_t* A, size_t k);

/** Get the reductum of the polynomial (the polynomial without the leading coefficient) */
void lp_polynomial_reductum(lp_polynomial_t* R, const lp_polynomial_t* A);

/** Get the model-based reductum of the polynomial (the polynomial without the leading coefficient) */
void lp_polynomial_reductum_m(lp_polynomial_t* R, const lp_polynomial_t* A, const lp_assignment_t* m);

/** Returns true if the polynomial is a constant */
int lp_polynomial_is_constant(const lp_polynomial_t* A);

/** Returns true if the polynomial is 0 */
int lp_polynomial_is_zero(const lp_polynomial_t* A);

/** Returns true if the polynomial is univariate */
int lp_polynomial_is_univariate(const lp_polynomial_t* A);

/**
 * Returns true if polynomial is univariate, i.e. has one variable unassigned,
 * and the unassigned variable is the top variable.
 */
int lp_polynomial_is_univariate_m(const lp_polynomial_t* A, const lp_assignment_t* m);

/** Returns the univariate polynomial (if univariate, or 0 otherwise) */
lp_upolynomial_t* lp_polynomial_to_univariate(const lp_polynomial_t* A);

/**
 * Returns the univariate polynomial of A partially evaluated with m.
 * m must assign all variables in A (but the top variable) to an integer value.
 */
lp_upolynomial_t* lp_polynomial_to_univariate_m(const lp_polynomial_t* A, const lp_assignment_t* m);

/** Returns true if the polynomial is a monomial */
int lp_polynomial_is_monomial(const lp_polynomial_t* A);

/** Returns A as a monomial. Assumes that A is a monomial */
void lp_polynomial_to_monomial(const lp_polynomial_t* A, lp_monomial_t* out);

/** Returns true if all of polynomial's variables are assigned */
int lp_polynomial_is_assigned(const lp_polynomial_t* A, const lp_assignment_t* m);

/** Returns the sign of the polynomial in the model (-1, 0, +1), or -2 if not all variables assigned */
int lp_polynomial_sgn(const lp_polynomial_t* A, const lp_assignment_t* m);

/** Returns the interval approximation of the polynomial value */
void lp_polynomial_interval_value(const lp_polynomial_t* A, const lp_interval_assignment_t* m, lp_interval_t* result);

/** returns the value of the polynomial in the model */
lp_value_t* lp_polynomial_evaluate(const lp_polynomial_t* A, const lp_assignment_t* m);

/** copies the integer value of a polynomial to out; assignment m must only assign integer values */
void lp_polynomial_evaluate_integer(const lp_polynomial_t* A, const lp_assignment_t* m, lp_integer_t* out);

/**
 * Compare the two polynomials in the ring. Not necessarily +/- 1, could be
 * any integer, only the sign matters.
 */
int lp_polynomial_cmp(const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/**
 * Compare the type of two polynomials. Type if either a constant or a
 * polynomial, where comparison is done only by the lead variable comparison
 * and constants are smaller than other polynomials. This is not a total
 * ORDER.
 */
int lp_polynomial_cmp_type(const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Returns the hash of the polynomial */
size_t lp_polynomial_hash(const lp_polynomial_t* A);

/** Compares two polynomials for equality. */
int lp_polynomial_eq(const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Returns true if A1 divides A2. */
int lp_polynomial_divides(const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Prints the polynomial to the given stream. */
int lp_polynomial_print(const lp_polynomial_t* A, FILE* out);

/** Returns the string representation of the polynomial. */
char* lp_polynomial_to_string(const lp_polynomial_t* A);

/** Compute S = A1 + A2. */
void lp_polynomial_add(lp_polynomial_t* S, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute S += M. */
void lp_polynomial_add_monomial(lp_polynomial_t* S, const lp_monomial_t* M);

/** Compute S = A1 - A2 in the given ring. */
void lp_polynomial_sub(lp_polynomial_t* S, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute N = -C. */
void lp_polynomial_neg(lp_polynomial_t* N, const lp_polynomial_t* A);

/** Compute P = A1 * A2. */
void lp_polynomial_mul(lp_polynomial_t* P, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute P = A1 * C. */
void lp_polynomial_mul_integer(lp_polynomial_t* P, const lp_polynomial_t* A1, const lp_integer_t* C);

/**
 * Multiplication with x^n.
 */
void lp_polynomial_shl(lp_polynomial_t* S, const lp_polynomial_t* A, unsigned n);

/** Compute P = C^n. */
void lp_polynomial_pow(lp_polynomial_t* P, const lp_polynomial_t* A, unsigned n);

/** Compute S += A1*A2. */
void lp_polynomial_add_mul(lp_polynomial_t* S, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute S -= A1*A2. */
void lp_polynomial_sub_mul(lp_polynomial_t* S, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/**
 * Reduce the polynomial A in Z[x,y] using B in Z[x,y] so that
 *
 *   P*A = Q*B + R
 *
 * and
 *
 *   P in Z[x]
 *   Q, R in Z[x,y]
 *
 * with
 *
 *   deg(R) < deg(B) or deg(R) = 0
 */
void lp_polynomial_reduce(const lp_polynomial_t* A, const lp_polynomial_t* B,
    lp_polynomial_t* P, lp_polynomial_t* Q, lp_polynomial_t* R);

/** Compute A1 = D*A2 (assumes that A2 divides A1). */
void lp_polynomial_div(lp_polynomial_t* D, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute A1 = D*A2 + R (assumes that exact division). */
void lp_polynomial_rem(lp_polynomial_t* R, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute a*A1 = D*A2 + R (pseudo remainder). */
void lp_polynomial_prem(lp_polynomial_t* R, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute a*A1 = D*A2 + R (pseudo remainder). */
void lp_polynomial_pdivrem(lp_polynomial_t* D, lp_polynomial_t* R, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute a*A1 = D*A2 + R (sparse pseudo remainder). */
void lp_polynomial_sprem(lp_polynomial_t* R, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute a*A1 = D*A2 + R (sparse pseudo remainder). */
void lp_polynomial_spdivrem(lp_polynomial_t* D, lp_polynomial_t* R, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute A1 = D*A2 + R (assumes that exact division). */
void lp_polynomial_divrem(lp_polynomial_t* D, lp_polynomial_t* R, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute A_d = A' (in the top variable). */
void lp_polynomial_derivative(lp_polynomial_t* A_d, const lp_polynomial_t* A);

/** Compute the greatest common divisor gcd(A1, A2). */
void lp_polynomial_gcd(lp_polynomial_t* gcd, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute the least common multiple lcm(A1, A2). */
void lp_polynomial_lcm(lp_polynomial_t* lcm, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/** Compute the content part of the polynomial: gcd(coefficients) */
void lp_polynomial_cont(lp_polynomial_t* cont, const lp_polynomial_t* A);

/** Compute the primitive part of the polynomial: A/cont(A) */
void lp_polynomial_pp(lp_polynomial_t* pp, const lp_polynomial_t* A);

/** Compute the primitive part and the content of the polynomial */
void lp_polynomial_pp_cont(lp_polynomial_t* pp, lp_polynomial_t* cont, const lp_polynomial_t* A);

/**
 * Compute the resultant of A1 and A2 in their top variable. Both A1 and A2
 * must be (non-trivial) polynomials over the same variable.
 */
void lp_polynomial_resultant(lp_polynomial_t* res, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/**
 * Compute the principal subresultant coefficients (psc) of A1 and A2. Both A1
 * and A2 must be (non-trivial) polynomials over the same variable. If
 * deg(A1) = m, deg(A2) = n, and output will be of size min(m, n)+1.
 */
void lp_polynomial_psc(lp_polynomial_t** psc, const lp_polynomial_t* A1, const lp_polynomial_t* A2);

/**
 * Compute the subresultant regular sub-chain (subres) of A1 and A2. Bot A1
 * and A2 must be (non-trivial) polynomials over the same variable. If
 * deg(A1) = m, deg(A2) = n, and output will be of size min(m, n)+1.
 */
void lp_polynomial_subres(lp_polynomial_t** subres, const lp_polynomial_t* A, const lp_polynomial_t* B);

/**
 * Compute the model-based GCD of the two polynomials. Adds the assumptions to
 * the vector.
 */
lp_polynomial_vector_t* lp_polynomial_mgcd(const lp_polynomial_t* p, const lp_polynomial_t* q, const lp_assignment_t* m);

/**
 * Get the square-free factorization of the given polynomial. It will allocate
 * the given arrays and return the polynomials and their multiplicities in
 * them.
 */
void lp_polynomial_factor_square_free(const lp_polynomial_t* A, lp_polynomial_t*** factors, size_t** multiplicities, size_t* size);

/**
 * Get the content-free factorization of the given polynomial. It will allocate
 * the given arrays and return the polynomials and their multiplicities in
 * them.
 */
void lp_polynomial_factor_content_free(const lp_polynomial_t* A, lp_polynomial_t*** factors, size_t** multiplicities, size_t* size);

/**
 * Given a polynomial p(x1, ..., xn, y) with y being the top variable, and an
 * assignment M that assigns x1, ..., xn, the function returns the roots of p
 * at M. The output array should be big enough to fit the root, i.e. at least
 * the degree of y. The array should be unconstructed, but you should destruct it
 * once done.
 */
void lp_polynomial_roots_isolate(const lp_polynomial_t* A, const lp_assignment_t* M, lp_value_t* roots, size_t* roots_size);

/**
 * Given a polynomial A(x1, ..., xn, y) with y being the top variable, a sign
 * condition, and an assignment M that assigns x1, ..., xn, the function returns
 * a subset of R where
 *
 *   sgn(A(M(x1), ..., M(xn), y)) = sgn_condition .
 *
 * If negated is true, the constraint is considered negated.
 */
lp_feasibility_set_t* lp_polynomial_constraint_get_feasible_set(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, int negated, const lp_assignment_t* M);

/**
 * Given a polynomial A(x1, ..., xn, y) with y being the top variable, a sign
 * condition, and an assignment M that assigns x1, ..., xn, the function returns
 * a subset of Zp where
 *
 *   sgn(A(M(x1), ..., M(xn), y)) = sgn_condition .
 *
 * If negated is true, the constraint is considered negated.
 */
lp_feasibility_set_int_t* lp_polynomial_constraint_get_feasible_set_Zp(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, int negated, const lp_assignment_t* M);

/**
 * Given a polynomial A(x1, ..., xn) and a sign condition,  the function returns
 * tries to infer bounds on the variables and stores them into the given interval
 * model.
 *
 * If negated is true, the constraint is considered negated.
 *
 * Returns 1 if something has been inferred, 0 if no bounds inferred, and -1 if there is no solutions.
 */
int lp_polynomial_constraint_infer_bounds(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, int negated, lp_interval_assignment_t* M);

/**
 * Explains the bound inference by providing a bounding polynomial.
 */
lp_polynomial_t* lp_polynomial_constraint_explain_infer_bounds(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, int negated, lp_variable_t x);

/**
 * Given a polynomial constraint, as above, evaluate its truth value.
 */
int lp_polynomial_constraint_evaluate(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, const lp_assignment_t* M);

/**
 * Substitutes the polynomial with M and checks the sign of the resulting real number.
 * If M does not fully evaluate A to a real, -1 is returned.
 */
int lp_polynomial_constraint_evaluate_subs(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, const lp_assignment_t* M);

/**
 * Given a polynomial constraint over an integer ring Zp, evaluate its truth value.
 * sgn_condition must either be (== 0) or (!= 0) and M must assign to Zp.
 */
int lp_polynomial_constraint_evaluate_Zp(const lp_polynomial_t* A, lp_sign_condition_t sgn_condition, const lp_assignment_t* M);

/**
 * Given a polynomial A(x1, ..., xn, y) with y being the top variable, a root index,
 * a sign condition, and an assignment M that assigns x1, ..., xn, the function
 * returns a subset or R where
 *
 * root_index < root_cound(A, M) && sgn(x - root(k, A(M(x1, ..., M(xn), y)) == sgn_condition
 *
 * If negated is true, the constraint is considered negated.
 */
lp_feasibility_set_t* lp_polynomial_root_constraint_get_feasible_set(const lp_polynomial_t* A, size_t root_index, lp_sign_condition_t sgn_condition, int negated, const lp_assignment_t* M);

/**
 * Given a root constraint as above, evaluate its truth value.
 */
int lp_polynomial_root_constraint_evaluate(const lp_polynomial_t* A, size_t root_index, lp_sign_condition_t sgn_condition, const lp_assignment_t* M);

/**
 * Function type called on polynomial traversal. It will be called on all monomials.
 */
typedef void (*lp_polynomial_traverse_f) (const lp_polynomial_context_t* ctx, lp_monomial_t* m, void* data);

/**
 * Run on the polynomial to traverse all monomials. The traverse_f function will be called on the monomial with
 * the associated data.
 */
void lp_polynomial_traverse(const lp_polynomial_t* A, lp_polynomial_traverse_f f, void* data);

/** Check the integrity of the polynomial */
int lp_polynomial_check_integrity(const lp_polynomial_t* A);

/**
 * Try to resolve the two constraints with Fourier-Motzkin. We use the model M to check if
 * the polynomials are univariate. Then we resolve. All assumption polynomials (a) are added to
 * the polynomial vector (sgn(a) == sgn(m(a))). Result is returned as (p R2 0) if it is
 * defined and 1 is returned. Otherwise 0 is returned.
 */
int lp_polynomial_constraint_resolve_fm(
    const lp_polynomial_t* p1, lp_sign_condition_t p1_sgn,
    const lp_polynomial_t* p2, lp_sign_condition_t p2_sgn,
    const lp_assignment_t* M,
    lp_polynomial_t* R, lp_sign_condition_t* R_sgn,
    lp_polynomial_vector_t* assumptions);

/**
 * Reduces the degree of the polynomials wrt. to the polynomial's int_ring_t K.
 * E.g. for K = x mod 5, x = x^5 for every value in K
 */
void lp_polynomial_reduce_degree_Zp(lp_polynomial_t *R, const lp_polynomial_t *A);

#ifdef __cplusplus
} /* close extern "C" { */
#endif
