#include "SOCExpression.h"

namespace robust_model {

template<class V>
template<ConvertibleTo<AffineExpression<typename V::Reference>> E>
SOCExpression<V>::SOCExpression(NormedAffineVector<V> const& normed_vector,
                                E const& affine):
        _normed_vector(normed_vector), _affine(affine) {}

template<class V>
template<ConvertibleTo<AffineExpression<typename V::Reference>> E>
SOCExpression<V>::SOCExpression(E const& affine) : _affine(affine) {}

template<class V>
SOCExpression<V>& SOCExpression<V>::operator+=(SOCExpression<V> const& other) {
    helpers::exception_check(is_affine() or other.is_affine(),
                             "Can not add two norms!");
    if (is_affine())
        _normed_vector = other.normed_vector();
    _affine += other.affine();
    return *this;
}

template<class V>
template<AffineAddable<typename V::Reference> T>
SOCExpression<V>& SOCExpression<V>::operator+=(T const& other) {
    _affine += other;
    return *this;
}

template<class V>
template<SOCSubtractable<V> T>
SOCExpression<V>& SOCExpression<V>::operator-=(T const& other) {
    return *this += -other;
}

template<class V>
SOCExpression<V> SOCExpression<V>::operator-() const {
    helpers::exception_check(is_affine(), "Can not negate norms!");
    return SOCExpression<V>(-affine());
}

template<class V>
template<SOCAddable<V> T>
SOCExpression<V> SOCExpression<V>::operator+(T const& other) const {
    return SOCExpression<V>(*this) += other;
}

template<class V>
template<SOCSubtractable<V> T>
SOCExpression<V> SOCExpression<V>::operator-(T const& other) const {
    return SOCExpression<V>(*this) -= other;
}

template<class V>
template<SOCSubtractable<V> T>
RawConstraint<SOCExpression<V>> SOCExpression<V>::operator>=(T const& other) const {
    return {ConstraintSense::GEQ,
            *this - other};
}

template<class V>
template<SOCSubtractable<V> T>
RawConstraint<SOCExpression<V>> SOCExpression<V>::operator<=(T const& other) const {
    return {ConstraintSense::LEQ,
            *this - other};
}

template<class V>
template<SOCSubtractable<V> T>
RawConstraint<SOCExpression<V>> SOCExpression<V>::operator==(T const& other) const {
    return {ConstraintSense::EQ,
            *this - other};
}

template<class V>
template<class W>
SOCExpression<W> SOCExpression<V>::translate_to_other(std::vector<VariableReference<W>> const& new_vars) const {
    if (is_affine()) {
        return SOCExpression<W>(affine().translate_to_other(new_vars));
    } else {
        return SOCExpression<W>(normed_vector().translate_to_other(new_vars), affine().translate_to_other(new_vars));
    }
}

template<class V>
template<class W, AffineAddable<typename W::Reference> T>
SOCExpression<W> SOCExpression<V>::substitute(std::vector<T> const& substitutions) const {
    if (is_affine()) {
        return SOCExpression<W>(affine().template substitute<typename W::Reference>(substitutions));
    } else {
        return SOCExpression<W>(normed_vector().template substitute<W>(substitutions),
                                affine().template substitute<typename W::Reference>(substitutions));
    }
}

template<class V>
bool SOCExpression<V>::is_affine() const {
    return not _normed_vector.has_value();
}

template<class V>
bool SOCExpression<V>::is_single_unscaled_variable() const {
    return is_affine() and
           (affine().linear().scaled_variables().size() == 1) and
           (affine().linear().scaled_variables().front().scale() == 1);
}

template<class V>
NormedAffineVector<V> const& SOCExpression<V>::normed_vector() const {
    return _normed_vector.value();
}

template<class V>
AffineExpression<typename V::Reference> const& SOCExpression<V>::affine() const {
    return _affine;
}

template<class V>
std::string SOCExpression<V>::to_string() const {
    std::string s = _affine.to_string();
    if (not is_affine()) {
        s += " + " + normed_vector().to_string();
    }
    return s;
}

template<class V>
template<class Iter>
SOCExpression<V> SOCExpression<V>::norm(Iter const& iter, VectorNormType norm_type) {
    std::vector<AffineExpression<typename V::Reference>>
            vectorized;
    for (auto const& item: iter) {
        vectorized.emplace_back(item);
    }
    return SOCExpression<V>(NormedAffineVector<V>::norm(vectorized, norm_type),
                            AffineExpression<typename V::Reference>{});
}

template<class V>
template<class S>
double SOCExpression<V>::value(S const& solution) const {
    if(is_affine())
        return affine().value(solution);
    return normed_vector().value(solution) + affine().value(solution);
}

template<class V>
double robust_model::SOCExpression<V>::lb() const {
    return normed_vector().lb() + affine().lb();
}

template<class V>
double robust_model::SOCExpression<V>::ub() const {
    return normed_vector().ub() + affine().ub();
}


}

