#pragma once

#include <cassert>
#include <cmath>

#include <ostream>

#include "gdconfig.hpp"
#include "gdtypes.hpp"
#include "general_dichotomy.hpp"
#include "weight.hpp"

// forward declarations
class BaseSolver;
namespace dichotomy {
    class Result;
}

namespace Utils {

// vector operations

/*!
 * \brief compute the dot product between a weight vector and a cost vector
 */
inline dichotomy::WEIGHT dot_product(dichotomy::WeightVec& wvec, dichotomy::CostVec& cvec) {

    assert(wvec.size() == cvec.size());

    dichotomy::WEIGHT result = 0.;
    for(std::size_t i = 0; i < wvec.size(); i ++) {
        result += wvec[i]*cvec[i];
    }

    return result;
}

/*!
 * \brief compute the difference of two weight vectors: left-right
 * \param left the left vector
 * \param right the right vector
 * \param result a vector to store the result
 */
inline void subtractWeightVec(dichotomy::WeightVec& left, dichotomy::WeightVec& right, dichotomy::WeightVec& result) {
    for(size_t i = 0; i < left.size(); i ++) {
        result[i] = left[i]-right[i];
    }
}

/*!
 * \brief compute the difference of two cost vectors: left-right
 * \param left the left vector
 * \param right the right vector
 * \param result a vector to store the result
 */
inline void subtractCostVec(dichotomy::CostVec& left, dichotomy::CostVec& right, dichotomy::CostVec& result) {
    for(size_t i = 0; i < left.size(); i ++) {
        result[i] = left[i]-right[i];
    }
}

/*!
 * \brief multiply a weight vector by a scalar (inplace)
 * \param vec the weight vector to multiply
 * \param value the multiplier
 */
inline void multWeightVec(dichotomy::WeightVec& vec, dichotomy::WEIGHT value) {
    for(size_t i = 0; i < vec.size(); i ++) {
        vec[i] *= value;
    }
}

/*!
 * \brief multiply a weight vector by a scalar
 * \param vec the weight vector to multiply
 * \param value the multiplier
 * \param output the output vector
 */
inline void multWeightVec(dichotomy::WeightVec& vec, dichotomy::WEIGHT value, dichotomy::WeightVec& output) {
    for(size_t i = 0; i < vec.size(); i ++) {
         output[i] = vec[i]*value;
    }
}

/*!
 * \brief add two weight vectors, the result is stored in the first argument (inplace)
 * \param left the left weight vector, store the output
 * \param right the right weight vector
 */
inline void addWeightVec(dichotomy::WeightVec& left, dichotomy::WeightVec& right) {
    for(size_t i = 0; i < left.size(); i ++) {
        left[i] += right[i];
    }
}


/*!
 * \brief check wether two cost vectors are equal or not
 * \param v1 the first cost vector
 * \param v2 the second cost vector
 */
inline bool compareCostVectors(dichotomy::CostVec& v1, dichotomy::CostVec& v2) {

    bool equal = true;

    for(std::size_t obj_ind = 0; obj_ind < v1.size(); obj_ind ++) {
        if(fabs(v1[obj_ind] - v2[obj_ind]) > dichotomy::epsilon1) {
            equal = false;
            break;
        }
    }

    return equal;

}

/*!
 * \brief check wether two weight vectors are equal or not
 * \param v1 the first weight vector
 * \param v2 the second WEIGHT vector
 */
inline bool compareWeightVectors(dichotomy::WeightVec& v1, dichotomy::WeightVec& v2) {

    bool equal = true;

    for(std::size_t obj_ind = 0; obj_ind < v1.size(); obj_ind ++) {
        if(fabs(v1[obj_ind] - v2[obj_ind]) > dichotomy::epsilon1) {
            equal = false;
            break;
        }
    }

    return equal;

}

/*!
 * \brief check wether two weights are equal, based on their adjacency
 * \param wleft the first weight to compare
 * \param wright the second weight to compare
 */
inline bool compareWeightAdj(Weight& wleft, Weight& wright) {
    bool equal = true;

    if(wleft.bnd.size() != wright.bnd.size()) {
        return false;
    }
    if(wleft.adj.size() != wright.adj.size()) {
        return false;
    }

    {
    auto itleft = wleft.adj.begin();
    for(auto& eltright: wright.adj) {
        if(*itleft != eltright) {
            return false;
        }
        itleft ++;
    }
    }

    {
    auto itleft = wleft.bnd.begin();
    for(auto& eltright: wright.bnd) {
        if(*itleft != eltright) {
            return false;
        }
        itleft ++;
    }
    }

    return equal;
}

/*!
 * \brief compare two scalar values
 * \return 0 if equal, 1 if left is smaller, 2 otherwise
 */
inline size_t compare(dichotomy::WEIGHT v1, dichotomy::WEIGHT v2) {

    if(v1 < v2-dichotomy::epsilon2) {
        return 1;
    } else if(v2 < v1-dichotomy::epsilon2) {
        return 2;
    }

    return 0;
}

// printing

/*!
 * \brief print a cost vector
 * \param costVec the cost vector to print
 * \param os the output stream
 */
void printCostVec(dichotomy::CostVec& costVec, std::ostream& os);

/*!
 * \brief print a weight vector
 * \param weightVec the weight vector to print
 * \param os the output stream
 */
void printWeightVec(dichotomy::WeightVec& weightVec, std::ostream& os);

/*!
 * \brief print the algorithm parameters
 * \param parameters the parameters
 * \param os the output stream
 */
void printParameters(GeneralDichotomy::Parameters& parameters, std::ostream& os);

// output

/*!
 * \brief export the solution to a file
 * \param solver the user-defined solver
 * \param result a result object containing the algorithm output
 * \param fname name of the file
 */
void exportDecomposition(BaseSolver& solver, dichotomy::Result& result, std::string fname);

};