#include "teca_ar_detect.h"

#include "teca_cartesian_mesh.h"
#include "teca_variant_array.h"
#include "teca_table.h"
#include "teca_calendar.h"
#include "teca_cartesian_mesh_util.h"

#include <iostream>
#include <sstream>
#include <limits>
#include <algorithm>
#include <cmath>

#if defined(TECA_HAS_BOOST)
#include <boost/program_options.hpp>
#endif

using std::ostream;
using std::vector;
using std::string;
using std::ostringstream;
using std::cerr;
using std::endl;

#define TECA_DEBUG 0
#if TECA_DEBUG > 0 && defined(TECA_HAS_VTK)
#include "teca_vtk_cartesian_mesh_writer.h"
#include "teca_programmable_source.h"
int write_mesh(
    const const_p_teca_cartesian_mesh &mesh,
    const const_p_teca_variant_array &vapor,
    const const_p_teca_variant_array &thres,
    const const_p_teca_variant_array &ccomp,
    const const_p_teca_variant_array &lsmask,
    const std::string &base_name);
#endif

// a description of the atmospheric river
struct atmospheric_river
{
    atmospheric_river() :
        pe(false), length(0.0),
        min_width(0.0), max_width(0.0),
        end_top_lat(0.0), end_top_lon(0.0),
        end_bot_lat(0.0), end_bot_lon(0.0)
    {}

    bool pe;
    double length;
    double min_width;
    double max_width;
    double end_top_lat;
    double end_top_lon;
    double end_bot_lat;
    double end_bot_lon;
};

ostream &operator<<(ostream &os, const atmospheric_river &ar)
{
    os << " type=" << (ar.pe ? "PE" : "AR")
        << " length=" << ar.length
        << " width=" << ar.min_width << ", " << ar.max_width
        << " bounds=" << ar.end_bot_lon << ", " << ar.end_bot_lat << ", "
        << ar.end_top_lon << ", " << ar.end_top_lat;
    return os;
}

unsigned sauf(const unsigned nrow, const unsigned ncol, unsigned int *image);

bool ar_detect(
    const_p_teca_variant_array lat,
    const_p_teca_variant_array lon,
    const_p_teca_variant_array land_sea_mask,
    const_p_teca_unsigned_int_array con_comp,
    unsigned long n_comp,
    double river_start_lat,
    double river_start_lon,
    double river_end_lat_low,
    double river_end_lon_low,
    double river_end_lat_high,
    double river_end_lon_high,
    double percent_in_mesh,
    double river_width,
    double river_length,
    double land_threshold_low,
    double land_threshold_high,
    atmospheric_river &ar);

// set locations in the output where the input array
// has values within the low high range.
template <typename T>
void threshold(
    const T *input, unsigned int *output,
    size_t n_vals, T low, T high)
{
    for (size_t i = 0; i < n_vals; ++i)
        output[i] = ((input[i] >= low) && (input[i] <= high)) ? 1 : 0;
}

// --------------------------------------------------------------------------
teca_ar_detect::teca_ar_detect() :
    water_vapor_variable("prw"),
    land_sea_mask_variable(""),
    low_water_vapor_threshold(20),
    high_water_vapor_threshold(75),
    search_lat_low(19.0),
    search_lon_low(180.0),
    search_lat_high(56.0),
    search_lon_high(250.0),
    river_start_lat_low(18.0),
    river_start_lon_low(180.0),
    river_end_lat_low(29.0),
    river_end_lon_low(233.0),
    river_end_lat_high(56.0),
    river_end_lon_high(238.0),
    percent_in_mesh(5.0),
    river_width(1250.0),
    river_length(2000.0),
    land_threshold_low(1.0),
    land_threshold_high(std::numeric_limits<double>::max())
{
    this->set_number_of_input_connections(1);
    this->set_number_of_output_ports(1);
}

// --------------------------------------------------------------------------
teca_ar_detect::~teca_ar_detect()
{}

#if defined(TECA_HAS_BOOST)

// --------------------------------------------------------------------------
void teca_ar_detect::get_properties_description(
    const string &prefix, options_description &opts)
{
    options_description ard_opts("Options for " + prefix + "(teca_ar_detect)");

    ard_opts.add_options()
        TECA_POPTS_GET(string, prefix, water_vapor_variable, "name of variable containing water vapor values")
        TECA_POPTS_GET(double, prefix, low_water_vapor_threshold, "low water vapor threshold")
        TECA_POPTS_GET(double, prefix, high_water_vapor_threshold, "high water vapor threshold")
        TECA_POPTS_GET(double, prefix, search_lat_low, "search space low latitude")
        TECA_POPTS_GET(double, prefix, search_lon_low, "search space low longitude")
        TECA_POPTS_GET(double, prefix, search_lat_high, "search space high latitude")
        TECA_POPTS_GET(double, prefix, search_lon_high, "search space high longitude")
        TECA_POPTS_GET(double, prefix, river_start_lat_low, "latitude used to classify as AR or PE")
        TECA_POPTS_GET(double, prefix, river_start_lon_low, "longitude used to classify as AR or PE")
        TECA_POPTS_GET(double, prefix, river_end_lat_low, "CA coastal region low latitude")
        TECA_POPTS_GET(double, prefix, river_end_lon_low, "CA coastal region low longitude")
        TECA_POPTS_GET(double, prefix, river_end_lat_high, "CA coastal region high latitude")
        TECA_POPTS_GET(double, prefix, river_end_lon_high, "CA coastal region high longitude")
        TECA_POPTS_GET(double, prefix, percent_in_mesh, "size of river in relation to search space area")
        TECA_POPTS_GET(double, prefix, river_width, "river width")
        TECA_POPTS_GET(double, prefix, river_length, "river length")
        TECA_POPTS_GET(string, prefix, land_sea_mask_variable, "name of variable containing land-sea mask values")
        TECA_POPTS_GET(double, prefix, land_threshold_low, "low land value")
        TECA_POPTS_GET(double, prefix, land_threshold_high, "high land value")
        ;

    opts.add(ard_opts);
}

// --------------------------------------------------------------------------
void teca_ar_detect::set_properties(
    const string &prefix, variables_map &opts)
{
    TECA_POPTS_SET(opts, string, prefix, water_vapor_variable)
    TECA_POPTS_SET(opts, double, prefix, low_water_vapor_threshold)
    TECA_POPTS_SET(opts, double, prefix, high_water_vapor_threshold)
    TECA_POPTS_SET(opts, double, prefix, search_lat_low)
    TECA_POPTS_SET(opts, double, prefix, search_lon_low)
    TECA_POPTS_SET(opts, double, prefix, search_lat_high)
    TECA_POPTS_SET(opts, double, prefix, search_lon_high)
    TECA_POPTS_SET(opts, double, prefix, river_start_lat_low)
    TECA_POPTS_SET(opts, double, prefix, river_start_lon_low)
    TECA_POPTS_SET(opts, double, prefix, river_end_lat_low)
    TECA_POPTS_SET(opts, double, prefix, river_end_lon_low)
    TECA_POPTS_SET(opts, double, prefix, river_end_lat_high)
    TECA_POPTS_SET(opts, double, prefix, river_end_lon_high)
    TECA_POPTS_SET(opts, double, prefix, percent_in_mesh)
    TECA_POPTS_SET(opts, double, prefix, river_width)
    TECA_POPTS_SET(opts, double, prefix, river_length)
    TECA_POPTS_SET(opts, string, prefix, land_sea_mask_variable)
    TECA_POPTS_SET(opts, double, prefix, land_threshold_low)
    TECA_POPTS_SET(opts, double, prefix, land_threshold_high)
}

#endif

// --------------------------------------------------------------------------
teca_metadata teca_ar_detect::get_output_metadata(
    unsigned int port,
    const std::vector<teca_metadata> &input_md)
{
#if TECA_DEBUG > 1
    cerr << teca_parallel_id()
        << "teca_ar_detect::get_output_metadata" << endl;
#endif
    (void)port;

    teca_metadata output_md(input_md[0]);
    return output_md;
}

// --------------------------------------------------------------------------
int teca_ar_detect::get_active_extent(
    p_teca_variant_array lat,
    p_teca_variant_array lon,
    std::vector<unsigned long> &extent) const
{
    extent.resize(6, 0l);

    TEMPLATE_DISPATCH_FP(
        teca_variant_array_impl,
        lat.get(),

        unsigned long high_j = lat->size() - 1;
        NT *p_lat = std::dynamic_pointer_cast<TT>(lat)->get();

        unsigned long high_i = lon->size() - 1;
        NT *p_lon = std::dynamic_pointer_cast<TT>(lon)->get();

        if (index_of(p_lon, 0, high_i, static_cast<NT>(this->search_lon_low), false, extent[0])
            || index_of(p_lon, 0, high_i, static_cast<NT>(this->search_lon_high), true, extent[1])
            || index_of(p_lat, 0, high_j, static_cast<NT>(this->search_lat_low), false, extent[2])
            || index_of(p_lat, 0, high_j, static_cast<NT>(this->search_lat_high), true, extent[3]))
        {
            TECA_ERROR(
                << "search space ["
                << this->search_lon_low << ", " << this->search_lon_high << ", "
                << this->search_lat_low << ", " << this->search_lat_high
                << "] is not contained in the current dataset bounds ["
                << p_lon[0] << ", " << p_lon[high_i] << ", "
                << p_lat[0] << ", " << p_lat[high_j] << "]")
            return -1;
        }
        return 0;
        )

    TECA_ERROR("invalid coordinate array type")
    return -1;
}

// --------------------------------------------------------------------------
std::vector<teca_metadata> teca_ar_detect::get_upstream_request(
    unsigned int port,
    const std::vector<teca_metadata> &input_md,
    const teca_metadata &request)
{
#if TECA_DEBUG > 1
    cerr << teca_parallel_id()
        << "teca_ar_detect::get_upstream_request" << endl;
#endif
    (void)port;

    vector<teca_metadata> up_reqs;

    teca_metadata md = input_md[0];

    // locate the extents of the user supplied region of
    // interest
    teca_metadata coords;
    if (md.get("coordinates", coords))
    {
        TECA_ERROR("metadata is missing \"coordinates\"")
        return up_reqs;
    }

    p_teca_variant_array lat;
    p_teca_variant_array lon;
    if (!(lat = coords.get("y")) || !(lon = coords.get("x")))
    {
        TECA_ERROR("metadata missing lat lon coordinates")
        return up_reqs;
    }

    vector<unsigned long> extent(6, 0l);
    if (this->get_active_extent(lat, lon, extent))
    {
        TECA_ERROR("failed to determine the active extent")
        return up_reqs;
    }

#if TECA_DEBUG > 1
    cerr << teca_parallel_id() << "active_bound = "
        << this->search_lon_low<< ", " << this->search_lon_high
        << ", " << this->search_lat_low << ", " << this->search_lat_high
        << endl;
    cerr << teca_parallel_id() << "active_extent = "
        << extent[0] << ", " << extent[1] << ", " << extent[2] << ", "
        << extent[3] << ", " << extent[4] << ", " << extent[5] << endl;
#endif

    // build the request
    vector<string> arrays;
    request.get("arrays", arrays);
    arrays.push_back(this->water_vapor_variable);
    if (!this->land_sea_mask_variable.empty())
        arrays.push_back(this->land_sea_mask_variable);

    teca_metadata up_req(request);
    up_req.insert("arrays", arrays);
    up_req.insert("extent", extent);

    up_reqs.push_back(up_req);
    return up_reqs;
}

// --------------------------------------------------------------------------
const_p_teca_dataset teca_ar_detect::execute(
    unsigned int port,
    const std::vector<const_p_teca_dataset> &input_data,
    const teca_metadata &request)
{
#if TECA_DEBUG > 1
    cerr << teca_parallel_id() << "teca_ar_detect::execute";
    this->to_stream(cerr);
    cerr << endl;
#endif
    (void)port;
    (void)request;

    // get the input dataset
    const_p_teca_cartesian_mesh mesh
        = std::dynamic_pointer_cast<const teca_cartesian_mesh>(input_data[0]);
    if (!mesh)
    {
        TECA_ERROR("invalid input. teca_cartesian_mesh is required")
        return nullptr;
    }

    // get coordinate arrays
    const_p_teca_variant_array lat = mesh->get_y_coordinates();
    const_p_teca_variant_array lon = mesh->get_x_coordinates();

    if (!lon || !lat)
    {
        TECA_ERROR("invalid mesh. missing lat lon coordinates")
        return nullptr;
    }

    // get land sea mask
    const_p_teca_variant_array land_sea_mask;
    if (this->land_sea_mask_variable.empty() ||
        !(land_sea_mask = mesh->get_point_arrays()->get(this->land_sea_mask_variable)))
    {
        // input doesn't have it, generate a stand in such
        // that land fall criteria will evaluate true
        size_t n = lat->size()*lon->size();
        p_teca_double_array lsm = teca_double_array::New(n, this->land_threshold_low);
        land_sea_mask = lsm;
    }

    // get time step
    unsigned long time_step;
    mesh->get_time_step(time_step);

    // get units
    std::string units;
    mesh->get_time_units(units);
    if ((units.find("days since") == string::npos) || (units.size() < 21))
    {
        TECA_ERROR("invalid time units" << endl << units)
        return nullptr;
    }

    // base/origin date
    long base_y = atoi(units.c_str() + 11);
    long base_m = atoi(units.c_str() + 16);
    long base_d = atoi(units.c_str() + 19);

    // get calendar
    std::string calendar;
    mesh->get_calendar(calendar);

    // compute the current date
    long curr_y = 0;
    long curr_m = 0;
    long curr_d = 0;

    if ((calendar == "standard") || (calendar == "gregorian"))
    {
        // assume units format is "days since YYYY-MM-DD"
        if (!valid_gregorian_date(base_y, base_m, base_d))
        {
            TECA_ERROR("CF time:units convert to an invalid date" << endl
                << units << " -> " << base_y << "-" << base_m << "-" << base_d)
            return nullptr;
        }
        long base_g = gregorian_number(base_y, base_m, base_d);

        // get offset of the current timestep
        double curr_g = 0.0;
        mesh->get_time(curr_g);

        // make date calc
        date_from_gregorian_number(
            static_cast<long>(curr_g) + base_g, curr_y, curr_m, curr_d);
    }

#if TECA_DEBUG > 1
    cerr << teca_parallel_id()
        << " " << time_step << " "
        << curr_y << "/" << curr_m << "/" << curr_d << endl;
#endif

    // get water vapor data
    vector<unsigned long> extent;
    mesh->get_extent(extent);

    unsigned long num_rows = extent[3] - extent[2] + 1;
    unsigned long num_cols = extent[1] - extent[0] + 1;
    unsigned long num_rc = num_rows*num_cols;

    const_p_teca_variant_array water_vapor
        = mesh->get_point_arrays()->get(this->water_vapor_variable);

    if (!water_vapor)
    {
        TECA_ERROR(
            << "Dataset missing water vapor variable \""
            << this->water_vapor_variable << "\"")
        return nullptr;
    }

    p_teca_table event = teca_table::New();

    event->declare_columns(
        "year", int(), "month", int(), "day", int(),
        "time_step", long(), "length", double(),
        "min width", double(), "max width", double(),
        "end_top_lat", double(), "end_top_lon", double(),
        "end_bot_lat", double(), "end_bot_lon", double(),
        "type", std::string());

    TEMPLATE_DISPATCH(
        const teca_variant_array_impl,
        water_vapor.get(),

        const NT *p_wv = dynamic_cast<TT*>(water_vapor.get())->get();

        // threshold
        p_teca_unsigned_int_array con_comp
            = teca_unsigned_int_array::New(num_rc, 0);

        unsigned int *p_con_comp = con_comp->get();

        threshold(p_wv, p_con_comp, num_rc,
            static_cast<NT>(this->low_water_vapor_threshold),
            static_cast<NT>(this->high_water_vapor_threshold));

#if TECA_DEBUG > 0 && defined(TECA_HAS_VTK)
        p_teca_variant_array thresh = con_comp->new_copy();
#endif

        // label
        int num_comp = sauf(num_rows, num_cols, p_con_comp);

#if TECA_DEBUG > 0 && defined(TECA_HAS_VTK)
        write_mesh(mesh, water_vapor, thresh, con_comp, land_sea_mask, "ar_mesh");
#endif

        // detect ar
        atmospheric_river ar;
        if (num_comp
            && ar_detect(lat, lon, land_sea_mask, con_comp, num_comp,
            this->river_start_lat_low, this->river_start_lon_low,
            this->river_end_lat_low, this->river_end_lon_low,
            this->river_end_lat_high, this->river_end_lon_high,
            this->percent_in_mesh, this->river_width,
            this->river_length, this->land_threshold_low,
            this->land_threshold_high, ar))
        {
#if TECA_DEBUG > 0
            cerr << teca_parallel_id() << " event detected " << time_step
                << " " << curr_y << "/" << curr_m << "/" << curr_d << endl;
#endif
            event << curr_y << curr_m << curr_d << time_step
                << ar.length << ar.min_width << ar.max_width
                << ar.end_top_lat << ar.end_top_lon
                << ar.end_bot_lat << ar.end_bot_lon
                << std::string(ar.pe ? "PE" : "AR");
        }
        )

    return event;
}

// --------------------------------------------------------------------------
void teca_ar_detect::to_stream(ostream &os) const
{
    os << " water_vapor_variable=" << this->water_vapor_variable
        << " land_sea_mask_variable=" << this->land_sea_mask_variable
        << " low_water_vapor_threshold=" << this->low_water_vapor_threshold
        << " high_water_vapor_threshold=" << this->high_water_vapor_threshold
        << " river_start_lon_low=" << this->river_start_lon_low
        << " river_start_lat_low=" << this->river_start_lat_low
        << " river_end_lon_low=" << this->river_end_lon_low
        << " river_end_lat_low=" << this->river_end_lat_low
        << " river_end_lon_high=" << this->river_end_lon_high
        << " river_end_lat_high=" << this->river_end_lat_high
        << " percent_in_mesh=" << this->percent_in_mesh
        << " river_width=" << this->river_width
        << " river_length=" << this->river_length
        << " land_threshodl_low=" << this->land_threshold_low
        << " land_threshodl_high=" << this->land_threshold_high;
}

// Code borrowed from John Wu's sauf.cpp
// Find the minimal value starting @arg ind.
inline unsigned afind(const std::vector<unsigned>& equiv,
              const unsigned ind)
{
    unsigned ret = ind;
    while (equiv[ret] < ret)
    {
        ret = equiv[ret];
    }
    return ret;
}

// Set the values starting with @arg ind.
inline void aset(std::vector<unsigned>& equiv,
         const unsigned ind, const unsigned val)
{
    unsigned i = ind;
    while (equiv[i] < i)
    {
        unsigned j = equiv[i];
        equiv[i] = val;
        i = j;
    }
    equiv[i] = val;
}

/*
* Purpose:        Scan with Array-based Union-Find
* Return vals:    Number of connected components
* Description:    SAUF -- Scan with Array-based Union-Find.
* This is an implementation that follows the decision try to minimize
* number of neighbors visited and uses the array-based union-find
* algorithms to minimize work on the union-find data structure.  It works
* with each pixel/cell of the 2D binary image individually.
* The 2D binary image is passed to sauf as a unsigned*.  On input, the
* zero value is treated as the background, and non-zero is treated as
* object.  On successful completion of this function, the non-zero values
* in array image is replaced by its label.
* The return value is the number of components found.
*/
unsigned sauf(const unsigned nrow, const unsigned ncol, unsigned *image)
{
    const unsigned ncells = ncol * nrow;
    const unsigned ncp1 = ncol + 1;
    const unsigned ncm1 = ncol - 1;
    std::vector<unsigned int> equiv;    // equivalence array
    unsigned nextLabel = 1;

    equiv.reserve(ncol);
    equiv.push_back(0);

    // the first cell of the first line
    if (*image != 0)
    {
    *image = nextLabel;
    equiv.push_back(nextLabel);
    ++ nextLabel;
    }
    // first row of cells
    for (unsigned i = 1; i < ncol; ++ i)
    {
    if (image[i] != 0)
    {
        if (image[i-1] != 0)
        {
        image[i] = image[i-1];
        }
        else
        {
        equiv.push_back(nextLabel);
        image[i] = nextLabel;
        ++ nextLabel;
        }
    }
    }

    // scan the rest of lines, check neighbor b first
    for (unsigned j = ncol; j < ncells; j += ncol)
    {
    unsigned nc, nd, k, l;

    // the first point of the line has two neighbors, and the two
    // neighbors must have at most one label (recorded as nc)
    if (image[j] != 0)
    {
        if (image[j-ncm1] != 0)
        nc = image[j-ncm1];
        else if (image[j-ncol] != 0)
        nc = image[j-ncol];
        else
        nc = nextLabel;
        if (nc != nextLabel) { // use existing label
        nc = equiv[nc];
        image[j] = nc;
        }
        else { // need a new label
        equiv.push_back(nc);
        image[j] = nc;
        ++ nextLabel;
        }
    }

    // the rest of the line
    for (unsigned i = j+1; i < j+ncol; ++ i)
    {
        if (image[i] != 0) {
        if (image[i-ncol] != 0) {
            nc = image[i-ncol];
            l = afind(equiv, nc);
            aset(equiv, nc, l);
            image[i] = l;
        }
        else if (i-ncm1<j && image[i-ncm1] != 0) {
            nc = image[i-ncm1];

            if (image[i-1] != 0)
            nd = image[i-1];
            else if (image[i-ncp1] != 0)
            nd = image[i-ncp1];
            else
            nd = nextLabel;
            if (nd < nextLabel) {
            k = afind(equiv, nc);
            l = afind(equiv, nd);
            if (l <= k) {
                aset(equiv, nc, l);
                aset(equiv, nd, l);
            }
            else {
                l = k;
                aset(equiv, nc, k);
                aset(equiv, nd, k);
            }
            image[i] = l;
            }
            else {
            l = afind(equiv, nc);
            aset(equiv, nc, l);
            image[i] = l;
            }
        }
        else if (image[i-1] != 0) {
            nc = image[i-1];
            l = afind(equiv, nc);
            aset(equiv, nc, l);
            image[i] = l;
        }
        else if (image[i-ncp1] != 0) {
            nc = image[i-ncp1];
            l = afind(equiv, nc);
            aset(equiv, nc, l);
            image[i] = l;
        }
        else { // need a new label
            equiv.push_back(nextLabel);
            image[i] = nextLabel;
            ++ nextLabel;
        }
        }
    }
    } // for (unsigned j ...

    // phase II: re-number the labels to be consecutive
    nextLabel = 0;
    const unsigned nequiv = equiv.size();
    for (unsigned i = 0; i < nequiv;  ++ i) {
    if (equiv[i] < i) { // chase one more time
#if defined(_DEBUG) || defined(DEBUG)
        std::cout << i << " final " << equiv[i] << " ==> "
              << equiv[equiv[i]] << std::endl;
#endif
        equiv[i] = equiv[equiv[i]];
    }
    else { // change to the next smallest unused label
#if defined(_DEBUG) || defined(DEBUG)
        std::cout << i << " final " << equiv[i] << " ==> "
              << nextLabel << std::endl;
#endif
        equiv[i] = nextLabel;
        ++ nextLabel;
    }
    }

    if (nextLabel < nequiv) {// relabel all cells to their minimal labels
    for (unsigned i = 0; i < ncells; ++ i)
        image[i] = equiv[image[i]];
    }

#if defined(_DEBUG) || defined(DEBUG)
    std::cout << "sauf(" << nrow << ", " << ncol << ") assigned "
          << nextLabel-1 << " label" << (nextLabel>2?"s":"")
          << ", used " << nequiv << " provisional labels"
          << std::endl;
#endif
    return nextLabel-1;
}

// do any of the detected points meet the river start
// criteria. retrun true if so.
template<typename T>
bool river_start_criteria_lat(
    const vector<int> &con_comp_r,
    const T *p_lat,
    T river_start_lat)
{
    unsigned long n = con_comp_r.size();
    for (unsigned long q = 0; q < n; ++q)
    {
        if (p_lat[con_comp_r[q]] >= river_start_lat)
            return true;
    }
    return false;
}

// do any of the detected points meet the river start
// criteria. retrun true if so.
template<typename T>
bool river_start_criteria_lon(
    const vector<int> &con_comp_c,
    const T *p_lon,
    T river_start_lon)
{
    unsigned long n = con_comp_c.size();
    for (unsigned long q = 0; q < n; ++q)
    {
        if (p_lon[con_comp_c[q]] >= river_start_lon)
            return true;
    }
    return false;
}

// helper return true if the start criteria is
// met, and classifies the ar as PE if it starts
// in the bottom boundary.
template<typename T>
bool river_start_criteria(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const T *p_lat,
    const T *p_lon,
    T start_lat,
    T start_lon,
    atmospheric_river &ar)
{
    return
         ((ar.pe = river_start_criteria_lat(con_comp_r, p_lat, start_lat))
         || river_start_criteria_lon(con_comp_c, p_lon, start_lon));
}

// do any of the detected points meet the river end
// criteria? (ie. does it hit the west coasts?) if so
// store a bounding box covering the river and return
// true.
template<typename T>
bool river_end_criteria(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const T *p_lat,
    const T *p_lon,
    T river_end_lat_low,
    T river_end_lon_low,
    T river_end_lat_high,
    T river_end_lon_high,
    atmospheric_river &ar)
{
    bool end_criteria = false;

    vector<int> end_col_idx;

    unsigned int count = con_comp_r.size();
    for (unsigned int i = 0; i < count; ++i)
    {
        // approximate land mask boundaries for the western coast of the US,
        T lon_val = p_lon[con_comp_c[i]];
        if ((lon_val >= river_end_lon_low) && (lon_val <= river_end_lon_high))
            end_col_idx.push_back(i);
    }

    // look for rows that fall between lat boundaries
    T top_lat = T();
    T top_lon = T();
    T bot_lat = T();
    T bot_lon = T();

    bool top_touch = false;
    unsigned int end_col_count = end_col_idx.size();
    for (unsigned int i = 0; i < end_col_count; ++i)
    {
        // approximate land mask boundaries for the western coast of the US,
        T lat_val = p_lat[con_comp_r[end_col_idx[i]]];
        if ((lat_val >= river_end_lat_low) && (lat_val <= river_end_lat_high))
        {
            T lon_val = p_lon[con_comp_c[end_col_idx[i]]];
            end_criteria = true;
            if (!top_touch)
            {
                top_touch = true;
                top_lat = lat_val;
                top_lon = lon_val;
            }
            bot_lat = lat_val;
            bot_lon = lon_val;
        }
    }

    ar.end_top_lat = top_lat;
    ar.end_top_lon = top_lon;
    ar.end_bot_lat = bot_lat;
    ar.end_bot_lon = bot_lon;

    return end_criteria;
}

/*
* Calculate geodesic distance between two lat, long pairs
* CODE borrowed from: from http://www.geodatasource.com/developers/c
* from http://osiris.tuwien.ac.at/~wgarn/gis-gps/latlong.html
* from http://www.codeproject.com/KB/cpp/Distancecplusplus.aspx
*/
template<typename T>
T geodesic_distance(T lat1, T lon1, T lat2, T lon2)
{
    T deg_to_rad = T(M_PI/180.0);

    T dlat1 = lat1*deg_to_rad;
    T dlon1 = lon1*deg_to_rad;
    T dlat2 = lat2*deg_to_rad;
    T dlon2 = lon2*deg_to_rad;

    T dLon = dlon1 - dlon2;
    T dLat = dlat1 - dlat2;

    T sin_dLat_half_sq = sin(dLat/T(2.0));
    sin_dLat_half_sq *= sin_dLat_half_sq;

    T sin_dLon_half_sq = sin(dLon/T(2.0));
    sin_dLon_half_sq *= sin_dLon_half_sq;

    T aHarv = sin_dLat_half_sq
        + cos(dlat1)*cos(dlat2)*sin_dLon_half_sq;

    T cHarv = T(2.0)*atan2(sqrt(aHarv), sqrt(T(1.0) - aHarv));

    T R = T(6371.0);
    T distance = R*cHarv;

    return distance;
}

/*
* This function calculates the average geodesic width
* As each pixel represents certain area, the total area
* is the product of the number of pixels and the area of
* one pixel. The average width is: the total area divided
* by the medial axis length
* We are calculating the average width, since we are not
* determining where exactly to cut off the tropical region
* to calculate the real width of an atmospheric river
*/
template <typename T>
T avg_width(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    T ar_len,
    const T *p_lat,
    const T *p_lon)
{
/*
    // TODO -- need bounds checking when doing things like
    // p_lat[con_comp_r[0] + 1]. also because it's potentially
    // a stretched cartesian mesh need to compute area of
    // individual cells

    // length of cell in lat direction
    T lat_val[2] = {p_lat[con_comp_r[0]], p_lat[con_comp_r[0] + 1]};
    T lon_val[2] = {p_lon[con_comp_c[0]], p_lon[con_comp_c[0]]};
    T dlat = geodesic_distance(lat_val[0], lon_val[0], lat_val[1], lon_val[1]);

    // length of cell in lon direction
    lat_val[1] = lat_val[0];
    lon_val[1] = p_lon[con_comp_c[0] + 1];
    T dlon = geodesic_distance(lat_val[0], lon_val[0], lat_val[1], lon_val[1]);
*/
    (void)con_comp_c;
    // compute area of the first cell in the input mesh
    // length of cell in lat direction
    T lat_val[2] = {p_lat[0], p_lat[1]};
    T lon_val[2] = {p_lon[0], p_lon[0]};
    T dlat = geodesic_distance(lat_val[0], lon_val[0], lat_val[1], lon_val[1]);

    // length of cell in lon direction
    lat_val[1] = lat_val[0];
    lon_val[1] = p_lon[1];
    T dlon = geodesic_distance(lat_val[0], lon_val[0], lat_val[1], lon_val[1]);

    // area
    T pixel_area = dlat*dlon;
    T total_area = pixel_area*con_comp_r.size();

    // avg width
    T avg_width = total_area/ar_len;
    return avg_width;
}

/*
* Find the middle point between two pairs of lat and lon values
* http://stackoverflow.com/questions/4164830/geographic-midpoint-between-two-coordinates
*/
template<typename T>
void geodesic_midpoint(T lat1, T lon1, T lat2, T lon2, T &mid_lat, T &mid_lon)
{
    T deg_to_rad = T(M_PI/180.0);
    T dLon = (lon2 - lon1) * deg_to_rad;
    T dLat1 = lat1 * deg_to_rad;
    T dLat2 = lat2 * deg_to_rad;
    T dLon1 = lon1 * deg_to_rad;

    T Bx = cos(dLat2) * cos(dLon);
    T By = cos(dLat2) * sin(dLon);

    mid_lat = atan2(sin(dLat1)+sin(dLat2),
        sqrt((cos(dLat1)+Bx)*(cos(dLat1)+Bx)+By*By));

    mid_lon = dLon1 + atan2(By, (cos(dLat1)+Bx));

    T rad_to_deg = T(180.0/M_PI);
    mid_lat *= rad_to_deg;
    mid_lon *= rad_to_deg;
}

/*
* Find the length along the medial axis of a connected component
* Medial length is the sum of the distances between the medial
* points in the connected component
*/
template<typename T>
T medial_length(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const T *p_lat,
    const T *p_lon)
{
    vector<int> jb_r1;
    vector<int> jb_c1;
    vector<int> jb_c2;

    long row_track = -1;

    unsigned long count = con_comp_r.size();
    for (unsigned long i = 0; i < count; ++i)
    {
        if (row_track != con_comp_r[i])
        {
            jb_r1.push_back(con_comp_r[i]);
            jb_c1.push_back(con_comp_c[i]);

            jb_c2.push_back(con_comp_c[i]);

            row_track = con_comp_r[i];
        }
        else
        {
            jb_c2.back() = con_comp_c[i];
        }
    }

    T total_dist = T();

    long b_count = jb_r1.size() - 1;
    for (long i = 0; i < b_count; ++i)
    {
        T lat_val[2];
        T lon_val[2];

        lat_val[0] = p_lat[jb_r1[i]];
        lat_val[1] = p_lat[jb_r1[i]];

        lon_val[0] = p_lon[jb_c1[i]];
        lon_val[1] = p_lon[jb_c2[i]];

        T mid_lat1;
        T mid_lon1;

        geodesic_midpoint(
            lat_val[0], lon_val[0], lat_val[1], lon_val[1],
            mid_lat1, mid_lon1);

        lat_val[0] = p_lat[jb_r1[i+1]];
        lat_val[1] = p_lat[jb_r1[i+1]];

        lon_val[0] = p_lon[jb_c1[i+1]];
        lon_val[1] = p_lon[jb_c2[i+1]];

        T mid_lat2;
        T mid_lon2;

        geodesic_midpoint(
            lat_val[0], lon_val[0], lat_val[1], lon_val[1],
            mid_lat2, mid_lon2);

        total_dist
            += geodesic_distance(mid_lat1, mid_lon1, mid_lat2, mid_lon2);
    }

    return total_dist;
}

/*
// Suren's function
// helper return true if the geometric conditions
// on an ar are satisfied. also stores the length
// and width of the river.
template <typename T>
bool river_geometric_criteria(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const T *p_lat,
    const T *p_lon,
    double river_length,
    double river_width,
    atmospheric_river &ar)
{
    ar.length = medial_length(con_comp_r, con_comp_c, p_lat, p_lon);

    ar.width = avg_width(con_comp_r, con_comp_c,
        static_cast<T>(ar.length), p_lat, p_lon);

    return (ar.length >= river_length) && (ar.width <= river_width);
}
*/

// Junmin's function for height of a triangle
template<typename T>
T triangle_height(T base, T s1, T s2)
{
    // area from Heron's fomula
    T p = (base + s1 + s2)/T(2);
    T area = p*(p - base)*(p - s1)*(p - s2);
    // detect impossible triangle
    if (area < T())
        return std::min(s1, s2);
    // height from A = 1/2 b h
    return T(2)*sqrt(area)/base;
}

// TDataProcessor::check_geodesic_width_top_down
// Junmin's function for detecting river based on
// it's geometric properties
template <typename T>
bool river_geometric_criteria(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const T *p_lat,
    const T *p_lon,
    double river_length,
    double river_width,
    atmospheric_river &ar)
{
    vector<int> distinct_rows;
    vector<int> leftmost_col;
    vector<int> rightmost_col;

    int row_track = -1;
    size_t count = con_comp_r.size();
    for (size_t i = 0; i < count; ++i)
    {
        if (row_track != con_comp_r[i])
        {
            row_track = con_comp_r[i];

            distinct_rows.push_back(con_comp_r[i]);
            leftmost_col.push_back(con_comp_c[i]);
            rightmost_col.push_back(con_comp_c[i]);
        }
        else
        {
            rightmost_col.back() = con_comp_c[i];
        }
    }

    // river metrics
    T length_from_top = T();
    T min_width = std::numeric_limits<T>::max();
    T max_width = std::numeric_limits<T>::lowest();

    for (long i = distinct_rows.size() - 2; i >= 0; --i)
    {
        // for each row w respect to row above it. triangulate
        // a quadrilateral composed of left and right most points
        // in this and the above rows. ccw ordering from lower
        // left corner is A,B,D,C.

        // low left-right distance
        T AB = geodesic_distance(
            p_lat[distinct_rows[i]], p_lon[leftmost_col[i]],
            p_lat[distinct_rows[i]], p_lon[rightmost_col[i]]);

        // left side bottom-top distance
        T AC = geodesic_distance(
            p_lat[distinct_rows[i]], p_lon[leftmost_col[i]],
            p_lat[distinct_rows[i+1]], p_lon[leftmost_col[i]]);

        // distance from top left to bottom right, across
        T BC = geodesic_distance(
            p_lat[distinct_rows[i]], p_lon[rightmost_col[i]],
            p_lat[distinct_rows[i+1]], p_lon[leftmost_col[i+1]]);

        // high left-right distance
        T CD = geodesic_distance(
            p_lat[distinct_rows[i+1]], p_lon[leftmost_col[i+1]],
            p_lat[distinct_rows[i+1]], p_lon[rightmost_col[i+1]]);

        // right side bottom-top distance
        T BD = geodesic_distance(
            p_lat[distinct_rows[i]], p_lon[rightmost_col[i]],
            p_lat[distinct_rows[i+1]], p_lon[rightmost_col[i+1]]);

        T height_from_b = triangle_height(AC, AB, BC);
        T height_from_c = triangle_height(BD, BC, CD);

        T curr_min = std::min(height_from_b, height_from_c);

        // test width criteria
        if (curr_min > river_width)
        {
            // TODO -- first time through the loop length == 0. is it intentional
            // to discard the detection or should length calc take place before this test?
            // note: first time through loop none of the event details have been recoreded
            if (length_from_top <= river_length)
            {
                 // too short to be a river
                return false;
            }
            else
            {
                 // part of a connected region is AR
                ar.min_width = static_cast<double>(min_width);
                ar.max_width = static_cast<double>(max_width);
                ar.length = static_cast<double>(length_from_top);
                return true;
            }
        }

        // update width
        min_width = std::min(min_width, curr_min);
        max_width = std::max(max_width, curr_min);

        // update length
        T mid_bot_lat;
        T mid_bot_lon;
        geodesic_midpoint(
            p_lat[distinct_rows[i]], p_lon[leftmost_col[i]],
            p_lat[distinct_rows[i]], p_lon[rightmost_col[i]],
            mid_bot_lat, mid_bot_lon);

        T mid_top_lat;
        T mid_top_lon;
        geodesic_midpoint(
            p_lat[distinct_rows[i+1]], p_lon[leftmost_col[i+1]],
            p_lat[distinct_rows[i+1]], p_lon[rightmost_col[i+1]],
            mid_top_lat, mid_top_lon);

        length_from_top += geodesic_distance(
            mid_bot_lat, mid_bot_lon, mid_top_lat, mid_top_lon);
    }

    // check the length criteria.
    // TODO: if we are here the widtrh critera was not met
    // so the following detection is based solely on the length?
    if (length_from_top > river_length)
    {
        // AR
        ar.min_width = static_cast<double>(min_width);
        ar.max_width = static_cast<double>(max_width);
        ar.length = static_cast<double>(length_from_top);
        return true;
    }

    return false;
}

// Junmin's function checkRightBoundary
// note: if land sea mask is not available land array
// must all be true.
template<typename T>
bool river_end_criteria(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const vector<bool> &land,
    const T *p_lat,
    const T *p_lon,
    T river_end_lat_low,
    T river_end_lon_low,
    T river_end_lat_high,
    T river_end_lon_high,
    atmospheric_river &ar)
{
    // locate component points within shoreline
    // box
    bool first_crossing = false;
    bool event_detected = false;

    T top_lat = T();
    T top_lon = T();
    T bot_lat = T();
    T bot_lon = T();

    std::vector<int> right_bound_col_idx;
    size_t count = con_comp_c.size();
    for (size_t i = 0; i < count; ++i)
    {
        T lat = p_lat[con_comp_r[i]];
        T lon = p_lon[con_comp_c[i]];

        if ((lat >= river_end_lat_low) && (lat <= river_end_lat_high)
            && (lon >= river_end_lon_low) && (lon <= river_end_lon_high))
        {
            if (!event_detected)
                event_detected = land[i];

            if (!first_crossing)
            {
                first_crossing = true;
                top_lat = lat;
                top_lon = lon;
            }
            bot_lat = lat;
            bot_lon = lon;
        }
    }

    ar.end_top_lat = top_lat;
    ar.end_top_lon = top_lon;
    ar.end_bot_lat = bot_lat;
    ar.end_bot_lon = bot_lon;

    return event_detected;
}

// Junmin's function
template<typename T>
void classify_event(
    const vector<int> &con_comp_r,
    const vector<int> &con_comp_c,
    const T *p_lat,
    const T *p_lon,
    T start_lat,
    T start_lon,
    atmospheric_river &ar)
{
    // classification determined by first detected point in event
    // is closer to left or to bottom
    T lat = p_lat[con_comp_r[0]];
    T lon = p_lon[con_comp_c[0]];

    ar.pe = false;
    if ((lat - start_lat) < (lon - start_lon))
        ar.pe = true; // PE
}

/*
* The main function that checks whether an AR event exists in
* given sub-plane of data. This currently applies only to the Western
* coast of the USA. return true if an ar is found.
*/
bool ar_detect(
    const_p_teca_variant_array lat,
    const_p_teca_variant_array lon,
    const_p_teca_variant_array land_sea_mask,
    const_p_teca_unsigned_int_array con_comp,
    unsigned long n_comp,
    double river_start_lat,
    double river_start_lon,
    double river_end_lat_low,
    double river_end_lon_low,
    double river_end_lat_high,
    double river_end_lon_high,
    double percent_in_mesh,
    double river_width,
    double river_length,
    double land_threshold_low,
    double land_threshold_high,
    atmospheric_river &ar)
{
    NESTED_TEMPLATE_DISPATCH_FP(
        const teca_variant_array_impl,
        lat.get(),
        1,

        NESTED_TEMPLATE_DISPATCH(
            const teca_variant_array_impl,
            land_sea_mask.get(),
            2,

            const NT1 *p_lat = dynamic_cast<TT1*>(lat.get())->get();
            const NT1 *p_lon = dynamic_cast<TT1*>(lon.get())->get();

            const NT2 *p_land_sea_mask
                = dynamic_cast<TT2*>(land_sea_mask.get())->get();

            NT1 start_lat = static_cast<NT1>(river_start_lat);
            NT1 start_lon = static_cast<NT1>(river_start_lon);
            NT1 end_lat_low = static_cast<NT1>(river_end_lat_low);
            NT1 end_lon_low = static_cast<NT1>(river_end_lon_low);
            NT1 end_lat_high = static_cast<NT1>(river_end_lat_high);
            NT1 end_lon_high = static_cast<NT1>(river_end_lon_high);

            unsigned long num_rows = lat->size();
            unsigned long num_cols = lon->size();

            // # in PE is % of points in regious mesh
            unsigned long num_rc = num_rows*num_cols;
            unsigned long thr_count = num_rc*percent_in_mesh/100.0;

            unsigned int *p_labels = con_comp->get();
            for (unsigned int i = 1; i <= n_comp; ++i)
            {
                // for all discrete connected component labels
                // verify if there exists an AR
                vector<int> con_comp_r;
                vector<int> con_comp_c;
                vector<bool> land;

                for (unsigned long r = 0, q = 0; r < num_rows; ++r)
                {
                    for (unsigned long c = 0; c < num_cols; ++c, ++q)
                    {
                        if (p_labels[q] == i)
                        {
                            // gather points of this connected component
                            con_comp_r.push_back(r);
                            con_comp_c.push_back(c);

                            // identify them as land or not
                            land.push_back(
                                (p_land_sea_mask[q] >= land_threshold_low)
                                && (p_land_sea_mask[q] < land_threshold_high));
                        }
                    }
                }

                // check for ar criteria
                unsigned long count = con_comp_r.size();
                if ((count > thr_count)
                    && river_end_criteria(
                        con_comp_r, con_comp_c, land,
                        p_lat, p_lon,
                        end_lat_low, end_lon_low,
                        end_lat_high, end_lon_high,
                        ar)
                    && river_geometric_criteria(
                        con_comp_r, con_comp_c, p_lat, p_lon,
                        river_length, river_width, ar))
                {
                    // determine if PE or AR
                    classify_event(
                        con_comp_r, con_comp_c, p_lat, p_lon,
                        start_lat, start_lon, ar);
                    return true;
                }
            }
            )
        )
    return false;
}

#if TECA_DEBUG > 0 && defined(TECA_HAS_VTK)
// helper to dump a dataset for debugging
int write_mesh(
    const const_p_teca_cartesian_mesh &mesh,
    const const_p_teca_variant_array &vapor,
    const const_p_teca_variant_array &thresh,
    const const_p_teca_variant_array &ccomp,
    const const_p_teca_variant_array &lsmask,
    const std::string &base_name)
{
    p_teca_cartesian_mesh m = teca_cartesian_mesh::New();
    m->copy_metadata(mesh);

    p_teca_array_collection pac = m->get_point_arrays();
    pac->append("vapor", std::const_pointer_cast<teca_variant_array>(vapor));
    pac->append("thresh", std::const_pointer_cast<teca_variant_array>(thresh));
    pac->append("ccomp", std::const_pointer_cast<teca_variant_array>(ccomp));
    pac->append("lsmask", std::const_pointer_cast<teca_variant_array>(lsmask));

    p_teca_programmable_source s = teca_programmable_source::New();
    s->set_execute_function(
        [m] (const teca_metadata &) -> const_p_teca_dataset
        { return m; }
        );

    p_teca_vtk_cartesian_mesh_writer w
        = teca_vtk_cartesian_mesh_writer::New();

    w->set_base_file_name(base_name);
    w->set_input_connection(s->get_output_port());
    w->update();

    return 0;
}
#endif

/* some of the datasets have date array with calcs already made!
    // get date
    // TODO -- handle date in a unform manner
    long YYYY = 0;
    long MM = 0;
    long DD = 0;
    const_p_teca_variant_array date
        = mesh->get_information_arrays()->get("date");

    if (date)
    {
        TEMPLATE_DISPATCH(
            const teca_variant_array_impl,
            date.get(),

            NT d = dynamic_cast<TT*>(date.get())->get()[0];
            YYYY = static_cast<long>(d)/10000l;
            MM = (static_cast<long>(d) - YYYY*10000l)/100l;
            DD = static_cast<long>(d) - YYYY*10000l - MM*100l;
            )
    }
*/


/*****************************************************************************
 *
 * This file is part of Mapnik (c++ mapping toolkit)
 *
 * Copyright (C) 2015 Artem Pavlenko
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *****************************************************************************/

#ifndef MAPNIK_GEOMETRY_IS_SIMPLE_HPP
#define MAPNIK_GEOMETRY_IS_SIMPLE_HPP

#include <boost/version.hpp>

// only Boost >= 1.56 contains the is_simple function
#if BOOST_VERSION >= 105600

#include <mapnik/geometry.hpp>
#include <mapnik/geometry_adapters.hpp>
#include <boost/geometry/algorithms/is_simple.hpp>

namespace mapnik { namespace geometry {

namespace detail {

struct geometry_is_simple
{
    using result_type = bool;

    template <typename T>
    result_type operator() (geometry<T> const& geom) const
    {
        return mapnik::util::apply_visitor(*this, geom);
    }

    result_type operator() (geometry_empty const& ) const
    {
        // An empty geometry has no anomalous geometric points, such as self intersection or self tangency.
        // Therefore, we will return true
        return true;
    }

    template <typename T>
    result_type operator() (geometry_collection<T> const& collection) const
    {
        for (auto const& geom : collection)
        {
            if ( !(*this)(geom)) return false;
        }
        return true;
    }

    template <typename T>
    result_type operator() (point<T> const& pt) const
    {
        return boost::geometry::is_simple(pt);
    }
    template <typename T>
    result_type operator() (line_string<T> const& line) const
    {
        if (line.empty())
        {
            // Prevent an empty line_string from segfaulting in boost geometry 1.58
            // once it is fixed this can be removed
            // https://svn.boost.org/trac/boost/ticket/11709
            return true;
        }
        return boost::geometry::is_simple(line);
    }
    template <typename T>
    result_type operator() (polygon<T> const& poly) const
    {
        return boost::geometry::is_simple(poly);
    }
    template <typename T>
    result_type operator() (multi_point<T> const& multi_pt) const
    {
        if (multi_pt.empty())
        {
            // This return is due to bug in boost geometry once it is fixed it can be removed
            // https://svn.boost.org/trac/boost/ticket/11710
            return true;
        }
        return boost::geometry::is_simple(multi_pt);
    }
    template <typename T>
    result_type operator() (multi_line_string<T> const& multi_line) const
    {
        if (multi_line.empty())
        {
            // This return is due to bug in boost geometry once it is fixed it can be removed
            // https://svn.boost.org/trac/boost/ticket/11710
            return true;
        }
        for (auto const& line : multi_line)
        {
            if (!(*this)(line)) return false;
        }
        return true;
    }
    template <typename T>
    result_type operator() (multi_polygon<T> const& multi_poly) const
    {
        if (multi_poly.empty())
        {
            // This return is due to bug in boost geometry once it is fixed it can be removed
            // https://svn.boost.org/trac/boost/ticket/11710
            return true;
        }
        for (auto const& poly : multi_poly)
        {
            if (!(*this)(poly)) return false;
        }
        return true;
    }

};

}

template <typename T>
inline bool is_simple(T const& geom)
{
    return detail::geometry_is_simple() (geom);
}

template <typename T>
inline bool is_simple(mapnik::geometry::geometry<T> const& geom)
{
    return util::apply_visitor(detail::geometry_is_simple(), geom);
}

}}

#endif // BOOST_VERSION >= 1.56
#endif // MAPNIK_GEOMETRY_IS_SIMPLE_HPP


// graph-tool -- a general graph modification and manipulation thingy
//
// Copyright (C) 2006-2018 Tiago de Paula Peixoto <tiago@skewed.de>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 3
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "graph_modularity.hh"

#include <boost/mpl/push_back.hpp>
#include <boost/python.hpp>

using namespace std;
using namespace boost;
using namespace graph_tool;

double modularity(GraphInterface& gi, boost::any weight, boost::any property)
{
    double Q = 0;

    typedef UnityPropertyMap<int, GraphInterface::edge_t> weight_map_t;
    typedef boost::mpl::push_back<edge_scalar_properties, weight_map_t>::type
        edge_props_t;

    if(weight.empty())
        weight = weight_map_t();

    run_action<>()
        (gi, [&](auto& g, auto& w, auto& b){ Q = get_modularity(g, w, b);},
         edge_props_t(), vertex_scalar_properties())
        (weight, property);

    return Q;
}

using namespace boost::python;

void export_modularity()
{
    def("modularity", &modularity);
}


#ifndef QIRAST
#define QIRAST
#define BOOST_SPIRIT_USE_PHOENIX_V3 1

#include <vector>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/fusion/include/std_tuple.hpp>
#include <boost/variant.hpp>

using namespace boost::spirit;
namespace phx = boost::phoenix;

namespace ast {

    struct lt;
    struct lte;
    struct gt;
    struct gte;
    struct eql;
    struct neq;
    struct add;
    struct sub;
    struct mul;
    struct div;
    struct mod;

    struct ifstat;

    template<typename T>
    struct vdec;

    template<typename Op>
    struct binary_op;

    template<typename T>
    struct unary_op;

    struct var_ref {
        std::string id;

        var_ref(const std::string& id_) : id(id_) {}
    };

    struct assign;

    using value = boost::variant<
        int,
        bool,
        boost::recursive_wrapper<ifstat>,
        boost::recursive_wrapper<vdec<int>>,
        boost::recursive_wrapper<vdec<bool>>,
        boost::recursive_wrapper<binary_op<lt>>,
        boost::recursive_wrapper<binary_op<lte>>,
        boost::recursive_wrapper<binary_op<gt>>,
        boost::recursive_wrapper<binary_op<gte>>,
        boost::recursive_wrapper<binary_op<eql>>,
        boost::recursive_wrapper<binary_op<neq>>,
        boost::recursive_wrapper<binary_op<add>>,
        boost::recursive_wrapper<binary_op<sub>>,
        boost::recursive_wrapper<binary_op<mul>>,
        boost::recursive_wrapper<binary_op<div>>,
        boost::recursive_wrapper<binary_op<mod>>,
        boost::recursive_wrapper<var_ref>,
        boost::recursive_wrapper<assign>
    >;

    struct ifstat {
        value cond;
        std::vector<value> then_stat;
        std::vector<value> else_stat;

        ifstat(const value& cond_, const std::vector<value>& then_stat_, const std::vector<value>& else_stat_)
            : cond(cond_), then_stat(then_stat_), else_stat(else_stat_) {}
    };

    template<typename T>
    struct vdec {
        std::string id;
        value val;

        vdec(std::string& id_, const value& val_) : id(id_), val(val_) {}
    };

    template<typename Op>
    struct binary_op {
        value lhs;
        value rhs;

        binary_op(const value& lhs_, const value& rhs_) : lhs(lhs_), rhs(rhs_) {}
    };

    template<typename T>
    struct unary_op {
        value val;

        unary_op(const value& val_) : val(val_) {}
    };

    struct assign {
        std::string id;
        value val;

        assign(std::string& id_, const value& val_) : id(id_), val(val_) {}
    };
}

#endif


#include <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <numeric>
#include <unordered_map>
#include <limits>
#include <boost/algorithm/string/find.hpp>
#include <cmath>
#include <boost/range/algorithm/count.hpp>

class Reader
{
    public:
        Reader() = default;

        std::vector<std::string> get_ingredients(std::string str)
        {
            std::vector<std::string> elements;
            while (!str.empty())
            {
                elements.push_back(str.substr(0, str.find(" ")));
                str = str.substr(str.find(" ") < SIZE_MAX ? str.find(" ") + 1 : str.size());
            }
            return elements;
        }

        std::vector<std::string> get_allergens(std::string str)
        {
            std::vector<std::string> elements;
            while (!str.empty())
            {
                elements.push_back(str.substr(0, str.find(",")));
                str = str.substr(str.find(",") < SIZE_MAX ? str.find(",") + 2 : str.size());
            }
            return elements;
        }

        // key - allergen, value - all ingredients named in same line as allergen
        std::unordered_map<std::string, std::vector<std::string>> map_allergens_and_ingredients;
        std::vector<std::string> all_ingredients;
        std::vector<std::string> all_ingredients_with_allergens;

        bool some_allergens_contains_more_options()
        {
            for(auto pair : map_allergens_and_ingredients)
            {
                if(pair.second.size() > 1) return true;
            }
            return false;
        }

        std::vector<std::string> find_max_value(std::unordered_map<std::string, int> count)
        {
            int max = std::numeric_limits<int>::min();
            std::vector<std::string> v;
            for(auto pair : count)
            {
                if(pair.second > max)
                {
                    max = pair.second;
                    v.clear();
                    v.push_back(pair.first);
                }
                else if(pair.second == max)
                {
                    v.push_back(pair.first);
                }
            }
            return v;
        }

        void take_only_best_ingredients()
        {
            for(auto pair : map_allergens_and_ingredients)
            {
                // key-ingredient, value-count ingredient
                std::unordered_map<std::string, int> count;
                for(int i = 0; i < pair.second.size(); i++)
                {
                    if(count.find(pair.second[i]) == count.end())
                    {
                        count[pair.second[i]] = 1;
                    }
                    else
                    {
                        count[pair.second[i]]++;
                    }
                }
                std::vector<std::string> max_str = find_max_value(count);
                std::vector<std::string> ingredients;
                for(int i = 0; i < pair.second.size(); i++)
                {
                    if(std::find(max_str.begin(), max_str.end(), pair.second[i]) != max_str.end() &&
                    std::find(ingredients.begin(), ingredients.end(), pair.second[i]) == ingredients.end())
                    {
                        ingredients.push_back(pair.second[i]);
                    }
                }
                map_allergens_and_ingredients[pair.first] = ingredients;
            }
        }

        void process_allergens_and_ingredients()
        {
            take_only_best_ingredients();
            std::unordered_map<std::string, std::vector<std::string>>
            new_map_allergens_and_ingredients = map_allergens_and_ingredients;
            while(some_allergens_contains_more_options())
            {
                for(auto& pair : new_map_allergens_and_ingredients)
                {
                    if(pair.second.size() != 1) continue;
                    for(auto& pair2 : new_map_allergens_and_ingredients)
                    {
                        if(std::find(pair2.second.begin(), pair2.second.end(), pair.second[0])
                        == pair2.second.end()) continue;
                        std::vector<std::string> new_ingredients;
                        for(std::string ingredient : new_ingredients)
                        {
                            if(ingredient != pair.second[0]) new_ingredients.push_back(ingredient);
                        }
                        new_map_allergens_and_ingredients[pair2.first] = new_ingredients;
                    }
                }
                map_allergens_and_ingredients = new_map_allergens_and_ingredients;
            }
        }

        Reader(std::string file_name)
        {
            std::ifstream file(file_name);
            std::string line;
            if(file.is_open())
            {
                while(std::getline(file, line))
                {
                    std::vector<std::string> ingredients = get_ingredients(line.substr(0, line.find("(") - 1));
                    all_ingredients.insert(all_ingredients.begin(), ingredients.begin(), ingredients.end());
                    std::vector<std::string> allergens = get_allergens(line.substr(line.find("contains") + 9,
                    line.size() - line.find("contains") - 8 - 2));
                    for(std::string allergen : allergens)
                    {
                        map_allergens_and_ingredients[allergen].insert(map_allergens_and_ingredients[allergen].begin(),
                        ingredients.begin(), ingredients.end());
                    }
                }
            }
            process_allergens_and_ingredients();
        }

        int ingredients_without_allergens_appearance()
        {
            int count = 0;
            for(auto ingredient : all_ingredients)
            {
                if(std::find(all_ingredients_with_allergens.begin(),
                all_ingredients_with_allergens.end(), ingredient) !=
                all_ingredients_with_allergens.end())
                {
                    count++;
                }
            }
            return count;
        }
};

void part1(const std::string& file_name)
{
    std::cout << "======\nPart 1\n======\n";
    Reader reader(file_name);
    std::cout << " = " << reader.ingredients_without_allergens_appearance() << '\n';
}

void part2(const std::string& file_name)
{
    std::cout << "======\nPart 2\n======\n";
}

int main()
{
    const std::string file_name = "/home/daria/Documents/AoC2020/input/day21.txt";
    part1(file_name);
    std::cout << '\n';
    part2(file_name);
    std::cout << '\n';
}


// Copyright (c) 2016 The UUV Simulator Authors.
// All rights reserved.
//
// 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.

/// \file UnderwaterCurrentPlugin.cc

#include <boost/algorithm/string.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include <gazebo/gazebo.hh>
#include <gazebo/msgs/msgs.hh>
#include <gazebo/physics/Link.hh>
#include <gazebo/physics/Model.hh>
#include <gazebo/physics/PhysicsEngine.hh>
#include <gazebo/physics/World.hh>
#include <gazebo/transport/TransportTypes.hh>
#include <sdf/sdf.hh>

#include <math.h>

#include <uuv_world_plugins/UnderwaterCurrentPlugin.hh>

using namespace gazebo;

GZ_REGISTER_WORLD_PLUGIN(UnderwaterCurrentPlugin)

UnderwaterCurrentPlugin::UnderwaterCurrentPlugin() {
  // Doing nothing for now
}

UnderwaterCurrentPlugin::~UnderwaterCurrentPlugin() {
  this->updateConnection.reset();
}

void UnderwaterCurrentPlugin::Load(physics::WorldPtr _world, sdf::ElementPtr _sdf) {
  GZ_ASSERT(_world != NULL, "World pointer is invalid");
  GZ_ASSERT(_sdf != NULL, "SDF pointer is invalid");

  this->world = _world;
  this->sdf = _sdf;

  // Read the namespace for topics and services
  this->ns = _sdf->Get<std::string>("namespace");

  gzmsg << "Loading underwater world..." << std::endl;
  // Initializing the transport node
  this->node = transport::NodePtr(new transport::Node());
  this->node->Init(this->world->Name());

  // Retrieve the current velocity configuration, if existent
  GZ_ASSERT(this->sdf->HasElement("constant_current"),
    "Constant current configuration not available");
  sdf::ElementPtr currentVelocityParams = this->sdf->GetElement(
    "constant_current");

  // Read the topic names from the SDF file
  if (currentVelocityParams->HasElement("topic"))
    this->currentVelocityTopic =
      currentVelocityParams->Get<std::string>("topic");
  else
    this->currentVelocityTopic = "current_velocity";

  GZ_ASSERT(!this->currentVelocityTopic.empty(),
    "Empty current velocity topic");

  if (currentVelocityParams->HasElement("velocity")) {
    sdf::ElementPtr elem = currentVelocityParams->GetElement("velocity");
    if (elem->HasElement("mean"))
        this->currentVelModel.mean = elem->Get<double>("mean");
    if (elem->HasElement("min"))
        this->currentVelModel.min = elem->Get<double>("min");
    if (elem->HasElement("max"))
        this->currentVelModel.max = elem->Get<double>("max");
    if (elem->HasElement("mu"))
        this->currentVelModel.mu = elem->Get<double>("mu");
    if (elem->HasElement("noiseAmp"))
        this->currentVelModel.noiseAmp = elem->Get<double>("noiseAmp");

    GZ_ASSERT(this->currentVelModel.min < this->currentVelModel.max, "Invalid current velocity limits");
    GZ_ASSERT(this->currentVelModel.mean >= this->currentVelModel.min, "Mean velocity must be greater than minimum");
    GZ_ASSERT(this->currentVelModel.mean <= this->currentVelModel.max, "Mean velocity must be smaller than maximum");
    GZ_ASSERT(this->currentVelModel.mu >= 0 && this->currentVelModel.mu < 1, "Invalid process constant");
    GZ_ASSERT(this->currentVelModel.noiseAmp < 1 && this->currentVelModel.noiseAmp >= 0, "Noise amplitude has to be smaller than 1");
  }

  this->currentVelModel.var = this->currentVelModel.mean;
  gzmsg << "Current velocity [m/s] Gauss-Markov process model:" << std::endl;
  this->currentVelModel.Print();

  if (currentVelocityParams->HasElement("horizontal_angle")) {
    sdf::ElementPtr elem = currentVelocityParams->GetElement("horizontal_angle");

    if (elem->HasElement("mean"))
      this->currentHorzAngleModel.mean = elem->Get<double>("mean");
    if (elem->HasElement("min"))
      this->currentHorzAngleModel.min = elem->Get<double>("min");
    if (elem->HasElement("max"))
      this->currentHorzAngleModel.max = elem->Get<double>("max");
    if (elem->HasElement("mu"))
      this->currentHorzAngleModel.mu = elem->Get<double>("mu");
    if (elem->HasElement("noiseAmp"))
      this->currentHorzAngleModel.noiseAmp = elem->Get<double>("noiseAmp");

    GZ_ASSERT(this->currentHorzAngleModel.min < this->currentHorzAngleModel.max, "Invalid current horizontal angle limits");
    GZ_ASSERT(this->currentHorzAngleModel.mean >= this->currentHorzAngleModel.min, "Mean horizontal angle must be greater than minimum");
    GZ_ASSERT(this->currentHorzAngleModel.mean <= this->currentHorzAngleModel.max, "Mean horizontal angle must be smaller than maximum");
    GZ_ASSERT(this->currentHorzAngleModel.mu >= 0 && this->currentHorzAngleModel.mu < 1, "Invalid process constant");
    GZ_ASSERT(this->currentHorzAngleModel.noiseAmp < 1 && this->currentHorzAngleModel.noiseAmp >= 0, "Noise amplitude for horizontal angle has to be between 0 and 1");
  }

  this->currentHorzAngleModel.var = this->currentHorzAngleModel.mean;
  gzmsg << "Current velocity horizontal angle [rad] Gauss-Markov process model:" << std::endl;
  this->currentHorzAngleModel.Print();

  if (currentVelocityParams->HasElement("vertical_angle"))  {

    sdf::ElementPtr elem = currentVelocityParams->GetElement("vertical_angle");

    if (elem->HasElement("mean"))
      this->currentVertAngleModel.mean = elem->Get<double>("mean");
    if (elem->HasElement("min"))
      this->currentVertAngleModel.min = elem->Get<double>("min");
    if (elem->HasElement("max"))
      this->currentVertAngleModel.max = elem->Get<double>("max");
    if (elem->HasElement("mu"))
      this->currentVertAngleModel.mu = elem->Get<double>("mu");
    if (elem->HasElement("noiseAmp"))
      this->currentVertAngleModel.noiseAmp = elem->Get<double>("noiseAmp");

    GZ_ASSERT(this->currentVertAngleModel.min < this->currentVertAngleModel.max, "Invalid current vertical angle limits");
    GZ_ASSERT(this->currentVertAngleModel.mean >= this->currentVertAngleModel.min, "Mean vertical angle must be greater than minimum");
    GZ_ASSERT(this->currentVertAngleModel.mean <= this->currentVertAngleModel.max, "Mean vertical angle must be smaller than maximum");
    GZ_ASSERT(this->currentVertAngleModel.mu >= 0 && this->currentVertAngleModel.mu < 1, "Invalid process constant");
    GZ_ASSERT(this->currentVertAngleModel.noiseAmp < 1 && this->currentVertAngleModel.noiseAmp >= 0, "Noise amplitude for vertical angle has to be between 0 and 1");
  }

  this->currentVertAngleModel.var = this->currentVertAngleModel.mean;
  gzmsg << "Current velocity horizontal angle [rad] Gauss-Markov process model:" << std::endl;
  this->currentHorzAngleModel.Print();

  // Initialize the time update
  this->lastUpdate = this->world->SimTime();
  this->currentVelModel.lastUpdate = this->lastUpdate.Double();
  this->currentHorzAngleModel.lastUpdate = this->lastUpdate.Double();
  this->currentVertAngleModel.lastUpdate = this->lastUpdate.Double();

  // Advertise the current velocity topic
  this->publishers[this->currentVelocityTopic] = this->node->Advertise<msgs::Vector3d>(this->ns + "/" + this->currentVelocityTopic);

  gzmsg << "Current velocity topic name: " << this->ns + "/" + this->currentVelocityTopic << std::endl;

  // Connect the update event
  this->updateConnection = event::Events::ConnectWorldUpdateBegin(boost::bind(&UnderwaterCurrentPlugin::Update, this, _1));

  gzmsg << "Underwater current plugin loaded!" << std::endl << "\tWARNING: Current velocity calculated in the ENU frame" << std::endl;
}

void UnderwaterCurrentPlugin::Init() {
  // Doing nothing for now
}

void UnderwaterCurrentPlugin::Update(const common::UpdateInfo & /** _info */) {

  common::Time time = this->world->SimTime();

  // Calculate the flow velocity and the direction using the Gauss-Markov model
  // Update current velocity
  double currentVelMag = this->currentVelModel.Update(time.Double());

  // Update current horizontal direction around z axis of flow frame
  double horzAngle = this->currentHorzAngleModel.Update(time.Double());

  // Update current horizontal direction around z axis of flow frame
  double vertAngle = this->currentVertAngleModel.Update(time.Double());

  // Generating the current velocity vector as in the NED frame
  this->currentVelocity = ignition::math::Vector3d(
      currentVelMag * cos(horzAngle) * cos(vertAngle),
      currentVelMag * sin(horzAngle) * cos(vertAngle),
      currentVelMag * sin(vertAngle));

  // Update time stamp
  this->lastUpdate = time;
  this->PublishCurrentVelocity();
}

void UnderwaterCurrentPlugin::PublishCurrentVelocity() {
  msgs::Vector3d currentVel;
  msgs::Set(&currentVel, ignition::math::Vector3d(this->currentVelocity.X(), this->currentVelocity.Y(), this->currentVelocity.Z()));
  this->publishers[this->currentVelocityTopic]->Publish(currentVel);
}


#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace boost::gregorian;

int diffDays(std::string s1, std::string s2) {
    date start(from_simple_string(s1));
    date end(from_simple_string(s2));
    days diff = start - end;
    return std::abs(diff.days());
}

int main() {
    std::string s1 = "2020-07-06";
    std::string s2 = to_simple_string(day_clock::local_day());
    std::cout << "diff: " << diffDays(s1, s2) << " days" << std::endl;
    return 0;
}


// Copyright (c) 2005 - 2014 Marc de Kamps
// All rights reserved.
//
// 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 copyright holder 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 OWNER 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.
//
//      If you use this software in work leading to a scientific publication, you should include a reference there to
//      the 'currently valid reference', which can be found at http://miind.sourceforge.net

#include <fstream>
#include <boost/timer/timer.hpp>
#include <TwoDLib.hpp>
#include <MPILib/include/utilities/Exception.hpp>

bool TestEquality(const TwoDLib::Mesh& mesh, const TwoDLib::Ode2DSystemGroup& group, MPILib::Number nmesh ){
	std::vector<double> vals(nmesh,0.0);

	for (MPILib::Index i = 0; i < mesh.NrStrips(); i++)
		for (MPILib::Index j = 0; j < mesh.NrCellsInStrip(i); j++){
			for (MPILib::Number m = 0; m < nmesh; m++)
			{
				vals[m] = group.Mass()[group.Map(m,i,j)];
			}
			double val = vals[0];
			for (MPILib::Number m = 0; m < nmesh; m++)
				if (vals[m] != val){
					std::cout << "Difference in: " << i << " " << j << " for mesh: " << m <<std::endl;
					exit(0);
				}
		}
	return true;
}

int main(int argc, char** argv)
{
  try {
  std::cout << "Start. " << std::endl;
  TwoDLib::Mesh mesh1("condee2a5ff4-0087-4d69-bae3-c0a223d03693.model");
  TwoDLib::Mesh mesh2("condee2a5ff4-0087-4d69-bae3-c0a223d03693.model");
  TwoDLib::Mesh mesh3("condee2a5ff4-0087-4d69-bae3-c0a223d03693.model");

  std::ifstream ifstrev("condee2a5ff4-0087-4d69-bae3-c0a223d03693.rev");
  std::vector<TwoDLib::Redistribution> vec_rev1 = TwoDLib::ReMapping(ifstrev);
  std::vector<TwoDLib::Redistribution> vec_rev2 = vec_rev1;
  std::vector<TwoDLib::Redistribution> vec_rev3 = vec_rev1;

  std::ifstream ifstres("condee2a5ff4-0087-4d69-bae3-c0a223d03693.res");
  std::vector<TwoDLib::Redistribution> vec_res1 = TwoDLib::ReMapping(ifstres);
  std::vector<TwoDLib::Redistribution> vec_res2 = vec_res1;
  std::vector<TwoDLib::Redistribution> vec_res3 = vec_res1;

  std::vector<TwoDLib::Mesh> mesh_vec{ mesh1, mesh2, mesh3 };
  std::vector<std::vector<TwoDLib::Redistribution> > vec_rev{ vec_rev1, vec_rev2, vec_rev3 };
  std::vector<std::vector<TwoDLib::Redistribution> > vec_res{ vec_res1, vec_res2, vec_res3 };

  std::cout << "Building group. " << std::endl;
  TwoDLib::Ode2DSystemGroup group(mesh_vec,vec_rev,vec_res);
  group.Initialize(0,0,0);
  group.Initialize(1,0,0);
  group.Initialize(2,0,0);

  std::cout << "Building matrices." << std::endl;
  TwoDLib::TransitionMatrix mat1("condee2a5ff4-0087-4d69-bae3-c0a223d03693_0_0.05_0_0_.mat");
  TwoDLib::TransitionMatrix mat2("condee2a5ff4-0087-4d69-bae3-c0a223d03693_0_0.05_0_0_.mat");
  TwoDLib::TransitionMatrix mat3("condee2a5ff4-0087-4d69-bae3-c0a223d03693_0_0.05_0_0_.mat");

  std::cout << "Filling CSR." << std::endl;
  TwoDLib::CSRMatrix csrmat1(mat1,group,0);
  TwoDLib::CSRMatrix csrmat2(mat2,group,1);
  TwoDLib::CSRMatrix csrmat3(mat3,group,2);

  std::cout << "Creating matrix vector." << std::endl;

  std::vector<TwoDLib::CSRMatrix> vecmat{csrmat1, csrmat2, csrmat3};

  // create a vector for the derivative
  std::vector<double> dydt(group.Mass().size());

  // Some parameters to mess around with are here
  MPILib::Time t_sim=1.0;
  MPILib::Number n_steps = static_cast<MPILib::Number>(floor(t_sim/mesh1.TimeStep()));
  TwoDLib::MasterParameter par(10);
  double h = 1./par._N_steps*mesh1.TimeStep();

  std::cout << "Defining rates" << std::endl;
  vector<double> vecrates{800., 900., 1000.};

  boost::timer::auto_cpu_timer t;
  std::cout << "Starting loop." << std::endl;
  for (MPILib::Index i = 0; i < n_steps; i++){
    group.Evolve();

    for (MPILib::Index i_part = 0; i_part < par._N_steps; i_part++ ){
      TwoDLib::ClearDerivative(dydt);
      TwoDLib::CalculateDerivative(group,dydt,vecmat,vecrates);
      TwoDLib::AddDerivative(group.Mass(),dydt,h);
    }

    group.RedistributeProbability();
    group.RemapReversal();

    if (i%100 == 0) std::cout << mesh1.TimeStep()*i << " " << group.F()[0] << " " << group.F()[1] <<  " " << group.F()[2] <<  std::endl;
  }
  t.stop();
  std::cout << "Overall time spend\n";
  t.report();

  std::ofstream ofst1("dens1.dat");
  std::ofstream ofst2("dens2.dat");
  std::ofstream ofst3("dens3.dat");

  std::vector<std::ostream*> vec_stream{ &ofst1, &ofst2, &ofst3 };
  group.Dump(vec_stream);
}
catch(...){
    std::cout << "Oops" << std::endl;
}
  return 0;
}