#ifndef LBFGS_HPP
#define LBFGS_HPP

#include <Eigen/Eigen>
#include <cmath>
#include <algorithm>

namespace lbfgs
{
    // ----------------------- Data Type Part -----------------------

    /**
     * L-BFGS optimization parameters.
     */
    struct lbfgs_parameter_t
    {
        /**
         * The number of corrections to approximate the inverse hessian matrix.
         *  The L-BFGS routine stores the computation results of previous m
         *  iterations to approximate the inverse hessian matrix of the current
         *  iteration. This parameter controls the size of the limited memories
         *  (corrections). The default value is 8. Values less than 3 are
         *  not recommended. Large values will result in excessive computing time.
         */
        int mem_size = 8;

        /**
         * Epsilon for grad convergence test. DO NOT USE IT in nonsmooth cases!
         *  Set it to 0.0 and use past-delta-based test for nonsmooth functions.
         *  This parameter determines the accuracy with which the solution is to
         *  be found. A minimization terminates when
         *      ||g(x)||_inf / max(1, ||x||_inf) < g_epsilon,
         *  where ||.||_inf is the infinity norm. The default value is 1.0e-5.
         *  This should be greater than 1.0e-6 in practice because L-BFGS does
         *  not directly reduce first-order residual. It still needs the function
         *  value which can be corrupted by machine_prec when ||g|| is small.
         */
        double g_epsilon = 1.0e-5;

        /**
         * Distance for delta-based convergence test.
         *  This parameter determines the distance, in iterations, to compute
         *  the rate of decrease of the cost function. If the value of this
         *  parameter is zero, the library does not perform the delta-based
         *  convergence test. The default value is 3.
         */
        int past = 3;

        /**
         * Delta for convergence test.
         *  This parameter determines the minimum rate of decrease of the
         *  cost function. The library stops iterations when the following
         *  condition is met:
         *      |f' - f| / max(1, |f|) < delta,
         *  where f' is the cost value of past iterations ago, and f is
         *  the cost value of the current iteration.
         *  The default value is 1.0e-6.
         */
        double delta = 1.0e-6;

        /**
         * The maximum number of iterations.
         *  The lbfgs_optimize() function terminates an minimization process with
         *  ::LBFGSERR_MAXIMUMITERATION status code when the iteration count
         *  exceedes this parameter. Setting this parameter to zero continues an
         *  minimization process until a convergence or error. The default value
         *  is 0.
         */
        int max_iterations = 0;

        /**
         * The maximum number of trials for the line search.
         *  This parameter controls the number of function and gradients evaluations
         *  per iteration for the line search routine. The default value is 64.
         */
        int max_linesearch = 64;

        /**
         * The minimum step of the line search routine.
         *  The default value is 1.0e-20. This value need not be modified unless
         *  the exponents are too large for the machine being used, or unless the
         *  problem is extremely badly scaled (in which case the exponents should
         *  be increased).
         */
        double min_step = 1.0e-20;

        /**
         * The maximum step of the line search.
         *  The default value is 1.0e+20. This value need not be modified unless
         *  the exponents are too large for the machine being used, or unless the
         *  problem is extremely badly scaled (in which case the exponents should
         *  be increased).
         */
        double max_step = 1.0e+20;

        /**
         * A parameter to control the accuracy of the line search routine.
         *  The default value is 1.0e-4. This parameter should be greater
         *  than zero and smaller than 1.0.
         */
        double f_dec_coeff = 1.0e-4;

        /**
         * A parameter to control the accuracy of the line search routine.
         *  The default value is 0.9. If the function and gradient
         *  evaluations are inexpensive with respect to the cost of the
         *  iteration (which is sometimes the case when solving very large
         *  problems) it may be advantageous to set this parameter to a small
         *  value. A typical small value is 0.1. This parameter should be
         *  greater than the f_dec_coeff parameter and smaller than 1.0.
         */
        double s_curv_coeff = 0.9;

        /**
         * A parameter to ensure the global convergence for nonconvex functions.
         *  The default value is 1.0e-6. The parameter performs the so called
         *  cautious update for L-BFGS, especially when the convergence is
         *  not sufficient. The parameter must be positive but might as well
         *  be less than 1.0e-3 in practice.
         */
        double cautious_factor = 1.0e-6;

        /**
         * The machine precision for floating-point values. The default is 1.0e-16.
         *  This parameter must be a positive value set by a client program to
         *  estimate the machine precision.
         */
        double machine_prec = 1.0e-16;
    };

    /**
     * Return values of lbfgs_optimize().
     *  Roughly speaking, a negative value indicates an error.
     */
    enum
    {
        /** L-BFGS reaches convergence. */
        LBFGS_CONVERGENCE = 0,
        /** L-BFGS satisfies stopping criteria. */
        LBFGS_STOP,
        /** The iteration has been canceled by the monitor callback. */
        LBFGS_CANCELED,

        /** Unknown error. */
        LBFGSERR_UNKNOWNERROR = -1024,
        /** Invalid number of variables specified. */
        LBFGSERR_INVALID_N,
        /** Invalid parameter lbfgs_parameter_t::mem_size specified. */
        LBFGSERR_INVALID_MEMSIZE,
        /** Invalid parameter lbfgs_parameter_t::g_epsilon specified. */
        LBFGSERR_INVALID_GEPSILON,
        /** Invalid parameter lbfgs_parameter_t::past specified. */
        LBFGSERR_INVALID_TESTPERIOD,
        /** Invalid parameter lbfgs_parameter_t::delta specified. */
        LBFGSERR_INVALID_DELTA,
        /** Invalid parameter lbfgs_parameter_t::min_step specified. */
        LBFGSERR_INVALID_MINSTEP,
        /** Invalid parameter lbfgs_parameter_t::max_step specified. */
        LBFGSERR_INVALID_MAXSTEP,
        /** Invalid parameter lbfgs_parameter_t::f_dec_coeff specified. */
        LBFGSERR_INVALID_FDECCOEFF,
        /** Invalid parameter lbfgs_parameter_t::s_curv_coeff specified. */
        LBFGSERR_INVALID_SCURVCOEFF,
        /** Invalid parameter lbfgs_parameter_t::machine_prec specified. */
        LBFGSERR_INVALID_MACHINEPREC,
        /** Invalid parameter lbfgs_parameter_t::max_linesearch specified. */
        LBFGSERR_INVALID_MAXLINESEARCH,
        /** The function value became NaN or Inf. */
        LBFGSERR_INVALID_FUNCVAL,
        /** The line-search step became smaller than lbfgs_parameter_t::min_step. */
        LBFGSERR_MINIMUMSTEP,
        /** The line-search step became larger than lbfgs_parameter_t::max_step. */
        LBFGSERR_MAXIMUMSTEP,
        /** Line search reaches the maximum, assumptions not satisfied or precision not achievable.*/
        LBFGSERR_MAXIMUMLINESEARCH,
        /** The algorithm routine reaches the maximum number of iterations. */
        LBFGSERR_MAXIMUMITERATION,
        /** Relative search interval width is at least lbfgs_parameter_t::machine_prec. */
        LBFGSERR_WIDTHTOOSMALL,
        /** A logic error (negative line-search step) occurred. */
        LBFGSERR_INVALIDPARAMETERS,
        /** The current search direction increases the cost function value. */
        LBFGSERR_INCREASEGRADIENT,
    };

    /**
     * Callback interface to provide cost function and gradient evaluations.
     *
     *  The lbfgs_optimize() function call this function to obtain the values of cost
     *  function and its gradients when needed. A client program must implement
     *  this function to evaluate the values of the cost function and its
     *  gradients, given current values of variables.
     *
     *  @param  instance    The user data sent for lbfgs_optimize() function by the client.
     *  @param  x           The current values of variables.
     *  @param  g           The gradient vector. The callback function must compute
     *                      the gradient values for the current variables.
     *  @retval double      The value of the cost function for the current variables.
     */
    typedef double (*lbfgs_evaluate_t)(void *instance,
                                       const Eigen::VectorXd &x,
                                       Eigen::VectorXd &g);

    /**
     * Callback interface to provide an upper bound at the beginning of the current line search.
     *
     *  The lbfgs_optimize() function call this function to obtain the values of the
     *  upperbound of the stepsize to search in, provided with the beginning values of
     *  variables before the line search, and the current step vector (can be descent direction).
     *  A client program can implement this function for more efficient linesearch. Any step
     *  larger than this bound should not be considered. For example, it has a very large or even
     *  inf function value. Note that the function value at the provided bound should be FINITE!
     *  If it is not used, just set it nullptr.
     *
     *  @param  instance    The user data sent for lbfgs_optimize() function by the client.
     *  @param  xp          The values of variables before current line search.
     *  @param  d           The step vector. It can be the descent direction.
     *  @retval double      The upperboud of the step in current line search routine,
     *                      such that (stpbound * d) is the maximum reasonable step.
     */
    typedef double (*lbfgs_stepbound_t)(void *instance,
                                        const Eigen::VectorXd &xp,
                                        const Eigen::VectorXd &d);

    /**
     * Callback interface to monitor the progress of the minimization process.
     *
     *  The lbfgs_optimize() function call this function for each iteration. Implementing
     *  this function, a client program can store or display the current progress
     *  of the minimization process. If it is not used, just set it nullptr.
     *
     *  @param  instance    The user data sent for lbfgs_optimize() function by the client.
     *  @param  x           The current values of variables.
     *  @param  g           The current gradient values of variables.
     *  @param  fx          The current value of the cost function.
     *  @param  step        The line-search step used for this iteration.
     *  @param  k           The iteration count.
     *  @param  ls          The number of evaluations called for this iteration.
     *  @retval int         Zero to continue the minimization process. Returning a
     *                      non-zero value will cancel the minimization process.
     */
    typedef int (*lbfgs_progress_t)(void *instance,
                                    const Eigen::VectorXd &x,
                                    const Eigen::VectorXd &g,
                                    const double fx,
                                    const double step,
                                    const int k,
                                    const int ls);

    /**
     * Callback data struct
     */
    struct callback_data_t
    {
        void *instance = nullptr;
        lbfgs_evaluate_t proc_evaluate = nullptr;
        lbfgs_stepbound_t proc_stepbound = nullptr;
        lbfgs_progress_t proc_progress = nullptr;
    };

    // ----------------------- L-BFGS Part -----------------------

    /**
     * Line search method for smooth or nonsmooth functions.
     *  This function performs line search to find a point that satisfy
     *  both the Armijo condition and the weak Wolfe condition. It is
     *  as robust as the backtracking line search but further applies
     *  to continuous and piecewise smooth functions where the strong
     *  Wolfe condition usually does not hold.
     *
     *  @see
     *      Adrian S. Lewis and Michael L. Overton. Nonsmooth optimization
     *      via quasi-Newton methods. Mathematical Programming, Vol 141,
     *      No 1, pp. 135-163, 2013.
     */
    inline int line_search_lewisoverton(Eigen::VectorXd &x,
                                        double &f,
                                        Eigen::VectorXd &g,
                                        double &stp,
                                        const Eigen::VectorXd &s,
                                        const Eigen::VectorXd &xp,
                                        const Eigen::VectorXd &gp,
                                        const double stpmin,
                                        const double stpmax,
                                        const callback_data_t &cd,
                                        const lbfgs_parameter_t &param)
    {
        int count = 0;
        bool brackt = false, touched = false;
        double finit, dginit, dgtest, dstest;
        double mu = 0.0, nu = stpmax;

        /* Check the input parameters for errors. */
        if (!(stp > 0.0))
        {
            return LBFGSERR_INVALIDPARAMETERS;
        }

        /* Compute the initial gradient in the search direction. */
        dginit = gp.dot(s);

        /* Make sure that s points to a descent direction. */
        if (0.0 < dginit)
        {
            return LBFGSERR_INCREASEGRADIENT;
        }

        /* The initial value of the cost function. */
        finit = f;
        dgtest = param.f_dec_coeff * dginit;
        dstest = param.s_curv_coeff * dginit;

        while (true)
        {
            x = xp + stp * s;

            /* Evaluate the function and gradient values. */
            f = cd.proc_evaluate(cd.instance, x, g);
            ++count;

            /* Test for errors. */
            if (std::isinf(f) || std::isnan(f))
            {
                return LBFGSERR_INVALID_FUNCVAL;
            }
            /* Check the Armijo condition. */
            if (f > finit + stp * dgtest)
            {
                nu = stp;
                brackt = true;
            }
            else
            {
                /* Check the weak Wolfe condition. */
                if (g.dot(s) < dstest)
                {
                    mu = stp;
                }
                else
                {
                    return count;
                }
            }
            if (param.max_linesearch <= count)
            {
                /* Maximum number of iteration. */
                return LBFGSERR_MAXIMUMLINESEARCH;
            }
            if (brackt && (nu - mu) < param.machine_prec * nu)
            {
                /* Relative interval width is at least machine_prec. */
                return LBFGSERR_WIDTHTOOSMALL;
            }

            if (brackt)
            {
                stp = 0.5 * (mu + nu);
            }
            else
            {
                stp *= 2.0;
            }

            if (stp < stpmin)
            {
                /* The step is the minimum value. */
                return LBFGSERR_MINIMUMSTEP;
            }
            if (stp > stpmax)
            {
                if (touched)
                {
                    /* The step is the maximum value. */
                    return LBFGSERR_MAXIMUMSTEP;
                }
                else
                {
                    /* The maximum value should be tried once. */
                    touched = true;
                    stp = stpmax;
                }
            }
        }
    }

    /**
     * Start a L-BFGS optimization.
     * Assumptions: 1. f(x) is either C2 or C0 but piecewise C2;
     *              2. f(x) is lower bounded;
     *              3. f(x) has bounded level sets;
     *              4. g(x) is either the gradient or subgradient;
     *              5. The gradient exists at the initial guess x0.
     * A user must implement a function compatible with ::lbfgs_evaluate_t (evaluation
     * callback) and pass the pointer to the callback function to lbfgs_optimize()
     * arguments. Similarly, a user can implement a function compatible with
     * ::lbfgs_stepbound_t to provide an external upper bound for stepsize, and
     * ::lbfgs_progress_t (progress callback) to obtain the current progress
     * (e.g., variables, function, and gradient, etc) and to cancel the iteration
     * process if necessary. Implementation of the stepbound and the progress callback
     * is optional: a user can pass nullptr if progress notification is not necessary.
     *
     *
     *  @param  x               The vector of decision variables.
     *                          THE INITIAL GUESS x0 SHOULD BE SET BEFORE THE CALL!
     *                          A client program can receive decision variables
     *                          through this vector, at which the cost and its
     *                          gradient are queried during minimization.
     *  @param  f               The ref to the variable that receives the final
     *                          value of the cost function for the variables.
     *  @param  proc_evaluate   The callback function to provide function f(x) and
     *                          gradient g(x) evaluations given a current values of
     *                          variables x. A client program must implement a
     *                          callback function compatible with lbfgs_evaluate_t
     *                          and pass the pointer to the callback function.
     *  @param  proc_stepbound  The callback function to provide values of the
     *                          upperbound of the stepsize to search in, provided
     *                          with the beginning values of variables before the
     *                          line search, and the current step vector (can be
     *                          negative gradient). A client program can implement
     *                          this function for more efficient linesearch. If it is
     *                          not used, just set it nullptr.
     *  @param  proc_progress   The callback function to receive the progress
     *                          (the number of iterations, the current value of
     *                          the cost function) of the minimization
     *                          process. This argument can be set to nullptr if
     *                          a progress report is unnecessary.
     *  @param  instance        A user data pointer for client programs. The callback
     *                          functions will receive the value of this argument.
     *  @param  param           The parameters for L-BFGS optimization.
     *  @retval int             The status code. This function returns a nonnegative
     *                          integer if the minimization process terminates without
     *                          an error. A negative integer indicates an error.
     */
    inline int lbfgs_optimize(Eigen::VectorXd &x,
                              double &f,
                              lbfgs_evaluate_t proc_evaluate,
                              lbfgs_stepbound_t proc_stepbound,
                              lbfgs_progress_t proc_progress,
                              void *instance,
                              const lbfgs_parameter_t &param)
    {
        int ret, i, j, k, ls, end, bound;
        double step, step_min, step_max, fx, ys, yy;
        double gnorm_inf, xnorm_inf, beta, rate, cau;

        const int n = x.size();
        const int m = param.mem_size;

        /* Check the input parameters for errors. */
        if (n <= 0)
        {
            return LBFGSERR_INVALID_N;
        }
        if (m <= 0)
        {
            return LBFGSERR_INVALID_MEMSIZE;
        }
        if (param.g_epsilon < 0.0)
        {
            return LBFGSERR_INVALID_GEPSILON;
        }
        if (param.past < 0)
        {
            return LBFGSERR_INVALID_TESTPERIOD;
        }
        if (param.delta < 0.0)
        {
            return LBFGSERR_INVALID_DELTA;
        }
        if (param.min_step < 0.0)
        {
            return LBFGSERR_INVALID_MINSTEP;
        }
        if (param.max_step < param.min_step)
        {
            return LBFGSERR_INVALID_MAXSTEP;
        }
        if (!(param.f_dec_coeff > 0.0 &&
              param.f_dec_coeff < 1.0))
        {
            return LBFGSERR_INVALID_FDECCOEFF;
        }
        if (!(param.s_curv_coeff < 1.0 &&
              param.s_curv_coeff > param.f_dec_coeff))
        {
            return LBFGSERR_INVALID_SCURVCOEFF;
        }
        if (!(param.machine_prec > 0.0))
        {
            return LBFGSERR_INVALID_MACHINEPREC;
        }
        if (param.max_linesearch <= 0)
        {
            return LBFGSERR_INVALID_MAXLINESEARCH;
        }

        /* Prepare intermediate variables. */
        Eigen::VectorXd xp(n);
        Eigen::VectorXd g(n);
        Eigen::VectorXd gp(n);
        Eigen::VectorXd d(n);
        Eigen::VectorXd pf(std::max(1, param.past));

        /* Initialize the limited memory. */
        Eigen::VectorXd lm_alpha = Eigen::VectorXd::Zero(m);
        Eigen::MatrixXd lm_s = Eigen::MatrixXd::Zero(n, m);
        Eigen::MatrixXd lm_y = Eigen::MatrixXd::Zero(n, m);
        Eigen::VectorXd lm_ys = Eigen::VectorXd::Zero(m);

        /* Construct a callback data. */
        callback_data_t cd;
        cd.instance = instance;
        cd.proc_evaluate = proc_evaluate;
        cd.proc_stepbound = proc_stepbound;
        cd.proc_progress = proc_progress;

        /* Evaluate the function value and its gradient. */
        fx = cd.proc_evaluate(cd.instance, x, g);

        /* Store the initial value of the cost function. */
        pf(0) = fx;

        /*
        Compute the direction;
        we assume the initial hessian matrix H_0 as the identity matrix.
        */
        d = -g;

        /*
        Make sure that the initial variables are not a stationary point.
        */
        gnorm_inf = g.cwiseAbs().maxCoeff();
        xnorm_inf = x.cwiseAbs().maxCoeff();

        if (gnorm_inf / std::max(1.0, xnorm_inf) < param.g_epsilon)
        {
            /* The initial guess is already a stationary point. */
            ret = LBFGS_CONVERGENCE;
        }
        else
        {
            /*
            Compute the initial step:
            */
            step = 1.0 / d.norm();

            k = 1;
            end = 0;
            bound = 0;

            while (true)
            {
                /* Store the current position and gradient vectors. */
                xp = x;
                gp = g;

                /* If the step bound can be provied dynamically, then apply it. */
                step_min = param.min_step;
                step_max = param.max_step;
                if (cd.proc_stepbound)
                {
                    step_max = cd.proc_stepbound(cd.instance, xp, d);
                    step_max = step_max < param.max_step ? step_max : param.max_step;
                    step = step < step_max ? step : 0.5 * step_max;
                }

                /* Search for an optimal step. */
                ls = line_search_lewisoverton(x, fx, g, step, d, xp, gp, step_min, step_max, cd, param);

                if (ls < 0)
                {
                    /* Revert to the previous point. */
                    x = xp;
                    g = gp;
                    ret = ls;
                    break;
                }

                /* Report the progress. */
                if (cd.proc_progress)
                {
                    if (cd.proc_progress(cd.instance, x, g, fx, step, k, ls))
                    {
                        ret = LBFGS_CANCELED;
                        break;
                    }
                }

                /*
                Convergence test.
                The criterion is given by the following formula:
                ||g(x)||_inf / max(1, ||x||_inf) < g_epsilon
                */
                gnorm_inf = g.cwiseAbs().maxCoeff();
                xnorm_inf = x.cwiseAbs().maxCoeff();
                if (gnorm_inf / std::max(1.0, xnorm_inf) < param.g_epsilon)
                {
                    /* Convergence. */
                    ret = LBFGS_CONVERGENCE;
                    break;
                }

                /*
                Test for stopping criterion.
                The criterion is given by the following formula:
                |f(past_x) - f(x)| / max(1, |f(x)|) < \delta.
                */
                if (0 < param.past)
                {
                    /* We don't test the stopping criterion while k < past. */
                    if (param.past <= k)
                    {
                        /* The stopping criterion. */
                        rate = std::fabs(pf(k % param.past) - fx) / std::max(1.0, std::fabs(fx));

                        if (rate < param.delta)
                        {
                            ret = LBFGS_STOP;
                            break;
                        }
                    }

                    /* Store the current value of the cost function. */
                    pf(k % param.past) = fx;
                }

                if (param.max_iterations != 0 && param.max_iterations <= k)
                {
                    /* Maximum number of iterations. */
                    ret = LBFGSERR_MAXIMUMITERATION;
                    break;
                }

                /* Count the iteration number. */
                ++k;

                /*
                Update vectors s and y:
                s_{k+1} = x_{k+1} - x_{k} = \step * d_{k}.
                y_{k+1} = g_{k+1} - g_{k}.
                */
                lm_s.col(end) = x - xp;
                lm_y.col(end) = g - gp;

                /*
                Compute scalars ys and yy:
                ys = y^t \cdot s = 1 / \rho.
                yy = y^t \cdot y.
                Notice that yy is used for scaling the hessian matrix H_0 (Cholesky factor).
                */
                ys = lm_y.col(end).dot(lm_s.col(end));
                yy = lm_y.col(end).squaredNorm();
                lm_ys(end) = ys;

                /* Compute the negative of gradients. */
                d = -g;

                /*
                Only cautious update is performed here as long as
                (y^t \cdot s) / ||s_{k+1}||^2 > \epsilon * ||g_{k}||^\alpha,
                where \epsilon is the cautious factor and a proposed value
                for \alpha is 1.
                This is not for enforcing the PD of the approxomated Hessian
                since ys > 0 is already ensured by the weak Wolfe condition.
                This is to ensure the global convergence as described in:
                Dong-Hui Li and Masao Fukushima. On the global convergence of
                the BFGS method for nonconvex unconstrained optimization problems.
                SIAM Journal on Optimization, Vol 11, No 4, pp. 1054-1064, 2011.
                */
                cau = lm_s.col(end).squaredNorm() * gp.norm() * param.cautious_factor;

                if (ys > cau)
                {
                    /*
                    Recursive formula to compute dir = -(H \cdot g).
                    This is described in page 779 of:
                    Jorge Nocedal.
                    Updating Quasi-Newton Matrices with Limited Storage.
                    Mathematics of Computation, Vol. 35, No. 151,
                    pp. 773--782, 1980.
                    */
                    ++bound;
                    bound = m < bound ? m : bound;
                    end = (end + 1) % m;

                    j = end;
                    for (i = 0; i < bound; ++i)
                    {
                        j = (j + m - 1) % m; /* if (--j == -1) j = m-1; */
                        /* \alpha_{j} = \rho_{j} s^{t}_{j} \cdot q_{k+1}. */
                        lm_alpha(j) = lm_s.col(j).dot(d) / lm_ys(j);
                        /* q_{i} = q_{i+1} - \alpha_{i} y_{i}. */
                        d += (-lm_alpha(j)) * lm_y.col(j);
                    }

                    d *= ys / yy;

                    for (i = 0; i < bound; ++i)
                    {
                        /* \beta_{j} = \rho_{j} y^t_{j} \cdot \gamm_{i}. */
                        beta = lm_y.col(j).dot(d) / lm_ys(j);
                        /* \gamm_{i+1} = \gamm_{i} + (\alpha_{j} - \beta_{j}) s_{j}. */
                        d += (lm_alpha(j) - beta) * lm_s.col(j);
                        j = (j + 1) % m; /* if (++j == m) j = 0; */
                    }
                }

                /* The search direction d is ready. We try step = 1 first. */
                step = 1.0;
            }
        }

        /* Return the final value of the cost function. */
        f = fx;

        return ret;
    }

    /**
     * Get string description of an lbfgs_optimize() return code.
     *
     *  @param err          A value returned by lbfgs_optimize().
     */
    inline const char *lbfgs_strerror(const int err)
    {
        switch (err)
        {
        case LBFGS_CONVERGENCE:
            return "Success: reached convergence (g_epsilon).";

        case LBFGS_STOP:
            return "Success: met stopping criteria (past f decrease less than delta).";

        case LBFGS_CANCELED:
            return "The iteration has been canceled by the monitor callback.";

        case LBFGSERR_UNKNOWNERROR:
            return "Unknown error.";

        case LBFGSERR_INVALID_N:
            return "Invalid number of variables specified.";

        case LBFGSERR_INVALID_MEMSIZE:
            return "Invalid parameter lbfgs_parameter_t::mem_size specified.";

        case LBFGSERR_INVALID_GEPSILON:
            return "Invalid parameter lbfgs_parameter_t::g_epsilon specified.";

        case LBFGSERR_INVALID_TESTPERIOD:
            return "Invalid parameter lbfgs_parameter_t::past specified.";

        case LBFGSERR_INVALID_DELTA:
            return "Invalid parameter lbfgs_parameter_t::delta specified.";

        case LBFGSERR_INVALID_MINSTEP:
            return "Invalid parameter lbfgs_parameter_t::min_step specified.";

        case LBFGSERR_INVALID_MAXSTEP:
            return "Invalid parameter lbfgs_parameter_t::max_step specified.";

        case LBFGSERR_INVALID_FDECCOEFF:
            return "Invalid parameter lbfgs_parameter_t::f_dec_coeff specified.";

        case LBFGSERR_INVALID_SCURVCOEFF:
            return "Invalid parameter lbfgs_parameter_t::s_curv_coeff specified.";

        case LBFGSERR_INVALID_MACHINEPREC:
            return "Invalid parameter lbfgs_parameter_t::machine_prec specified.";

        case LBFGSERR_INVALID_MAXLINESEARCH:
            return "Invalid parameter lbfgs_parameter_t::max_linesearch specified.";

        case LBFGSERR_INVALID_FUNCVAL:
            return "The function value became NaN or Inf.";

        case LBFGSERR_MINIMUMSTEP:
            return "The line-search step became smaller than lbfgs_parameter_t::min_step.";

        case LBFGSERR_MAXIMUMSTEP:
            return "The line-search step became larger than lbfgs_parameter_t::max_step.";

        case LBFGSERR_MAXIMUMLINESEARCH:
            return "Line search reaches the maximum try number, assumptions not satisfied or precision not achievable.";

        case LBFGSERR_MAXIMUMITERATION:
            return "The algorithm routine reaches the maximum number of iterations.";

        case LBFGSERR_WIDTHTOOSMALL:
            return "Relative search interval width is at least lbfgs_parameter_t::machine_prec.";

        case LBFGSERR_INVALIDPARAMETERS:
            return "A logic error (negative line-search step) occurred.";

        case LBFGSERR_INCREASEGRADIENT:
            return "The current search direction increases the cost function value.";

        default:
            return "(unknown)";
        }
    }

} // namespace lbfgs

#endif


#pragma once
#include "lue/framework/core/debug.hpp"
#include "lue/framework/core/define.hpp"
#include "lue/framework/core/erase.hpp"
#include "lue/framework/core/shape.hpp"
#include "lue/framework/core/span.hpp"
#include "lue/framework/core/type_traits.hpp"
#include "lue/assert.hpp"
#include "lue/configure.hpp"
#include <boost/container/vector.hpp>

namespace lue {

    template<
        typename Element,
        Rank rank>
    class Array
    {

        public:

            using Span = DynamicSpan<Element, rank>;

            using Subspan = DynamicSubspan<Element, rank>;

            using Slice = std::pair<lue::Index, lue::Index>;

            using Slices = std::array<Slice, rank>;

            using Shape = lue::Shape<lue::Index, rank>;

            using Elements = boost::container::vector<Element>;

            using Iterator = typename Elements::iterator;

            using ConstIterator = typename Elements::const_iterator;

            Array():

                _shape{},
                _elements{},
                _span{}

            {
                // The shape is filled with indeterminate values! Explicitly
                // set things up for the default constructed instance.
                _shape.fill(0);
                _span = Span{_elements.data(), _shape};

                assert_invariants();
            }

            explicit Array(
                Shape const& shape):

                _shape{shape},
                _elements(lue::nr_elements(shape)),
                _span{_elements.data(), _shape}

            {
                assert_invariants();
            }

            Array(
                Shape const& shape,
                Element const value):

                _shape{shape},
                _elements(lue::nr_elements(shape), value),
                _span{_elements.data(), _shape}

            {
                assert_invariants();
            }

            Array(
                Shape const& shape,
                std::initializer_list<Element> values):

                _shape{shape},
                _elements(lue::nr_elements(shape)),
                _span{_elements.data(), _shape}

            {
                lue_hpx_assert(
                    std::distance(values.begin(), values.end()) ==
                    lue::nr_elements(_shape));

                std::move(values.begin(), values.end(), _elements.begin());

                assert_invariants();
            }

            template<
                typename InputIterator>
            Array(
                Shape const& shape,
                InputIterator begin,
                InputIterator end):

                _shape{shape},
                _elements(lue::nr_elements(shape)),
                _span{_elements.data(), _shape}

            {
                std::move(begin, end, _elements.begin());

                assert_invariants();
            }

            Array(
                Array const& other):

                _shape{other._shape},
                _elements{other._elements},
                _span{_elements.data(), _shape}

            {
                assert_invariants();
            }

            Array(
                Array&& other):

                _shape{std::move(other._shape)},
                _elements{std::move(other._elements)},
                _span{_elements.data(), _shape}

            {
                other._shape.fill(0);
                other._elements.clear();
                other._span = Span{other._elements.data(), other._shape};

                other.assert_invariants();
                lue_hpx_assert(other.empty());
                assert_invariants();
            }

            ~Array()=default;

            Array& operator=(
                Array const& other)
            {
                if(this != &other)
                {
                    _shape = other._shape;
                    _elements = other._elements;
                    _span = Span{_elements.data(), _shape};
                }

                assert_invariants();

                return *this;
            }

            Array& operator=(
                Array&& other)
            {
                if(this != &other)
                {
                    _shape = std::move(other._shape);
                    other._shape.fill(0);

                    _elements = std::move(other._elements);
                    other._elements.clear();

                    _span = Span{_elements.data(), _shape};
                    other._span = Span{other._elements.data(), other._shape};
                }

                other.assert_invariants();
                lue_hpx_assert(other.empty());
                assert_invariants();

                return *this;
            }

            bool empty() const
            {
                return _elements.empty();
            }

            Shape const& shape() const
            {
                return _shape;
            }

            lue::Index extent(Rank const dimension_idx) const
            {
                return _shape[dimension_idx];
            }

            Iterator begin()
            {
                return _elements.begin();
            }

            Iterator end()
            {
                return _elements.end();
            }

            ConstIterator begin() const
            {
                return _elements.begin();
            }

            ConstIterator end() const
            {
                return _elements.end();
            }

            void reshape(
                Shape const& shape)
            {
                if(_shape != shape)
                {
                    _elements.resize(lue::nr_elements(shape));
                    _shape = shape;
                    _span = Span{_elements.data(), _shape};
                }

                assert_invariants();
            }

            void erase(
                Rank const dimension_idx,
                Index const hyperslab_begin_idx,
                Index const hyperslab_end_idx)
            {
                _shape = lue::erase(_elements, _shape, dimension_idx, hyperslab_begin_idx, hyperslab_end_idx);
                _span = Span{_elements.data(), _shape};

                assert_invariants();
            }

            Element const* data() const
            {
                return _elements.data();
            }

            Element* data()
            {
                return _elements.data();
            }

            template<
                typename... Idxs>
            Element const& operator()(
                Idxs... idxs) const
            {
                if constexpr(BuildOptions::validate_idxs)
                {
                    validate_idxs(_shape, idxs...);
                }

                return _span(idxs...);
            }

            template<
                typename... Idxs>
            Element& operator()(
                Idxs... idxs)
            {
                if constexpr(BuildOptions::validate_idxs)
                {
                    validate_idxs(_shape, idxs...);
                }

                return _span(idxs...);
            }

            Element const& operator[](
                std::size_t idx) const
            {
                return this->data()[idx];
            }

            Element& operator[](
                std::size_t idx)
            {
                return this->data()[idx];
            }

            Span const& span() const
            {
                return _span;
            }

        private:

            void assert_invariants() const
            {
                // lue_hpx_assert(
                //     static_cast<Size>(_elements.size()) == lue::nr_elements(_shape));
                lue_hpx_assert(lue::nr_elements(_shape) == static_cast<Size>(_elements.size()));
                lue_hpx_assert(_span.size() == static_cast<Size>(_elements.size()));
                lue_hpx_assert(_span.data() == _elements.data());
            }

            // Shape of array
            Shape          _shape;

            // 1D buffer with array elements
            Elements       _elements;

            // Span for converting nD indices to linear indices
            Span           _span;

    };

    namespace detail {

        template<
            typename E,
            Rank r>
        class ArrayTraits<lue::Array<E, r>>
        {

            public:

                using Element = E;

                constexpr static Rank rank = r;

                using Shape = typename lue::Array<E, r>::Shape;

        };

    }  // namespace detail

    template<
        typename Element,
        Rank rank>
    inline auto const& shape(
        Array<Element, rank> const& array)
    {
        return array.shape();
    }

    template<
        typename Element,
        Rank rank>
    inline auto nr_elements(
        Array<Element, rank> const& array)
    {
        return nr_elements(shape(array));
    }

}  // namespace lue


// very simple converter; does not work all the mtx files!
// create "./tmp.coo" as intermediate file (COO format)
#include <frovedis.hpp>
#include <frovedis/matrix/crs_matrix.hpp>

#include <boost/program_options.hpp>

using namespace std;
using namespace frovedis;

template <class T>
void mtx_to_crs(string& input, string& output_matrix, bool binary) {
  std::ifstream ifs(input.c_str());
  std::ofstream ofs("./tmp.coo");
  std::string line;
  bool is_symmetric = false;
  bool is_pattern = false;
  std::getline(ifs,line);
  if(!(line.find("symmetric") == string::npos)) is_symmetric = true;
  if(!(line.find("pattern") == string::npos)) is_pattern = true;
  while(std::getline(ifs,line)) {
    if(line[0] == '%') continue;
    else break;
  }
  size_t num_row, num_col, nnz;
  istringstream iss(line);
  iss >> num_row >> num_col >> nnz;
  while(std::getline(ifs,line)) {
    if(is_pattern) {
      ofs << line << " " << 1 << endl;
      if(is_symmetric) {
        istringstream iss2(line);
        size_t row, col;
        iss2 >> row >> col;
        if(row != col) ofs << col << " " << row << " " << 1 << endl;
      }
    } else {
      ofs << line << endl;
      if(is_symmetric) {
        istringstream iss2(line);
        size_t row, col;
        string val; // for arbitrary precision
        iss2 >> row >> col >> val;
        if(row != col) ofs << col << " " << row << " " << val << endl;
      }
    }
  }
  auto crs = make_crs_matrix_loadcoo<T>("./tmp.coo");
  crs.data.mapv(+[](crs_matrix_local<T>& m, size_t num_col){m.local_num_col = num_col;},
                broadcast(num_col));
  crs.num_col = num_col;
  if(binary) crs.savebinary(output_matrix);
  else {
    crs.save(output_matrix);
  }
}

int main(int argc, char* argv[]) {
  use_frovedis use(argc, argv);

  using namespace boost::program_options;

  string input, output_matrix;
  bool binary;

  options_description opt("option");
  opt.add_options()
    ("help,h", "print help")
    ("input,i", value<string>(&input)->required(), "input libsvm format (1-based) file")
    ("output-matrix,o", value<string>(&output_matrix)->required(), "output crs matrix file")
    ("double", "assume double data")
    ("float", "assume float data")
    ("int", "assume int data")
    ("binary,b", bool_switch(&binary)->default_value(false), "use binary output")
    ;

  variables_map argmap;
  store(command_line_parser(argc,argv).options(opt).allow_unregistered().
        run(), argmap);

  if(argmap.count("help")){
    cerr << opt << endl;
    exit(1);
  }

  notify(argmap);

  if(argmap.count("double")) {
    mtx_to_crs<double>(input, output_matrix, binary);
  } else if(argmap.count("float")) {
    mtx_to_crs<float>(input, output_matrix, binary);
  } else if(argmap.count("int")) {
    mtx_to_crs<int>(input, output_matrix, binary);
  } else {
    cerr << "data type is not specified" << endl;
    cerr << opt << endl;
    exit(1);
  }

}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#define NT2_UNIT_MODULE "nt2 boost.simd.ieee toolbox - modf/scalar Mode"

//////////////////////////////////////////////////////////////////////////////
// unit test behavior of boost.simd.ieee components in scalar mode
//////////////////////////////////////////////////////////////////////////////
/// created by jt the 04/12/2010
///
#include <boost/simd/toolbox/ieee/include/functions/modf.hpp>
#include <boost/simd/sdk/simd/native.hpp>
#include <boost/fusion/tuple.hpp>
#include <boost/simd/include/functions/trunc.hpp>
#include <boost/simd/include/functions/frac.hpp>

#include <boost/type_traits/is_same.hpp>
#include <boost/dispatch/functor/meta/call.hpp>
#include <nt2/sdk/unit/tests.hpp>
#include <nt2/sdk/unit/module.hpp>
#include <boost/simd/toolbox/constant/constant.hpp>

NT2_TEST_CASE_TPL ( modf_real__1_0,  BOOST_SIMD_REAL_TYPES)
{

  using boost::simd::modf;
  using boost::simd::tag::modf_;
  typedef typename boost::dispatch::meta::as_floating<T>::type ftype;
  typedef typename boost::dispatch::meta::as_integer<T>::type iT;
  typedef typename boost::dispatch::meta::call<modf_(T)>::type r_t;
  typedef typename boost::simd::meta::scalar_of<r_t>::type sr_t;
  typedef typename boost::simd::meta::scalar_of<r_t>::type ssr_t;
  typedef boost::fusion::vector<T,T> wished_r_t;

  // return type conformity test
  NT2_TEST( (boost::is_same < r_t, wished_r_t >::value) );
   T f, n;
  T a[6] = {T(0), T(1), T(1.5), boost::simd::Inf<T>(), boost::simd::Minf<T>(), boost::simd::Nan<T>()};

  for(int i=0; i < 6; i++)
    {
      modf(a[i], n, f);
      NT2_TEST_EQUAL(f, boost::simd::frac(a[i]));
      NT2_TEST_EQUAL(n, boost::simd::trunc(a[i]));
    }
} // end of test for floating_

NT2_TEST_CASE_TPL ( modf_unsigned_int__1_0,  BOOST_SIMD_UNSIGNED_TYPES)
{

  using boost::simd::modf;
  using boost::simd::tag::modf_;
  typedef typename boost::dispatch::meta::as_floating<T>::type ftype;
  typedef typename boost::dispatch::meta::as_integer<T>::type iT;
  typedef typename boost::dispatch::meta::call<modf_(T)>::type r_t;
  typedef typename boost::simd::meta::scalar_of<r_t>::type sr_t;
  typedef typename boost::simd::meta::scalar_of<r_t>::type ssr_t;
  typedef boost::fusion::vector<T,T> wished_r_t;

  // return type conformity test
  NT2_TEST( (boost::is_same < r_t, wished_r_t >::value) );
  T f, n;
  T a[6] = {T(0), T(1), T(10), T(-10), boost::simd::Valmin<T>(), boost::simd::Valmax<T>()};

  for(int i=0; i < 6; i++)
    {
      modf(a[i], n, f);
      NT2_TEST_EQUAL(f, boost::simd::Zero<T>());
      NT2_TEST_EQUAL(n, boost::simd::trunc(a[i]));
    }
} // end of test for unsigned_int_

NT2_TEST_CASE_TPL ( modf_signed_int__1_0,  BOOST_SIMD_INTEGRAL_SIGNED_TYPES)
{

  using boost::simd::modf;
  using boost::simd::tag::modf_;
  typedef typename boost::dispatch::meta::as_floating<T>::type ftype;
  typedef typename boost::dispatch::meta::as_integer<T>::type iT;
  typedef typename boost::dispatch::meta::call<modf_(T)>::type r_t;
  typedef typename boost::simd::meta::scalar_of<r_t>::type sr_t;
  typedef typename boost::simd::meta::scalar_of<r_t>::type ssr_t;
  typedef boost::fusion::vector<T,T> wished_r_t;

  // return type conformity test
  NT2_TEST( (boost::is_same < r_t, wished_r_t >::value) );
  T f, n;
  T a[6] = {T(0), T(1), T(10), boost::simd::Valmin<T>(), boost::simd::Valmax<T>()};

  for(int i=0; i < 5; i++)
    {
      modf(a[i], n, f);
      NT2_TEST_EQUAL(f, boost::simd::Zero<T>());
      NT2_TEST_EQUAL(n, boost::simd::trunc(a[i]));
    }

} // end of test for signed_int_


/**
 *
 * Copyright (c) 2010 Matthias Walter (xammy@xammy.homelinux.net)
 *
 * Authors: Matthias Walter
 *
 * Distributed under the Boost Software License, Version 1.0. (See
 * accompanying file LICENSE_1_0.txt or copy at
 * http://www.boost.org/LICENSE_1_0.txt)
 *
 */

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/lookup_edge.hpp>
#include <boost/test/minimal.hpp>
#include <boost/graph/bipartite.hpp>

/// Verifies a 2-coloring

template <typename Graph, typename ColorMap>
void check_two_coloring (const Graph& g, const ColorMap color_map)
{
  typedef boost::graph_traits <Graph> traits;
  typename traits::edge_iterator edge_iter, edge_end;

  for (boost::tie (edge_iter, edge_end) = boost::edges (g); edge_iter != edge_end; ++edge_iter)
  {
    typename traits::vertex_descriptor source, target;
    source = boost::source (*edge_iter, g);
    target = boost::target (*edge_iter, g);
    BOOST_REQUIRE (boost::get(color_map, source) != boost::get(color_map, target));
  }
}

/// Tests for a vertex sequence to define an odd cycle

template <typename Graph, typename RandomAccessIterator>
void check_odd_cycle (const Graph& g, RandomAccessIterator first, RandomAccessIterator beyond)
{
  typedef boost::graph_traits <Graph> traits;

  typename traits::vertex_descriptor first_vertex, current_vertex, last_vertex;

  BOOST_CHECK ((beyond - first) % 2 == 1);

  //  std::cout << "odd_cycle: " << int(*first) << std::endl;

  for (first_vertex = current_vertex = *first++; first != beyond; ++first)
  {
    //    std::cout << "odd_cycle: " << int(*first) << std::endl;

    last_vertex = current_vertex;
    current_vertex = *first;

    BOOST_REQUIRE (boost::lookup_edge (current_vertex, last_vertex, g).second);
  }

  BOOST_REQUIRE (boost::lookup_edge (first_vertex, current_vertex, g).second);
}

/// Call the is_bipartite and find_odd_cycle functions and verify their results.

template <typename Graph, typename IndexMap>
void check_bipartite (const Graph& g, IndexMap index_map, bool is_bipartite)
{
  typedef boost::graph_traits <Graph> traits;
  typedef std::vector <boost::default_color_type> partition_t;
  typedef std::vector <typename traits::vertex_descriptor> vertex_vector_t;
  typedef boost::iterator_property_map <partition_t::iterator, IndexMap> partition_map_t;

  partition_t partition (boost::num_vertices (g));
  partition_map_t partition_map (partition.begin (), index_map);

  vertex_vector_t odd_cycle (boost::num_vertices (g));

  bool first_result = boost::is_bipartite (g, index_map, partition_map);

  BOOST_REQUIRE (first_result == boost::is_bipartite(g, index_map));

  if (first_result)
    check_two_coloring (g, partition_map);

  BOOST_CHECK (first_result == is_bipartite);

  typename vertex_vector_t::iterator second_first = odd_cycle.begin ();
  typename vertex_vector_t::iterator second_beyond = boost::find_odd_cycle (g, index_map, partition_map, second_first);

  if (is_bipartite)
  {
    BOOST_CHECK (second_beyond == second_first);
    check_two_coloring (g, partition_map);
  }
  else
  {
    check_odd_cycle (g, second_first, second_beyond);
  }

  second_beyond = boost::find_odd_cycle (g, index_map, second_first);
  if (is_bipartite)
  {
    BOOST_CHECK (second_beyond == second_first);
  }
  else
  {
    check_odd_cycle (g, second_first, second_beyond);
  }
}

int test_main (int argc, char **argv)
{
  typedef boost::adjacency_list <boost::vecS, boost::vecS, boost::undirectedS> vector_graph_t;
  typedef boost::adjacency_list <boost::listS, boost::listS, boost::undirectedS> list_graph_t;
  typedef std::pair <int, int> E;

  typedef std::map <boost::graph_traits <list_graph_t>::vertex_descriptor, size_t> index_map_t;
  typedef boost::associative_property_map <index_map_t> index_property_map_t;

  /**
   * Create the graph drawn below.
   *
   *       0 - 1 - 2
   *       |       |
   *   3 - 4 - 5 - 6
   *  /      \   /
   *  |        7
   *  |        |
   *  8 - 9 - 10
   **/

  E bipartite_edges[] = { E (0, 1), E (0, 4), E (1, 2), E (2, 6), E (3, 4), E (3, 8), E (4, 5), E (4, 7), E (5, 6), E (
      6, 7), E (7, 10), E (8, 9), E (9, 10) };
  vector_graph_t bipartite_vector_graph (&bipartite_edges[0],
      &bipartite_edges[0] + sizeof(bipartite_edges) / sizeof(E), 11);
  list_graph_t
      bipartite_list_graph (&bipartite_edges[0], &bipartite_edges[0] + sizeof(bipartite_edges) / sizeof(E), 11);

  /**
   * Create the graph drawn below.
   *
   *       2 - 1 - 0
   *       |       |
   *   3 - 6 - 5 - 4
   *  /      \   /
   *  |        7
   *  |       /
   *  8 ---- 9
   *
   **/

  E non_bipartite_edges[] = { E (0, 1), E (0, 4), E (1, 2), E (2, 6), E (3, 4), E (3, 8), E (4, 5), E (4, 7), E (5, 6),
      E (6, 7), E (7, 9), E (8, 9) };
  vector_graph_t non_bipartite_vector_graph (&non_bipartite_edges[0], &non_bipartite_edges[0]
      + sizeof(non_bipartite_edges) / sizeof(E), 10);
  list_graph_t non_bipartite_list_graph (&non_bipartite_edges[0], &non_bipartite_edges[0] + sizeof(non_bipartite_edges)
      / sizeof(E), 10);

  /// Create index maps

  index_map_t bipartite_index_map, non_bipartite_index_map;
  boost::graph_traits <list_graph_t>::vertex_iterator vertex_iter, vertex_end;
  size_t i = 0;
  for (boost::tie (vertex_iter, vertex_end) = boost::vertices (bipartite_list_graph); vertex_iter != vertex_end; ++vertex_iter)
  {
    bipartite_index_map[*vertex_iter] = i++;
  }
  index_property_map_t bipartite_index_property_map = index_property_map_t (bipartite_index_map);

  i = 0;
  for (boost::tie (vertex_iter, vertex_end) = boost::vertices (non_bipartite_list_graph); vertex_iter != vertex_end; ++vertex_iter)
  {
    non_bipartite_index_map[*vertex_iter] = i++;
  }
  index_property_map_t non_bipartite_index_property_map = index_property_map_t (non_bipartite_index_map);

  /// Call real checks

  check_bipartite (bipartite_vector_graph, boost::get (boost::vertex_index, bipartite_vector_graph), true);
  check_bipartite (bipartite_list_graph, bipartite_index_property_map, true);

  check_bipartite (non_bipartite_vector_graph, boost::get (boost::vertex_index, non_bipartite_vector_graph), false);
  check_bipartite (non_bipartite_list_graph, non_bipartite_index_property_map, false);

  /// Test some more interfaces

  BOOST_REQUIRE (is_bipartite (bipartite_vector_graph));
  BOOST_REQUIRE (!is_bipartite (non_bipartite_vector_graph));

  return 0;
}


/*
@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/functor/adjust_mcd.hpp>
#include <boost/hana/functor/fmap_mcd.hpp>
#include <boost/hana/functor/laws.hpp>

#include <boost/hana/detail/assert.hpp>
#include <boost/hana/detail/constexpr.hpp>
#include <boost/hana/detail/minimal/functor.hpp>
#include <boost/hana/integral.hpp>
#include <boost/hana/list/instance.hpp>

#include <tuple>
using namespace boost::hana;

template <int i>
constexpr auto x = int_<i>;

BOOST_HANA_CONSTEXPR_LAMBDA auto f = [](auto x) {
    return std::make_tuple(1, x);
};

BOOST_HANA_CONSTEXPR_LAMBDA auto g = [](auto x) {
    return std::make_tuple(2, x);
};

template <typename mcd>
void test() {
    constexpr auto functor = detail::minimal::functor<mcd>;

    BOOST_HANA_CONSTEXPR_ASSERT(Functor::laws::check(
        list(functor(x<0>)),
        list(f),
        list(g)
    ));
}

int main() {
    test<Functor::fmap_mcd>();
    test<Functor::adjust_mcd>();
}


#include "OptimalVerticalScenario.hpp"

#include <ilcplex/ilocplex.h>
#include <cmath>

#include <boost/filesystem.hpp>

using namespace IMT;

void OptimalVerticalScenario::ClearCplexVariablesImpl(int k, int u, int b)
{
}

void OptimalVerticalScenario::RunImpl(int k, int u, int b)
{
    //contraints 1f: can only download current viewpoint
    for (int chunklag = -GetNbLagChunk(); chunklag < GetNbProcessedChunk()-1; ++chunklag)
    {
        for (int chunkId = 0; chunkId < GetNbProcessedChunk(); ++chunkId)
        {
            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
            {
                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
                {
                    for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
                    {
                        //if (!m_users[u].IsAtViewpoint(std::max(k*GetNbProcessedChunk(), chunklag+1), viewpointId))
                        //{
                        //    m_d[GetDId(chunkId, chunklag, viewpointId, tileId, qualityLevel)].setUB(0);
                        //}
                        m_model.add(m_d[GetDId(chunkId, chunklag, viewpointId, tileId, qualityLevel)] <= m_y[GetYId(std::max(0, chunklag),viewpointId, m_users[u].GetNbSwitchUntil(std::max(0, chunklag), k, GetNbProcessedChunk()), u, k)] + m_y[GetYId(std::max(0, chunklag+1), viewpointId, m_users[u].GetNbSwitchUntil(std::max(0, chunklag+1), k, GetNbProcessedChunk()), u, k)]);
                    }
                }
            }
        }
    }

    //contraints on switching timestamp
    //float switchingDecisionTime = 0.5;
    //for (int chunklag = -GetNbLagChunk(); chunklag < GetNbProcessedChunk()-1; ++chunklag)
    for (int chunklag = 0; chunklag < GetNbProcessedChunk()-1; ++chunklag)
    {
        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
        {
            //If the user want to switch
            if (m_users[u].IsAtViewpoint(chunklag+1+k*GetNbProcessedChunk(), viewpointId) && !m_users[u].IsAtViewpoint(chunklag+k*GetNbProcessedChunk(), viewpointId))
            {
                IloExpr sum(m_env);
                for (int chunkId = std::max(0, chunklag+1); chunkId < GetNbProcessedChunk(); ++chunkId)
                {
                    for (int tileId = 0; tileId < GetNbTile(); ++tileId)
                    {
                        for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
                        {
                            sum += m_d[GetDId(chunkId, chunklag, viewpointId, tileId, qualityLevel)];
                        }
                    }
                }
                //cannot start download before the switching decision
                //m_model.add(sum <= m_bandwidths[b].GetBandwidth(chunklag+k*GetNbProcessedChunk())*(1-switchingDecisionTime+m_stall[chunklag+GetNbLagChunk()]));
                if (m_users[u].GetSwitchingDecisionTime(chunklag+k*GetNbProcessedChunk()) < 0)
                {
                    std::cerr << "Wrong switching decision time!!! " << m_users[u].GetSwitchingDecisionTime(chunklag+k*GetNbProcessedChunk()) << " " << chunklag + k*GetNbProcessedChunk() << " " << viewpointId << std::endl;
                    exit(1);
                }
                m_model.add(sum <= m_bandwidths[b].GetBandwidth(chunklag+k*GetNbProcessedChunk())*(std::max(0.f, 1-m_users[u].GetSwitchingDecisionTime(chunklag+k*GetNbProcessedChunk())-transmissionDelay)+STALL_COUNT_IN_SECOND*m_stall[chunklag+GetNbLagChunk()]));
           }
        }
    }

    ////Download segment in display order: ds[chunkLagId][chunkId] == 1 if segment of chunkId  was downloaded during chunk chunkLagId
    //for (int  chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
    //{
    //    for (int  chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
    //    {
    //        IloExpr sum(m_env);
    //        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
    //        {
    //            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
    //            {
    //                for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
    //                {
    //                    sum += m_d[GetDId(chunkId,chunkLagId, viewpointId, tileId, qualityLevel)];
    //                }
    //            }
    //        }
    //        IloExpr sum2(m_env);
    //        for (int cLagId = chunkLagId + 1; cLagId < GetNbChunk(); ++cLagId)
    //        {
    //            for (int  c = 0; c < chunkId; ++c)
    //            {
    //                sum2 += m_ds[GetDsId(c, cLagId)];
    //            }
    //        }
    //        m_model.add(IloIfThen(m_env, m_ds[GetDsId(chunkId, chunkLagId)] == 1, sum2 == 0));
    //    }
    //}
    //////for (int  chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbProcessedChunk(); ++chunkLagId)
    //////{
    //////    for (int  chunkId = std::max(1, chunkLagId-1); chunkId < GetNbProcessedChunk(); ++chunkId)
    //////    {
    //////        if (m_users[u].GetSwitchingDecisionTime(std::max(0, chunkId-1)+k*GetNbProcessedChunk()) >= 0)
    //////        //if (m_users[u].GetWantedViewpoint(std::max(0, chunkId-1)+k*GetNbProcessedChunk()) != m_users[u].GetWantedViewpoint(std::max(0, chunkId)+k*GetNbProcessedChunk()))
    //////        {
    //////            int counter = 0;
    //////            int counter2 = 0;
    //////            IloExpr sum(m_env);
    //////            for (int  chunkLagIdPrime = chunkLagId+1; chunkLagIdPrime < GetNbProcessedChunk(); ++chunkLagIdPrime)
    //////            {
    //////                //for (int  chunkIdPrime = std::max(chunkId-1, 0); chunkIdPrime < chunkId; ++chunkIdPrime)
    //////                //for (int  chunkIdPrime = 0; chunkIdPrime < chunkId; ++chunkIdPrime)
    //////                for (int  chunkIdPrime = 0; chunkIdPrime < chunkId; ++chunkIdPrime)
    //////                {
    //////                    for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
    //////                    {
    //////                        for (int tileId = 0; tileId < GetNbTile(); ++tileId)
    //////                        {
    //////                            for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
    //////                            {
    //////                                //sum += m_d[GetDId(chunkIdPrime, chunkLagIdPrime, viewpointId, tileId, qualityLevel)];
    //////                                //++counter;
    //////                            }
    //////                        }
    //////                    }
    //////                    sum += m_ds[GetDsId(chunkIdPrime, chunkLagIdPrime)];
    //////                    ++counter2;
    //////                }
    //////            }
    //////            IloExpr ds_sum(m_env);
    //////            for (int  chunkIdPrime = chunkId; chunkIdPrime < GetNbProcessedChunk(); ++chunkIdPrime)
    //////            {
    //////                ds_sum += m_ds[GetDsId(chunkIdPrime, chunkLagId)];
    //////                //m_model.add(IloIfThen(m_env, m_ds[GetDsId(chunkIdPrime, chunkLagId)] == 1, sum == 0));
    //////            }
    //////            //m_model.add(sum <= (D_MAX*counter + counter2)*(1-m_ds[GetDsId(chunkId, chunkLagId)]));
    //////            //m_model.add(IloIfThen(m_env, m_ds[GetDsId(chunkId, chunkLagId)] == 1, sum == 0));
    //////            m_model.add(IloIfThen(m_env, ds_sum >= 1, sum == 0));
    //////        }
    //////    }
    //////}
}

//void OptimalVerticalScenario::Run(void)
//{
//    float DistortionMax = NOT_DOWNLOADED_DISTORTION;
//    //Init the Cplex environment
//    IloEnv env;
//    IloModel model(env);
//
//    //Create the decision variables
//    IloArray<IloArray<IloArray<IloIntVarArray>>> x(env, GetNbChunk());
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        x[chunkId] = IloArray<IloArray<IloIntVarArray>>(env, GetNbViewpoint());
//        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//        {
//            x[chunkId][viewpointId] = IloArray<IloIntVarArray>(env, GetNbTile());
//            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//            {
//                x[chunkId][viewpointId][tileId] = IloIntVarArray(env, GetNbQuality()+1, 0, 1);
//                for (int qualityLevel = -1; qualityLevel < GetNbQuality(); ++qualityLevel)
//                {
//                    x[chunkId][viewpointId][tileId][qualityLevel+1].setName(("x["+
//                                std::to_string(chunkId) + "][" +
//                                std::to_string(viewpointId) + "][" +
//                                std::to_string(tileId) + "][" +
//                                std::to_string(qualityLevel) + "]").c_str());
//                }
//            }
//        }
//    }
//    IloArray<IloIntVarArray> y(env, GetNbChunk());
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        y[chunkId] = IloIntVarArray(env, GetNbViewpoint(), 0, 1);
//    }
//    IloArray<IloArray<IloArray<IloArray<IloNumVarArray>>>> d(env, GetNbChunk());
//    //Create the decision variables
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        d[chunkId] = IloArray<IloArray<IloArray<IloNumVarArray>>>(env, GetNbChunk()+GetNbLagChunk());
//        for (int chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//        {
//            d[chunkId][chunkLagId+GetNbLagChunk()] = IloArray<IloArray<IloNumVarArray>>(env, GetNbViewpoint());
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId] = IloArray<IloNumVarArray>(env, GetNbTile());
//                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//                {
//                    d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId] = IloNumVarArray(env, GetNbQuality(), 0, 1, ILOFLOAT);
//                    //d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId] = IloNumVarArray(env, GetNbQuality(), 0, 1, ILOINT);
//                    for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                    {
//                        d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel].setName(("d["+
//                                    std::to_string(chunkId) + "][" +
//                                    std::to_string(viewpointId) + "][" +
//                                    std::to_string(tileId) + "][" +
//                                    std::to_string(qualityLevel) + "]").c_str());
//
//                    }
//                }
//            }
//        }
//    }
//    IloIntVarArray stall(env, GetNbChunk()+GetNbLagChunk(), 0, 25000);
//
//    //Contraints
//    //Contraints 1a and 1b
//    for (int chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//    {
//        IloExpr sum1a(env);
//        IloExpr sum1b(env);
//        for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//        {
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//                {
//                    for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                    {
//                        if (int(chunkId) <= int(chunkLagId))
//                        {
//                            //sum1b += d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel];
//                            d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel].setUB(0);
//                        }
//                        else
//                        {
//                            sum1a += m_as.GetSeg(chunkId, viewpointId, tileId, qualityLevel).GetBitrate() * d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel];
//                        }
//                    }
//                }
//            }
//        }
//
//
//        IloConstraint ic1a = sum1a <= m_bandwidths[b].GetBandwidth(chunkLagId)*(1+stall[chunkLagId+GetNbLagChunk()]);
//        ic1a.setName(("Const1a["+std::to_string(chunkLagId)+"]").c_str());
//        model.add(ic1a);
//       // if (chunkLagId >= 0)
//       // {
//       //     IloConstraint ic1b = sum1b == IloInt(0);
//       //     ic1b.setName(("Const1b["+std::to_string(chunkLagId)+"]").c_str());
//       //     model.add(ic1b);
//       // }
//    }
//    //contraints 1c and 1d
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//        {
//            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//            {
//                for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                {
//                    IloExpr sum(env);
//                    for (int chunkLagId = -GetNbLagChunk(); chunkLagId < int(GetNbChunk()); ++chunkLagId)
//                    {
//                        sum += d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel];
//                    }
//                    IloConstraint ic1c = sum <= IloInt(1);
//                    //IloConstraint ic1c = sum1c == x[chunkId][viewpointId][tileId][qualityLevel+1];
//                    ic1c.setName(("Const1c[" + std::to_string(chunkId) +
//                                "][" + std::to_string(viewpointId) + "][" +
//                                std::to_string(tileId) + "][" +
//                                std::to_string(qualityLevel) + "]").c_str());
//                    model.add(ic1c);
//                    IloConstraint ic1d = x[chunkId][viewpointId][tileId][qualityLevel+1] <= sum;
//                    ic1d.setName(("Const1d[" + std::to_string(chunkId) +
//                                "][" + std::to_string(viewpointId) + "][" +
//                                std::to_string(tileId) + "][" +
//                                std::to_string(qualityLevel) + "]").c_str());
//                    model.add(ic1d);
//                }
//            }
//        }
//    }
//    //contraints 1e
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//       for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//       {
//           IloExpr sum(env);
//           float visibility = m_user.GetTileVisibility(chunkId, tileId);
//           for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//           {
//                for (int qualityLevel = -1; qualityLevel < GetNbQuality(); ++qualityLevel)
//                {
//                    sum += x[chunkId][viewpointId][tileId][qualityLevel+1];
//                }
//           }
//           IloConstraint ic1e = std::ceil(visibility) == sum;
//           ic1e.setName(("Const1e[" + std::to_string(chunkId) +
//                           "][" +
//                           std::to_string(tileId) + "]").c_str());
//           model.add(ic1e);
//        }
//    }
//    //contraints 1f: can only download current viewpoint
//    for (int chunklag = -GetNbLagChunk(); chunklag < GetNbChunk()-1; ++chunklag)
//    {
//        for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//        {
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//                {
//                    for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                    {
//                        if (!m_user.IsAtViewpoint(std::max(0, chunklag+1), viewpointId))
//                        {
//                            d[chunkId][chunklag+GetNbLagChunk()][viewpointId][tileId][qualityLevel].setUB(0);
//                        }
//                        //model.add(d[chunkId][chunklag+GetNbLagChunk()][viewpointId][tileId][qualityLevel] <= y[std::max(0, chunklag)][viewpointId] + y[std::max(0, chunklag+1)][viewpointId]);
//                    }
//                }
//            }
//        }
//    }
//    //contraints: exactly one viewpoint displayed
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        model.add(IloSum(y[chunkId]) == 1);
//    }
//    //contraints: display only one viewpoint
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//        {
//            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//            {
//                for (int qualityLevel = -1; qualityLevel < GetNbQuality(); ++qualityLevel)
//                {
//                    model.add(x[chunkId][viewpointId][tileId][qualityLevel+1] <= y[chunkId][viewpointId]);
//                }
//            }
//        }
//    }
//    //Display only one viewpoint -> the last displayed or the current wanted viewpoint
//    for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//    {
//        if (m_user.IsAtViewpoint(0, viewpointId))
//        {
//            y[0][viewpointId].setLB(1);
//        }
//        else
//        {
//            y[0][viewpointId].setUB(0);
//        }
//    }
//    for (int chunkId = 1; chunkId < GetNbChunk(); ++chunkId)
//    {
//        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//        {
//            model.add( y[chunkId][viewpointId] <= y[chunkId-1][viewpointId] + (m_user.IsAtViewpoint(chunkId, viewpointId) ? 1 : 0));
//        }
//    }
//
//    //Download segment in display order: ds[chunkLagId][chunkId] == 1 if segment of chunkId  was downloaded during chunk chunkLagId
//    IloArray<IloIntVarArray> ds(env, GetNbChunk()+GetNbLagChunk());
//    for (int chunkId = 0; chunkId < GetNbChunk()+GetNbLagChunk(); ++chunkId)
//    {
//        ds[chunkId] = IloIntVarArray(env, GetNbChunk(), 0, 1);
//    }
//    for (int  chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//    {
//        for (int  chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//        {
//            IloExpr sum(env);
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//                {
//                    for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                    {
//                        sum += d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel];
//                    }
//                }
//            }
//            IloExpr sum2(env);
//            for (int cLagId = chunkLagId + 1; cLagId < GetNbChunk(); ++cLagId)
//            {
//                for (int  c = 0; c < chunkId; ++c)
//                {
//                    sum2 += ds[cLagId+GetNbLagChunk()][c];
//                }
//            }
//            model.add(IloIfThen(env, ds[chunkLagId+GetNbLagChunk()][chunkId] == 1, sum2 == 0));
//            model.add((ds[chunkLagId+GetNbLagChunk()][chunkId] == 0) ==  (sum == 0));
//        }
//    }
//    //for (int  chunkId = 1; chunkId < GetNbChunk(); ++chunkId)
//    //{
//    //    for (int  chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//    //    {
//    //        IloExpr sumChunkId(env);
//    //        IloExpr sumPreviousChunkId(env);
//    //        int count = 0;
//    //        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//    //        {
//    //            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//    //            {
//    //                for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//    //                {
//    //                    sumChunkId += d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel];
//    //                    for (int  nextChunkLagId = chunkLagId+1; nextChunkLagId < chunkId-1; ++nextChunkLagId)
//    //                    {
//    //                        sumPreviousChunkId += d[chunkId-1][nextChunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel];
//    //                        count += 1;
//    //                    }
//    //                }
//    //            }
//    //        }
//    //        if (count > 0)
//    //        {
//    //            model.add( (sumChunkId == 0) || (sumPreviousChunkId == 0));
//    //        }
//    //    }
//    //}
//
//
//
//    //Objective function:
//    float beta = LAG_COST;
//    IloExpr obj(env);
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//       for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//       {
//           float tileVisibility = m_user.GetTileVisibility(chunkId, tileId);
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                for (int qualityLevel = -1; qualityLevel < GetNbQuality(); ++qualityLevel)
//                {
//                    if (qualityLevel == -1)
//                    {
//                        obj += x[chunkId][viewpointId][tileId][qualityLevel+1]*(DistortionMax+(m_user.IsAtViewpoint(chunkId, viewpointId) ? 0 : beta))*tileVisibility;
//                    }
//                    else
//                    {
//                        obj += x[chunkId][viewpointId][tileId][qualityLevel+1]*(m_as.GetSeg(chunkId, viewpointId, tileId, qualityLevel).GetDistortion()+(m_user.IsAtViewpoint(chunkId, viewpointId) ? 0 : beta))*tileVisibility;
//                    }
//                }
//            }
//        }
//    }
//    const float alpha = STALL_QoE_IMPACT;
//    model.add(IloMinimize(env, obj/GetNbChunk()+alpha*IloSum(stall)/GetNbChunk()));
//
//    std::cout << std::endl << "Ready to run" << std::endl;
//
//    IloCplex cplex(model);
//    //cplex.setParam(IloCplex::EpGap, m_conf->epGap);
//    cplex.setParam(IloCplex::EpGap, m_optimalGap);
//    cplex.setParam(IloCplex::TiLim, 330);
//    //cplex.setParam(IloCplex::Threads, m_conf->nbThread);
//    cplex.setParam(IloCplex::Threads, m_nbThread);
//    //cplex.setParam(IloCplex::MIPOrdType, 2);
//    cplex.setParam(IloCplex::BrDir, -1);
//    //cplex.setParam(IloCplex::Probe, 3);
//    //cplex.setParam(IloCplex::PolishAfterNode, 2000);
//    cplex.setParam(IloCplex::PolishAfterTime, 250);
//    cplex.setParam(IloCplex::MIPEmphasis, 1);
//    cplex.setParam(IloCplex::RINSHeur, 25);
//    cplex.setParam(IloCplex::DisjCuts, 2);
//    // cplex.setParam(IloCplex::NodeFileInd,3);
//    // cplex.setParam(IloCplex::WorkDir,".");
//    // cplex.setParam(IloCplex::MemoryEmphasis,true);
//    //cplex.setParam(IloCplex::DataCheck, true);
//
//    //cplex.exportModel((std::string("model.lp")).c_str());
//
//
//    //if(GetNbTile() > 6)
//    //{
//    //    IloNumVarArray startVar(env);
//    //    IloNumArray startVal(env);
//    //    std::cout << "Generate initial solution" << std::endl;
//    //    std::vector<float> bandwidth(GetNbChunk()+GetNbLagChunk(), 0);
//    //    float totalBandwidth = 0;
//    //    for (int chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//    //    {
//    //        bandwidth[chunkLagId+GetNbLagChunk()] = m_bandwidths[b].GetBandwidth(chunkLagId);
//    //        totalBandwidth += bandwidth[chunkLagId];
//    //    }
//    //    float totalVisibleLowQuality = 0;
//	//	for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    //    {
//    //    	for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//	//        {
//    //	        for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//    //            {
//    //                if (m_user.GetVisibility(chunkId, viewpointId, tileId) > 0)
//    //                {
//    //                    totalVisibleLowQuality += m_as.GetSeg(chunkId, viewpointId, tileId, 0).GetBitrate();
//    //                }
//    //            }
//    //        }
//    //    }
//    //    int nbStall = 0;
//    //    while(totalBandwidth < totalVisibleLowQuality)
//    //    {
//    //        bandwidth[0] += m_bandwidths[b].GetBandwidth(-GetNbLagChunk());
//    //        totalBandwidth += m_bandwidths[b].GetBandwidth(-GetNbLagChunk());
//    //        nbStall += 1;
//    //    }
//    //    std::cout << "nbStall = " << nbStall << " " <<  totalBandwidth << " >= " << totalVisibleLowQuality << std::endl;
//    //    std::vector<float> d_chunkTile(GetNbChunk()*GetNbViewpoint()*GetNbTile(), 0);
//    //    auto ToId = [&](int chunkId, int viewpointId, int tileId) {return chunkId*GetNbViewpoint()*GetNbTile()+viewpointId*GetNbTile()+tileId;};
//    //    std::vector<int> count(GetNbChunk(), 0);
//    //    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    //    {
//    //        for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//    //        {
//    //            if (m_user.GetTileVisibility(chunkId, tileId) > 0)
//    //            {
//    //                ++count[chunkId];
//    //            }
//    //        }
//    //    }
//    //    for (int chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//    //    {
//    //        std::vector<int> usedChunk(GetNbChunk(), 0);
//    //        startVar.add(stall[chunkLagId+GetNbLagChunk()]);
//    //        if (chunkLagId == -GetNbLagChunk())
//    //        {
//    //            startVal.add(0);
//    //        }
//    //        else
//    //        {
//    //            startVal.add(nbStall);
//    //        }
//    //        std::vector<float> d_start(GetNbChunk()*GetNbViewpoint()*GetNbTile(), 0);
//
//    //        for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    //        {
//    //            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//    //            {
//    //                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//    //                {
//    //                    if ( (chunkLagId <= 0 || m_user.IsAtViewpoint(chunkLagId+1, viewpointId) || m_user.IsAtViewpoint(chunkLagId, viewpointId)) && bandwidth[chunkLagId+GetNbLagChunk()] > 0 && d_chunkTile[ToId(chunkId, viewpointId, tileId)] < 1 && m_user.GetVisibility(chunkId, viewpointId, tileId) > 0)
//    //                    {
//    //                        if (chunkId > 0  && count[chunkId-1] != 0)
//    //                        {
//    //                            continue;
//    //                        }
//    //                        float remaining = (1-d_chunkTile[ToId(chunkId, viewpointId, tileId)])*m_as.GetSeg(chunkId, viewpointId, tileId, 0).GetBitrate();
//    //                        //std::cout << chunkId<< "; " << viewpointId << "; " << tileId << " R= " << remaining << " Before " << bandwidth[chunkLagId+GetNbLagChunk()];
//    //                        if (bandwidth[chunkLagId+GetNbLagChunk()] >= remaining)
//    //                        {
//    //                           // std::cout << " * ";
//    //                            bandwidth[chunkLagId+GetNbLagChunk()] -= remaining;
//    //                            d_start[ToId(chunkId, viewpointId, tileId)] = (1-d_chunkTile[ToId(chunkId, viewpointId, tileId)]);
//    //                            d_chunkTile[ToId(chunkId, viewpointId, tileId)] = 1;
//    //                            --count[chunkId];
//    //                        }
//    //                        else
//    //                        {
//    //                            //std::cout << " # ";
//    //                            float used = bandwidth[chunkLagId+GetNbLagChunk()];
//    //                            remaining -= bandwidth[chunkLagId+GetNbLagChunk()];
//    //                            bandwidth[chunkLagId+GetNbLagChunk()] = 0;
//    //                            d_start[ToId(chunkId, viewpointId, tileId)] = used / m_as.GetSeg(chunkId, viewpointId, tileId, 0).GetBitrate();
//    //                            d_start[ToId(chunkId, viewpointId, tileId)] += d_start[ToId(chunkId, viewpointId, tileId)];
//    //                        }
//    //                        //std::cout << " After " << bandwidth[chunkLagId+GetNbLagChunk()]  << "R = " << (1-d_chunkTile[ToId(chunkId, viewpointId, tileId)])*m_as.GetSeg(chunkId, viewpointId, tileId, 0).GetBitrate() << std::endl;;
//    //                        usedChunk[chunkId] = 1;
//    //                    }
//    //                }
//    //            }
//    //        }
//    //        std::cout << chunkLagId << ": ";
//    //        for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    //        {
//    //            startVar.add(ds[chunkLagId+GetNbLagChunk()][chunkId]);
//    //            startVal.add(usedChunk[chunkId]);
//    //            if (usedChunk[chunkId] == 1)
//    //            {
//    //                std::cout << chunkId << " ";
//    //            }
//    //        }
//    //        std::cout << std::endl;
//    //    }
//    //    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    //    {
//    //        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//    //        {
//    //            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//    //            {
//    //                for (int qualityLevel = -1; qualityLevel < GetNbQuality(); ++qualityLevel)
//    //                {
//    //                    if (qualityLevel == 0 && m_user.GetVisibility(chunkId, viewpointId, tileId) > 0)
//    //                    {
//    //                        startVar.add(x[chunkId][viewpointId][tileId][qualityLevel+1]);
//    //                        startVal.add(1);
//    //                    }
//    //                    else
//    //                    {
//    //                        startVar.add(x[chunkId][viewpointId][tileId][qualityLevel+1]);
//    //                        startVal.add(0);
//    //                    }
//    //                }
//    //            }
//    //            if (m_user.IsAtViewpoint(chunkId, viewpointId))
//    //            {
//    //                startVar.add(y[chunkId][viewpointId]);
//    //                startVal.add(1);
//    //            }
//    //            else
//    //            {
//    //                startVar.add(y[chunkId][viewpointId]);
//    //                startVal.add(0);
//    //            }
//    //        }
//    //    }
//    //    std::cout << "Inject Solution" << std::endl;
//    //    cplex.addMIPStart(startVar, startVal);
//    //    startVar.end();
//    //    startVal.end();
//    //    std::cout << "Test " << std::endl;
//    //}
//
//    std::cout << "Start solving" << std::endl;
//
//    //if(fs::exists("lastSol.txt"))
//    //{
//    //    cplex.readSolution((std::string("lastSol.txt")).c_str());
//    //}
//    cplex.solve();
//    //cplex.writeSolution((std::string("lastSol.txt")).c_str());
//
//    std::cout << "Solving done\n" << std::endl;
//
//    std::cout << "Solution status " << cplex.getStatus() << std::endl;
//    std::cout <<  "Objective value " << cplex.getObjValue() << std::endl;
//    std::cout <<  "Solution gap " << cplex.getMIPRelativeGap() << "\n" << std::endl;
//
//    m_objectiveValue = cplex.getObjValue();
//    m_solutionGap = cplex.getMIPRelativeGap();
//    m_solutionStatus = cplex.getStatus() == IloAlgorithm::Optimal ? "Optimal" : (cplex.getStatus() == IloAlgorithm::Infeasible ? "Infeasible" : ( cplex.getStatus() == IloAlgorithm::Feasible ? "Feasible": "Unknown"));
//
//
//    //Get the results
//    IloArray<IloArray<IloArray<IloNumArray>>> x_sol(env, GetNbChunk());
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        x_sol[chunkId] = IloArray<IloArray<IloNumArray>>(env, GetNbViewpoint());
//        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//        {
//            x_sol[chunkId][viewpointId] = IloArray<IloNumArray>(env, GetNbTile());
//            for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//            {
//                x_sol[chunkId][viewpointId][tileId] = IloNumArray(env, GetNbQuality()+1);
//                cplex.getValues(x_sol[chunkId][viewpointId][tileId], x[chunkId][viewpointId][tileId]);
//                for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                {
//                    if (x_sol[chunkId][viewpointId][tileId][qualityLevel+1] > 0.5)
//                    {
//                        m_as.GetSeg(chunkId, viewpointId, tileId, qualityLevel).SetSelected();
//                    }
//                }
//		x_sol[chunkId][viewpointId][tileId].end();
//		x[chunkId][viewpointId][tileId].end();
//            }
//	    x_sol[chunkId][viewpointId].end();
//	    x[chunkId][viewpointId].end();
//        }
//	x_sol[chunkId].end();
//	x[chunkId].end();
//    }
//    x_sol.end();
//    x.end();
//    IloArray<IloNumArray> y_sol(env, GetNbChunk());
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        y_sol[chunkId] = IloNumArray(env, GetNbViewpoint());
//        cplex.getValues(y_sol[chunkId], y[chunkId]);
//        for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//        {
//            if (y_sol[chunkId][viewpointId] == 1)
//            {
//                m_displayedViewpoint[chunkId]= viewpointId;
//                break;
//            }
//        }
//        y[chunkId].end();
//    }
//    y.end();
//    IloArray<IloArray<IloArray<IloArray<IloNumArray>>>> d_sol(env, GetNbChunk());
//    for (int chunkId = 0; chunkId < GetNbChunk(); ++chunkId)
//    {
//        d_sol[chunkId] = IloArray<IloArray<IloArray<IloNumArray>>>(env, GetNbChunk()+GetNbLagChunk());
//        for (int chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//        {
//            d_sol[chunkId][chunkLagId+GetNbLagChunk()] = IloArray<IloArray<IloNumArray>>(env, GetNbViewpoint());
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                d_sol[chunkId][chunkLagId+GetNbLagChunk()][viewpointId] = IloArray<IloNumArray>(env, GetNbTile());
//                for (int tileId = 0; tileId < GetNbTile(); ++tileId)
//                {
//                    d_sol[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId] = IloNumArray(env, GetNbQuality());
//                    cplex.getValues(d_sol[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId], d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId]);
//                    for (int qualityLevel = 0; qualityLevel < GetNbQuality(); ++qualityLevel)
//                    {
//                        SetDownloadRatio(chunkId, chunkLagId, viewpointId, tileId, qualityLevel, d_sol[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId][qualityLevel]);
//                    }
//		    d_sol[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId].end();
//		    d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId][tileId].end();
//                }
//		d_sol[chunkId][chunkLagId+GetNbLagChunk()][viewpointId].end();
//		d[chunkId][chunkLagId+GetNbLagChunk()][viewpointId].end();
//            }
//	d_sol[chunkId][chunkLagId+GetNbLagChunk()].end();
//        d[chunkId][chunkLagId+GetNbLagChunk()].end();
//        }
//    d_sol[chunkId].end();
//    d[chunkId].end();
//    }
//    d_sol.end();
//    d.end();
//    IloNumArray stall_sol(env, GetNbChunk()+GetNbLagChunk());
//    cplex.getValues(stall_sol, stall);
//    for (int chunkLagId = -GetNbLagChunk(); chunkLagId < GetNbChunk(); ++chunkLagId)
//    {
//        m_nbStall[chunkLagId+GetNbLagChunk()] = stall_sol[chunkLagId+GetNbLagChunk()];
//    }
//    stall_sol.end();
//    stall.end();
//
//    for (int chunkId = 0; chunkId < GetNbChunk()+GetNbLagChunk(); ++chunkId)
//    {
//        IloNumArray ds_sol(env, GetNbChunk());
//        cplex.getValues(ds_sol, ds[chunkId]);
//        std::cout  << "ChunkLagId " << int(chunkId)-GetNbLagChunk() <<" ("<< m_user.GetWantedViewpoint(std::max(0, int(chunkId)-GetNbLagChunk())) <<"): ";
//        for (int c = 0; c < GetNbChunk(); ++c)
//        {
//            bool downloadVidChunk(false);
//            //if (ds_sol[c] == 1)
//            std::vector<int> viewpointList;
//            for (int viewpointId = 0; viewpointId < GetNbViewpoint(); ++viewpointId)
//            {
//                bool done = false;
//                for (int tileId = 0; tileId < GetNbTile() && !done; ++tileId)
//                {
//                    for (int qualityLevel = 0; qualityLevel < GetNbQuality() && !done; ++qualityLevel)
//                    {
//                        if (m_downloadRatio[ToDownloadRatioVecId(c, chunkId-GetNbLagChunk(),viewpointId,tileId,qualityLevel)] == INVALIDE_FLOAT_VALUE)
//                        {
//                            std::cout << "Error: invalid float" << std::endl;
//                        }
//                        if (!done && m_downloadRatio[ToDownloadRatioVecId(c, chunkId-GetNbLagChunk(),viewpointId,tileId,qualityLevel)] > 0)
//                        {
//                            done = true;
//                            viewpointList.push_back(viewpointId);
//                            downloadVidChunk = true;
//                        }
//                    }
//                }
//            }
//
//            if (downloadVidChunk || ds_sol[c] == 1)
//            {
//                std::cout << c << (ds_sol[c] == 1 ? "*" : "") << (downloadVidChunk ? "#" : "") <<"(";
//                bool first(true);
//                for(auto v: viewpointList)
//                {
//                    if (!first) std::cout << ", ";
//                    std::cout << v;
//                    first = false;
//                }
//                //std::cout.seekp(-2, std::ios_base::end);
//                std::cout << ") ";
//            }
//        }
//        std::cout << std::endl;
//        ds[chunkId].end();
//        ds_sol.end();
//    }
//    ds.end();
//    //cleanup
//    cplex.end();
//    obj.end();
//    model.end();
//    env.end();
//}