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

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

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

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

class QuadExpression
{
  public:
/**
\~russian
* Создает пустое выражение

\~english
* Creates an empty expression

\~chinese-traditional
* Creates an empty expression
*/
    QuadExpression() : _expr(CreateQuadExpression(), releasable_object_deleter)
    {
    }

/**
\~russian
* Создает выражение на основе переменных и коэффициента
\param[in] var1 переменная №1 в выражении
\param[in] var2 переменная №2 в выражении
\param[in] coeff коэффициент в выражении

\~english
* Creates an expression based on variables and coefficient
\param[in] var1 variable #1 in expression
\param[in] var2 variable #2 in expression
\param[in] coeff coefficient in expression

\~chinese-traditional
* Creates an expression based on variables and coefficient
\param[in] var1 variable #1 in expression
\param[in] var2 variable #2 in expression
\param[in] coeff coefficient in expression
*/
    QuadExpression(const Variable &var1, const Variable &var2, double coeff = 1.0)
        : _expr(CreateQuadExpression(), releasable_object_deleter)
    {
        _expr->AddTerm(var1.Get(), var2.Get(), coeff);
    }

/**
\private
*/
    QuadExpression(arhiplex::IQuadExpression *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
* Возвращает переменную №1 по индексу элемента в выражении
\param[in] i индекс элемента в выражении
\return Объект переменной

\~english
* Returns variable #1 by element index in expression
\param[in] i element index in expression
\return Variable object

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

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

\~english
* Returns variable #2 by element index in expression
\param[in] i element index in expression
\return Variable object

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


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

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

\~chinese-traditional
* Returns the coefficient of variables by the index of the element in the expression
\param[in] i index of the element in the expression
\return Value of the coefficient
*/
    double GetTermCoeff(int i) const
    {
        auto ret = _expr->GetTermCoeff(i);
        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 for the variables 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 for the variables in the element
*/
    void SetTermCoeff(int i, double value)
    {
        _expr->SetTermCoeff(i, value);
        CHECKERR(_expr);
    }
	

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

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

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

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

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

\~chinese-traditional
* Adds an expression to an expression
\param[in] expr quadratic expression
\param[in] mult coefficient to the expression being added
*/
    void AddExpression(const QuadExpression &expr, double mult = 1.0)
    {
        _expr->AddExpression(expr.Get(), mult);
        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 add
\param[in] mult the coefficient to the expression to add
*/
    void AddExpression(const LinearExpression &expr, double mult = 1.0)
    {
        LinearExpression lin_expr(_expr->GetLinearPart()); 
        lin_expr.AddExpression(expr, 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
* Создает копию выражения, не привязанную к модели или другому ограничению
\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
*/
    QuadExpression CreateFreeCopy() const
    {
        return _expr->CreateFreeCopy();
    }

/**
\privatesection
*/

    QuadExpression& operator+=(double c)
    {
        LinearExpression expr(_expr->GetLinearPart());
        expr+=c;
        return *this;
    }

    QuadExpression& operator+=(const Variable &var)
    {
        LinearExpression expr(_expr->GetLinearPart());
        expr += var;
        return *this;
    }

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

    QuadExpression& operator-=(double c)
    {
        LinearExpression expr(_expr->GetLinearPart());
        expr -= c;
        return *this;
    }

    QuadExpression& operator-=(const Variable &var)
    {
        LinearExpression expr(_expr->GetLinearPart());
        expr -= var;
        return *this;
    }

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

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

        LinearExpression expr(_expr->GetLinearPart());
        expr *= c;

        CHECKERR(_expr);
        return *this;
    }

    QuadExpression& operator/=(double c)
    {
        for (int i = 0; i < GetTermsCount(); i++)
        {
            _expr->SetTermCoeff(i, GetTermCoeff(i) / c);
            CHECKERR(_expr);
        }
        LinearExpression expr(_expr->GetLinearPart());
        expr /= c;

        CHECKERR(_expr);
        return *this;
    }

    LinearExpression GetLinearPart() const
    {
        return LinearExpression(_expr->GetLinearPart());
    }

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

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

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

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

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

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

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

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

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

    friend QuadExpression operator-(const QuadExpression& left, const LinearExpression& lin_expr)
    {
        QuadExpression ret = left.CreateFreeCopy();
        LinearExpression expr(ret.GetLinearPart());
        expr.AddExpression(lin_expr, -1.0);
        return ret;
    }

    friend QuadExpression operator+(const QuadExpression& left, const LinearExpression& lin_expr)
    {
        QuadExpression ret = left.CreateFreeCopy();
        LinearExpression expr(ret.GetLinearPart());
        expr.AddExpression(lin_expr, 1.0);
        return ret;
    }


    friend QuadExpression operator-(const Variable &var, const QuadExpression &right)
    {

        QuadExpression ret = right.CreateFreeCopy();
        ret *= -1.0;
        LinearExpression expr(ret.GetLinearPart());
        expr.AddTerm(var, 1.0);
        return ret;
    }

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

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

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

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

    friend Constraint operator>=(const QuadExpression &left, const QuadExpression &right);

    friend Constraint operator>=(const QuadExpression &left, const LinearExpression &right);
    friend Constraint operator>=(const LinearExpression &left, const QuadExpression &right);

    friend Constraint operator>=(const QuadExpression &left, const Variable &var);
    friend Constraint operator>=(const Variable &var, const QuadExpression &right);

    friend Constraint operator>=(const QuadExpression &left, double c);
    friend Constraint operator>=(double c, const QuadExpression &right);


    friend Constraint operator<=(const QuadExpression &left, const QuadExpression &right);

    friend Constraint operator<=(const QuadExpression &left, const LinearExpression &right);
    friend Constraint operator<=(const LinearExpression &left, const QuadExpression &right);

    friend Constraint operator<=(const QuadExpression &left, const Variable &var);
    friend Constraint operator<=(const Variable &var, const QuadExpression &right);

    friend Constraint operator<=(const QuadExpression &left, double c);
    friend Constraint operator<=(double c, const QuadExpression &right);


    friend Constraint operator==(const QuadExpression &left, const QuadExpression &right);

    friend Constraint operator==(const QuadExpression &left, const LinearExpression &right);

    friend Constraint operator==(const QuadExpression &left, const Variable &var);
//    friend Constraint operator==(const Variable &var, const QuadExpression &right);

    friend Constraint operator==(const QuadExpression &left, double c);
//    friend Constraint operator==(double c, const QuadExpression &right);

    friend QuadExpression operator-(const QuadExpression &left, const LinearExpression &right);
    friend GeneralExpression operator-(const QuadExpression &left, const GeneralExpression &right);
    friend QuadExpression operator+(const QuadExpression &left, const LinearExpression &right);
    friend GeneralExpression operator+(const QuadExpression &left, const GeneralExpression &right);
    friend LinearExpression GetLinearExpression(const QuadExpression &expr);

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

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

} // namespace arhiplex