/*=============================================================================
Copyright 2018 Pranam Lashkari <plashkari628@gmail.com>
Copyright 2019 Sarthak Singhal <singhalsarthak2007@gmail.com>
Copyright 2020 Rohit Ranjan    <rohitrjn629@gmail.com>

Distributed under the Boost Software License, Version 1.0. (See accompanying
file License.txt or copy at https://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

#ifndef BOOST_ASTRONOMY_COORDINATE_SPHERICAL_COSLAT_DIFFERENTIAL_HPP
#define BOOST_ASTRONOMY_COORDINATE_SPHERICAL_COSLAT_DIFFERENTIAL_HPP

#include <tuple>
#include <type_traits>

#include <math.h>
#include <boost/geometry/strategies/strategy_transform.hpp>
#include <boost/geometry/core/cs.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/algorithms/transform.hpp>
#include <boost/geometry/algorithms/equals.hpp>
#include <boost/static_assert.hpp>
#include <boost/units/quantity.hpp>
#include <boost/units/get_dimension.hpp>
#include <boost/units/systems/si/dimensionless.hpp>
#include <boost/units/systems/si/plane_angle.hpp>

#include <boost/astronomy/detail/is_base_template_of.hpp>
#include <boost/astronomy/coordinate/diff/base_differential.hpp>
#include <boost/astronomy/coordinate/diff/cartesian_differential.hpp>
#include <boost/astronomy/coordinate/diff/spherical_differential.hpp>

namespace boost { namespace astronomy { namespace coordinate {

namespace bu = boost::units;
namespace bg = boost::geometry;

//!Represents the differential in spherical representation including cos(latitude) term
//!Uses three components to represent a differential (dlatitude, dlongitude_coslat, ddistance)

template
<
    typename CoordinateType = double,
    typename LatQuantity = bu::quantity<bu::si::plane_angle, CoordinateType>,
    typename LonQuantity = bu::quantity<bu::si::plane_angle, CoordinateType>,
    typename DistQuantity = bu::quantity<bu::si::dimensionless, CoordinateType>
>
struct spherical_coslat_differential : public base_differential
    <3, geometry::cs::spherical<radian>, CoordinateType>
{
    ///@cond INTERNAL
    BOOST_STATIC_ASSERT_MSG(
        ((std::is_same<typename bu::get_dimension<LatQuantity>::type,
            bu::plane_angle_dimension>::value) &&
            (std::is_same<typename bu::get_dimension<LonQuantity>::type,
            bu::plane_angle_dimension>::value)),
        "Latitude and Longitude must be of plane angle type");
    BOOST_STATIC_ASSERT_MSG((std::is_floating_point<CoordinateType>::value),
        "CoordinateType must be a floating-point type");
    ///@endcond

public:
    typedef LatQuantity quantity1;
    typedef LonQuantity quantity2;
    typedef DistQuantity quantity3;

    //default constructor no initialization
    spherical_coslat_differential() {}

    //!constructs object from provided components of differential
    spherical_coslat_differential
    (
        LatQuantity const& dlat,
        LonQuantity const& dlon_coslat,
        DistQuantity const& ddistance
    )
    {
        this->set_dlat_dlon_coslat_ddist(dlat, dlon_coslat, ddistance);
    }

    //!constructs object from boost::geometry::model::point object
    template
    <
        std::size_t OtherDimensionCount,
        typename OtherCoordinateSystem,
        typename OtherCoordinateType
    >
    spherical_coslat_differential
    (
        bg::model::point
        <
            OtherCoordinateType,
            OtherDimensionCount,
            OtherCoordinateSystem
        > const& pointObject
    )
    {
        bg::model::point<OtherCoordinateType, 3, bg::cs::cartesian> temp;
        bg::transform(pointObject, temp);
        bg::transform(temp, this->diff);
    }

    //copy constructor
    spherical_coslat_differential
    (
        spherical_coslat_differential
        <
            CoordinateType,
            LatQuantity,
            LonQuantity,
            DistQuantity
        > const& other
    )
    {
        this->diff = other.get_differential();
    }

    // !constructs object from any type of differential
    template <typename Differential>
    spherical_coslat_differential(Differential const& other)
    {
        BOOST_STATIC_ASSERT_MSG((boost::astronomy::detail::is_base_template_of
            <boost::astronomy::coordinate::base_differential, Differential>::value),
            "No constructor found with given argument type");

        BOOST_STATIC_ASSERT_MSG(
        ((std::is_same<typename bu::get_dimension<DistQuantity>::type,
        typename bu::get_dimension<typename Differential::quantity3>::type>::value)),
        "Two differentials must have same dimensions");

        auto tempDiff = make_spherical_coslat_differential(other);
        bg::model::point
        <
            typename std::conditional
            <
                sizeof(CoordinateType) >= sizeof(typename Differential::type),
                CoordinateType,
                typename Differential::type
            >::type,
            3,
            bg::cs::spherical<radian>
        > tempPoint;

        bg::set<0>(tempPoint,
            static_cast<
            bu::quantity<bu::si::plane_angle, CoordinateType>
            >(tempDiff.get_dlat()).value());
        bg::set<1>(tempPoint,
            static_cast<
            bu::quantity<bu::si::plane_angle, CoordinateType>
            >(tempDiff.get_dlon_coslat()).value());
        bg::set<2>(tempPoint,
            static_cast<DistQuantity>(tempDiff.get_ddist()).value());

        this->diff = tempPoint;
    }

    //! returns the (dlat, dlon_coslat, ddistance) in the form of tuple
    std::tuple<LatQuantity, LonQuantity, DistQuantity> get_dlat_dlon_coslat_ddist() const
    {
        return std::make_tuple(this->get_dlat(), this->get_dlon_coslat(),
            this->get_ddist());
    }

    //!returns the dlat component of differential
    LatQuantity get_dlat() const
    {
        return static_cast<LatQuantity>
            (
                bu::quantity<bu::si::plane_angle, CoordinateType>::from_value
                    (bg::get<0>(this->diff))
            );
    }

    //!returns the dlon_coslat component of differential
    LonQuantity get_dlon_coslat() const
    {
        return static_cast<LonQuantity>
            (
                bu::quantity<bu::si::plane_angle, CoordinateType>::from_value
                    (bg::get<1>(this->diff))
            );
    }

    //!returns the ddistance component of differential
    DistQuantity get_ddist() const
    {
        return DistQuantity::from_value(bg::get<2>(this->diff));
    }

    //!set value of (dlat, dlon_coslat, ddistance) in current object
    void set_dlat_dlon_coslat_ddist
    (
        LatQuantity const& dlat,
        LonQuantity const& dlon_coslat,
        DistQuantity const& ddistance
    )
    {
        this->set_dlat(dlat);
        this->set_dlon_coslat(dlon_coslat);
        this->set_ddist(ddistance);
    }

    //!set value of dlat component of differential
    void set_dlat(LatQuantity const& dlat)
    {
        bg::set<0>
            (
            this->diff,
            static_cast<bu::quantity<bu::si::plane_angle, CoordinateType>>(dlat).value()
            );
    }

    //!set value of dlon_coslat component of differential
    void set_dlon_coslat(LonQuantity const& dlon_coslat)
    {
        bg::set<1>
            (
            this->diff,
            static_cast<bu::quantity<bu::si::plane_angle, CoordinateType>>
                (dlon_coslat).value()
            );
    }

    //!set value of ddistance component of differential
    void set_ddist(DistQuantity const& ddistance)
    {
        bg::set<2>(this->diff, ddistance.value());
    }

    //!operator for addition of differential
    template
    <
        typename Addend
    >
    spherical_coslat_differential
    <
        CoordinateType,
        LatQuantity,
        LonQuantity,
        DistQuantity
    >
    operator +(Addend const& addend) const
    {
        auto cartesian1 = make_cartesian_differential
            <CoordinateType, DistQuantity, DistQuantity, DistQuantity>(this->diff);
        auto cartesian2 = make_cartesian_differential(addend);

        auto temp = cartesian1 + cartesian2;

        spherical_coslat_differential
        <
            CoordinateType,
            LatQuantity,
            LonQuantity,
            DistQuantity
        > result = make_spherical_coslat_differential(temp);

        return result;
    }

    //!operator for multiplication of differential
    template
    <
        typename OtherQuantity
    >
    auto operator *(OtherQuantity const& dt) const
    {

        spherical_coslat_differential
            <CoordinateType, LatQuantity, LonQuantity, DistQuantity> temp(this->diff);

        spherical_coslat_differential
        <
            CoordinateType,
            LatQuantity,
            LonQuantity,
            bu::quantity<typename bu::multiply_typeof_helper
            <
                typename DistQuantity::unit_type,
                typename OtherQuantity::unit_type>::type,
                CoordinateType
            >
        > product
        (
            temp.get_dlat(),
            temp.get_dlon_coslat(),
            temp.get_ddist() * dt
        );

        return product;
    }
}; //spherical_coslat_differential

//!constructs object from provided components of differential
template
<
    typename CoordinateType,
    template <typename Unit1, typename CoordinateType_> class LatQuantity,
    template <typename Unit2, typename CoordinateType_> class LonQuantity,
    template <typename Unit3, typename CoordinateType_> class DistQuantity,
    typename Unit1,
    typename Unit2,
    typename Unit3
>
spherical_coslat_differential
<
    CoordinateType,
    LatQuantity<Unit1, CoordinateType>,
    LonQuantity<Unit2, CoordinateType>,
    DistQuantity<Unit3, CoordinateType>
>
make_spherical_coslat_differential
(
    LatQuantity<Unit1, CoordinateType> const& dlat,
    LonQuantity<Unit2, CoordinateType> const& dlon_coslat,
    DistQuantity<Unit3, CoordinateType> const& ddist
)
{
    return spherical_coslat_differential
        <
            CoordinateType,
            LatQuantity<Unit1, CoordinateType>,
            LonQuantity<Unit2, CoordinateType>,
            DistQuantity<Unit3, CoordinateType>
        >(dlat, dlon_coslat, ddist);
}

//!constructs object from provided components of differential with different units
template
<
    typename ReturnCoordinateType,
    typename ReturnLatQuantity,
    typename ReturnLonQuantity,
    typename ReturnDistQuantity,
    typename CoordinateType,
    typename LatQuantity,
    typename LonQuantity,
    typename DistQuantity
>
spherical_coslat_differential
<
    ReturnCoordinateType,
    ReturnLatQuantity,
    ReturnLonQuantity,
    ReturnDistQuantity
>
make_spherical_coslat_differential
(
    spherical_coslat_differential
    <
        CoordinateType,
        LatQuantity,
        LonQuantity,
        DistQuantity
    > const& other
)
{
    return make_spherical_coslat_differential(
        static_cast<ReturnLatQuantity>(other.get_dlat()),
        static_cast<ReturnLonQuantity>(other.get_dlon_coslat()),
        static_cast<ReturnDistQuantity>(other.get_ddist())
    );
}

//!constructs object from provided differential
template
<
    typename CoordinateType,
    typename LatQuantity,
    typename LonQuantity,
    typename DistQuantity
>
spherical_coslat_differential
<
    CoordinateType,
    LatQuantity,
    LonQuantity,
    DistQuantity
>
make_spherical_coslat_differential
(
    spherical_coslat_differential
    <
        CoordinateType,
        LatQuantity,
        LonQuantity,
        DistQuantity
    > const& other
)
{
    return spherical_coslat_differential
        <
            CoordinateType,
            LatQuantity,
            LonQuantity,
            DistQuantity
        >(other);
}

//!constructs object from boost::geometry::model::point object
template
<
    typename CoordinateType = double,
    typename LatQuantity = bu::quantity<bu::si::plane_angle, CoordinateType>,
    typename LonQuantity = bu::quantity<bu::si::plane_angle, CoordinateType>,
    typename DistQuantity = bu::quantity<bu::si::dimensionless, CoordinateType>,
    std::size_t OtherDimensionCount,
    typename OtherCoordinateSystem,
    typename OtherCoordinateType
>
spherical_coslat_differential
<
    CoordinateType,
    LatQuantity,
    LonQuantity,
    DistQuantity
>
make_spherical_coslat_differential
(
    bg::model::point
    <
        OtherCoordinateType,
        OtherDimensionCount,
        OtherCoordinateSystem
    > const& pointObject
)
{
    return spherical_coslat_differential
        <
            CoordinateType,
            LatQuantity,
            LonQuantity,
            DistQuantity
        >(pointObject);
}

//!constructs object from any type of differential
template
<
    typename OtherDifferential
>
auto make_spherical_coslat_differential
(
    OtherDifferential const& other
)
{
    auto temp = make_spherical_differential(other);
    typedef decltype(temp) spherical_type;

    temp.set_dlon(temp.get_dlon() * cos(static_cast<bu::quantity
        <bu::si::plane_angle, typename spherical_type::type>>(temp.get_dlat()).value()));

    return spherical_coslat_differential
        <
            typename spherical_type::type,
            bu::quantity<bu::si::plane_angle, typename spherical_type::type>,
            bu::quantity<bu::si::plane_angle, typename spherical_type::type>,
            typename spherical_type::quantity3
        >(temp.get_differential());
}

//!constructs cartesian object from spherical_coslat_differential
//function placed in this file to avoid circular dependency of header files
template
<
    typename CoordinateType,
    template <typename Unit1, typename CoordinateType_> class LatQuantity,
    template <typename Unit2, typename CoordinateType_> class LonQuantity,
    template <typename Unit3, typename CoordinateType_> class DistQuantity,
    typename Unit1,
    typename Unit2,
    typename Unit3
>
auto
make_cartesian_differential
(
    spherical_coslat_differential
    <
        CoordinateType,
        LatQuantity<Unit1, CoordinateType>,
        LonQuantity<Unit2, CoordinateType>,
        DistQuantity<Unit3, CoordinateType>
    > const& other
)
{
    typedef spherical_coslat_differential
    <
        CoordinateType,
        LatQuantity<Unit1, CoordinateType>,
        LonQuantity<Unit2, CoordinateType>,
        DistQuantity<Unit3, CoordinateType>
    > spherical_type;
    bg::model::point<typename spherical_type::type, 3, bg::cs::spherical<radian>> temp;

    temp = other.get_differential();

    bg::set<1>(temp, bg::get<1>(temp) / cos(bg::get<0>(temp)));

    return cartesian_differential
    <
        typename spherical_type::type,
        typename spherical_type::quantity3,
        typename spherical_type::quantity3,
        typename spherical_type::quantity3
    >(temp);
}

}}} //namespace boost::astronomy::coordinate

#endif // !BOOST_ASTRONOMY_COORDINATE_SPHERICAL_COSLAT_DIFFERENTIAL_HPP


/*
Copyright 2017 InitialDLab

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.
*/
#include "aggragate_experiments.h"

#include <vector>
#include <random>

#include <boost/date_time/posix_time/posix_time.hpp>

#include "mongo_types.h"
#include "experiments/Data_Source_Information.h"
#include "experiments/experiment_utilities.h"
#include "rtree/rtree.h"

// this experiment is using chernoff bounds to calculate expected error
// delta is the expected probability of failure
// alpha is the expected variation between the actual value and aggregated value
// spread is the maximum - minimum value
// omega is 2 * alpha / spread  (this is used to quantify error under various spreads)
// r is the number of samples used for an aggregation.

void aggragate_experiments::perform_listed_experiments_GEO()
{
    // experiment settings for easy modification
    int n = 100; // the number of regions to test
    long seed = 1L; // the seed to use.  Change for actual experiment;
    float min_query_size = 0.01;
    float max_query_size = 0.30;
    std::uniform_real_distribution<float> cover_distribution(min_query_size, max_query_size);
    std::default_random_engine generator;
    generator.seed(seed);

    std::vector<double> delta_to_test{ 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80 };
    std::vector<double> omega_to_test{ 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80 };

    // open the data
    auto data_source = Data_Source_Information::get_data_information(Geolife);

    std::vector<mongo_types::box3d> regions_to_test;

    // randomly select several regions which we will test
    for (int i = 0; i < n; i++)
        regions_to_test.push_back(utilities::get_random_query_box(cover_distribution(generator), *data_source));

    // test the listed experiments
    perform_listed_experiments(data_source, regions_to_test, delta_to_test, omega_to_test);
}

void aggragate_experiments::perform_listed_experiments_OSM()
{
    // experiment settings for easy modification
    int n = 1000; // the number of regions to test
    long seed = 1L; // the seed to use.  Change for actual experiment;
    float min_query_size = 0.01;
    float max_query_size = 0.30;
    std::uniform_real_distribution<float> cover_distribution(min_query_size, max_query_size);
    std::default_random_engine generator;
    generator.seed(seed);

    std::vector<double> delta_to_test{ 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80 };
    std::vector<double> omega_to_test{ 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80 };

    // open the data
    auto data_source = Data_Source_Information::get_data_information(OSM_nodes);

    std::vector<mongo_types::box3d> regions_to_test;

    // randomly select several regions which we will test
    for (int i = 0; i < n; i++)
        regions_to_test.push_back(utilities::get_random_query_box(cover_distribution(generator), *data_source));

    // test the listed experiments
    perform_listed_experiments(data_source, regions_to_test, delta_to_test, omega_to_test);
}

void aggragate_experiments::perform_listed_experiments(std::shared_ptr<Data_Source_Information> data_source,
                                                       std::vector<mongo_types::box3d> &sample_ranges,
                                                       std::vector<double> &delta_values,
                                                       std::vector<double> &omega_values)
{
    // generate the exact queries for the experiments.
    std::vector<streaming_aggragate_query_time> exact_results;
    for (auto range : sample_ranges)
    {
        exact_results.emplace_back(range);
    }

    // scan through the data, gathering exact query data //
    // (also printing the progress of the scan, because it can take a very long time) //
    auto convert_function = data_source->get_convert_function();
    long elements_to_read = data_source->get_element_count();
    long elements_read = 0;
    // open input file
    std::fstream in_file(data_source->get_source_raw().c_str(), std::ios_base::in);
    std::string str;
    while (!in_file.eof())
    {
        std::getline(in_file, str);

        // empty string are considered null, so don't enter any data from a zero length string
        if (str.size() == 0)
            continue;
        mongo_types::sample_entry current_item(convert_function(str));

        for (int i = 0; i < exact_results.size(); i++)
            exact_results[i].submit(current_item);
        //for (auto exact_query : exact_results)
        //    exact_query.submit(current_item);

        ++elements_read;

        if (elements_read % 1000000 == 0)
            std::cout << ((0.0 + elements_read) / elements_to_read) << std::endl;
    }

    // open the sampling tree
    using rtree_t = rtree::rtree <
        mongo_types::entry,
        mongo_types::sample_entry,
        mongo_types::box3d,
        256,
        256
    >;
    std::unique_ptr<rtree_t> rtree_ptr;
    rtree_ptr.reset(new rtree_t(data_source->get_source_created()));

    // perform the experiments for each delta and omega values
    for (auto delta : delta_values) {
        for (auto omega : omega_values) {
            for (auto region : exact_results) {
                boost::posix_time::ptime start_time{ boost::posix_time::second_clock::local_time() };

                double spread = region.get_queryBox().max_corner().get<2>() - region.get_queryBox().min_corner().get<2>();
                double alpha = omega * spread / 2.0;
                int r = std::ceil(-(spread * spread / (2.0 * alpha)) * std::log(delta / 2.0));

                // setup query which we will be doing the aggregation over
                streaming_aggragate_query_time sampled_query(region.get_queryBox());
                auto cursor = rtree_ptr->sample_query(sampled_query.get_queryBox());

                 std::vector<mongo_types::sample_entry> samples;
                samples.reserve(r);

                // fulfill query and get submitted aggregation
                int count = cursor.estimate_count();
                // std::cerr << (1.0 * r / count) << std::endl;
                cursor.get_samples(r, std::back_inserter(samples));
                for (auto element : samples)
                    sampled_query.submit(element);

                write_experiment_entry(data_source->get_source_created(),
                    start_time,
                    delta,
                    spread,
                    r,
                    omega,
                    alpha,
                    region,
                    sampled_query);
            }
        }
    }

}

void aggragate_experiments::open_outputfile()
{
    m_output.reset(new std::fstream{ m_outputFilename, std::fstream::out | std::fstream::app });

    if (!m_output->tellp())
    {
        write_header();
    }
}

void aggragate_experiments::write_header()
{
    *m_output << "input file" << m_dataSeperator
        << "Start time"       << m_dataSeperator
        << "query box min"    << m_dataSeperator
        << "query box max"    << m_dataSeperator
        << "actual min"       << m_dataSeperator
        << "actual max"       << m_dataSeperator
        << "actual spread"    << m_dataSeperator
        << "delta"            << m_dataSeperator
        << "alpha"            << m_dataSeperator
        << "spread"           << m_dataSeperator
        << "r"                << m_dataSeperator
        << "omega"            << m_dataSeperator
        << "actual mean"      << m_dataSeperator
        << "est mean"         << m_dataSeperator
        << "within alpha"
        << std::endl;
}

void aggragate_experiments::write_experiment_entry(std::string inputFile,
                                                   boost::posix_time::ptime start_time,
                                                   double delta,
                                                   double spread,
                                                   int r,
                                                   double omega,
                                                   double alpha,
                                                   streaming_aggragate_query_time actual,
                                                   streaming_aggragate_query_time sampled)
{
    *m_output << start_time << m_dataSeperator
        << start_time << m_dataSeperator
         << '(' << actual.get_queryBox().min_corner().get<0>() << ',' << actual.get_queryBox().min_corner().get<1>() << ',' << actual.get_queryBox().min_corner().get<2>() << ')' << m_dataSeperator
         << '(' << actual.get_queryBox().max_corner().get<0>() << ',' << actual.get_queryBox().max_corner().get<1>() << ',' << actual.get_queryBox().max_corner().get<2>() << ')' << m_dataSeperator
        << actual.get_min()  << m_dataSeperator
        << actual.get_max()  << m_dataSeperator
        << (actual.get_max() - actual.get_min()) << m_dataSeperator
        << delta             << m_dataSeperator
        << alpha             << m_dataSeperator
        << spread            << m_dataSeperator
        << r                 << m_dataSeperator
        << omega             << m_dataSeperator
        << std::setprecision(10) << actual.get_avg()  << m_dataSeperator
        << std::setprecision(10) << sampled.get_avg() << m_dataSeperator
        << ((std::abs(actual.get_avg() - sampled.get_avg()) < alpha) ? "1" : "0")
        << std::endl;

}


//
//  Copyright (c) 2000-2002
//  Joerg Walter, Mathias Koch
//
//  Permission to use, copy, modify, distribute and sell this software
//  and its documentation for any purpose is hereby granted without fee,
//  provided that the above copyright notice appear in all copies and
//  that both that copyright notice and this permission notice appear
//  in supporting documentation.  The authors make no representations
//  about the suitability of this software for any purpose.
//  It is provided "as is" without express or implied warranty.
//
//  The authors gratefully acknowledge the support of
//  GeNeSys mbH & Co. KG in producing this work.
//

#ifndef _BOOST_UBLAS_LU_
#define _BOOST_UBLAS_LU_

#include <boost/numeric/ublas/operation.hpp>
#include <boost/numeric/ublas/vector_proxy.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include <boost/numeric/ublas/vector.hpp>
#include <boost/numeric/ublas/triangular.hpp>

// LU factorizations in the spirit of LAPACK and Golub & van Loan

namespace boost { namespace numeric { namespace ublas {

    template<class T = std::size_t, class A = unbounded_array<T> >
    class permutation_matrix:
        public vector<T, A> {
    public:
        typedef vector<T, A> vector_type;
        typedef typename vector_type::size_type size_type;

        // Construction and destruction
        BOOST_UBLAS_INLINE
        permutation_matrix (size_type size):
            vector<T, A> (size) {
            for (size_type i = 0; i < size; ++ i)
                (*this) (i) = i;
        }
        BOOST_UBLAS_INLINE
        ~permutation_matrix () {}

        // Assignment
        BOOST_UBLAS_INLINE
        permutation_matrix &operator = (const permutation_matrix &m) {
            vector_type::operator = (m);
            return *this;
        }
    };

    template<class PM, class MV>
    BOOST_UBLAS_INLINE
    void swap_rows (const PM &pm, MV &mv, vector_tag) {
        typedef typename PM::size_type size_type;
        typedef typename MV::value_type value_type;

        size_type size = pm.size ();
        for (size_type i = 0; i < size; ++ i) {
            if (i != pm (i))
                std::swap (mv (i), mv (pm (i)));
        }
    }
    template<class PM, class MV>
    BOOST_UBLAS_INLINE
    void swap_rows (const PM &pm, MV &mv, matrix_tag) {
        typedef typename PM::size_type size_type;
        typedef typename MV::value_type value_type;

        size_type size = pm.size ();
        for (size_type i = 0; i < size; ++ i) {
            if (i != pm (i))
                row (mv, i).swap (row (mv, pm (i)));
        }
    }
    // Dispatcher
    template<class PM, class MV>
    BOOST_UBLAS_INLINE
    void swap_rows (const PM &pm, MV &mv) {
        swap_rows (pm, mv, typename MV::type_category ());
    }

    // LU factorization without pivoting
    template<class M>
    typename M::size_type lu_factorize (M &m) {
        typedef M matrix_type;
        typedef typename M::size_type size_type;
        typedef typename M::value_type value_type;

#if BOOST_UBLAS_TYPE_CHECK
        matrix_type cm (m);
#endif
        int singular = 0;
        size_type size1 = m.size1 ();
        size_type size2 = m.size2 ();
        size_type size = (std::min) (size1, size2);
        for (size_type i = 0; i < size; ++ i) {
            matrix_column<M> mci (column (m, i));
            matrix_row<M> mri (row (m, i));
            if (m (i, i) != value_type/*zero*/()) {
                project (mci, range (i + 1, size1)) *= value_type (1) / m (i, i);
            } else if (singular == 0) {
                singular = i + 1;
            }
            project (m, range (i + 1, size1), range (i + 1, size2)).minus_assign (
                outer_prod (project (mci, range (i + 1, size1)),
                            project (mri, range (i + 1, size2))));
        }
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (singular != 0 ||
                           detail::expression_type_check (prod (triangular_adaptor<matrix_type, unit_lower> (m),
                                                                triangular_adaptor<matrix_type, upper> (m)),
                                                          cm), internal_logic ());
#endif
        return singular;
    }

    // LU factorization with partial pivoting
    template<class M, class PM>
    typename M::size_type lu_factorize (M &m, PM &pm) {
        typedef M matrix_type;
        typedef typename M::size_type size_type;
        typedef typename M::value_type value_type;

#if BOOST_UBLAS_TYPE_CHECK
        matrix_type cm (m);
#endif
        int singular = 0;
        size_type size1 = m.size1 ();
        size_type size2 = m.size2 ();
        size_type size = (std::min) (size1, size2);
        for (size_type i = 0; i < size; ++ i) {
            matrix_column<M> mci (column (m, i));
            matrix_row<M> mri (row (m, i));
            size_type i_norm_inf = i + index_norm_inf (project (mci, range (i, size1)));
            BOOST_UBLAS_CHECK (i_norm_inf < size1, external_logic ());
            if (m (i_norm_inf, i) != value_type/*zero*/()) {
                if (i_norm_inf != i) {
                    pm (i) = i_norm_inf;
                    row (m, i_norm_inf).swap (mri);
                } else {
                    BOOST_UBLAS_CHECK (pm (i) == i_norm_inf, external_logic ());
                }
                project (mci, range (i + 1, size1)) *= value_type (1) / m (i, i);
            } else if (singular == 0) {
                singular = i + 1;
            }
            project (m, range (i + 1, size1), range (i + 1, size2)).minus_assign (
                outer_prod (project (mci, range (i + 1, size1)),
                            project (mri, range (i + 1, size2))));
        }
#if BOOST_UBLAS_TYPE_CHECK
        swap_rows (pm, cm);
        BOOST_UBLAS_CHECK (singular != 0 ||
                           detail::expression_type_check (prod (triangular_adaptor<matrix_type, unit_lower> (m),
                                                                triangular_adaptor<matrix_type, upper> (m)), cm), internal_logic ());
#endif
        return singular;
    }

    template<class M, class PM>
    typename M::size_type axpy_lu_factorize (M &m, PM &pm) {
        typedef M matrix_type;
        typedef typename M::size_type size_type;
        typedef typename M::value_type value_type;
        typedef vector<value_type> vector_type;

#if BOOST_UBLAS_TYPE_CHECK
        matrix_type cm (m);
#endif
        int singular = 0;
        size_type size1 = m.size1 ();
        size_type size2 = m.size2 ();
        size_type size = (std::min) (size1, size2);
#ifndef BOOST_UBLAS_LU_WITH_INPLACE_SOLVE
        matrix_type mr (m);
        mr.assign (zero_matrix<value_type> (size1, size2));
        vector_type v (size1);
        for (size_type i = 0; i < size; ++ i) {
            matrix_range<matrix_type> lrr (project (mr, range (0, i), range (0, i)));
            vector_range<matrix_column<matrix_type> > urr (project (column (mr, i), range (0, i)));
            urr.assign (solve (lrr, project (column (m, i), range (0, i)), unit_lower_tag ()));
            project (v, range (i, size1)).assign (
                project (column (m, i), range (i, size1)) -
                axpy_prod<vector_type> (project (mr, range (i, size1), range (0, i)), urr));
            size_type i_norm_inf = i + index_norm_inf (project (v, range (i, size1)));
            BOOST_UBLAS_CHECK (i_norm_inf < size1, external_logic ());
            if (v (i_norm_inf) != value_type/*zero*/()) {
                if (i_norm_inf != i) {
                    pm (i) = i_norm_inf;
                    std::swap (v (i_norm_inf), v (i));
                    project (row (m, i_norm_inf), range (i + 1, size2)).swap (project (row (m, i), range (i + 1, size2)));
                } else {
                    BOOST_UBLAS_CHECK (pm (i) == i_norm_inf, external_logic ());
                }
                project (column (mr, i), range (i + 1, size1)).assign (
                    project (v, range (i + 1, size1)) / v (i));
                if (i_norm_inf != i) {
                    project (row (mr, i_norm_inf), range (0, i)).swap (project (row (mr, i), range (0, i)));
                }
            } else if (singular == 0) {
                singular = i + 1;
            }
            mr (i, i) = v (i);
        }
        m.assign (mr);
#else
        matrix_type lr (m);
        matrix_type ur (m);
        lr.assign (identity_matrix<value_type> (size1, size2));
        ur.assign (zero_matrix<value_type> (size1, size2));
        vector_type v (size1);
        for (size_type i = 0; i < size; ++ i) {
            matrix_range<matrix_type> lrr (project (lr, range (0, i), range (0, i)));
            vector_range<matrix_column<matrix_type> > urr (project (column (ur, i), range (0, i)));
            urr.assign (project (column (m, i), range (0, i)));
            inplace_solve (lrr, urr, unit_lower_tag ());
            project (v, range (i, size1)).assign (
                project (column (m, i), range (i, size1)) -
                axpy_prod<vector_type> (project (lr, range (i, size1), range (0, i)), urr));
            size_type i_norm_inf = i + index_norm_inf (project (v, range (i, size1)));
            BOOST_UBLAS_CHECK (i_norm_inf < size1, external_logic ());
            if (v (i_norm_inf) != value_type/*zero*/()) {
                if (i_norm_inf != i) {
                    pm (i) = i_norm_inf;
                    std::swap (v (i_norm_inf), v (i));
                    project (row (m, i_norm_inf), range (i + 1, size2)).swap (project (row (m, i), range (i + 1, size2)));
                } else {
                    BOOST_UBLAS_CHECK (pm (i) == i_norm_inf, external_logic ());
                }
                project (column (lr, i), range (i + 1, size1)).assign (
                    project (v, range (i + 1, size1)) / v (i));
                if (i_norm_inf != i) {
                    project (row (lr, i_norm_inf), range (0, i)).swap (project (row (lr, i), range (0, i)));
                }
            } else if (singular == 0) {
                singular = i + 1;
            }
            ur (i, i) = v (i);
        }
        m.assign (triangular_adaptor<matrix_type, strict_lower> (lr) +
                  triangular_adaptor<matrix_type, upper> (ur));
#endif
#if BOOST_UBLAS_TYPE_CHECK
        swap_rows (pm, cm);
        BOOST_UBLAS_CHECK (singular != 0 ||
                           detail::expression_type_check (prod (triangular_adaptor<matrix_type, unit_lower> (m),
                                                                triangular_adaptor<matrix_type, upper> (m)), cm), internal_logic ());
#endif
        return singular;
    }

    // LU substitution
    template<class M, class E>
    void lu_substitute (const M &m, vector_expression<E> &e) {
        typedef const M const_matrix_type;
        typedef vector<typename E::value_type> vector_type;

#if BOOST_UBLAS_TYPE_CHECK
        vector_type cv1 (e);
#endif
        inplace_solve (m, e, unit_lower_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (triangular_adaptor<const_matrix_type, unit_lower> (m), e), cv1), internal_logic ());
        vector_type cv2 (e);
#endif
        inplace_solve (m, e, upper_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (triangular_adaptor<const_matrix_type, upper> (m), e), cv2), internal_logic ());
#endif
    }
    template<class M, class E>
    void lu_substitute (const M &m, matrix_expression<E> &e) {
        typedef const M const_matrix_type;
        typedef matrix<typename E::value_type> matrix_type;

#if BOOST_UBLAS_TYPE_CHECK
        matrix_type cm1 (e);
#endif
        inplace_solve (m, e, unit_lower_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (triangular_adaptor<const_matrix_type, unit_lower> (m), e), cm1), internal_logic ());
        matrix_type cm2 (e);
#endif
        inplace_solve (m, e, upper_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (triangular_adaptor<const_matrix_type, upper> (m), e), cm2), internal_logic ());
#endif
    }
    template<class M, class PMT, class PMA, class MV>
    void lu_substitute (const M &m, const permutation_matrix<PMT, PMA> &pm, MV &mv) {
        swap_rows (pm, mv);
        lu_substitute (m, mv);
    }
    template<class E, class M>
    void lu_substitute (vector_expression<E> &e, const M &m) {
        typedef const M const_matrix_type;
        typedef vector<typename E::value_type> vector_type;

#if BOOST_UBLAS_TYPE_CHECK
        vector_type cv1 (e);
#endif
        inplace_solve (e, m, upper_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (e, triangular_adaptor<const_matrix_type, upper> (m)), cv1), internal_logic ());
        vector_type cv2 (e);
#endif
        inplace_solve (e, m, unit_lower_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (e, triangular_adaptor<const_matrix_type, unit_lower> (m)), cv2), internal_logic ());
#endif
    }
    template<class E, class M>
    void lu_substitute (matrix_expression<E> &e, const M &m) {
        typedef const M const_matrix_type;
        typedef matrix<typename E::value_type> matrix_type;

#if BOOST_UBLAS_TYPE_CHECK
        matrix_type cm1 (e);
#endif
        inplace_solve (e, m, upper_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (e, triangular_adaptor<const_matrix_type, upper> (m)), cm1), internal_logic ());
        matrix_type cm2 (e);
#endif
        inplace_solve (e, m, unit_lower_tag ());
#if BOOST_UBLAS_TYPE_CHECK
        BOOST_UBLAS_CHECK (detail::expression_type_check (prod (e, triangular_adaptor<const_matrix_type, unit_lower> (m)), cm2), internal_logic ());
#endif
    }
    template<class MV, class M, class PMT, class PMA>
    void lu_substitute (MV &mv, const M &m, const permutation_matrix<PMT, PMA> &pm) {
        swap_rows (pm, mv);
        lu_substitute (mv, m);
    }

}}}

#endif


//
// $Id: peakpickerqtof.hpp 6337 2014-06-06 20:04:10Z witek96 $
//
//
// Original author: Witold Wolski <wewolski@gmail.com>
//
// Copyright : ETH Zurich
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

#ifndef PEAKPICKER_H
#define PEAKPICKER_H

#include <boost/math/special_functions.hpp>
#include "pwiz/utility/findmf/base/resample/convert2dense.hpp"
#include "pwiz/utility/findmf/base/filter/filter.hpp"
#include "pwiz/utility/findmf/base/ms/simplepicker.hpp"
#include "pwiz/utility/findmf/base/filter/gaussfilter.hpp"
#include "pwiz/utility/findmf/base/base/interpolate.hpp"
#include "pwiz/utility/findmf/base/resample/determinebinwidth.hpp"
#include "pwiz/utility/findmf/base/base/copyif.hpp"

namespace ralab{
  namespace base{
    namespace ms{

      /// resamples spectrum, apply smoothing,
      /// determines zero crossings,
      /// integrates peaks.

      template<typename TReal>
      struct SimplePeakArea{
        TReal integwith_;

        SimplePeakArea(TReal integwith):integwith_(integwith){}

        /// intagrates the peak intesnities
        template<typename Tzerocross, typename Tintensity, typename Tout>
        void operator()( Tzerocross beginZ,
                         Tzerocross endZ,
                         Tintensity intensity,
                         Tintensity resmpled,
                         Tout area)const
        {
          typedef typename std::iterator_traits<Tout>::value_type AreaType;
          for( ; beginZ != endZ ; ++beginZ , ++area )
          {
            size_t idx = static_cast<size_t>( *beginZ );
            size_t start = static_cast<size_t>( boost::math::round( idx - integwith_ ) );
            size_t end = static_cast<size_t>( boost::math::round( idx + integwith_ + 2.) );
            AreaType aread = 0.;
            for( ; start != end ; ++start )
            {
              aread += *(resmpled + start);
            }
            *area = aread;
          }
        }
      };

      /// extends peak to the left and to the right to the next local minimum or a predefined threshol
      /// or a maximum allowed extension.
      template<typename TReal>
      struct LocalMinPeakArea{
        typedef TReal value_type;
        TReal integwith_;
        TReal threshold_;

        LocalMinPeakArea(TReal integwith,//!<maximal allowed peak width +- in pixel
                         TReal threshold = .1// minimum intensity
            ):integwith_(integwith),threshold_(threshold){}

        /// intagrates the peak intesnities
        template< typename Tzerocross, typename Tintensity, typename Tout >
        void operator()( Tzerocross beginZ,
                         Tzerocross endZ,
                         Tintensity intensity,
                         Tintensity resampled,
                         Tout area) const
        {
          typedef typename std::iterator_traits<Tout>::value_type AreaType;
          for( ; beginZ != endZ ; ++beginZ , ++area )
          {
            size_t idx = static_cast<size_t>( *beginZ );
            size_t start = static_cast<size_t>( boost::math::round( idx - integwith_ ) );
            size_t end = static_cast<size_t>( boost::math::round( idx + integwith_ + 2) );

            Tintensity st = intensity + start;
            Tintensity en = intensity + end;
            Tintensity center = intensity + idx;
            std::ptrdiff_t x1 = std::distance(st, center);
            std::ptrdiff_t y1 = std::distance(center,en);
            mextend(st , en , center);
            std::ptrdiff_t x2 = std::distance(intensity,st);
            std::ptrdiff_t y2 = std::distance(intensity,en);
            std::ptrdiff_t pp = std::distance(st,en);
            AreaType areav = std::accumulate(resampled+x2,resampled+y2,0.);
            *area = areav;
          }
        }

      private:
        ///exend peak to left and rigth
        template<typename TInt >
        void mextend( TInt &start, TInt &end, TInt idx) const
        {
          typedef typename std::iterator_traits<TInt>::value_type Intensitytype;
          //
          for(TInt intens = idx ; intens >= start;  --intens){
            Intensitytype val1 = *intens;
            Intensitytype val2 = *(intens-1);
            if(val1 > threshold_){
              if(val1 < val2 ){
                start = intens;
                break;
              }
            }
            else{
              start = intens;
              break;
            }
          }

          for(TInt intens = idx ; intens <= end;  ++intens){
            Intensitytype val1 = *intens;
            Intensitytype val2 = *(intens+1);
            if(val1 > threshold_){
              if(val1 < val2 ){
                end = intens;
                break;
              }
            }
            else{
              end = intens;
              break;
            }
          }
        }
      };

      /// resamples spectrum, apply smoothing,
      /// determines zero crossings,
      /// integrates peaks.
      template<typename TReal, template <typename B> class TIntegrator >
      struct PeakPicker{
        typedef TReal value_type;
        typedef TIntegrator<value_type> PeakIntegrator;

        TReal resolution_;
        ralab::base::resample::Convert2Dense c2d_; // resamples spectrum
        std::vector<TReal> resampledmz_, resampledintensity_; // keeps result of convert to dense
        std::vector<TReal> filter_, zerocross_, smoothedintensity_; // working variables
        std::vector<TReal> peakmass_, peakarea_; //results
        TReal smoothwith_;
        TReal integrationWidth_;
        ralab::base::ms::SimplePicker<TReal> simplepicker_;
        ralab::base::resample::SamplingWith sw_;
        PeakIntegrator integrator_;
        TReal intensitythreshold_;
        bool area_;
        uint32_t maxnumbersofpeaks_;

        PeakPicker(TReal resolution, //!< instrument resolution
                   std::pair<TReal, TReal> & massrange, //!< mass range of spectrum
                   TReal width = 2., //!< smooth width
                   TReal intwidth = 2., //!< integration width used for area compuation
                   TReal intensitythreshold = 10., // intensity threshold
                   bool area = true,//!< compute area or height? default - height.
                   uint32_t maxnumberofpeaks = 0, //!< maximum of peaks returned by picker
                   double c2d = 1e-5  //!< instrument resampling with small default dissables automatic determination
            ): resolution_(resolution),c2d_( c2d ) ,smoothwith_(width),
          integrationWidth_(intwidth),sw_(),integrator_(integrationWidth_),
          intensitythreshold_(intensitythreshold),area_(area),maxnumbersofpeaks_(maxnumberofpeaks)
        {
          c2d_.defBreak(massrange,ralab::base::resample::resolution2ppm(resolution));
          c2d_.getMids(resampledmz_);
          ralab::base::filter::getGaussianFilterQuantile(filter_,width);
        }

        template<typename Tmass, typename Tintensity>
        void operator()(Tmass begmz, Tmass endmz, Tintensity begint )
        {
          typename std::iterator_traits<Tintensity>::value_type minint = *std::upper_bound(begint,begint+std::distance(begmz,endmz),0.1);

          //determine sampling with
          double a = sw_(begmz,endmz);
          //resmpale the spectrum
          c2d_.am_ = a;
          c2d_.convert2dense(begmz,endmz, begint, resampledintensity_);

          //smooth the resampled spectrum
          ralab::base::filter::filter(resampledintensity_ , filter_ , smoothedintensity_ , true);
          //determine zero crossings
          zerocross_.resize( smoothedintensity_.size()/2 );
          size_t nrzerocross = simplepicker_( smoothedintensity_.begin( ) , smoothedintensity_.end() , zerocross_.begin(), zerocross_.size());

          peakmass_.resize(nrzerocross);
          //determine mass of zerocrossing
          ralab::base::base::interpolate_linear( resampledmz_.begin() , resampledmz_.end() ,
                                                 zerocross_.begin(),  zerocross_.begin()+nrzerocross ,
                                                 peakmass_.begin());

          //determine peak area
          if(area_){
            peakarea_.resize(nrzerocross);
            integrator_( zerocross_.begin(), zerocross_.begin() + nrzerocross ,
                         smoothedintensity_.begin(),resampledintensity_.begin(), peakarea_.begin() );
          }else{
            //determine intensity
            peakarea_.resize(nrzerocross);
            ralab::base::base::interpolate_cubic( smoothedintensity_.begin() , smoothedintensity_.end() ,
                                                  zerocross_.begin(),  zerocross_.begin()+nrzerocross ,
                                                  peakarea_.begin());
          }

          TReal threshold = static_cast<TReal>(minint) * intensitythreshold_;

          if(maxnumbersofpeaks_ > 0){
            double threshmax = getNToppeaks();
            if(threshmax > threshold)
              threshold = threshmax;
          }

          if(threshold > 0.01){
            filter(threshold);
          }
        }

        /// get min instensity of peak to qualify for max-intensity;
        TReal getNToppeaks(){
          TReal intthres  = 0.;
          if(maxnumbersofpeaks_ < peakarea_.size())
          {
            std::vector<TReal> tmparea( peakarea_.begin() , peakarea_.end() );
            std::nth_element(tmparea.begin(),tmparea.end() - maxnumbersofpeaks_ , tmparea.end());
            intthres = *(tmparea.end() - maxnumbersofpeaks_);
          }
          return intthres;
        }

        /// clean the masses using the threshold
        void filter(TReal threshold){
          typename std::vector<TReal>::iterator a = ralab::base::utils::copy_if(peakarea_.begin(),peakarea_.end(),peakmass_.begin(),
                                                                                peakmass_.begin(),boost::bind(std::greater<TReal>(),_1,threshold));
          peakmass_.resize(std::distance(peakmass_.begin(),a));
          typename std::vector<TReal>::iterator b = ralab::base::utils::copy_if(peakarea_.begin(),peakarea_.end(),
                                                                                peakarea_.begin(),boost::bind(std::greater<TReal>(),_1,threshold));
          peakarea_.resize(std::distance(peakarea_.begin(),b));
          //int x = 1;
        }

        const std::vector<TReal> & getPeakMass(){
          return peakmass_;
        }

        const std::vector<TReal> & getPeakArea(){
          return peakarea_;
        }

        const std::vector<TReal> & getResampledMZ(){
          return resampledmz_;
        }

        const std::vector<TReal> & getResampledIntensity(){
          return resampledintensity_;
        }

        const std::vector<TReal> & getSmoothedIntensity(){
          return smoothedintensity_;
        }
      };
    }//ms
  }//base
}//ralab

#endif // PEAKPICKER_H


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2016 Francisca Gil Ureta <gilureta@cs.nyu.edu>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.

#include "edge_topology.h"
#include "sort_vectors_ccw.h"
#include "streamlines.h"
#include "per_face_normals.h"
#include "polyvector_field_matchings.h"
#include "segment_segment_intersect.h"
#include "triangle_triangle_adjacency.h"
#include "barycenter.h"
#include "slice.h"

#include <Eigen/Geometry>

IGL_INLINE void igl::streamlines_init(
                                      const Eigen::MatrixXd V,
                                      const Eigen::MatrixXi F,
                                      const Eigen::MatrixXd &temp_field,
                                      const bool treat_as_symmetric,
                                      StreamlineData &data,
                                      StreamlineState &state,
                                      double percentage
                                      ){
  using namespace Eigen;
  using namespace std;

  igl::edge_topology(V, F, data.E, data.F2E, data.E2F);
  igl::triangle_triangle_adjacency(F, data.TT);

  // prepare vector field
  // --------------------------
  int half_degree = temp_field.cols() / 3;
  int degree = treat_as_symmetric ? half_degree * 2 : half_degree;
  data.degree = degree;

  Eigen::MatrixXd FN;
  Eigen::VectorXi order;
  Eigen::RowVectorXd sorted;

  igl::per_face_normals(V, F, FN);
  data.field.setZero(F.rows(), degree * 3);
  for (unsigned i = 0; i < F.rows(); ++i){
    const Eigen::RowVectorXd &n = FN.row(i);
    Eigen::RowVectorXd temp(1, degree * 3);
    if (treat_as_symmetric)
      temp << temp_field.row(i), -temp_field.row(i);
    else
      temp = temp_field.row(i);
    igl::sort_vectors_ccw(temp, n, order, sorted);

    // project vectors to tangent plane
    for (int j = 0; j < degree; ++j)
    {
      Eigen::RowVector3d pd = sorted.segment(j * 3, 3);
      pd = (pd - (n.dot(pd)) * n).normalized();
      data.field.block(i, j * 3, 1, 3) = pd;
    }
  }
  Eigen::VectorXd curl;
  igl::polyvector_field_matchings(data.field, V, F, false, treat_as_symmetric, data.match_ab, data.match_ba, curl);

  // create seeds for tracing
  // --------------------------
  Eigen::VectorXi samples;
  int nsamples;

  nsamples = percentage * F.rows();
  Eigen::VectorXd r;
  r.setRandom(nsamples, 1);
  r = (1 + r.array()) / 2.;
  samples = (r.array() * F.rows()).cast<int>();
  data.nsample = nsamples;

  Eigen::MatrixXd BC, BC_sample;
  igl::barycenter(V, F, BC);
  igl::slice(BC, samples, 1, BC_sample);

  // initialize state for tracing vector field

  state.start_point = BC_sample.replicate(degree,1);
  state.end_point = state.start_point;

  state.current_face = samples.replicate(1, degree);

  state.current_direction.setZero(nsamples, degree);
  for (int i = 0; i < nsamples; ++i)
    for (int j = 0; j < degree; ++j)
      state.current_direction(i, j) = j;

}

IGL_INLINE void igl::streamlines_next(
                                      const Eigen::MatrixXd V,
                                      const Eigen::MatrixXi F,
                                      const StreamlineData & data,
                                      StreamlineState & state
                                      ){
  using namespace Eigen;
  using namespace std;

  int degree = data.degree;
  int nsample = data.nsample;

  state.start_point = state.end_point;

  for (int i = 0; i < degree; ++i)
  {
    for (int j = 0; j < nsample; ++j)
    {
      int f0 = state.current_face(j,i);
      if (f0 == -1) // reach boundary
        continue;
      int m0 = state.current_direction(j, i);

      // the starting point of the vector
      const Eigen::RowVector3d &p = state.start_point.row(j + nsample * i);
      // the direction where we are trying to go
      const Eigen::RowVector3d &r = data.field.block(f0, 3 * m0, 1, 3);

      // new state,
      int f1, m1;

      for (int k = 0; k < 3; ++k)
      {
        f1 = data.TT(f0, k);

        // edge vertices
        const Eigen::RowVector3d &q = V.row(F(f0, k));
        const Eigen::RowVector3d &qs = V.row(F(f0, (k + 1) % 3));
        // edge direction
        Eigen::RowVector3d s = qs - q;

        double u;
        double t;
        if (igl::segments_intersect(p, r, q, s, t, u))
        {
          // point on next face
          state.end_point.row(j + nsample * i) = p + t * r;
          state.current_face(j,i) = f1;

          // matching direction on next face
          int e1 = data.F2E(f0, k);
          if (data.E2F(e1, 0) == f0)
            m1 = data.match_ab(e1, m0);
          else
            m1 = data.match_ba(e1, m0);

          state.current_direction(j, i) = m1;
          break;
        }

      }

    }
  }
}


// This file is part of LatNet Builder.
//
// Copyright (C) 2012-2021  The LatNet Builder author's, supervised by Pierre L'Ecuyer, Universite de Montreal.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "netbuilder/NetConstructionTraits.h"

#include "latbuilder/SeqCombiner.h"
#include "latbuilder/Traversal.h"

#include <boost/dynamic_bitset.hpp>
#include <vector>
#include <list>

#include "latbuilder/TextStream.h"

namespace NetBuilder {

    const std::string NetConstructionTraits<NetConstruction::SOBOL>::name = "Sobol";

    typedef typename NetConstructionTraits<NetConstruction::SOBOL>::GenValue GenValue;

    typedef typename NetConstructionTraits<NetConstruction::SOBOL>::SizeParameter SizeParameter;

    bool NetConstructionTraits<NetConstruction::SOBOL>::checkGenValue(const GenValue& genValue, const SizeParameter& sizeParameter)
    {
        auto dimension = genValue.first;

        if (dimension==0)
        {
            return (genValue.second.size()==1 && genValue.second.front()==0);
        }

        unsigned int degree = nthPrimitivePolynomialDegree(dimension);

        if (genValue.second.size() != degree){
            return false;
        }

        for(unsigned int j = 0; j < degree; ++j)
        {
              if (!(genValue.second[j] % 2 == 1)){ //each direction number is odd
                  return false;
              }
              if (!(genValue.second[j]< (unsigned int) (2<<j))){ // each direction number is small enough
                  return false;
              }
        }
        return true;
    }

    unsigned int NetConstructionTraits<NetConstruction::SOBOL>::nRows(const SizeParameter& param) { return (unsigned int) param; }

    unsigned int NetConstructionTraits<NetConstruction::SOBOL>::nCols(const SizeParameter& param) { return (unsigned int) param; }

    static const std::array<unsigned int,21200> degrees =
    {{
        #include "../data/primitive_polynomials_degrees.csv"
    }};

    static const std::array<unsigned long,21200> representations =
    {{
        #include "../data/primitive_polynomials_representations.csv"
    }};

    NetConstructionTraits<NetConstruction::SOBOL>::PrimitivePolynomial  NetConstructionTraits<NetConstruction::SOBOL>::nthPrimitivePolynomial(Dimension n)
    {
        // primitive polynomials are hard-coded because their computation is really complex.
        if (n>0 && n <= 21200)
        return std::pair<unsigned int,uInteger>(degrees[n-1],representations[n-1]);
        else{
            return std::pair<unsigned int,uInteger>(0,0);
        }
    }

    unsigned int  NetConstructionTraits<NetConstruction::SOBOL>::nthPrimitivePolynomialDegree(Dimension n)
    {
        // primitive polynomials are hard-coded because their computation is really complex.
        if (n>0 && n <= 21200)
        return degrees[n-1];
        else{
            return 0;
        }
    }

    void makeIteration(GeneratingMatrix& mat, std::list<boost::dynamic_bitset<>>& reg, const boost::dynamic_bitset<>& mask, unsigned int k)
    {
        assert(k <= mat.nCols() && k<= mat.nRows());
        boost::dynamic_bitset<> newDirNum = reg.front();
        newDirNum.resize(k);
        assert(reg.size()==mask.size());
        unsigned j = 0;
        for(boost::dynamic_bitset<> tmp : reg)
        {
            if (mask[j])
            {
                tmp.resize(k);
                tmp = tmp << (reg.size()-j);
                newDirNum ^= tmp;
            }
            ++j;
        }
        reg.pop_front();
        for(unsigned int i = 0; i < k; ++i)
        {
            mat(i, k-1) = newDirNum[k-i-1];
        }
        reg.push_back(std::move(newDirNum));
    }

    GeneratingMatrix*  NetConstructionTraits<NetConstruction::SOBOL>::createGeneratingMatrix(const GenValue& genValue, const SizeParameter& sizeParam, const Dimension& dimension_j, const unsigned int nRows)
    {
        unsigned int m  = nCols(sizeParam);
        unsigned int finalnRows = (nRows == 0)? m : nRows;
        Dimension coord = genValue.first;

        if (coord==0) // special case for the first dimension
        {
            GeneratingMatrix* tmp = new GeneratingMatrix(m,m);
            for(unsigned int k = 0; k<m; ++k){
            (*tmp)(k,k) = 1; // start with identity
            }
            return tmp;
        }

        // compute the vector defining the linear recurrence on the columns of the matrix
        PrimitivePolynomial p = nthPrimitivePolynomial(coord);
        auto degree = p.first;
        auto poly_rep = p.second;
        boost::dynamic_bitset<> mask(degree,(poly_rep << 1) + 1);
        unsigned int matrixSize = std::max(degree,m);
        GeneratingMatrix* tmp = new GeneratingMatrix(matrixSize, matrixSize);
        std::list<boost::dynamic_bitset<>> reg;
        unsigned int k = 1;
        for(auto dirNum : genValue.second)
        {
            reg.push_back(boost::dynamic_bitset<>(k,dirNum));
            for(unsigned int i = 0; i < k; ++i)
            {
                (*tmp)(i,k-1) = reg.back()[k-i-1];
            }
            ++k;
        }
        while (k<=matrixSize)
        {
            makeIteration(*tmp, reg, mask, k);
            ++k;
        }
        tmp->resize(finalnRows, m);
        return tmp;
    }

   NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::GenValueSpaceCoordSeq(Dimension coord):
    m_coord(coord),
    m_underlyingSeq(underlyingSeqs(coord))
    {};

    NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator::const_iterator(const GenValueSpaceCoordSeq& seq):
        m_coord(seq.coord()),
        m_underlyingIterator(seq.underlyingSeq().begin()),
        m_value(GenValue(m_coord, *m_underlyingIterator))
    {};

    NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator::const_iterator(const GenValueSpaceCoordSeq& seq, end_tag):
        m_coord(seq.coord()),
        m_underlyingIterator(seq.underlyingSeq().end())
    {};

    bool NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator::equal(const const_iterator& other) const
    {
        return m_underlyingIterator == other.m_underlyingIterator;
    }

    const NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator::value_type& NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator::dereference() const
    {
        return m_value;
    }

    void NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator::increment()
    {
        if (m_underlyingIterator != m_underlyingIterator.seq().end())
        {
            ++m_underlyingIterator;
            m_value = GenValue(m_coord, *m_underlyingIterator);
        }
    }

    NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::begin() const
    {
        return const_iterator(*this);
    }

    NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::const_iterator NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::end() const
    {
        return const_iterator(*this, typename const_iterator::end_tag{} );
    }

    Dimension NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::coord() const
    {
        return m_coord;
    }

    size_t  NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::size() const
    {
        return m_underlyingSeq.size();
    }

    const LatBuilder::SeqCombiner<std::vector<uInteger>,LatBuilder::CartesianProduct>& NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::underlyingSeq() const
    {
        return m_underlyingSeq;
    }

    std::vector<std::vector<uInteger>> NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq::underlyingSeqs(Dimension coord)
    {
        if (coord == 0)
        {
            return {{0}};
        }
        else
        {
            unsigned int size = nthPrimitivePolynomialDegree(coord);
            std::vector<uInteger> dirNums{1};
            dirNums.reserve( (1 << (size - 1))  - 1);

            std::vector<std::vector<uInteger>> seqs;
            seqs.reserve(size);
            seqs.push_back(dirNums);

            uInteger upperBound = 2;
            for(unsigned int i = 1; i < size; ++i)
            {
                upperBound = 2 * upperBound;
                for(uInteger k = dirNums.back() + 2; k < upperBound; k += 2)
                {
                    dirNums.push_back(k);
                }
                seqs.push_back(dirNums);
            }
            return seqs;
        }
    }

    typename NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceCoordSeq NetConstructionTraits<NetConstruction::SOBOL>::genValueSpaceCoord(Dimension coord, const SizeParameter& sizeParameter)
    {
        return GenValueSpaceCoordSeq(coord);
    }

    typename NetConstructionTraits<NetConstruction::SOBOL>::GenValueSpaceSeq NetConstructionTraits<NetConstruction::SOBOL>::genValueSpace(Dimension dimension, const SizeParameter& sizeParameter)
    {
        std::vector<GenValueSpaceCoordSeq> seqs;
        seqs.reserve(dimension);
        for(Dimension coord = 0; coord < dimension; ++coord)
        {
            seqs.push_back(genValueSpaceCoord(coord, sizeParameter));
        }
        return GenValueSpaceSeq(seqs);
    }

    std::string NetConstructionTraits<NetConstruction::SOBOL>::format(const std::vector<std::shared_ptr<GeneratingMatrix>>& genMatrices, const std::vector<std::shared_ptr<GenValue>>& genVals, const SizeParameter& sizeParameter, OutputStyle outputStyle, unsigned int interlacingFactor)
    {
        std::string res;
        std::string dimension = std::to_string(genVals.size()/interlacingFactor);
        unsigned int k = nCols(sizeParameter);

        if (outputStyle == OutputStyle::TERMINAL){
            res += "Sobol Digital Net - Direction numbers =\n";
            for (unsigned int coord = 0; coord < genVals.size(); coord++){
                res+="  ";
                for(const auto& dirNum : genVals[coord]->second)
                {
                    res+= std::to_string(dirNum);
                    res+= " ";
                }
                res.pop_back();
                res+="\n";
            }
        }

        else if (outputStyle == OutputStyle::SOBOLJK){
            res += "# Parameters for Sobol points, in JK format\n";
            res += dimension + "    # s = " + dimension + " dimensions\n";
            if (interlacingFactor > 1){
                res+= std::to_string(interlacingFactor) + "    # Interlacing factor" + "\n";
                res+= std::to_string(genVals.size()) + "    # Number of components = interlacing factor x dimension" + "\n";
            }
            res += std::to_string(k) + "    # k = " + std::to_string(k) + ",  n = 2^"+ std::to_string(k) + " = " + std::to_string((int)pow(2, k)) + " points\n";
            res +="#  d  a  m_{j,c}\n";
            for (unsigned int coord = 1; coord < genVals.size(); coord++){
                res+= std::to_string(coord +1) + "  ";
                PrimitivePolynomial p = nthPrimitivePolynomial(coord);
                auto degree = p.first;
                auto poly_rep = p.second;
                res+= std::to_string(degree) + "  ";
                res+= std::to_string(poly_rep) + "  ";
                for(const auto& dirNum : genVals[coord]->second)
                {
                    res+= std::to_string(dirNum);
                    res+= " ";
                }
                res.pop_back();
                res+="\n";

            }
            res.pop_back();
        }

        else if (outputStyle == OutputStyle::SOBOL){
            res += "# Initial direction numbers m_{j,c} for Sobol points\n";
            res += dimension + "    # s = " + dimension + " dimensions\n";
            if (interlacingFactor > 1){
                res+= std::to_string(interlacingFactor) + "    # Interlacing factor" + "\n";
                res+= std::to_string(genVals.size()) + "    # Number of components = interlacing factor x dimension" + "\n";
            }
            res += std::to_string(k) + "    # k = " + std::to_string(k) + ",  n = 2^"+ std::to_string(k) + " = " + std::to_string((int)pow(2, k)) + " points\n";
            res +="# m_{j,c}, starting from the second coordinate\n";
            for (unsigned int coord = 1; coord < genVals.size(); coord++){
                for(const auto& dirNum : genVals[coord]->second)
                {
                    res += std::to_string(dirNum);
                    res += " ";
                }
                res.pop_back();
                res+="\n";

            }
            res.pop_back();
        }
        return res;
    }
}


/*

Copyright (c) 2005-2018, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef _STEADYSTATEEXTRAPOLATIONRUNNER_HPP_
#define _STEADYSTATEEXTRAPOLATIONRUNNER_HPP_

#ifdef CHASTE_CVODE

#include "AbstractSteadyStateRunner.hpp"
#include <boost/circular_buffer.hpp>
//#include "VectorHelperFunctions.hpp"

class SteadyStateExtrapolationRunner : public AbstractSteadyStateRunner
{

  /**
 * This class is to get a cell model to steady state by using extrapolation.
 *
 * We use the Mixed Root Mean Square (MRMS) error to measure how far from the steady state
 * we are. The PMCC of the MRMS errors is then used to check whether we can approxiamte the
 * decay of the state variables with an exponential. We then use simple linear regression
 * to find this exponential and extrapolate the variables.
 */

private:
  /* The number of points to use for our PMCC calculation and simple linear regression*/
  const unsigned int mBufferSize = 50;
  /*The maximum number of times we will attempt to extrapolate*/
  const unsigned int mMaxJumps = 100;
  /*The MRMS value for which we are close enough to the steady state that the absolute error in APD90 is probably less that 0.01ms*/
  const double mThreshold = 1.8e-07;
  /** Relative and absolute tolerances */
  const double TolRel = 1e-8;
  const double TolAbs = 1e-8;
  /** How far to extrapolate towards our approximation of the limit of the state variable. */
  double  mExtrapolationCoefficient = 0.9;
  /** The period and duration of the regular stimulus function */
  double  mPeriod;
  double  mDuration;
  /**The terminal state variables of the last pace we calculated*/
  std::vector<double> mStateVariables;
  /*The final state variables before the last extrapolation. This is used to reset in case an extrapolation causes an error*/
  std::vector<double> mSafeStateVariables;

  /** The stimulus used to pace the model - this must be a RegularStimulus */
  boost::shared_ptr<RegularStimulus> mpStimulus;

  /**Contains mBufferSize previous values of each state variable*/
  boost::circular_buffer<std::vector<double>>  mStatesBuffer;
  /**Contains mBufferSize previous values of each state vMRMS value*/
  boost::circular_buffer<double> mMrmsBuffer;

  /**Keeps track of the number of times we have extrapolated*/
  unsigned int mJumps = 0;

  /**Run the cell to steady state */
  virtual void RunToSteadyStateImplementation();

  /**
     * Calculate the MRMS error between two vectors
     *
     * @param A  The second vector
     * @param B  The first  vector
     */
  double CalculateMrms(std::vector<double> A, std::vector<double> B);

  /**
     * Calculate the PMCC of a circular_buffer of variables. This is used to calculate the PMCC of the previous MRMS values.
     *
     * @param values The values that we are calculating the PMCC of.
     */

  double CalculatePMCC(boost::circular_buffer<double> values){
    if(values.size()<=2 || values.size() <= 2){
      return -NAN;
    }

    const unsigned int N = values.size();
    const double sum_x = N*(N-1)/2;
    const double sum_x2 = (N-1)*N*(2*N-1)/6;

  double  sum_y = 0, sum_y2 = 0, sum_xy = 0;

  for(unsigned int i = 0; i < N; i++){
    sum_y += values[i];
    sum_y2+= values[i]*values[i];
    sum_xy+= i*values[i];
  }
  double pmcc = (N*sum_xy - sum_x*sum_y)/sqrt((N*sum_x2 - sum_x*sum_x)*(N*sum_y2 - sum_y*sum_y));
  return pmcc;
  }

    /**
     * Calculate the PMCC of a circular_buffer of (x,y) pairs. This is used to calculate the PMCC of the previous MRMS values.
     *
     * @param x The x values of the (x,y) pairs
     *
     * @param y The y values of the (x,y) pairs
 */

  double CalculatePMCC(std::vector<double> x, std::vector<double> y){
    const unsigned int N = x.size();
    if(x.size() <= 2){
      /*We don't have enough values*/
      return -NAN;
    }
    double sum_x = 0, sum_x2 = 0, sum_y = 0, sum_y2 = 0, sum_xy = 0;

    /*Calculate the required sums */
    for(unsigned int i = 0; i < N; i++){
      sum_x  += x[i];
      sum_x2 += x[i]*x[i];
      sum_y  += y[i];
      sum_y2 += y[i]*y[i];
      sum_xy += x[i]*y[i];
    }
    /*Return the PMCC*/
    return (N*sum_xy - sum_x*sum_y)/sqrt((N*sum_x2 - sum_x*sum_x)*(N*sum_y2 - sum_y*sum_y));
  }

  /**
   * Applies the extrapolation method to one state variable if it is sensible to do so. Returns
   * true if the state variable has been extrapolated.
   *
   * @state_index The index of the state variable that we wish to extrapolate
   */
  bool ExtrapolateState(unsigned int state_index);

   /**
   * Applies the extrapolation method to every state variable
   *
   * @state_index The index of the state variable that we wish to extrapolate
   */
  bool ExtrapolateStates();

  /**
   * Simulates one pace and extrapolates the state variables if the PMCC
   * of the previous MRMS values is approximately -1
   */
  bool RunPace();

public:
/**
     * Constructor of a helper class for getting action potential models to steady state.
     *
     * @param pModel  The cell model to run to steady state.
     * @param period  The period for the model to be paced at.
     */

  SteadyStateExtrapolationRunner(boost::shared_ptr<AbstractCvodeCell> pModel, double period)
    : AbstractSteadyStateRunner(pModel), mPeriod(period){
    /*This ensures that we are using a regular stimulus*/
    mpStimulus = mpModel->UseCellMLDefaultStimulus();
    mpStimulus->SetPeriod(mPeriod);
    mDuration = mpStimulus->GetDuration();

    /* Set Solver Tolerances */
    mpModel->SetTolerances(TolRel, TolAbs);

    /* Set the maximum number of timesteps for CVODE to use. This prevents
     * CV_TOO_MUCH_WORK errors */
    mpModel->SetMaxSteps(1e5);

    /* Initialise mStateVariables */
    mStateVariables = mpModel->GetStdVecStateVariables();

    /* Set the capacity of the circular buffers */
    mMrmsBuffer.set_capacity(mBufferSize);
    mStatesBuffer.set_capacity(mBufferSize);
  };

};

#endif // CHASTE_CVODE

#endif // _STEADYSTATEEXTRAPOLATIONRUNNER_HPP_


// The MIT License (MIT)
//
// Copyright (c) 2015 Christopher Jefferson
//
// 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.

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <iostream>
#include <signal.h>

#include <boost/multiprecision/cpp_int.hpp>

using namespace boost::multiprecision;

//  g++ shiftqueens-boost-1024.cc -I/usr/local/Cellar/boost/1.57.0/include/ -o shiftqueens-boost-1024 -O3
// for i in `ls`; do  ../../shiftqueens-boost-1024 $(cat $i | tr -cd " 0-9") > ../results.d25/$i.info; done

//#define PRINT_INFO

#ifdef PRINT_INFO
#define INFO(...) printf(__VA_ARGS__);
#define PAD(x) pad(x);
#define PRINTBITS(x) printBits(x)
#else
#define INFO(...)
#define PAD(x)
#define PRINTBITS(x)
#endif

//#define PRINT_SOL

#ifdef PRINT_SOL
static int sol[100];
#endif

#include <sys/resource.h>
#include <sys/times.h>

double gettime() {
	struct rusage rusage;
	if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
		return (double)rusage.ru_utime.tv_sec +
			(double)rusage.ru_utime.tv_usec / 1000000.0;

	std::cerr << "Fatal error in reading time\n";
	exit(1);
}

volatile int trig = false;

void trigger_function(int /* signum */) {
  trig = true;
}

void setup_timelimit(int timeout) {
	signal(SIGXCPU, trigger_function);
	signal(SIGALRM, trigger_function);
	rlimit lim;
	lim.rlim_cur = timeout;
	lim.rlim_max = timeout + 5;
	setrlimit(RLIMIT_CPU, &lim);
}

long long node_count = 0;

static int1024_t all;
static const int1024_t zero = 0;
static const int1024_t one = 1;
static int depth;
static int count = 0;

int fillLeft[1024];
int fillRight[1024];
int skipDepth[1024];

#ifdef PRINT_SOL
void print_sol()
{
	for(int i = 0; i < depth; ++i)
	{
		if(skipDepth[i])
			printf("-");
		else
			printf("|");
		for(int j = 0; j < depth; ++j)
		{
			if(1<<j == sol[i])
				printf("#|");
			else
			  printf(" |");
		}
		printf("\n");
		for(int j = 0; j < depth; ++j)
		printf("--");
		printf("-\n");
	}
	printf("\n");
}
#endif

#ifdef PRINT_INFO
void printBits(int1024_t bits)
{
	for(int i = 0; i < depth; ++i) {
		printf(bits & one ? "1" : "0");
		bits = bits >> 1;
	}
}
#endif

void pad(int curDepth)
{
		for(int i=0; i < curDepth; ++i)
			printf(" ");
}

void print_info() {
        std::cout << "Solutions: " << count << " ";
        std::cout << "Nodes: " << node_count << " ";
        std::cout << "Timeout: " << trig << " ";
        std::cout << "Time: " << gettime() << "\n";
}

void tryFunc(int1024_t ld, int1024_t cols, int1024_t rd, int curDepth)
{
		node_count++;
		if(trig) {
			print_info();
			std::cout << "Timeout Reached. Exiting.\n";
			exit(0);
		}
		PAD(curDepth);
		PRINTBITS(ld);
		INFO(",");
		PRINTBITS(cols);
		INFO(",");
		PRINTBITS(rd);
		INFO(":%d ", curDepth);
	if(curDepth == depth)
	{
		INFO("\n");
		count++;
		std::cout << "Solution Found\n";
#ifdef PRINT_SOL
		print_sol();
#endif
		print_info();

		exit(0);
	}

	int1024_t shiftld = ld << 1;
	int1024_t shiftrd = rd >> 1;

	assert((shiftld & one) == 0);
	assert((shiftrd & (one << (depth - 1))) == 0);

	if(fillLeft[curDepth+1])
	{
		INFO("*");
		shiftld |= one;
	}
	else
	INFO(" ");

	if(fillRight[curDepth+1])
	{
		INFO("*");
		shiftrd |= one << (depth - 1);
	}
	else INFO(" ");

	// If we already put a queen on this depth, skip it.
	if(skipDepth[curDepth])
	{
		INFO(" -- skip\n");
		return tryFunc(shiftld, cols, shiftrd, curDepth + 1);
	}

	PRINTBITS(shiftld);
		INFO(",");
		PRINTBITS(shiftrd);
		INFO("\n");

	{
		int1024_t poss = ~(ld | cols | rd) & all;
		while(poss != zero) {
			int1024_t bit = poss & -poss;
			poss -= bit;
#ifdef PRINT_SOL
			sol[curDepth] = bit;
#endif
			PAD(curDepth);
			PRINTBITS(bit);
			INFO(" added\n");
			tryFunc((bit<<1)|shiftld, cols|bit, (bit>>1)|shiftrd, curDepth + 1);
		}
	}
}

int main(int argc, char** argv)
{
	// SETUP TIMELIMIT HERE
	setup_timelimit(1800);

	if(argc % 2 != 0)
	{
		printf("./shiftqueens n queen1x queen1y queen2x queen2y ...\n");
		exit(1);
	}

	depth = atoi(argv[1]);
	all = (one << depth) - 1;

	int1024_t cols = 0;
	int1024_t ld = 0;
	int1024_t rd = 0;

	printf("%d\n", argc);

	for(int i = 2; i < argc; i+=2)
	{
		int queenx = atoi(argv[i]);
		int queeny = atoi(argv[i+1]);
		// printf("%d,%d\n", queenx, queeny);

		if(skipDepth[queeny]) {
			printf("inconsistent y\n");
			exit(1);
		}

		skipDepth[queeny] = 1;
#ifdef PRINT_SOL
		sol[queeny] = one << queenx;
#endif

		int1024_t colpos = one << queenx;
		if((cols & colpos)!=zero) {
			printf("Inconsistent start\n");
			exit(1);
		}
		cols |= colpos;

		{
			int rightintersect = queenx + queeny;
			if(rightintersect < depth)
			{
				int1024_t newrd = one << rightintersect;
				if((rd & newrd)!=zero) {
					printf("inconsistent diagonal\n");
					exit(1);
				}
				rd |= newrd;
			}
			else
			{
					rightintersect -= (depth - 1);
					if(fillRight[rightintersect]) {
						printf("inconsistent diagonal - type 2\n");
						exit(1);
					}
					fillRight[rightintersect] = 1;
			}
		}

		{
			int leftintersect = queenx - queeny;
			if(leftintersect >= 0)
			{
				int1024_t newld = one << leftintersect;
				if((ld & newld)!=zero) {
					printf("inconsistent left diagonal\n");
					exit(1);
				}
				ld |= newld;
			}
			else
			{
					leftintersect *= -1;
					if(fillLeft[leftintersect]) {
						printf("inconsistent left diagonal - type 2\n");
						exit(1);
					}
					fillLeft[leftintersect] = 1;
			}
		}
}

	count = 0;
	tryFunc(ld, cols, rd, 0);
	std::cout << "No solution" << "\n";
	print_info();
	exit(0);
}


/***********************************************************************
//      AUTHOR:  Zhengwei Xie (Mr), <xiezhengwei@hsc.pku.edu.cn>
//      COMPANY:  Department of Pharmacology, School of Basic Medical
//				Sciences, Peking University,
//				38 Xueyuan Lu, Haidian District,
//				Beijing, 100191, China
//      VERSION:  1.0
//      PAPER :
//		"Genome-scale fluxes were predicted under the guide of enzyme
//		abundance using a novel Hy-per-Cube Shrink Algorithm"
//		Zhengwei Xie, Tianyu Zhang and Qi Ouyang
***********************************************************************/

#include <map>
#include <cmath>
#include <ctime>
#include <string>
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <stdio.h>
#include <glpk.h>
#include <algorithm>
#include <boost/algorithm/string.hpp>
#include <iomanip>

#include "func.h"

using namespace std;
using namespace boost;
#define INFI 1e20
#define ERR 1e-20
#define UP 1
#define LOW 0

int BIO=1;
int ROW = -1;
double LOWCUT = 1e-1;
double MaxFlux=0.0;

map<int,int> tp;
void tp_init() {
    tp[0] = GLP_FX;
    tp[1] = GLP_DB;
    tp[2] = GLP_LO;
    tp[3] = GLP_UP;
}

int
main (int argc, char **argv)
{
	BIO = 0;
	string fname="";
	string confile="";
	string boundsfile="";
	string target_file="";
	string fvafile="";
	string flux_na="";
	string trace_na="";
	string method ="ratio";
	string help="";
	double mxx = 0;
	tp_init();
	for(int i=1; i<argc; i++) {
		if(strcmp(argv[i],"-i") == 0) {
			i++;
			cout<<"Model file : "<<argv[i]<<endl;
			fname += argv[i];
		} else if(strcmp(argv[i],"-c") == 0) {
			i++;
			cout<<"Constraint file (optional) : "<<argv[i]<<endl;
			confile += argv[i];
		} else if(strcmp(argv[i],"-l") == 0) {
			i++;
			cout<<"Inputted boudary file, optional: "<<argv[i]<<endl;
			boundsfile+= argv[i];
		} else if(strcmp(argv[i],"-b") == 0) {
			i++;
			cout<<"Enzyme abundance file (EV) : "<<argv[i]<<endl;
			target_file = argv[i];
		} else if(strcmp(argv[i],"-o") == 0) {
			i++;
			cout<<"Fluxes output file (FV) : "<<argv[i]<<endl;
			flux_na += argv[i];
		} else if(strcmp(argv[i],"--row") == 0) {
			i++;
			cout<<"Number of row in EV file : "<<argv[i]<<endl;
			ROW = atoi(argv[i]);
		}  else if(strcmp(argv[i],"--max") == 0) {
			i++;
			cout<<"Maximum value in EV (optional) : "<<argv[i]<<endl;
			mxx = atof(argv[i]);
		} else if(strcmp(argv[i],"--bio") == 0) {
			i++;
			cout<<"Column index of biomass flux: "<<argv[i]<<endl;
			BIO = atoi(argv[i]);
		} else if(strcmp(argv[i],"--fva") == 0) {
			i++;
			cout<<"Perform FVA and output the file in (optional) : "<<argv[i]<<endl;
			fvafile += argv[i];
		} else if(strcmp(argv[i],"-t") == 0) {
			i++;
			cout<<"Print trace and print to (optional) : "<<argv[i]<<endl;
			trace_na += argv[i];
		}  else if(strcmp(argv[i],"-h") == 0 || strcmp(argv[i],"--help") == 0 ) {
			help += argv[i];
		}  else {
			cout<<"Unrecognized option : "<<argv[i]<<endl;
			exit(0);
		}
	}
	if(fname.length() < 1 || help.length() > 0) {
		cout<<"		Usage : ./hcsa -i toy.mps --row 3 -b EV.txt -o flux.txt -t trace.txt\n";
		cout<<"		-i model.mps\n";
		cout<<"		-c constraint.txt: Extra Constraint file (optional)\n";
		cout<<"		-l boundary.txt: FVA file, Inputted boudary file, optional\n";
		cout<<"		-b EV.txt: Enzyme abundance file (EV)\n";
		cout<<"		-o flux.txt: Fluxes output file (FV) \n";
		cout<<"		--row N: Number of row in EV file\n";
		cout<<"		--max val: Maximum value in EV (optional) \n";
		cout<<"		--bio N: Column index of biomass flux\n";
		cout<<"		--fva variable.txt: Perform FVA and output the file in (optional) \n";
		cout<<"		-t tracefile.txt: print trace and print to (optional) \n";
		cout<<"		-h or --help : Print this help.\n";
		exit(-1);
	}
	if(ROW <= 0) {
		cout<<"Please specify the row number of target file\n";
		cout<<"     --row N \n";
		exit(-2);
	}
	//////////////////////////////////////////////////////////////
	//Loading input files
	glp_prob *lp=NULL;
	lp=glp_create_prob();
	glp_read_mps(lp,GLP_MPS_FILE,NULL,fname.c_str());

	cout<<"Successfully read model from "<<fname<<endl;
	if(strlen(confile.c_str()) > 0) {
		cout<<"Constraint file is "<<confile<<endl;
		get_constraints(lp,confile,tp);
	}
	fstream out2;
	if(flux_na.length() > 0) {
		out2.open(flux_na.c_str(),ios::out);
		cout<<"Output file is :"<<flux_na<<endl;
	} else {
		out2.open("internal_points",ios::out);
		cout<<"Output file is : internal_points. "<<endl;
	}
	fstream out3;
	if(trace_na.length() > 0) {
		out3.open(trace_na.c_str(),ios::out);
	}
	//////////////////////////////////////////////////////////////
	//FVA
	int N = glp_get_num_cols(lp);
	map<int,double> lbs;
	map<int,double> ubs;
	if(boundsfile.length()>0) {
		cout<<"Got bounds from file : "<<boundsfile<<endl;
		readbounds(&lbs,&ubs,boundsfile);
	} else {
		for(int i=1;i<=N;i++) {
			glp_set_obj_coef(lp,i,0.);
		}
		cout<<"Generating column bounds . . .\n";
		for(int i=1;i<=N;i++) {
			glp_set_obj_coef(lp,i,1);
			glp_set_obj_dir(lp,GLP_MAX);
			glp_simplex(lp,NULL);
			ubs[i] = glp_get_col_prim(lp,i);
			glp_set_obj_dir(lp,GLP_MIN);
			glp_simplex(lp,NULL);
			lbs[i] = glp_get_col_prim(lp,i);
			glp_set_obj_coef(lp,i,0);
		}
	}

	//////////////////////////////////////////////////////////////
	//
	cout<<"Initializing directions . . .\n";
	srand(time(0));

	//////////////////////////////////////////////////////////////
	vector<double> target;// = new double[row+1];
	for(int i=1;i<=N+1;i++) {
		target.push_back(0.0);
	}
	read_target(target_file,&target,ROW);

	cout<<"Running HCSA ... "<<endl;
	vector<double> flux;
	for(int i=1;i<=N+1;i++) {
		flux.push_back(0.0);
	}
	hcsa(lp,lbs,ubs,&flux,&target,&out3,method,mxx);
	cout<<"Printing out the flux distribution.\n";
	for(int i=1;i<=N;i++) {
		out2<<flux[i]<<"\n";
	}

	//////////////////////////////////////////////////////////////
	return 0;
}