// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2011 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2011 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2011 Mateusz Loskot, London, UK.

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// Use, modification and distribution is subject to 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)

#ifndef BOOST_GEOMETRY_STRATEGIES_CARTESIAN_POINT_IN_POLY_CROSSINGS_MULTIPLY_HPP
#define BOOST_GEOMETRY_STRATEGIES_CARTESIAN_POINT_IN_POLY_CROSSINGS_MULTIPLY_HPP

#include <boost/geometry/core/coordinate_type.hpp>
#include <boost/geometry/util/select_calculation_type.hpp>

namespace boost { namespace geometry
{

namespace strategy { namespace within
{

/*!
\brief Within detection using cross counting,
\ingroup strategies
\tparam Point \tparam_point
\tparam PointOfSegment \tparam_segment_point
\tparam CalculationType \tparam_calculation
\see http://tog.acm.org/resources/GraphicsGems/gemsiv/ptpoly_haines/ptinpoly.c
\note Does NOT work correctly for point ON border
\qbk{
[heading See also]
[link geometry.reference.algorithms.within.within_3_with_strategy within (with strategy)]
}
 */

template
<
    typename Point,
    typename PointOfSegment = Point,
    typename CalculationType = void
>
class crossings_multiply
{
    typedef typename select_calculation_type
        <
            Point,
            PointOfSegment,
            CalculationType
        >::type calculation_type;

    class flags
    {
        bool inside_flag;
        bool first;
        bool yflag0;

    public :

        friend class crossings_multiply;

        inline flags()
            : inside_flag(false)
            , first(true)
            , yflag0(false)
        {}
    };

public :

    typedef Point point_type;
    typedef PointOfSegment segment_point_type;
    typedef flags state_type;

    static inline bool apply(Point const& point,
            PointOfSegment const& seg1, PointOfSegment const& seg2,
            flags& state)
    {
        calculation_type const tx = get<0>(point);
        calculation_type const ty = get<1>(point);
        calculation_type const x0 = get<0>(seg1);
        calculation_type const y0 = get<1>(seg1);
        calculation_type const x1 = get<0>(seg2);
        calculation_type const y1 = get<1>(seg2);

        if (state.first)
        {
            state.first = false;
            state.yflag0 = y0 >= ty;
        }

        bool yflag1 = y1 >= ty;
        if (state.yflag0 != yflag1)
        {
            if ( ((y1-ty) * (x0-x1) >= (x1-tx) * (y0-y1)) == yflag1 )
            {
                state.inside_flag = ! state.inside_flag;
            }
        }
        state.yflag0 = yflag1;
        return true;
    }

    static inline int result(flags const& state)
    {
        return state.inside_flag ? 1 : -1;
    }
};

}} // namespace strategy::within

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_STRATEGIES_CARTESIAN_POINT_IN_POLY_CROSSINGS_MULTIPLY_HPP


/*******************************************************************************
 * Copyright (c) 2014, 2016  IBM Corporation and others
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *******************************************************************************/

#ifndef GaussianProcessLight_hpp
#define GaussianProcessLight_hpp

#include <iostream>
#include <limits>
#include <Eigen/Dense>

#include "KernelFunction.hpp"
#include "GaussianProcess.hpp"
#include "SerializeUtils.hpp"

namespace loc{

    class GaussianProcessLight : public GaussianProcess{

    private:
        // variables to be serialized
        std::vector<GaussianProcess> LGPs_;     //Local Gaussian Processes
        std::vector<Eigen::VectorXd> centers_;  //center for each LGP

        double sigmaN_ = 1.0;
        GaussianKernel gaussianKernel_;

    public:
        static const int N_FEATURES = 4;
        constexpr static const double MIN_DENOMINATOR = std::numeric_limits<double>::min() * 1e+16;

        GaussianProcessLight() = default;

        enum ClusteringType{
            GREEDY,
            KMEANS
        };
        ClusteringType clType = KMEANS;
        bool usesOverlap = true;
        int mLocalsMixed_ = 3;

        // A function for serealization
        template<class Archive>
        void serialize(Archive& ar){
            ar(CEREAL_NVP(LGPs_));
            ar(CEREAL_NVP(centers_));
            ar(CEREAL_NVP(sigmaN_));
            ar(CEREAL_NVP(gaussianKernel_));

            std::vector<std::string> names;
            try{
                ar(CEREAL_NVP(mLocalsMixed_));
            }catch(cereal::Exception& e){
                names.push_back("mLocalsMixed_");
            }

            if(0<names.size()){
                std::stringstream ss;
                ss << "[notice] parameters not found: ";
                for(const auto& name: names){
                    ss << name << ",";
                }
                std::cerr << ss.str() << std::endl;
            }
        }

        GaussianProcessLight& sigmaN(double sigmaN){
            sigmaN_ = sigmaN;
            return *this;
        }

        double sigmaN() const{
            return sigmaN_;
        }

        //Calculate maximum cluster size based on max complexity of the function predict()
        static const size_t MAX_CLUSTER_SIZE(const size_t MAX_COMPLEXITY,
                                             const size_t MAX_N_OVERLAP,
                                             const size_t N_LOCALS_MIXED) {
            const size_t k = MAX_N_OVERLAP;     //max # cluster overlaps for a sample
            const size_t M = N_LOCALS_MIXED;    //# local models mixed in prediction
            const size_t MAX_AMP = (std::min(k, M) + 1) * std::max(k, M);
            const size_t MAX_CLUSTER_SIZE = MAX_COMPLEXITY / MAX_AMP;

            const size_t MIN_FEASIBLE_CLUSTER_SIZE = 50;
            if (MAX_CLUSTER_SIZE < MIN_FEASIBLE_CLUSTER_SIZE) {
                std::cout << "WARNING: MAX_CLUSTER_SIZE=" << MAX_CLUSTER_SIZE << " is less than MIN_FEASIBLE_CLUSTER_SIZE=" << MIN_FEASIBLE_CLUSTER_SIZE << std::endl;
            }
            return MAX_CLUSTER_SIZE;
        }

        GaussianProcessLight& fit(const Eigen::MatrixXd& X, const Eigen::MatrixXd& Y)
        {
            //Clustering samples
            const size_t TARGET_N_CLUSTER = 1 + (X.rows() / MAX_CLUSTER_SIZE(800, 3, 3));
            CentroidBasedClusteringResult cr;
            if(clType==GREEDY){
                cr = clusteringGreedyWithMagicThreshold(X, Y, 1e-8);
            }else if(clType==KMEANS){
                cr = kMeansClustering(X, Y, TARGET_N_CLUSTER);
            }

            cr.printSummary();

            if(usesOverlap){
            //Improve the clusters
            const double OVERLAP_SCALE = 0.001;
            improveWithOverlap(cr, OVERLAP_SCALE, X, Y);
            cr.printSummary();
            }

            std::cout << "clustered into " << cr.nCluster() << " local models" << std::endl;

            centers_ = cr.centers;

            //Get local models by cluster
            for (auto k=0; k < cr.nCluster(); k++) {
                GaussianProcess gp;
                gp.sigmaN(sigmaN_);
                gp.gaussianKernel(gaussianKernel_);

                gp.fit(cr.XC[k], cr.YC[k]);

                LGPs_.push_back(gp);
            }

            return *this;
        }

        double predict(double x[], int index) const
        {
            std::vector<int> indices(1, index);
            std::vector<double> ypreds = predict(x, indices);
            return ypreds.at(0);
        }

        //TODO change return type: Eigen::VectorXd would be better
        std::vector<double> predict(double x[], const std::vector<int>& indices) const
        {
            const size_t M = mLocalsMixed_;
            const size_t n = centers_.size();

            std::vector<double> weights(n);
            for (size_t i=0; i < n; ++i) {
                const double* c = centers_.at(i).data();
                weights[i] = gaussianKernel_.computeKernel(x, c);
            }

            //indices of k-nearest (=top-k weight) neigbors
            std::vector<size_t> neighbors = top_k(weights, std::min(M, n));

            Eigen::VectorXd sum_wy = Eigen::VectorXd::Zero(indices.size());
            double sum_w = 0.0;
            for (auto m : neighbors) {
                double w = weights.at(m);
                std::vector<double> tmp = LGPs_.at(m).predict(x, indices);
                Eigen::VectorXd y = Eigen::Map<Eigen::VectorXd>(tmp.data(), indices.size());
                sum_wy += w * y;
                sum_w  += w;
            }

            Eigen::VectorXd y_hat = sum_wy;
            if (sum_w > MIN_DENOMINATOR) {
                y_hat /= sum_w;
            } else {
                std::vector<double> top1ypred = LGPs_.at(neighbors.at(0)).predict(x, indices);
                y_hat = Eigen::Map<Eigen::VectorXd>(top1ypred.data(), indices.size());
//                std::cout << "WARN: sum_w~=0 in predict() with " << n << " LGPs"
//                          << " >> predicted only with the nearest local model."<< std::endl;
            }
            std::vector<double> ypreds(y_hat.data(), y_hat.data() + indices.size());
            return ypreds;
        }

        /**
         * Estimate parameters as preparation
         */
        void fitCV(const Eigen::MatrixXd& X, const Eigen::MatrixXd& Y, const Eigen::MatrixXd& Actives)
        {
            GaussianProcess gp;
            gp.sigmaN(sigmaN_);
            gp.gaussianKernel(gaussianKernel_);

            // estimate parameters using GaussianProcess::fitCV
            gp.fitCV(X, Y, Actives);

            // set estimated parameters to this
            sigmaN_ = gp.sigmaN();
            gaussianKernel_ = gp.gaussianKernel();

            this->fit(X, Y);
        }

//        Eigen::VectorXd predictVarianceF(double x[]) const;
//        Eigen::VectorXd predictVarianceF(const Eigen::VectorXd& kstar) const{;
//        double computeLogLikelihood(double x[], const Eigen::VectorXd& y) const;

    private:
        class CentroidBasedClusteringResult{
        public:
            std::vector<Eigen::MatrixXd> XC;
            std::vector<Eigen::MatrixXd> YC;
            std::vector<Eigen::VectorXd> centers = {};
            size_t nCluster() const { return centers.size(); }
            void printSummary() const;
            void printAll() const;
        };

        /**
         * k-means++ clustering
         */
        CentroidBasedClusteringResult kMeansClustering(const Eigen::MatrixXd& X,
                                                       const Eigen::MatrixXd& Y,
                                                       const size_t TARGET_N_CLUSTER) const;

        /**
         * Greedy clustering
         */
        CentroidBasedClusteringResult clusteringGreedyWithMagicThreshold(const Eigen::MatrixXd& X,
                                                                         const Eigen::MatrixXd& Y,
                                                                         const double MAGIC_W_THRESHOLD) const;

        void improveWithOverlap(CentroidBasedClusteringResult& cr,
                                const double OVERLAP_SCALE,
                                const Eigen::MatrixXd& X,
                                const Eigen::MatrixXd& Y) const;

    public:
        // TODO move to an appropriate util class
        static std::vector<size_t> top_k(const std::vector<double>& values, const size_t k)
        {
            assert(k <= values.size());
            size_t n = values.size();
            std::vector<size_t> labels(n);
            for (auto i=0; i < n; ++i) { labels[i] = i; }

            auto comp = [&values](size_t i, size_t j) { return values[i] > values[j]; };

            if (n == k) {
                std::sort(labels.begin(), labels.end(), comp);
                return labels;
            }

            assert(n > k);  //need +1 elements to make heap
            auto first = labels.begin(), last = labels.begin() + k, end = labels.end();
            std::make_heap(first, last + 1, comp);
            std::pop_heap(first, last + 1, comp);
            for (auto it = last + 1; it != end; it++) {
                if (values[*it] <= values[*first]) {
                    continue;
                } else {
                    *last = *it;
                    std::pop_heap(first, last + 1, comp);
                }
            }
            std::sort_heap(first, last, comp);
            return std::vector<size_t>{first, last};
        }

//    private:
//        CentroidBasedClusteringResult aggregativeClustering(const Eigen::MatrixXd& X,
//                                                            const Eigen::MatrixXd& Y,
//                                                            const size_t TARGET_N_CLUSTER) const
//        class CentroidBasedCluster{
//        public:
//            CentroidBasedCluster(size_t i, const Eigen::MatrixXd& X) {
//                labels.push_back(i);
//                center = X.row(i);
//            };
//            std::vector<size_t> labels;
//            Eigen::VectorXd center;
//            void merge(const CentroidBasedCluster& that) {
//                std::copy(that.labels.begin(),that.labels.end(),std::back_inserter(this->labels));
//                std::sort(this->labels.begin(), this->labels.end());
//                double nis = this->labels.size();
//                double nat = that.labels.size();
//                this->center = (nis * this->center + nat * that.center) / (nis + nat);
//            }
//        };

    };
}

#endif /* GaussianProcessLight_hpp */


/*
 Copyright (C) 2019 Quaternion Risk Management Ltd
 All rights reserved.

 This file is part of ORE, a free-software/open-source library
 for transparent pricing and risk analysis - http://opensourcerisk.org

 ORE is free software: you can redistribute it and/or modify it
 under the terms of the Modified BSD License.  You should have received a
 copy of the license along with this program.
 The license is also available online at <http://opensourcerisk.org>

 This program is distributed on the basis that it will form a useful
 contribution to risk analytics and model standardisation, but WITHOUT
 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
*/

#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <ql/instruments/makecapfloor.hpp>
#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
#include <ql/quotes/derivedquote.hpp>
#include <qle/termstructures/capfloorhelper.hpp>

using namespace QuantLib;
using std::ostream;

namespace {
void no_deletion(OptionletVolatilityStructure*) {}
} // namespace

namespace QuantExt {

// The argument to RelativeDateBootstrapHelper below is not simply the `quote`. Instead we create a DerivedQuote that,
// each time it is asked for its value, it returns a premium by calling CapFloorHelper::npv with the `quote` value. In
// this way, the `BootstrapHelper<T>::quoteError()` is always based on the cap floor premium and we do not have imply
// the volatility. This leads to all kinds of issues when deciding on max and min values in the iterative bootstrap
// where the quote volatility type input is of one type and the optionlet structure that we are trying to build is of
// another different type.
CapFloorHelper::CapFloorHelper(Type type, const Period& tenor, Rate strike, const Handle<Quote>& quote,
                               const boost::shared_ptr<IborIndex>& iborIndex,
                               const Handle<YieldTermStructure>& discountingCurve, bool moving,
                               const QuantLib::Date& effectiveDate, QuoteType quoteType,
                               QuantLib::VolatilityType quoteVolatilityType, QuantLib::Real quoteDisplacement,
                               bool endOfMonth, bool firstCapletExcluded)
    : RelativeDateBootstrapHelper<OptionletVolatilityStructure>(
          Handle<Quote>(boost::make_shared<DerivedQuote<boost::function<Real(Real)> > >(
              quote, boost::bind(&CapFloorHelper::npv, this, _1)))),
      type_(type), tenor_(tenor), strike_(strike), iborIndex_(iborIndex), discountHandle_(discountingCurve),
      moving_(moving), effectiveDate_(effectiveDate), quoteType_(quoteType), quoteVolatilityType_(quoteVolatilityType),
      quoteDisplacement_(quoteDisplacement), endOfMonth_(endOfMonth), firstCapletExcluded_(firstCapletExcluded),
      rawQuote_(quote), initialised_(false) {

    if (quoteType_ == Premium) {
        QL_REQUIRE(type_ != Automatic, "Cannot have CapFloorHelper type 'Automatic' with quote type of Premium");
    }

    QL_REQUIRE(!(moving_ && effectiveDate_ != Date()),
               "A fixed effective date does not make sense for a moving helper");

    registerWith(iborIndex_);
    registerWith(discountHandle_);

    initializeDates();
    initialised_ = true;
}

void CapFloorHelper::initializeDates() {

    if (!initialised_ || moving_) {
        CapFloor::Type capFloorType = CapFloor::Cap;
        if (type_ == CapFloorHelper::Floor) {
            capFloorType = CapFloor::Floor;
        }

        // Initialise the instrument and a copy
        // The strike can be Null<Real>() to indicate an ATM cap floor helper
        Rate dummyStrike = strike_ == Null<Real>() ? 0.01 : strike_;
        capFloor_ = MakeCapFloor(capFloorType, tenor_, iborIndex_, dummyStrike, 0 * Days)
                        .withEndOfMonth(endOfMonth_)
                        .withEffectiveDate(effectiveDate_, firstCapletExcluded_);
        capFloorCopy_ = MakeCapFloor(capFloorType, tenor_, iborIndex_, dummyStrike, 0 * Days)
                            .withEndOfMonth(endOfMonth_)
                            .withEffectiveDate(effectiveDate_, firstCapletExcluded_);

        // Maturity date is just the maturity date of the cap floor
        maturityDate_ = capFloor_->maturityDate();

        // We need the leg underlying the cap floor to determine the remaining date members
        const Leg& leg = capFloor_->floatingLeg();

        // Earliest date is the first optionlet fixing date
        boost::shared_ptr<CashFlow> cf = leg.front();
        boost::shared_ptr<FloatingRateCoupon> frc = boost::dynamic_pointer_cast<FloatingRateCoupon>(cf);
        QL_REQUIRE(frc, "Expected the first cashflow on the cap floor instrument to be a FloatingRateCoupon");
        earliestDate_ = frc->fixingDate();

        // Remaining dates are each equal to the fixing date on the final optionlet
        cf = leg.back();
        frc = boost::dynamic_pointer_cast<FloatingRateCoupon>(cf);
        QL_REQUIRE(frc, "Expected the final cashflow on the cap floor instrument to be a FloatingRateCoupon");
        pillarDate_ = latestDate_ = latestRelevantDate_ = frc->fixingDate();
    }
}

void CapFloorHelper::setTermStructure(OptionletVolatilityStructure* ovts) {

    if (strike_ == Null<Real>()) {
        // If the strike is Null<Real>(), we want an ATM helper
        Rate atm = capFloor_->atmRate(**discountHandle_);
        capFloor_ = MakeCapFloor(capFloor_->type(), tenor_, iborIndex_, atm, 0 * Days)
                        .withEndOfMonth(endOfMonth_)
                        .withEffectiveDate(effectiveDate_, firstCapletExcluded_);
        capFloorCopy_ = MakeCapFloor(capFloor_->type(), tenor_, iborIndex_, atm, 0 * Days)
                            .withEndOfMonth(endOfMonth_)
                            .withEffectiveDate(effectiveDate_, firstCapletExcluded_);

    } else if (type_ == CapFloorHelper::Automatic && quoteType_ != Premium) {
        // If the helper is set to automatically choose the underlying instrument type, do it now based on the ATM rate
        Rate atm = capFloor_->atmRate(**discountHandle_);
        CapFloor::Type capFloorType = atm > strike_ ? CapFloor::Floor : CapFloor::Cap;
        if (capFloor_->type() != capFloorType) {
            capFloor_ = MakeCapFloor(capFloorType, tenor_, iborIndex_, strike_, 0 * Days)
                            .withEndOfMonth(endOfMonth_)
                            .withEffectiveDate(effectiveDate_, firstCapletExcluded_);
            capFloorCopy_ = MakeCapFloor(capFloorType, tenor_, iborIndex_, strike_, 0 * Days)
                                .withEndOfMonth(endOfMonth_)
                                .withEffectiveDate(effectiveDate_, firstCapletExcluded_);
        }
    }

    // Set this helper's optionlet volatility structure
    boost::shared_ptr<OptionletVolatilityStructure> temp(ovts, no_deletion);
    ovtsHandle_.linkTo(temp, false);

    // Set the term structure pointer member variable in the base class
    RelativeDateBootstrapHelper<OptionletVolatilityStructure>::setTermStructure(ovts);

    // Set this helper's pricing engine depending on the type of the optionlet volatilities
    if (ovts->volatilityType() == ShiftedLognormal) {
        capFloor_->setPricingEngine(boost::make_shared<BlackCapFloorEngine>(discountHandle_, ovtsHandle_));
    } else {
        capFloor_->setPricingEngine(boost::make_shared<BachelierCapFloorEngine>(discountHandle_, ovtsHandle_));
    }

    // If the quote type is not a premium, we will need to use capFloorCopy_ to return the premium from the volatility
    // quote
    if (quoteType_ != Premium) {
        if (quoteVolatilityType_ == ShiftedLognormal) {
            capFloorCopy_->setPricingEngine(boost::make_shared<BlackCapFloorEngine>(
                discountHandle_, rawQuote_, ovtsHandle_->dayCounter(), quoteDisplacement_));
        } else {
            capFloorCopy_->setPricingEngine(
                boost::make_shared<BachelierCapFloorEngine>(discountHandle_, rawQuote_, ovtsHandle_->dayCounter()));
        }
    }
}

Real CapFloorHelper::impliedQuote() const {
    QL_REQUIRE(termStructure_ != 0, "CapFloorHelper's optionlet volatility term structure has not been set");
    capFloor_->recalculate();
    return capFloor_->NPV();
}

void CapFloorHelper::accept(AcyclicVisitor& v) {
    if (Visitor<CapFloorHelper>* v1 = dynamic_cast<Visitor<CapFloorHelper>*>(&v))
        v1->visit(*this);
    else
        RelativeDateBootstrapHelper<OptionletVolatilityStructure>::accept(v);
}

Real CapFloorHelper::npv(Real quoteValue) {
    if (quoteType_ == Premium) {
        return quoteValue;
    } else {
        // If the quote value is a volatility, return the premium
        return capFloorCopy_->NPV();
    }
}

ostream& operator<<(ostream& out, CapFloorHelper::Type type) {
    switch (type) {
    case CapFloorHelper::Cap:
        return out << "Cap";
    case CapFloorHelper::Floor:
        return out << "Floor";
    case CapFloorHelper::Automatic:
        return out << "Automatic";
    default:
        QL_FAIL("Unknown CapFloorHelper::Type (" << Integer(type) << ")");
    }
}

ostream& operator<<(ostream& out, CapFloorHelper::QuoteType type) {
    switch (type) {
    case CapFloorHelper::Volatility:
        return out << "Volatility";
    case CapFloorHelper::Premium:
        return out << "Premium";
    default:
        QL_FAIL("Unknown CapFloorHelper::QuoteType (" << Integer(type) << ")");
    }
}

} // namespace QuantExt


//| Copyright Inria May 2015
//| This project has received funding from the European Research Council (ERC) under
//| the European Union's Horizon 2020 research and innovation programme (grant
//| agreement No 637972) - see http://www.resibots.eu
//|
//| Contributor(s):
//|   - Jean-Baptiste Mouret (jean-baptiste.mouret@inria.fr)
//|   - Antoine Cully (antoinecully@gmail.com)
//|   - Konstantinos Chatzilygeroudis (konstantinos.chatzilygeroudis@inria.fr)
//|   - Federico Allocati (fede.allocati@gmail.com)
//|   - Vaios Papaspyros (b.papaspyros@gmail.com)
//|   - Roberto Rama (bertoski@gmail.com)
//|
//| This software is a computer library whose purpose is to optimize continuous,
//| black-box functions. It mainly implements Gaussian processes and Bayesian
//| optimization.
//| Main repository: http://github.com/resibots/limbo
//| Documentation: http://www.resibots.eu/limbo
//|
//| This software is governed by the CeCILL-C license under French law and
//| abiding by the rules of distribution of free software.  You can  use,
//| modify and/ or redistribute the software under the terms of the CeCILL-C
//| license as circulated by CEA, CNRS and INRIA at the following URL
//| "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and  rights to copy,
//| modify and redistribute granted by the license, users are provided only
//| with a limited warranty  and the software's author,  the holder of the
//| economic rights,  and the successive licensors  have only  limited
//| liability.
//|
//| In this respect, the user's attention is drawn to the risks associated
//| with loading,  using,  modifying and/or developing or reproducing the
//| software by the user in light of its specific status of free software,
//| that may mean  that it is complicated to manipulate,  and  that  also
//| therefore means  that it is reserved for developers  and  experienced
//| professionals having in-depth computer knowledge. Users are therefore
//| encouraged to load and test the software's suitability as regards their
//| requirements in conditions enabling the security of their systems and/or
//| data to be ensured and,  more generally, to use and operate it in the
//| same conditions as regards security.
//|
//| The fact that you are presently reading this means that you have had
//| knowledge of the CeCILL-C license and that you accept its terms.
//|
#ifndef LIMBO_OPT_RPROP_HPP
#define LIMBO_OPT_RPROP_HPP

#include <algorithm>

#include <Eigen/Core>

#include <limbo/opt/optimizer.hpp>
#include <limbo/tools/macros.hpp>
#include <limbo/tools/math.hpp>

namespace limbo {
    namespace defaults {
        struct opt_rprop {
            /// @ingroup opt_defaults
            /// number of max iterations
            BO_PARAM(int, iterations, 300);

            /// gradient norm epsilon for stopping
            BO_PARAM(double, eps_stop, 0.0);
        };
    }
    namespace opt {
        /// @ingroup opt
        /// Gradient-based optimization (rprop)
        /// - partly inspired by libgp: https://github.com/mblum/libgp
        /// - reference :
        /// Blum, M., & Riedmiller, M. (2013). Optimization of Gaussian
        /// Process Hyperparameters using Rprop. In European Symposium
        /// on Artificial Neural Networks, Computational Intelligence
        /// and Machine Learning.
        ///
        /// Parameters:
        /// - int iterations
        /// - double eps_stop
        template <typename Params>
        struct Rprop {
            template <typename F>
            Eigen::VectorXd operator()(const F& f, const Eigen::VectorXd& init, bool bounded) const
            {
                assert(Params::opt_rprop::eps_stop() >= 0.);

                size_t param_dim = init.size();
                double delta0 = 0.1;
                double deltamin = 1e-6;
                double deltamax = 50;
                double etaminus = 0.5;
                double etaplus = 1.2;
                double eps_stop = Params::opt_rprop::eps_stop();

                Eigen::VectorXd delta = Eigen::VectorXd::Ones(param_dim) * delta0;
                Eigen::VectorXd grad_old = Eigen::VectorXd::Zero(param_dim);
                Eigen::VectorXd params = init;

                if (bounded) {
                    for (int j = 0; j < params.size(); j++) {
                        if (params(j) < 0)
                            params(j) = 0;
                        if (params(j) > 1)
                            params(j) = 1;
                    }
                }

                Eigen::VectorXd best_params = params;
                double best = log(0);

                for (int i = 0; i < Params::opt_rprop::iterations(); ++i) {
                    auto perf = opt::eval_grad(f, params);
                    double lik = opt::fun(perf);
                    if (lik > best) {
                        best = lik;
                        best_params = params;
                    }
                    Eigen::VectorXd grad = -opt::grad(perf);
                    grad_old = grad_old.cwiseProduct(grad);

                    for (int j = 0; j < grad_old.size(); ++j) {
                        if (grad_old(j) > 0) {
                            delta(j) = std::min(delta(j) * etaplus, deltamax);
                        }
                        else if (grad_old(j) < 0) {
                            delta(j) = std::max(delta(j) * etaminus, deltamin);
                            grad(j) = 0;
                        }
                        params(j) += -tools::signum(grad(j)) * delta(j);

                        if (bounded && params(j) < 0)
                            params(j) = 0;
                        if (bounded && params(j) > 1)
                            params(j) = 1;
                    }

                    grad_old = grad;
                    if (grad_old.norm() < eps_stop)
                        break;
                }

                return best_params;
            }
        };
    }
}

#endif


//
//  MosekProgram.cpp
//  Gravity
//
//  Created by Guanglei Wang on 14/7/17.
//
//
#include <Eigen/Dense>
#include "MosekProgram.h"
using namespace mosek;
using namespace monty;

MosekProgram::MosekProgram() {
    _mosek_model = new fusion::Model("noname");
    _mosek_model->setLogHandler([=](const std::string & msg){std::cout << msg << std::flush;});

}

MosekProgram::MosekProgram(Model* m) {
    _mosek_model = new fusion::Model("noname");
    _mosek_model->setLogHandler([=](const std::string & msg){std::cout << msg << std::flush;});
    _model = m;
}

MosekProgram::~MosekProgram() {
    _mosek_model->dispose();
}

void MosekProgram::update_model() {};

// remain to do
bool MosekProgram::solve(bool relax) {
//    _mosek_model->setSolverParam("log", 10);
    // Set max solution time
//    _mosek_model->setSolverParam("mioMaxTime", 360.0);
    // Set max relative gap (to its default value)
    _mosek_model->setSolverParam("intpntCoTolRelGap", 1e-6);
//    _mosek_model->setSolverParam("presolveUse", "on");
//    _mosek_model->setSolverParam("intpntCoTolPfeas", 1);
//    _mosek_model->setSolverParam("intpntQoTolPfeas", 1);
//    _mosek_model->setSolverParam("intpntCoTolMuRed", 1);
    // Set max absolute gap (to its default value)
//    _mosek_model->setSolverParam("mioTolRelGap", 1e-4);
//    _mosek_model->setSolverParam("mioTolAbsGap", 0.0);
//    _mosek_model->setSolverParam("numThreads",1);
    //if(!_output) _mosek_model->setSolverParam("log", 0);
    if(relax) {
        for (auto &vv: _mosek_vars)
            vv->makeContinuous();
    }
    _mosek_model->solve();

    if (!relax) {
        cout <<_mosek_model->getProblemStatus(fusion::SolutionType::Integer) << endl;
        cout << _mosek_model->getPrimalSolutionStatus() << endl;
    }
    else {
        cout <<_mosek_model->getProblemStatus(fusion::SolutionType::Interior) << endl;
        cout << _mosek_model->getPrimalSolutionStatus() << endl;
    }

    switch (_mosek_model->getPrimalSolutionStatus()) {
        case mosek::fusion::SolutionStatus::Undefined:
            _status = "Undefined";
            break;
        case mosek::fusion::SolutionStatus::Unknown:
            _status = "Unknown";
            break;
        case mosek::fusion::SolutionStatus::Optimal:
            _status = "Optimal";
            break;
        case mosek::fusion::SolutionStatus::NearOptimal:
            _status = "NearOptimal";
            break;
        case mosek::fusion::SolutionStatus::Feasible:
            _status = "Feasible";
            break;
        case mosek::fusion::SolutionStatus::NearFeasible:
            _status = "NearFeasible";
            break;
        case mosek::fusion::SolutionStatus::Certificate:
            _status = "Certificate";
            break;
        case mosek::fusion::SolutionStatus::NearCertificate:
            _status = "NearCertificate";
            break;
        case mosek::fusion::SolutionStatus::IllposedCert:
            _status = "IllposedCert";
            break;
        default:
            break;
    }
    DebugOn("Cost = " << _mosek_model->primalObjValue() << std::endl);

    // set the optimal value.

    _mosek_model->acceptedSolutionStatus(mosek::fusion::AccSolutionStatus::Feasible);
    _model->_obj_val = _mosek_model->primalObjValue();

    // Note that there is only one way to retrieve solutions, i.e.,
    // variable.level(). It only returns double. Thus, we need to cast the
    // solution to required types.
    //std::cout << "Solution = " << std::endl;
    // cout << "dim: " << _mosek_vars.size() << endl;
    //auto sol = _mosek_vars[0]->level();
    //mosek::fusion::Variable::t s= _mosek_vars[0];
    //cout << "size of s: " << s->size() << endl;

    for (auto i = 0; i < _mosek_vars.size(); i++) {
        auto sol = _mosek_vars[i]->level();
        if(i >= _model->_vars.size()) continue;
        if (_model->_vars[i]->get_intype()== binary_ ||_model->_vars[i]->get_intype()== integer_) {
            for (auto j = 0; j < _model->_vars[i]->get_nb_instances(); j++) {
                auto val = (*sol)[j];
                val = round(val);
                poly_set_val(j, val , _model->_vars[i]);
            }
        }
        else if(!_model->_vars[i]->_is_matrix){
            for (auto j = 0; j < _model->_vars[i]->get_nb_instances(); j++) {
                poly_set_val(j, (*sol)[j], _model->_vars[i]);
            }
        }
        else {
            int n = _model->_vars[i]->_dim[0];
            for(auto j1 = 0; j1 < n; j1++) {
                for(auto j2 = j1; j2 < n; j2++) {
                    string key = to_string(j1)+","+to_string(j2);
                    ((param<double>*)_model->_vars[i])->set_val(key, (*sol)[j1*n+j2]);
                }
            }

        }
    }
    return 0;
}

void MosekProgram::fill_in_mosek_vars() {
    param_* v;
    for(auto& v_p: _model->_vars)
    {
        v = v_p.second;
        if(v->get_id()==-1) {
            throw invalid_argument("Variable needs to be added to model first: use add_var(v) function:" + v->get_name());
        }
        switch (v->get_intype()) {
        case float_: {
            if (v->get_type() == var_c) {
                auto real_var = (var<float>*)v;
                //for (int i = 0; i < real_var->get_nb_instances(); i++) {
                //    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name()+"_"+to_string(i),fusion::Domain::inRange(real_var->get_lb(i), real_var->get_ub(i))));
                //}
                auto lb  = new_array_ptr<double,1>(real_var->get_nb_instances());
                auto ub  = new_array_ptr<double,1>(real_var->get_nb_instances());
                for (int i = 0; i < real_var->get_nb_instances(); i++) {
                    (*lb)[i] = real_var->get_lb(i);
                    (*ub)[i] = real_var->get_ub(i);
                }
                if(!real_var->_in_q_cone && !real_var->_psd) _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), real_var->get_nb_instances(), fusion::Domain::inRange(lb,ub)));
                else if(real_var->_in_q_cone) _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inQCone(real_var->get_nb_instances())));
                else {
//                    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inPSDCone((1 + sqrt(1+8*real_var->get_nb_instances()))/2)));
                    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inPSDCone(real_var->_dim[0])));
                }
            }
            else {
                auto sdp_var = (sdpvar<float>*)v;
                _mosek_vars.push_back(_mosek_model->variable(sdp_var->get_name(), fusion::Domain::inPSDCone(sdp_var->_symdim)));
            }
            break;
        }
        case long_: {
            if (v->get_type() == var_c) {
                auto real_var = (var<long double>*)v;
                //for (int i = 0; i < real_var->get_nb_instances(); i++) {
                //    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name()+"_"+to_string(i),fusion::Domain::inRange(real_var->get_lb(i), real_var->get_ub(i))));
                //}
                auto lb  = new_array_ptr<double,1>(real_var->get_nb_instances());
                auto ub  = new_array_ptr<double,1>(real_var->get_nb_instances());
                for (int i = 0; i < real_var->get_nb_instances(); i++) {
                    (*lb)[i] = real_var->get_lb(i);
                    (*ub)[i] = real_var->get_ub(i);
                }
                if(!real_var->_in_q_cone)_mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), real_var->get_nb_instances(), fusion::Domain::inRange(lb,ub)));
                else _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inQCone(real_var->get_nb_instances())));
            }
            else {
                auto sdp_var = (sdpvar<long double>*)v;
                _mosek_vars.push_back(_mosek_model->variable(sdp_var->get_name(), fusion::Domain::inPSDCone(sdp_var->get_nb_instances())));
            }
            break;
        }
        case double_: {
            if (v->get_type() == var_c) {
                auto real_var = (var<double>*)v;
                //for (int i = 0; i < real_var->get_nb_instances(); i++) {
                //    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name()+"_"+to_string(i),fusion::Domain::inRange(real_var->get_lb(i), real_var->get_ub(i))));
                //}
                auto lb  = new_array_ptr<double,1>(real_var->get_nb_instances());
                auto ub  = new_array_ptr<double,1>(real_var->get_nb_instances());
                for (int i = 0; i < real_var->get_nb_instances(); i++) {
                    (*lb)[i] = real_var->get_lb(i);
                    (*ub)[i] = real_var->get_ub(i);
                }
                if(!real_var->_in_q_cone && !real_var->_psd) _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), real_var->get_nb_instances(), fusion::Domain::inRange(lb,ub)));
                else if(real_var->_in_q_cone) _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inQCone(real_var->get_nb_instances())));
                else {
//                    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inPSDCone((1 + sqrt(1+8*real_var->get_nb_instances()))/2.)));
                    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), fusion::Domain::inPSDCone(real_var->_dim[0])));
                }
            }
            else {
                auto sdp_var = (sdpvar<double>*)v;
                size_t num = sdp_var->_symdim;
                auto c  = _mosek_model->variable(sdp_var->get_name(), fusion::Domain::inPSDCone(num));
                _mosek_vars.push_back(c);
                std::cout << c->toString() << endl;

            }
            break;
        }
        case integer_: {
            if (v->get_type() == var_c) {
                auto real_var = (var<int>*)v;
                auto lb  = new_array_ptr<double,1>(real_var->get_nb_instances());
                auto ub  = new_array_ptr<double,1>(real_var->get_nb_instances());
                for (int i = 0; i < real_var->get_nb_instances(); i++) {
                    (*lb)[i] = real_var->get_lb(i);
                    (*ub)[i] = real_var->get_ub(i);
                }
                _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), real_var->get_nb_instances(), fusion::Domain::integral(fusion::Domain::inRange(lb,ub))));
            }
            else {
                auto sdp_var = (sdpvar<int>*)v;
                _mosek_vars.push_back(_mosek_model->variable(sdp_var->get_name(), fusion::Domain::inPSDCone(sdp_var->get_nb_instances())));
            }
            break;
        }
        case short_: {
            if (v->get_type() == var_c) {
                auto real_var = (var<short>*)v;
                auto lb  = new_array_ptr<double,1>(real_var->get_nb_instances());
                auto ub  = new_array_ptr<double,1>(real_var->get_nb_instances());
                for (int i = 0; i < real_var->get_nb_instances(); i++) {
                    (*lb)[i] = real_var->get_lb(i);
                    (*ub)[i] = real_var->get_ub(i);
                }
                _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), real_var->get_nb_instances(), fusion::Domain::integral(fusion::Domain::inRange(lb,ub))));
            }
            else {
                auto sdp_var = (sdpvar<short>*)v;
                _mosek_vars.push_back(_mosek_model->variable(sdp_var->get_name(), fusion::Domain::inPSDCone(sdp_var->get_nb_instances())));
            }
            break;
        }
        case binary_: {
            if (v->get_type() == var_c) {
                auto real_var = (var<bool>*)v;
                //for (unsigned int i = 0; i < real_var->get_nb_instances(); i++) {
                //    _mosek_vars.push_back(_mosek_model->variable(real_var->get_name()+"_"+to_string(i),fusion::Domain::binary()));
                //}
                _mosek_vars.push_back(_mosek_model->variable(real_var->get_name(), real_var->get_nb_instances(), fusion::Domain::binary()));
            }
            else {
                auto sdp_var = (sdpvar<bool>*)v;
                _mosek_vars.push_back(_mosek_model->variable(sdp_var->get_name(), fusion::Domain::inPSDCone(sdp_var->get_nb_instances())));
            }
            break;
        }
        default:
            break;
        }
    }
}

/*void MosekProgram::create_mosek_constraints() {
    //size_t idx = 0;
    //size_t nb_inst = 0;
    size_t idx = 0, idx_inst = 0,idx1 = 0, idx2 = 0, idx_inst1 = 0, idx_inst2 = 0, nb_inst = 0, inst = 0;
    size_t c_idx_inst = 0;
    shared_ptr<Constraint> c;
    for(auto& p: _model->_cons) {
        c = p.second;
//        c->print();
        if (c->is_nonlinear()) {
            cout <<  "We haven't implemented quadratic expressions interface for mosek" << endl;
            throw invalid_argument("Mosek cannot handle nonlinear constraints that are not convex quadratic.\n");
        }
        nb_inst = c->get_nb_instances();
        auto fusion_sums = new ndarray<fusion::Expression::t,1>(shape(nb_inst));
        for (int i = 0; i< nb_inst; i++) {
            // get constant part first.
            monty::rc_ptr<mosek::fusion::Expression > expr= fusion::Expr::constTerm(poly_eval(c->get_cst())); // expr is a pointer to the Expression.
            for (auto& it1: c->get_lterms()) {
                //idx = it1.second._p->get_id();
                idx = it1.second._p->get_vec_id();
                //cout << "get_id: " << it1.second._p->get_id() << endl;
                //cout << "get_vec_id: " << it1.second._p->get_vec_id() << endl;

                CType vartype = it1.second._p->get_type();
                if (vartype == var_c) {
                    if(!it1.second._p->_is_matrix) {

                        if (it1.second._coef->_is_transposed) {
                            auto coefs = new_array_ptr<double, 1>(it1.second._p->get_nb_instances());
                            for (int j = 0; j < it1.second._p->get_nb_instances(); j++) {
                                (*coefs)(j) = poly_eval(it1.second._coef, j);
                            }
                            expr = fusion::Expr::add(expr, fusion::Expr::dot(coefs, _mosek_vars[idx]));
                            //    cout << "expr" << expr->toString() << endl;
                        } else {
                            if (is_indexed(it1.second._p)) {
                                idx_inst = it1.second._p->get_id_inst(i);
                            } else {
                                idx_inst = inst;
                            }
                            if (is_indexed(it1.second._coef)) {
                                c_idx_inst = get_poly_id_inst(it1.second._coef);
                            } else {
                                c_idx_inst = inst;
                            }
                            auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, c_idx_inst),
                                                           _mosek_vars[idx]->index(idx_inst));

                            if (!it1.second._sign) {
                                lterm = fusion::Expr::mul(-1, lterm);
                            }
                            expr = fusion::Expr::add(expr, lterm);
                        }
                    }else{
                        if (it1.second._coef->_is_transposed) {
                            auto coefs = new_array_ptr<double, 1>(it1.second._p->get_nb_instances());
                            for (int j = 0; j < it1.second._p->get_nb_instances(); j++) {
                                (*coefs)(j) = poly_eval(it1.second._coef, j);
                            }
                            expr = fusion::Expr::add(expr, fusion::Expr::dot(coefs, _mosek_vars[idx]));
                            //    cout << "expr" << expr->toString() << endl;
                        } else {
                            pair<size_t, size_t>  pair;
                            if (is_indexed(it1.second._p)) {
//                                idx_inst = it1.second._p->get_id_inst(i);
                                pair = it1.second._p->get_sdp_inst(i);
                            } else {
                                idx_inst = inst;
                            }
                            if (is_indexed(it1.second._coef)) {
                                c_idx_inst = get_poly_id_inst(it1.second._coef);
                            } else {
                                c_idx_inst = inst;
                            }
                            DebugOff("\nindex: " << pair.first << ", " << pair.second);
                            auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, c_idx_inst),
                                                           _mosek_vars[idx]->index(pair.first,pair.second));

                            if (!it1.second._sign) {
                                lterm = fusion::Expr::mul(-1, lterm);
                            }
                            expr = fusion::Expr::add(expr, lterm);
                        }
                    }

                }
                else if(vartype == sdpvar_c) {
                    if (it1.second._coef->_is_transposed) {
                        auto coefs = new_array_ptr<double,1>(it1.second._p->get_nb_instances());
                        for (int j = 0; j<it1.second._p->get_nb_instances(); j++) {
                            (*coefs)(j) = poly_eval(it1.second._coef,j);
                        }
                        expr = fusion::Expr::add(expr,fusion::Expr::dot(coefs,_mosek_vars[idx]));
                    }
                    else {
                        pair<size_t, size_t>  pair = make_pair(0, 0);

                        if (is_indexed(it1.second._p)) {
                            pair = it1.second._p->get_sdpid();
                        }

                        if (is_indexed(it1.second._coef)) {
                            c_idx_inst = get_poly_id_inst(it1.second._coef);
                        }
                        else {
                            c_idx_inst = inst;
                        }
                        auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, c_idx_inst), _mosek_vars[idx]->index(pair.first, pair.second));
                        if (!it1.second._sign) {
                            lterm = fusion::Expr::mul(-1, lterm);
                        }
                        expr = fusion::Expr::add(expr,lterm);
                    }

                }else{ //param
                    if (it1.second._coef->_is_transposed) {
                        auto coefs = new_array_ptr<double,1>(it1.second._p->get_nb_instances());
                        for (int j = 0; j<it1.second._p->get_nb_instances(); j++) {
                            (*coefs)(j) = poly_eval(it1.second._coef,j);
                        }
                        expr = fusion::Expr::add(expr,fusion::Expr::dot(coefs,_mosek_vars[idx]));
                        //    cout << "expr" << expr->toString() << endl;
                    }
                    else { //this results in always taking the first instance? it doesn't use i at all?
                        if (is_indexed(it1.second._p)) {
                            idx_inst = it1.second._p->get_id_inst(i); //fixed here, look for similar errors
                        }
                        else {
                            idx_inst = inst;
                        }
                        if (is_indexed(it1.second._coef)) {
                            c_idx_inst = get_poly_id_inst(it1.second._coef);
                        }
                        else {
                            c_idx_inst = inst;
                        }
                        auto lterm = fusion::Expr::constTerm(poly_eval(it1.second._coef, c_idx_inst) * poly_eval(it1.second._p, i)); // was idx_inst
                        if (!it1.second._sign) {
                            lterm = fusion::Expr::mul(-1, lterm);
                        }
                        expr = fusion::Expr::add(expr,lterm);
                    }
                }
            }

            DebugOff("\nconstr in Mosek: " << expr->toString());
            if(c->get_type()==geq) {
                DebugOff("\n >= " << c->get_rhs());
                _mosek_model->constraint(c->get_name()+to_string(i), expr, fusion::Domain::greaterThan(c->get_rhs()));
            }
            else if(c->get_type()==leq) {
                DebugOff("\n <= " << c->get_rhs());
                _mosek_model->constraint(c->get_name()+to_string(i), expr, fusion::Domain::lessThan(c->get_rhs()));
            }
            else if(c->get_type()==eq) {
                DebugOff("\n" << expr->toString());
                DebugOff(" = " << c->get_rhs());
                _mosek_model->constraint(c->get_name()+to_string(i), expr, fusion::Domain::equalsTo(c->get_rhs()));
            }
            inst++;
        }
    }
}*/

size_t get_num_qterms(map<string, qterm>& qterms) {
    size_t qn = 0;
    for (auto& it1: qterms) {
        if ((it1.second._coef->_is_transposed || it1.second._coef->_is_matrix) && !it1.second._p->first->_is_matrix) {
            size_t dim = it1.second._p->first->get_nb_instances();
            qn += dim;
        }
        else if(it1.second._p->first->_is_transposed){
            size_t dim = it1.second._p->first->get_nb_instances();
            qn += dim;
        }
        else qn++;
    }
    return qn;
}

fusion::Expression::t MosekProgram::create_lin_expr(map<string, lterm>& lt, constant_* cst, size_t inst){
    size_t idx = 0, idx_inst = 0, c_idx_inst = 0;
    auto fusion_lterms = new_array_ptr<fusion::Expression::t,1>(lt.size()+1);
    (*fusion_lterms)[0] = fusion::Expr::constTerm(poly_eval(cst,inst));
    int lterm_idx = 1;

    for (auto& it1: lt) {
        idx = it1.second._p->get_vec_id();

        CType vartype = it1.second._p->get_type();
        if (vartype == var_c) {
//                    cout << "\nvar = " << it1.second._p->get_name();
//                    cout << "\nfull Mosek var: " << _mosek_vars[idx]->toString();
            if(!it1.second._p->_is_matrix) {
                if (it1.second._coef->_is_transposed) {
                    auto coefs = new_array_ptr<double, 1>(it1.second._p->get_nb_instances(inst));
                    auto vars = new_array_ptr<fusion::Variable::t,1>(it1.second._p->get_nb_instances(inst));
                    for (int j = 0; j < it1.second._p->get_nb_instances(inst); j++) {
                        (*coefs)(j) = poly_eval(it1.second._coef,inst,j);
                        idx_inst = it1.second._p->get_id_inst(inst,j);
//                                if(idx_inst >= _mosek_vars[idx]->size()){
//                                    cout << "\nidx_inst >= var size: idx_inst = " << idx_inst << ", var size = " << _mosek_vars[idx]->size();
//                                }
                        (*vars)(j) = _mosek_vars[idx]->index(idx_inst);
//                                cout << "\nj, idx, idx_inst = " << j << ", " << idx << ", " << idx_inst << ", var = " <<  _mosek_vars[idx]->index(idx_inst)->toString();
                    }
                    rc_ptr<fusion::Variable> P;
                    if(it1.second._p->get_nb_instances(inst)!=1) P = fusion::Var::vstack(vars);
                    else P = _mosek_vars[idx]->index(idx_inst);
//                            cout << "\nCoef = ";
//                            for(auto& coef: *coefs)
//                                cout << coef << "; ";
//                            cout << "\nP = " << P->toString();
                    auto lterm = fusion::Expr::dot(coefs, P);
                    if (!it1.second._sign) lterm = fusion::Expr::mul(-1, lterm);
                    (*fusion_lterms)[lterm_idx] = lterm;
                    //expr = fusion::Expr::add(expr, fusion::Expr::dot(coefs, _mosek_vars[idx]));
                } else {
                    if (is_indexed(it1.second._p)) idx_inst = it1.second._p->get_id_inst(inst);
                    else idx_inst = inst;

                    if (is_indexed(it1.second._coef)) c_idx_inst = get_poly_id_inst(it1.second._coef);
                    else c_idx_inst = inst;

                    auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, inst), //was c_idx_inst instead of i
                                                   _mosek_vars[idx]->index(idx_inst));

                    if (!it1.second._sign) lterm = fusion::Expr::mul(-1, lterm);
                    (*fusion_lterms)[lterm_idx] = lterm;
                    //expr = fusion::Expr::add(expr, lterm);
                }
            }else{
                if (it1.second._coef->_is_transposed) {
                    auto coefs = new_array_ptr<double, 1>(it1.second._p->get_nb_instances());
                    for (int j = 0; j < it1.second._p->get_nb_instances(); j++) {
                        (*coefs)(j) = poly_eval(it1.second._coef, j);
                    }
                    (*fusion_lterms)[lterm_idx] = fusion::Expr::dot(coefs, _mosek_vars[idx]);
                    //expr = fusion::Expr::add(expr, fusion::Expr::dot(coefs, _mosek_vars[idx]));
                } else {
                    pair<size_t, size_t>  pair;
                    if (is_indexed(it1.second._p)) {
//                                idx_inst = it1.second._p->get_id_inst(i);
                        pair = it1.second._p->get_sdp_inst(inst);
                    } else idx_inst = inst;

                    if (is_indexed(it1.second._coef)) c_idx_inst = get_poly_id_inst(it1.second._coef);
                    else c_idx_inst = inst;

                    DebugOff("\nindex: " << pair.first << ", " << pair.second);
                    DebugOff("\nMosek var:" << _mosek_vars[idx]->toString());

                    auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, c_idx_inst),
                                                   _mosek_vars[idx]->index(pair.first,pair.second));

                    if (!it1.second._sign) lterm = fusion::Expr::mul(-1, lterm);
                    (*fusion_lterms)[lterm_idx] = lterm;
                    //expr = fusion::Expr::add(expr, lterm);
                }
            }

        }
        else{ //param
            if (it1.second._coef->_is_transposed) {
                auto coefs = new_array_ptr<double,1>(it1.second._p->get_nb_instances());
                for (int j = 0; j<it1.second._p->get_nb_instances(); j++) {
                    (*coefs)(j) = poly_eval(it1.second._coef,j);
                }
                (*fusion_lterms)[lterm_idx] = fusion::Expr::dot(coefs,_mosek_vars[idx]);
                //expr = fusion::Expr::add(expr,fusion::Expr::dot(coefs,_mosek_vars[idx]));
            }
            else { //this results in always taking the first instance? it doesn't use i at all?
                if (is_indexed(it1.second._p)) idx_inst = it1.second._p->get_id_inst(inst); //fixed here, look for similar errors
                else idx_inst = inst;

                if (is_indexed(it1.second._coef)) c_idx_inst = get_poly_id_inst(it1.second._coef);
                else c_idx_inst = inst;

                auto lterm = fusion::Expr::constTerm(poly_eval(it1.second._coef, c_idx_inst) * poly_eval(it1.second._p, inst)); // was idx_inst
                if (!it1.second._sign) lterm = fusion::Expr::mul(-1, lterm);

                (*fusion_lterms)[lterm_idx] = lterm;
//                        expr = fusion::Expr::add(expr,lterm);
            }
        }
        lterm_idx++;
    }
    fusion::Expression::t res;
    res = fusion::Expr::add(std::shared_ptr<ndarray<fusion::Expression::t,1>>(fusion_lterms));
    return res;
}

//TODO: check use inst
fusion::Expression::t MosekProgram::form_Fx(map<string, qterm>& qterms, size_t qn, size_t inst){
    size_t aidx = 0, idx1 = 0, idx2 = 0, idx_inst = 0;

    //create a var map
    map<pair<size_t,size_t>,int> qvars;
    int idx_in_A = 0;
    for(auto& it1: qterms) {
        idx1 = it1.second._p->first->get_vec_id();
        idx2 = it1.second._p->second->get_vec_id();
        if ((it1.second._coef->_is_transposed || it1.second._coef->_is_matrix) && !it1.second._p->first->_is_matrix) {
            auto dim = it1.second._p->first->get_nb_instances(inst);
            for (int j = 0; j < dim; j++) {
                idx_inst = it1.second._p->first->get_id_inst(inst, j);
                pair<size_t, size_t> p = make_pair(idx1, idx_inst);
                if (qvars.find(p) == qvars.end()) {
                    qvars.insert(make_pair(p, idx_in_A));
                    idx_in_A++;
                }
                idx_inst = it1.second._p->second->get_id_inst(inst, j);
                p = make_pair(idx2, idx_inst);
                if (qvars.find(p) == qvars.end()) {
                    qvars.insert(make_pair(p, idx_in_A));
                    idx_in_A++;
                }
            }
        } else if (it1.second._p->first->_is_transposed) {
//                auto dim = _p->first->get_nb_instances(i);
//                for (int j = 0; j<dim; j++)
//                    res += poly_eval(_coef,i,j) * poly_eval(_p->first, i,j)* poly_eval(_p->second, i,j);
        } else {
            idx_inst = it1.second._p->first->get_id_inst(inst);
            pair<size_t, size_t> p = make_pair(idx1, idx_inst);
            if (qvars.find(p) == qvars.end()) {
                qvars.insert(make_pair(p, idx_in_A));
                idx_in_A++;
            }
            idx_inst = it1.second._p->second->get_id_inst(inst);
            p = make_pair(idx2, idx_inst);
            if (qvars.find(p) == qvars.end()) {
                qvars.insert(make_pair(p, idx_in_A));
                idx_in_A++;
            }
        }
    }
    auto qvars_arr = new_array_ptr<fusion::Variable::t,1>(qvars.size());
    for(auto& it1: qvars) {
        (*qvars_arr)[it1.second] = _mosek_vars[it1.first.first]->index(it1.first.second);
        DebugOff("\nqvar with index " << it1.second << " is (" << it1.first.first << ", " << it1.first.second << ")");
        DebugOff("\ncorresp mosek vars are " << _mosek_vars[it1.first.first]->index(it1.first.second)->toString());
    }

    Eigen::MatrixXf A = Eigen::MatrixXf::Zero(qvars.size(),qvars.size());
    fusion::Variable::t xi = fusion::Var::vstack(qvars_arr);
//    auto qvarsi = new_array_ptr<fusion::Variable::t,1>(qn);

    size_t j2 = 0;
    for (auto& it1: qterms) {
        double sign;
        if(!it1.second._sign) sign = -2;
        else sign = 2;
        idx1 = it1.second._p->first->get_vec_id();
        idx2 = it1.second._p->second->get_vec_id();
        if ((it1.second._coef->_is_transposed || it1.second._coef->_is_matrix) && !it1.second._p->first->_is_matrix) {
            throw invalid_argument("This type of expression in the constraint is not implemented.");
//            auto dim = it1.second._p->first->get_nb_instances(inst);
//            auto qvarsi = new_array_ptr<fusion::Variable::t,1>(dim);
//            for (int j = 0; j<dim; j++) {
////                    res += poly_eval(_coef,i,j) * poly_eval(_p->first, i,j)* poly_eval(_p->second, i,j);
//                if(it1.second._p->first->get_id_inst(inst,j) != it1.second._p->second->get_id_inst(inst,j))
//                    throw invalid_argument("Bilinear expressions in Mosek objective are not implemented.");
//                A(aidx,aidx) = sign*poly_eval(it1.second._coef,inst,j);
//                aidx++;
//                idx_inst = it1.second._p->first->get_id_inst(inst,j);
////                (*qvarsi)(j) = _mosek_vars[idx1]->index(idx_inst);
//            }
//            xi = fusion::Var::vstack(qvarsi);
        }
        else if(it1.second._p->first->_is_transposed){
//                auto dim = _p->first->get_nb_instances(i);
//                for (int j = 0; j<dim; j++)
//                    res += poly_eval(_coef,i,j) * poly_eval(_p->first, i,j)* poly_eval(_p->second, i,j);
            throw invalid_argument("This type of expression in the constraint is not implemented.");
        }
        else {
//                res = poly_eval(_coef,i) * poly_eval(_p->first, i) * poly_eval(_p->second, i);
            idx_inst = it1.second._p->first->get_id_inst(inst);
            int aidx1 = qvars.find(make_pair(idx1,idx_inst))->second;
            DebugOff("\nvars: " << _mosek_vars[idx1]->index(idx_inst)->toString());
            idx_inst = it1.second._p->second->get_id_inst(inst);
            int aidx2 = qvars.find(make_pair(idx2,idx_inst))->second;
            DebugOff(", " << _mosek_vars[idx2]->index(idx_inst)->toString());
            if(aidx1==aidx2) A(aidx1,aidx2) = sign*poly_eval(it1.second._coef,inst);
            else {
                A(aidx1,aidx2) = 0.5*sign*poly_eval(it1.second._coef,inst);
                A(aidx2,aidx1) = 0.5*sign*poly_eval(it1.second._coef,inst);
            }
//            aidx++;

//            (*qvarsi)(j2) = _mosek_vars[idx1]->index(idx_inst);
//            j2++;
        }
//            if (!it1.second._sign) res *= -1;
    }
    DebugOff("\nA = \n" << A);
    DebugOff("\nx = " << xi->toString());

    Eigen::LLT<Eigen::MatrixXf> lltOfA(A); // compute the Cholesky decomposition of A
    Eigen::MatrixXf L = lltOfA.matrixL();
//    Eigen::MatrixXf L = A.sqrt();
    DebugOff("\nL: " << L);

    std::shared_ptr<ndarray<double, 1>> Farr = new_array_ptr<double,1>(qn*qn);
    for(size_t Fi = 0; Fi < L.rows(); Fi++) {
        for(size_t Fj = 0; Fj < L.cols(); Fj++) {
            (*Farr)[Fi*(L.cols())+Fj] = L(Fi,Fj);
        }
    }
    fusion::Matrix::t F = fusion::Matrix::dense(qvars.size(),qvars.size(),Farr);
    DebugOff("\nF = " << F->toString());
    fusion::Expression::t Fx = fusion::Expr::mul(F, xi);
    DebugOff("\nFx = " << Fx->toString());

    return Fx;
}

void MosekProgram::create_mosek_constraints() {
    size_t idx = 0, idx_inst = 0, idx1 = 0, idx2 = 0, idx_inst1 = 0, idx_inst2 = 0, nb_inst = 0, inst = 0;
    size_t c_idx_inst = 0;
    shared_ptr<Constraint> c;
    for(auto& p: _model->_cons) {
        c = p.second;
        DebugOff("\nconstr " << c->get_name());
        if(c->is_nonlinear() && !c->is_quadratic())
            throw invalid_argument("Mosek cannot handle nonlinear constraints that are not convex quadratic.\n");

        nb_inst = c->get_nb_instances();

        if (c->is_quadratic()) {
            size_t qn = get_num_qterms(c->get_qterms());
            auto fusion_cols = new_array_ptr<fusion::Expression::t,1>(nb_inst);
            for (int i = 0; i< nb_inst; i++) {
                //create matrix F;
                fusion::Expression::t Fx = form_Fx(c->get_qterms(),qn,i);

                //build the linear expression
                fusion::Expression::t lin_expr_i = create_lin_expr(c->get_lterms(), c->get_cst(), i);

                //arrange it all into a row, add the row to an array
                auto Earr = new_array_ptr<fusion::Expression::t,1>(2 + qn);
                (*Earr)[0] = fusion::Expr::constTerm(1);
                (*Earr)[1] = fusion::Expr::mul(lin_expr_i,-1);
                for(size_t Fxi = 0; Fxi < qn; Fxi++) {
                    (*Earr)[Fxi+2] = Fx->index(Fxi);
                }
                fusion::Expression::t qexpr = fusion::Expr::hstack(Earr);
                DebugOff("\nqexpr = " << qexpr->toString());

                (*fusion_cols)[i] = qexpr;
            }

            // use the array of rows to create a matrix
            auto M = fusion::Expr::vstack(fusion_cols);
            DebugOff("\nM = " << M->toString());

            // add a conic constraint
            auto mosek_constr = _mosek_model->constraint(c->get_name(), M, fusion::Domain::inRotatedQCone(nb_inst,2+qn));
            DebugOff("\nConstraint generated: " << mosek_constr->toString());
        } // quadratic expression
        else{
            auto fusion_sums = new_array_ptr<fusion::Expression::t, 1>(nb_inst);
            for (int i = 0; i < nb_inst; i++) {
                fusion::Expression::t lin_expr = create_lin_expr(c->get_lterms(), c->get_cst(), i);
                (*fusion_sums)[i] = lin_expr;
                inst++;
            }
            auto E = fusion::Expr::vstack(fusion_sums);

            DebugOff("\nconstr " << c->get_name() << " in Mosek: " << E->toString());
            if (c->get_type() == geq) {
                DebugOff("\n >= " << c->get_rhs());
                _mosek_model->constraint(c->get_name(), E, fusion::Domain::greaterThan(c->get_rhs()));
            } else if (c->get_type() == leq) {
                DebugOff("\n <= " << c->get_rhs());
                _mosek_model->constraint(c->get_name(), E, fusion::Domain::lessThan(c->get_rhs()));
            } else if (c->get_type() == eq) {
                DebugOff(" = " << c->get_rhs());
                _mosek_model->constraint(c->get_name(), E, fusion::Domain::equalsTo(c->get_rhs()));
            }
        } // linear expression
    } //for(p: _model->_cons)
}

fusion::Expression::t MosekProgram::form_Fx(map<string, qterm>& qterms, size_t qn){
    size_t aidx = 0, idx, idx_inst;
    Eigen::MatrixXf A = Eigen::MatrixXf::Zero(qn,qn);
    fusion::Variable::t xi;
    for (auto& it1: qterms) {
        double sign;
        if(!it1.second._sign) sign = -2;
        else sign = 2;
        idx = it1.second._p->first->get_vec_id();
        if ((it1.second._coef->_is_transposed || it1.second._coef->_is_matrix) && !it1.second._p->first->_is_matrix) {
            auto dim = it1.second._p->first->get_nb_instances();
            auto qvarsi = new_array_ptr<fusion::Variable::t,1>(dim);
            for (int j = 0; j<dim; j++) { //now only supports quadratic (not bilinear) terms
//                    res += poly_eval(_coef,i,j) * poly_eval(_p->first, i,j)* poly_eval(_p->second, i,j);
                if(it1.second._p->first->get_id_inst(j) != it1.second._p->second->get_id_inst(j))
                    throw invalid_argument("Bilinear expressions in Mosek objective are not implemented.");
                A(aidx,aidx) = sign*poly_eval(it1.second._coef,0,j);
                aidx++;
                idx_inst = it1.second._p->first->get_id_inst(j);
                (*qvarsi)(j) = _mosek_vars[idx]->index(idx_inst);
            }
            xi = fusion::Var::vstack(qvarsi);
//                (*qvars)(qtermi) = xi; qtermi++;
        }
        else if(it1.second._p->first->_is_transposed){
//                auto dim = _p->first->get_nb_instances(i);
//                for (int j = 0; j<dim; j++)
//                    res += poly_eval(_coef,i,j) * poly_eval(_p->first, i,j)* poly_eval(_p->second, i,j);
            throw invalid_argument("This type of expression in the objective is not implemented.");
        }
        else {
//                res = poly_eval(_coef,i) * poly_eval(_p->first, i) * poly_eval(_p->second, i);
            throw invalid_argument("This type of expression in the objective is not implemented.");
        }
//            if (!it1.second._sign) res *= -1;
    }
    DebugOff("\nA = " << A);
    DebugOff("\nx = " << xi->toString());

    Eigen::LLT<Eigen::MatrixXf> lltOfA(A); // compute the Cholesky decomposition of A
    Eigen::MatrixXf L = lltOfA.matrixL();
    DebugOff("\nL: " << L);

    std::shared_ptr<ndarray<double, 1>> Farr = new_array_ptr<double,1>(qn*qn);
    for(size_t Fi = 0; Fi < L.rows(); Fi++) {
        for(size_t Fj = 0; Fj < L.cols(); Fj++) {
            (*Farr)[Fi*(L.cols())+Fj] = L(Fi,Fj);
        }
    }
    fusion::Matrix::t F = fusion::Matrix::dense(qn,qn,Farr);
    DebugOff("\nF = " << F->toString());
    fusion::Expression::t Fx = fusion::Expr::mul(F, xi);
    DebugOff("\nFx = " << Fx->toString());

    return Fx;
}

void MosekProgram::set_mosek_objective() {
    size_t idx = 0, idx_inst = 0, idx1 = 0, idx2 = 0, idx_inst1 = 0, idx_inst2 = 0, qn;
    size_t c_idx_inst = 0;
    // initialize with the constant part.
    monty::rc_ptr< ::mosek::fusion::Expression >  expr= fusion::Expr::constTerm(poly_eval(_model->_obj.get_cst())); // expr is a pointer to the Expression.
    if(_model->_obj.get_qterms().empty() == false){
        //cerr << "\nMosek doesn't support quadratic objectives!\n";
        qn = get_num_qterms(_model->_obj.get_qterms());

        _mosek_vars.push_back(_mosek_model->variable("r_obj", 1, fusion::Domain::unbounded()));
        //obj = r + (linear part) (see Mosek modelling cookbook)
        expr = fusion::Expr::add(expr,_mosek_vars[_mosek_vars.size()-1]);
        //auto qvars = new_array_ptr<fusion::Variable::t,1>(_model->_obj.get_qterms().size());
        fusion::Expression::t Fx = form_Fx(_model->_obj.get_qterms(),qn);

        auto Earr = new_array_ptr<fusion::Expression::t,1>(2 + qn);
        (*Earr)[0] = fusion::Expr::constTerm(1);
        (*Earr)[1] = _mosek_vars[_mosek_vars.size()-1]->asExpr();
        for(size_t Fxi = 0; Fxi < qn; Fxi++) {
            (*Earr)[Fxi+2] = Fx->index(Fxi);
        }
        fusion::Expression::t qexpr = fusion::Expr::vstack(Earr);
        DebugOn("\nqexpr = " << qexpr->toString());

        //(1,r,Fx) in Qr
        _mosek_model->constraint(qexpr, fusion::Domain::inRotatedQCone());
    }
    for (auto& it1: _model->_obj.get_lterms()) {
        //idx = it1.second._p->get_id();
        idx = it1.second._p->get_vec_id();
        CType vartype = it1.second._p->get_type();
        if (vartype == var_c) {
            if (it1.second._coef->_is_transposed) {
                auto coefs = new_array_ptr<double,1>((it1.second._p->get_nb_instances()));
                for (int j = 0; j<it1.second._p->get_nb_instances(); j++) {
                    (*coefs)(j)=poly_eval(it1.second._coef, j);
                }
                expr = fusion::Expr::add(expr,fusion::Expr::dot(coefs,_mosek_vars[idx]));
            }
            else {
                // get pos.
                idx_inst = it1.second._p->get_id_inst();
                c_idx_inst = get_poly_id_inst(it1.second._coef);
                //auto t = _mosek_vars[idx]->index
                auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, c_idx_inst), _mosek_vars[idx]->index(idx_inst));
                if (!it1.second._sign) {
                    lterm = fusion::Expr::mul(-1, lterm);
                }
                expr = fusion::Expr::add(expr,lterm);
            }
        }
        else {
            // sdpvar_c
            if (it1.second._coef->_is_transposed) {
                auto coefs = new_array_ptr<double,1>((it1.second._p->get_nb_instances()));
                for (int j = 0; j<it1.second._p->get_nb_instances(); j++) {
                    (*coefs)(j)=poly_eval(it1.second._coef, j);
                }
                expr = fusion::Expr::add(expr,fusion::Expr::dot(coefs,_mosek_vars[idx]));
            }
            else {
                // retrive the index of the parameter.
                // This is quite different from the general variable definition.
                pair<size_t, size_t>  pair = it1.second._p->get_sdpid();
                c_idx_inst = get_poly_id_inst(it1.second._coef);
                auto lterm = fusion::Expr::mul(poly_eval(it1.second._coef, c_idx_inst), _mosek_vars[idx]->index(pair.first,pair.second));

                if (!it1.second._sign) {
                    lterm = fusion::Expr::mul(-1, lterm);
                }
                expr = fusion::Expr::add(expr,lterm);
            }
        }
    }
    if (_model->_objt == maximize) {
        _mosek_model->objective("obj", mosek::fusion::ObjectiveSense::Maximize, expr);
    }
    else {
        _mosek_model->objective("obj", mosek::fusion::ObjectiveSense::Minimize, expr);
    }
//    cout << "\nObj = " << expr->toString() << endl;
}

void MosekProgram::prepare_model() {
    double time_before = get_wall_time();
    fill_in_mosek_vars();
    create_mosek_constraints();
    set_mosek_objective();
    cout << "\nTime spent on building model = " << get_wall_time() - time_before;
    //    print_constraints();
}


#include <ctype.h>
#include <regex>
#include <algorithm>

#include <boost/log/trivial.hpp>

#include "lib/stats/relative_strength_index.h"
#include "lib/stats/exponential_moving_average.h"
#include "lib/stats/simple_moving_average.h"
#include "lib/stats/bollinger.h"
#include "lib/rsiscript.h"

/**
 * Parse a math-related script. There are several phases.
 *
 * 1. Replace variables.
 * 2. Identify sections in parenthesis. Process these first.
 * 3. Process the math equations/comparisons.
 *
 * @param const char *script [ex: (1+3)/(2 * (4 + 6))]
 * @return const char *result
 */
int script_max_paren_depth = 50;
std::string rsiscript::parse(const char* const script, const stockinfo &data) {
	std::string err = "0", repl, expr = script;
	std::size_t pos, lparen_pos, rparen_pos = 0;
	unsigned int lparens, rparens;
	bool found = false;

	last_variables_used.clear();
	last_variables.clear();
	if (script == nullptr)
		return err;

	BOOST_LOG_TRIVIAL(trace) << "Script: " << script;
	expr = replace_variables(expr, data);

	do {
		// Search for a ')'.
		pos = rparen_pos = expr.find(")", rparen_pos);
		found = (rparen_pos != std::string::npos);

		// Find the matching '('.
		if (found) {
			rparens = 1;
			lparens = 0;

			while (pos > 0) {
				pos--;

				if (expr[pos] == ')') // TODO: Should not be possible.
					rparens++;
				else if (expr[pos] == '(') {
					lparens++;

					// Will only happen with a '(' find.
					if (lparens == rparens) {
						lparen_pos = pos;
						break;
					}
				}
			}

			// ERROR: Mismatched parenthesis.
			if (rparens != lparens) {
				BOOST_LOG_TRIVIAL(error) << "Mismatched parenthesis: " << script;
				printf("ERROR: Mismatched parenthesis: %s\n", script);
				return err;
			}

			// Parse the substring. Do not include the current parenthesis set.
			repl = parse(expr.substr(lparen_pos + 1, rparen_pos - lparen_pos - 1).c_str(), data);
			repl = exec_script_calculate(repl);
			expr.replace(lparen_pos, rparen_pos - lparen_pos + 1, repl);

			rparen_pos -= (rparen_pos - lparen_pos - repl.length());
		}
	} while (found);

	expr = exec_script_calculate(expr);
	BOOST_LOG_TRIVIAL(info) << "End of run: " << expr;
	return expr;
}

/**
 * Find/replace the variables in our script.
 *
 * @return string
 */
std::string rsiscript::replace_variables(const std::string &script, const stockinfo &data) {
	std::string expr = script;
	std::string err = "0", repl;
	std::size_t pos, lparen_pos, rparen_pos = 0;
	unsigned int lparens, rparens;
	bool found = false;

	do {
		// Search for a ')'.
		pos = rparen_pos = expr.find("}", rparen_pos);
		found = (rparen_pos != std::string::npos);

		// Find the matching '('.
		if (found) {
			rparens = 1;
			lparens = 0;

			while (pos > 0) {
				pos--;

				if (expr[pos] == '}') // TODO: Should not be possible.
					rparens++;
				else if (expr[pos] == '{') {
					lparens++;

					// Will only happen with a '(' find.
					if (lparens == rparens) {
						lparen_pos = pos;
						break;
					}
				}

				/*if (lparens > script_max_paren_depth) {
					BOOST_LOG_TRIVIAL(error) << "Found too many opening parens: " << lparens;
					printf("Found too many opening parens: %iu\n", lparens);
					return nullptr;
				}*/
			}

			// ERROR: Mismatched parenthesis.
			if (rparens != lparens) {
				BOOST_LOG_TRIVIAL(error) << "Mismatched brackets: " << script;
				printf("ERROR: Mismatched brackets: %s\n", script.c_str());
				return err;
			}

			// Parse the substring. Do not include the current parenthesis set.
			repl = replace_variables(expr.substr(lparen_pos + 1, rparen_pos - lparen_pos - 1), data);
			BOOST_LOG_TRIVIAL(trace) << "Replacing: " << repl;
			repl = variables(repl, data);
			// TODO: Replace all instances of this same variable?
			expr.replace(lparen_pos, rparen_pos - lparen_pos + 1, repl);

			rparen_pos -= (rparen_pos - lparen_pos - repl.length());
		}
	} while (found);

	BOOST_LOG_TRIVIAL(trace) << "Replaced variables: " << expr;

	return expr;
}

/**
 * Parse script variables into values.
 *
 * Format: high
 * Format: rsi
 * Format: high:52wk
 * Format: rsi:52wk(28)
 *
 * @param string req The variable to process.
 * @param stockinfo data The stock data to use in processing req.
 * @return string The script without any more variables.
 */
std::string rsiscript::variables(const std::string &req, const stockinfo &data) {
	std::string ret = "0";
	std::vector<std::string> tokens;
	stockinfo *working_data = (stockinfo *)&data;
	stockinfo week_data;

	tokenize(req, tokens, ":", true);
	BOOST_LOG_TRIVIAL(trace) << "Variable tokens: " << tokens.size();

	// TODO: Parse options for stat variables. Weekly, monthly, RSI/SMA/EMA/BB/&c.
	std::string period;
	if (tokens.size() >= 2) {
		period = tokens[1];
	}
	if (period.length()) {
		int number;
		timeperiods tp;
		parse_period(period, number, tp);

		week_data = working_data->rollup(number, tp);
		working_data = &week_data;
	}

	if (!(*working_data).length()) {
		return ret;
	}

	// Process the requested variable.
	std::string var = tokens[0];
	if (var.compare("open") == 0) {
		ret = last_variable(req, (*working_data)[0]->open);
	}
	else if (var.compare("high") == 0) {
		ret = last_variable(req, (*working_data)[0]->high);
	}
	else if (var.compare("low") == 0) {
		ret = last_variable(req, (*working_data)[0]->low);
	}
	else if (var.compare("close") == 0) {
		ret = last_variable(req, (*working_data)[0]->close);
	}
	else if (var.compare("volume") == 0) {
		ret = last_variable(req, (*working_data)[0]->volume);
	}
	else if (var.compare("rsi") == 0) {
		relative_strength_index rsi;
		double *rsi_data = rsi.generate(*working_data, 14, (*working_data).length() - 26);
		ret = last_variable(req, *rsi_data);
		free(rsi_data);
	}
	else if (var.compare("sma") == 0) {
		simple_moving_average sma;
		double *sma_data = sma.generate(*working_data, 20, (*working_data).length() - 26);
		ret = last_variable(req, *sma_data);
		free(sma_data);
	}
	else if (var.compare("ema") == 0) {
		simple_moving_average ema;
		double *ema_data = ema.generate(*working_data, 20, (*working_data).length() - 26);
		ret = last_variable(req, *ema_data);
		free(ema_data);
	}
	else if (var.compare("bb_top") == 0) {
		simple_moving_average sma;
		bollinger bb;
		double *sma_data = sma.generate(*working_data, 20, (*working_data).length() - 26);
		double *bb_data = bb.bands(*working_data, 14, (*working_data).length() - 26);
		ret = last_variable(req, *sma_data + *bb_data);
		free(sma_data);
		free(bb_data);
	}
	else if (var.compare("bb_bottom") == 0) {
		simple_moving_average sma;
		bollinger bb;
		double *sma_data = sma.generate(*working_data, 20, (*working_data).length() - 26);
		double *bb_data = bb.bands(*working_data, 14, (*working_data).length() - 26);
		ret = last_variable(req, *sma_data - *bb_data);
		free(sma_data);
		free(bb_data);
	}

	return ret;
}

/**
 * Parse a string like "52weeks" roughly into "52" and "week." It would have been nice to use regex for this, but the
 * regex parser does not appear to work in this dev's compiler.
 *
 * @todo We could refactor this to place number & period into a struct.
 *
 * @param req String
 * @param number Return value.
 * @param period Return value.
 */
void rsiscript::parse_period(std::string req, int &number, timeperiods &period) {
	std::istringstream i(req);
	i >> number;

	if (!number) {
		number = 1;
	}

	if (req.compare("week"))
		period = week;
	else if (req.compare("month"))
		period = month;
	else if (req.compare("year"))
		period = year;
	else //if (req.compare("day"))
		period = day;

	return;
}

/**
 * Store name + value in last_variables.
 *
 * @param name
 * @param value
 * @return string value
 */
template<typename T>
std::string rsiscript::last_variable(const std::string &name, T value) {
	std::string ret = std::to_string(value);
	bool add = !last_variables_used.size();

	if (!add) {
		add = std::find(last_variables_used.begin(), last_variables_used.end(), name) == last_variables_used.end();
	}

	if (add) {
		if (last_variables.length()) {
			last_variables += ", ";
		}
		last_variables += name + " = " + ret;
		last_variables_used.push_back(name);
	}

	return ret;
}

/**
 * Direct the order of operator usage.
 *
 * Phase 1: ^
 * Phase 2: * / %
 * Phase 3: + -
 * Phase 4: > < =
 * Phase 5: | &
 *
 * @todo >= <=
 *
 * @return string The value calculated.
 */
const std::string rsiscript::exec_script_calculate(const std::string &script) {
	std::string ret;

	BOOST_LOG_TRIVIAL(trace) << "Script chunk: " << script;

	// Operational passes. The terminating 0's allow us to use string functions.
	const char first_pass[] = {'^', 0};
	const char second_pass[] = {'*', 'x', '/', '%', 0};
	const char third_pass[] = {'+', '-', 0};
	const char fourth_pass[] = {'>', '<', '=', 0};
	const char fifth_pass[] = {'|', '&', 0};

	// Pass over the string three times to give us proper order of operation.
	ret = exec_script_operations(script, first_pass);
	ret = exec_script_operations(ret, second_pass);
	ret = exec_script_operations(ret, third_pass);
	ret = exec_script_operations(ret, fourth_pass);
	ret = exec_script_operations(ret, fifth_pass);

	BOOST_LOG_TRIVIAL(trace) << "Script chunk reduced: " << ret;

	return ret;
}

/**
 * Find numbers and their operators for direct calculation.
 *
 * @return string The calculated value(s).
 */
std::string rsiscript::exec_script_operations(const std::string &script, const char *operators) {
	std::size_t sc_len = script.length();
	const char digits[] = "0123456789.";
	std::size_t x, start, operation, end;
	bool isdigit, calc_end, calc_start = false;
	long num1l, num2l;
	double num1d, num2d;
	std::string ret = script, result, num1str, num2str;

	for (x = 0; x < sc_len; x++) {
		/**
		 * This logic assumes well-formed expressions: 4+(*1+2) => 4+*3 => 4+3 => 7.
		 */

		// Ignore space characters.
		if (isspace(ret[x]))
			continue;

		// Mark the beginning of numbers as we work through the string.
		isdigit = (strchr(digits, ret[x]) != NULL);
		if (!calc_start && isdigit) {
			calc_start = true;
			start = x;
		}
		// After we find a number, find an operation.
		else if (calc_start && (strchr(operators, ret[x]) != NULL)) {
			operation = x;
			calc_end = false;

			// Is there another number after the operator?
			// TODO: Allow negative numbers.
			for (x++; x < sc_len; x++) {
				if (strchr(digits, ret[x]) != NULL) {
					calc_end = true;
					break;
				}
			}

			// If we found another number...
			if (calc_end) {
				// Find the end of the number;
				end = sc_len;
				for (x++; x < sc_len; x++) {
					if (strchr(digits, ret[x]) == NULL) {
						end = x - 1;
						break;
					}
				}

				// Pull this expression apart.
				num1str = ret.substr(start, operation - start);
				num2str = ret.substr(operation + 1, end - operation);

				BOOST_LOG_TRIVIAL(trace) << "Script operation: " << num1str << ", " << ret[operation] << ", " << num2str;

				// If this is a long/integer operation
				if ((num1str.find(".") == std::string::npos) && (num2str.find(".") == std::string::npos)) {
					num1l = atol(num1str.c_str());
					num2l = atol(num2str.c_str());

					result = exec_script_calculate_operation(num1l, num2l, ret[operation]);
				} else {
					num1d = atof(num1str.c_str());
					num2d = atof(num2str.c_str());

					result = exec_script_calculate_operation(num1d, num2d, ret[operation]);
				}

				ret.replace(start, end - start + 1, result);
				sc_len = ret.length();
				x = start; // Re-process this result since it might start the next calculation.

				BOOST_LOG_TRIVIAL(trace) << "Script operation result: " << ret;
			} // End calculation.

			//calc_start = false;
		} // End operator section.
		else if (!isdigit) {
			// If this is not a valid digit, a space, or in the current operations list, start over.
			calc_start = false;
		}
	} // Next character.

	return ret;
}

/**
 * Takes two numeric (int/double) values and a comparison operator. Performs the required operation.
 *
 * @return int/double
 */
template<typename T>
std::string rsiscript::exec_script_calculate_operation(const T val1, const T val2, char operation) {
	double res;
	T result;

	switch(operation) {
		case '*':
		case 'x':
			result = val1 * val2;
			break;
		case '/':
			res = (double)val1 / (double)val2;
			break;
		case '+':
			result = val1 + val2;
			break;
		case '-':
			result = val1 - val2;
			break;
		case '^':
			result = pow(val1, val2);
			break;
		case '%':
			result = remainder(val1, val2);
			break;
		case '>':
			result = (val1 > val2) ? 1 : 0;
			break;
		case '<':
			result = (val1 < val2) ? 1 : 0;
			break;
		case '=':
			result = (val1 == val2) ? 1 : 0;
			break;
		case '|':
			result = (val1 || val2) ? 1 : 0;
			break;
		case '&':
			result = (val1 && val2) ? 1 : 0;
			break;
		default:
			result = 0;
			BOOST_LOG_TRIVIAL(error) << "Unknown script operation: " << operation;
	}

	// Force (double) for division.
	return (operation == '/') ? std::to_string(res) : std::to_string(result);
}

/**
 * Split a string.
 *
 * @link https://stackoverflow.com/a/1493195/850782
 *
 * @param string str
 * @param tokens The return structure.
 * @param string delimiters The characters to split for (default: " ").
 * @param bool trimEmpty Should empty values be skipped (default: true)?
 * @return None. Use tokens.
 */
template<class ContainerT>
void rsiscript::tokenize(const std::string& str, ContainerT& tokens,
				const std::string& delimiters, bool trimEmpty)
{
	std::string::size_type pos, lastPos = 0, length = str.length();

	using value_type	= typename ContainerT::value_type;
	using size_type		= typename ContainerT::size_type;

	while(lastPos < length + 1)
	{
		pos = str.find_first_of(delimiters, lastPos);
		if(pos == std::string::npos)
		{
			pos = length;
		}

		if(pos != lastPos || !trimEmpty)
			tokens.push_back(value_type(str.data()+lastPos,
					(size_type)pos-lastPos ));

		lastPos = pos + 1;
	}
}


// Boost.Geometry (aka GGL, Generic Geometry Library)
// This file is manually converted from PROJ4

// Copyright (c) 2008-2012 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017.
// Modifications copyright (c) 2017, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle

// Use, modification and distribution is subject to 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Geometry Library by Barend Gehrels (Geodan, Amsterdam)

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#ifndef BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_AUTH_HPP
#define BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_AUTH_HPP

#include <cassert>
#include <cmath>

#include <boost/geometry/core/assert.hpp>

namespace boost { namespace geometry { namespace projections {

namespace detail {

static const int APA_SIZE = 3;

/* determine latitude from authalic latitude */
template <typename T>
inline bool pj_authset(T const& es, T* APA)
{
    BOOST_GEOMETRY_ASSERT(0 != APA);

    static const T P00 = .33333333333333333333;
    static const T P01 = .17222222222222222222;
    static const T P02 = .10257936507936507936;
    static const T P10 = .06388888888888888888;
    static const T P11 = .06640211640211640211;
    static const T P20 = .01641501294219154443;

    T t = 0;

    // if (APA = (double *)pj_malloc(APA_SIZE * sizeof(double)))
    {
        APA[0] = es * P00;
        t = es * es;
        APA[0] += t * P01;
        APA[1] = t * P10;
        t *= es;
        APA[0] += t * P02;
        APA[1] += t * P11;
        APA[2] = t * P20;
    }
    return true;
}

template <typename T>
inline T pj_authlat(T const& beta, const T* APA)
{
    BOOST_GEOMETRY_ASSERT(0 != APA);

    T const t = beta + beta;

    return(beta + APA[0] * sin(t) + APA[1] * sin(t + t) + APA[2] * sin(t + t + t));
}

} // namespace detail
}}} // namespace boost::geometry::projections

#endif // BOOST_GEOMETRY_PROJECTIONS_IMPL_PJ_AUTH_HPP