/**
\private
*/
#pragma once
#include "../arhiplex_cpp.h"
#include "../arhiplex_itf.h"
#include "../arhiplex_utils.h"
#include "utils.h"
#include "variable.h"
#include <memory>

namespace arhiplex
{
class QuadExpression;
class GeneralExpression;
/**
\~russian
* Обеспечивает работу с линейными выражениями - добавление/удаление элементов и изменение коэффициентов.
* Элемент выражения - переменная * коэффициент.
\brief Линейное выражение

\~english
* Provides work with linear expressions - adding/removing elements and changing coefficients.
* Expression element - variable * coefficient.
\brief Linear expression

\~chinese-traditional
* Provides work with linear expressions - adding/removing elements and changing coefficients.
* Expression element - variable * coefficient.
\brief Linear expression
*/

class LinearExpression
{
  public:
/**
\~russian
* Создает пустое выражение с возможностью задать константу
\param[in] constant константа в выражении

\~english
* Creates an empty expression with the ability to specify a constant
\param[in] constant constant in the expression

\~chinese-traditional
* Creates an empty expression with the ability to specify a constant
\param[in] constant constant in the expression
*/
    LinearExpression(double constant = 0.0) : _expr(CreateLinearExpression(), releasable_object_deleter)
    {
        if(constant)
            _expr->SetConstant(constant);
    }

/**
\~russian
* Создает  выражение на основе переменной и коэффициента
\param[in] var переменная в выражении
\param[in] coeff коэффициент переменной в выражении

\~english
* Creates an expression based on a variable and a coefficient
\param[in] var variable in expression
\param[in] coeff coefficient of variable in expression

\~chinese-traditional
* Creates an expression based on a variable and a coefficient
\param[in] var variable in expression
\param[in] coeff coefficient of variable in expression
*/
    LinearExpression(const Variable &var, double coeff = 1.0)
        : _expr(CreateLinearExpression(), releasable_object_deleter)
    {
        _expr->AddTerm(var.Get(), coeff);
    }

/**
\private
*/
    LinearExpression(arhiplex::ILinearExpression *expr) : _expr(expr, releasable_object_deleter)
    {
    }

/**
\~russian
* Возвращает кол-во элементов в выражении
\return Кол-во элементов в выражении

\~english
* Returns the number of elements in the expression
\return Number of elements in the expression

\~chinese-traditional
* Returns the number of elements in the expression
\return Number of elements in the expression
*/
    int GetTermsCount() const
    {
        int ret = _expr->GetTermsCount();
        CHECKERR(_expr);
        return ret;
    }

/**
\~russian
* Возвращает истину, если выражение пустое
\return истина, если выражение пустое

\~english
* Returns true if expression is empty
\return true if expression is empty

\~chinese-traditional
* Returns true if expression is empty
\return true if expression is empty
*/
    bool empty() const
    {
        const bool empty = _expr->GetTermsCount() == 0;
        CHECKERR(_expr);
        return empty;
    }

/**
\~russian
* Задаёт имя выражения
\param[in] expr_name имя выражения

\~english
* Specifies the name of the expression
\param[in] expr_name the name of the expression

\~chinese-traditional
* Specifies the name of the expression
\param[in] expr_name the name of the expression
*/
    void SetName(const char* expr_name)
    {
        _expr->SetName(expr_name);
        CHECKERR(_expr);
    }

/**
\~russian
* Получает имя выражения
\return имя выражения

\~english
* Gets the name of the expression
\return the name of the expression

\~chinese-traditional
* Gets the name of the expression
\return the name of the expression
*/
    const char* GetName() const
    {
        const char* name = _expr->GetName();
        CHECKERR(_expr);
        return name;
    }
/**
\~russian
* Возвращает переменную по индексу элемента в выражении
\param[in] i индекс элемента в выражении
\return Объект переменной

\~english
* Returns a variable by the index of the element in the expression
\param[in] i index of the element in the expression
\return Variable object

\~chinese-traditional
* Returns a variable by the index of the element in the expression
\param[in] i index of the element in the expression
\return Variable object
*/
    Variable GetTermVariable(int i) const
    {
        auto ret = _expr->GetTermVariable(i);
        CHECKERR(_expr);
        return ret;
    }

/**
\~russian
* Возвращает индекс переменной по индексу элемента в выражении
\param[in] i индекс элемента в выражении
\return индекс переменной

\~english
* Returns the index of the variable by the index of the element in the expression
\param[in] i index of the element in the expression
\return index of the variable

\~chinese-traditional
* Returns the index of the variable by the index of the element in the expression
\param[in] i index of the element in the expression
\return index of the variable
*/
    int GetTermVariableIndex(int i) const
    {
        auto ret = _expr->GetTermVariableIndex(i);
        CHECKERR(_expr);
        return ret;
    }

/**
\~russian
* Возвращает коэффициент переменной по индексу элемента в выражении
\param[in] i индекс элемента в выражении
\return Значение коэффициента переменной

\~english
* Returns the coefficient of the variable by the index of the element in the expression
\param[in] i index of the element in the expression
\return Value of the coefficient of the variable

\~chinese-traditional
* Returns the coefficient of the variable by the index of the element in the expression
\param[in] i index of the element in the expression
\return Value of the coefficient of the variable
*/
    double GetTermCoeff(int i) const
    {
        auto ret = _expr->GetTermCoeff(i);
        CHECKERR(_expr);
        return ret;
    }

/**
\~russian
* Возвращает константу в выражении
\return значение константы

\~english
* Returns the constant in the expression
\return the value of the constant

\~chinese-traditional
* Returns the constant in the expression
\return the value of the constant
*/
    double GetConstant() const
    {
        auto ret = _expr->GetConstant();
        CHECKERR(_expr);
        return ret;
    }

/**
\~russian
* Задает коэффициент переменной по индексу выражения
\param[in] i Индекс элемента в выражении
\param[in] value Значение коэффициента при переменной в элементе

\~english
* Sets the coefficient of the variable by the expression index
\param[in] i Index of the element in the expression
\param[in] value Value of the coefficient of the variable in the element

\~chinese-traditional
* Sets the coefficient of the variable by the expression index
\param[in] i Index of the element in the expression
\param[in] value Value of the coefficient of the variable in the element
*/
    void SetTermCoeff(int i, double value)
    {
        _expr->SetTermCoeff(i, value);
        CHECKERR(_expr);
    }
	
/**
\~russian
* Задает константу в выражении
\param[in] constant значение константы

\~english
* Specifies a constant in the expression
\param[in] constant constant value

\~chinese-traditional
* Specifies a constant in the expression
\param[in] constant constant value
*/
    void SetConstant(double constant)
    {
        _expr->SetConstant(constant);
        CHECKERR(_expr);
    }
	
/**
\~russian
* Добавляет константу к выражению
\param[in] constant значение константы

\~english
* Adds a constant to the expression
\param[in] constant constant value

\~chinese-traditional
* Adds a constant to the expression
\param[in] constant constant value
*/
    void AddConstant(double constant)
    {
        _expr->AddConstant(constant);
        CHECKERR(_expr);
    }

/**
\~russian
* Добавляет элемент к выражению
\param[in] var переменная
\param[in] coeff коэффициент к переменной

\~english
* Adds an element to the expression
\param[in] var variable
\param[in] coeff coefficient to variable

\~chinese-traditional
* Adds an element to the expression
\param[in] var variable
\param[in] coeff coefficient to variable
*/

    void AddTerm(const Variable &var, double coeff = 1.0)
    {
        _expr->AddTerm(var.Get(), coeff);
        CHECKERR(_expr);
    }
	
/**
\~russian
* Добавляет выражение к выражению
\param[in] expr добавляемое выражение
\param[in] mult коэффициент к добавляемому выражению

\~english
* Adds an expression to an expression
\param[in] expr the expression to add
\param[in] mult the coefficient to the expression to add

\~chinese-traditional
* Adds an expression to an expression
\param[in] expr the expression to addd
\param[in] mult the coefficient to the expression to add
*/

    void AddExpression(const LinearExpression &expr, double mult = 1.0)
    {
        _expr->AddExpression(expr.Get(), mult);
        CHECKERR(_expr);
    }

/**
\~russian
* Удаляет элемент выражения по индексу
\param[in] idx индекс удаляемого элемента в выражении

\~english
* Removes an element of an expression by index
\param[in] idx index of the element to remove in the expression

\~chinese-traditional
* Removes an element of an expression by index
\param[in] idx index of the element to remove in the expression
*/
    void RemoveTerm(int idx)
    {
        _expr->RemoveTerm(idx);
        CHECKERR(_expr);
    }

/**
\~russian
* Удаляет переменную из выражения
\param[in] var удаляемая переменная

\~english
* Removes a variable from an expression
\param[in] var variable to remove

\~chinese-traditional
* Removes a variable from an expression
\param[in] var variable to remove
*/
    void RemoveVariable(const Variable &var)
    {
        _expr->RemoveVariable(var.Get());
        CHECKERR(_expr);
    }

/**
\~russian
* Создает копию выражения, не привязанную к модели или другому ограничению
\return копия выражения

\~english
* Creates a copy of the expression that is not bound to a model or other constraint
\return copy of the expression

\~chinese-traditional
* Creates a copy of the expression that is not bound to a model or other constraint
\return copy of the expression
*/
    LinearExpression CreateFreeCopy() const
    {
        return _expr->CreateFreeCopy();
    }

/**
\privatesection
*/

    LinearExpression& operator+=(double c)
    {
        _expr->AddConstant(c);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator+=(const Variable &var)
    {
        _expr->AddTerm(var.Get(), 1.0);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator+=(const LinearExpression &expr)
    {
        _expr->AddExpression(expr.Get(), 1.0);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator-=(double c)
    {
        _expr->AddConstant(-c);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator-=(const Variable &var)
    {
        _expr->AddTerm(var.Get(), -1.0);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator-=(const LinearExpression &expr)
    {
        _expr->AddExpression(expr.Get(), -1.0);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator*=(double c)
    {
        for (int i = 0; i < GetTermsCount(); i++)
        {
            _expr->SetTermCoeff(i, GetTermCoeff(i) * c);
            CHECKERR(_expr);
        }
        _expr->SetConstant(GetConstant() * c);
        CHECKERR(_expr);
        return *this;
    }

    LinearExpression& operator/=(double c)
    {
        for (int i = 0; i < GetTermsCount(); i++)
        {
            _expr->SetTermCoeff(i, GetTermCoeff(i) / c);
            CHECKERR(_expr);
        }
        _expr->SetConstant(GetConstant() / c);
        CHECKERR(_expr);
        return *this;
    }

    friend LinearExpression operator+(const LinearExpression &left, double c)
    {
        LinearExpression ret = left.CreateFreeCopy();
        ret.AddConstant(c);
        return ret;
    }

    friend LinearExpression operator+(double c, const LinearExpression &right)
    {
        LinearExpression ret = right.CreateFreeCopy();
        ret.AddConstant(c);
        return ret;
    }

    friend LinearExpression operator+(const LinearExpression &left, const Variable &var)
    {
        LinearExpression ret = left.CreateFreeCopy();
        ret.AddTerm(var, 1.0);
        return ret;
    }

    friend LinearExpression operator+(const Variable &var, const LinearExpression &right)
    {
        LinearExpression ret = right.CreateFreeCopy();
        ret.AddTerm(var, 1.0);
        return ret;
    }

    friend LinearExpression operator+(const LinearExpression &left, const LinearExpression &right)
    {
        LinearExpression ret = left.CreateFreeCopy();
        ret += right;
        return ret;
    }

    friend LinearExpression operator-(const LinearExpression &left, double c)
    {
        LinearExpression ret = left.CreateFreeCopy();
        ret.AddConstant(-c);
        return ret;
    }

    friend LinearExpression operator-(double c, const LinearExpression &right)
    {
        LinearExpression ret = right.CreateFreeCopy();
        ret *= -1.0;
        ret.AddConstant(c);
        return ret;
    }

    friend LinearExpression operator-(const LinearExpression &expr)
    {
        return 0.0 - expr;
    }

    friend LinearExpression operator-(const LinearExpression &left, const Variable &var)
    {
        LinearExpression ret = left.CreateFreeCopy();
        ret.AddTerm(var, -1.0);
        return ret;
    }

    friend LinearExpression operator-(const Variable &var, const LinearExpression &right)
    {
        LinearExpression ret = right.CreateFreeCopy();
        ret *= -1.0;
        ret.AddTerm(var, 1.0);
        return ret;
    }

    friend LinearExpression operator-(const LinearExpression &left, const LinearExpression &right)
    {
        LinearExpression ret = left.CreateFreeCopy();
        ret -= right;
        return ret;
    }

    friend LinearExpression operator*(const LinearExpression &expr, double c)
    {
        LinearExpression ret = expr.CreateFreeCopy();
        ret *= c;
        return ret;
    }

    friend LinearExpression operator/(const LinearExpression &expr, double c)
    {
        LinearExpression ret = expr.CreateFreeCopy();
        ret /= c;
        return ret;
    }

    friend LinearExpression operator*(double c, const LinearExpression &expr)
    {
        LinearExpression ret = expr.CreateFreeCopy();
        ret *= c;
        return ret;
    }

    friend Constraint operator>=(const LinearExpression &left, const LinearExpression &right);
    friend Constraint operator>=(const LinearExpression &left, const Variable &var);
    friend Constraint operator>=(const Variable &var, const LinearExpression &right);
    friend Constraint operator>=(const LinearExpression &left, double c);
    friend Constraint operator>=(double c, const LinearExpression &right);
    friend Constraint operator<=(const LinearExpression &left, const LinearExpression &right);
    friend Constraint operator<=(const LinearExpression &left, const Variable &var);
    friend Constraint operator<=(const Variable &var, const LinearExpression &right);
    friend Constraint operator<=(const LinearExpression &left, double c);
    friend Constraint operator<=(double c, const LinearExpression &right);
    friend Constraint operator==(const LinearExpression &left, const LinearExpression &right);
    friend Constraint operator==(const LinearExpression &left, const Variable &var);
    friend Constraint operator==(const Variable &var, const LinearExpression &right);
    friend Constraint operator==(const LinearExpression &left, double c);
    friend Constraint operator==(double c, const LinearExpression &right);

    friend QuadExpression operator*(const LinearExpression &expr, const Variable &var);
    friend QuadExpression operator*(const LinearExpression &expr1, const LinearExpression &expr2);
    friend GeneralExpression operator*(const LinearExpression &expr1, const QuadExpression &expr2);
    friend GeneralExpression operator*(const LinearExpression &expr1, const GeneralExpression &expr2);
    friend GeneralExpression operator/(const LinearExpression &expr, const Variable &var);
    friend GeneralExpression operator/(const LinearExpression &expr1, const LinearExpression &expr2);
    friend GeneralExpression operator/(const LinearExpression &expr1, const QuadExpression &expr2);
    friend GeneralExpression operator/(const LinearExpression &expr1, const GeneralExpression &expr2);
    friend QuadExpression GetQuadExpression(const LinearExpression &expr);
    friend LinearExpression operator-(const LinearExpression &left, const QuadExpression &right);
    friend GeneralExpression operator-(const LinearExpression &left, const GeneralExpression &right);
    friend LinearExpression operator+(const LinearExpression &left, const QuadExpression &right);
    friend GeneralExpression operator+(const LinearExpression &left, const GeneralExpression &right);
    friend GeneralExpression operator/(double c, const LinearExpression& expr);

    arhiplex::ILinearExpression *Get() const noexcept
    {
        return _expr.get();
    }
/**
\privatesection
*/

  private:
    std::shared_ptr<arhiplex::ILinearExpression> _expr;
};

} // namespace arhiplex