#pragma once

#include <atomic>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <mutex>

#include <boost/variant.hpp>

namespace prometheus {

typedef std::map<std::string, std::string> Labels;

class CounterSample {
 public:
    double value;
};

class GaugeSample {
 public:
    double value;
};

class BucketSample {
 public:
    uint64_t cumulativeCount;
    double upperBound;
};

class HistogramSample {
 public:
    uint64_t count;
    double sum;
    std::vector<BucketSample> buckets;
};

class QuantileSample {
 public:
    double quantile;
    double value;
};

class SummarySample {
 public:
    double count;
    double sum;
    std::vector<QuantileSample> quantiles;
};

class UntypedSample {
 public:
    double value;
};

typedef boost::variant<CounterSample, GaugeSample, HistogramSample, SummarySample, UntypedSample> Sample;

class MetricFamilySample {
 public:
    Labels labels;
    Sample sample;
};

enum class MetricType { COUNTER, GAUGE, HISTOGRAM, SUMMARY, UNTYPED };

class MetricFamilySamples
{
 public:
    std::string name;
    std::string help;
    MetricType type;
    std::vector<MetricFamilySample> samples;
};

class Collector;
typedef std::shared_ptr<Collector> CollectorPtr;

class Collector
{
 public:
    virtual std::vector<MetricFamilySamples> collect() = 0;
};

class CounterValue;
typedef std::shared_ptr<CounterValue> CounterValuePtr;

class CounterValue {
 public:
    void inc() { inc(1.0); }
    void inc(double val) {
        auto current = counter_.load();
        while (!counter_.compare_exchange_weak(current, current + val)) {
        }
    }
    CounterSample collect() { return CounterSample{counter_}; }
 protected:
    std::atomic<double> counter_ = { 0.0 };
};

class GaugeValue;
typedef std::shared_ptr<GaugeValue> GaugeValuePtr;

class GaugeValue {
 public:
    void inc() { inc(1.0); }
    void inc(double val) {
        auto current = gauge_.load();
        while (!gauge_.compare_exchange_weak(current, current + val)) {
        }
    }
    void dec() { dec(1.0); }
    void dec(double val) {
        auto current = gauge_.load();
        while (!gauge_.compare_exchange_weak(current, current - val)) {
        }
    }
    void set(double val) {
        gauge_ = val;
    }
    GaugeSample collect() { return GaugeSample{gauge_}; }
 protected:
    std::atomic<double> gauge_ = { 0.0 };
};

class HistogramValue;
typedef std::shared_ptr<HistogramValue> HistogramValuePtr;

class Bucket {
 public:
    Bucket(double limit) : limit_(limit) {}
    std::atomic<uint64_t> count_ = { 0 };
    double limit_ = { 0.0 };
};

class HistogramValue {
 public:
    HistogramValue(std::vector<double> buckets) {
        for(auto b : buckets) {
            std::unique_ptr<Bucket> bucket(new Bucket(b));
            buckets_.push_back(std::move(bucket));
        }
    }

    void observe(double val) {
        for (auto& b : buckets_) {
            //put the value into all buckets which has a limit above the value
            if (b->limit_ >= val) {
                b->count_++;
            }
        }
        count_++;
        auto current = sum_.load();
        while (!sum_.compare_exchange_weak(current, current + val)) {
        }
    }
    HistogramSample collect() {
        std::vector<BucketSample> bucketSamples;
        for (auto& b : buckets_) {
            bucketSamples.push_back(BucketSample{b->count_, b->limit_});
        }
        bucketSamples.push_back(BucketSample{count_, std::numeric_limits<double>::infinity()});
        return HistogramSample{count_, sum_, bucketSamples};
    }
 protected:
    std::atomic<uint64_t> count_ = { 0 };
    std::atomic<double> sum_ = { 0.0 };
    std::vector<std::unique_ptr<Bucket>> buckets_;
};

class UntypedValue {
 public:
    void set(double value) {
        value_ = value;
    }
    UntypedSample collect() {
        return UntypedSample{value_};
    }
 protected:
    std::atomic<double> value_;
};

// Convert a ValueType to a MetricType enum
template<typename T> struct GetType { static MetricType type(); };
template<> inline MetricType GetType<CounterValue>::type()   { return MetricType::COUNTER; }
template<> inline MetricType GetType<GaugeValue>::type()     { return MetricType::GAUGE; }
template<> inline MetricType GetType<HistogramValue>::type() { return MetricType::HISTOGRAM; }
template<> inline MetricType GetType<UntypedValue>::type()   { return MetricType::UNTYPED; }

// The simple collector is a the basic class for all simple
// collectors. A simple collector is one which can just collect one
// metric type.
template<typename T>
class SimpleCollector : public Collector, public std::enable_shared_from_this<SimpleCollector<T> >
{
 public:

    static std::shared_ptr<SimpleCollector<T> > create(const std::string& name, const std::string& help, std::function< std::shared_ptr<T>(void)> typeCreator)
    {
        auto ptr = std::shared_ptr<SimpleCollector<T> >(new SimpleCollector<T>(name, help, typeCreator));
        ptr->init();
        return ptr;
    }
    void init() {
    }

    SimpleCollector(const std::string& n, const std::string& h, std::function< std::shared_ptr<T>(void)> typeCreator) : name(n), help(h), typeCreator_(typeCreator) { }
    std::shared_ptr<T> labels() {
        // return default metric without labels
        return labels(Labels());
    }

    std::shared_ptr<T> labels(Labels labels) {
        std::lock_guard<std::mutex> lock(mutex_);
        std::shared_ptr<T> metric = metricMap[labels];
        if (!metric) {
            metric = typeCreator_();
            metricMap[labels] = metric;
        }
        return metric;
    }

    void remove(Labels labels) { metricMap.erase(labels); }
    void clear() { metricMap.clear(); }
    MetricFamilySamples collectSamples() {
        MetricFamilySamples samples {name, help, GetType<T>::type()};

        for (auto c : metricMap) {
            samples.samples.push_back(MetricFamilySample {c.first, c.second->collect() });
        }
        return samples;
    };
    std::vector<MetricFamilySamples> collect() {
        return { collectSamples() };
    }
    std::string name;
    std::string help;
    std::map<Labels, std::shared_ptr<T> > metricMap;
 private:
    std::function< std::shared_ptr<T> (void)> typeCreator_;
    std::mutex mutex_;
};

typedef SimpleCollector<CounterValue> Counter;
typedef std::shared_ptr<Counter> CounterPtr;

typedef SimpleCollector<GaugeValue> Gauge;
typedef std::shared_ptr<Gauge> GaugePtr;

typedef SimpleCollector<HistogramValue> Histogram;
typedef std::shared_ptr<Histogram> HistogramPtr;

typedef SimpleCollector<UntypedValue> Untyped;
typedef std::shared_ptr<Untyped> UntypedPtr;

class CollectorRegistry;
typedef std::shared_ptr<CollectorRegistry> CollectorRegistryPtr;

class CollectorRegistry {
 public:
    // Using add and remove since register is a reserved keyword.
    void add(CollectorPtr collector) {
        std::lock_guard<std::mutex> lock(mutex_);
        collectors.push_back(collector);
    }
    void remove(CollectorPtr collector);
    std::vector<CollectorPtr> collectors;
 private:
    std::mutex mutex_;
};

class DefaultCollectorRegistry {
 public:
    static CollectorRegistryPtr instance() {
        static std::mutex mutex_;
        static CollectorRegistryPtr registry_;
        std::lock_guard<std::mutex> lock(mutex_);
        if (!registry_) {
            registry_ = std::make_shared<CollectorRegistry>();
        }
        return registry_;
    }
};

class CounterBuilder {
 public:
    CounterBuilder name(const std::string& name) { name_ = name; return *this; }
    CounterBuilder help(const std::string& help) { help_ = help; return *this; }

    CounterPtr build() {
        return Counter::create(name_, help_, [](){ return std::make_shared<CounterValue>(); });
    }
    CounterPtr add(CollectorRegistryPtr registry = DefaultCollectorRegistry::instance()) {
        CounterPtr ptr = build();
        registry->add(ptr);
        return ptr;

    }
 private:
    std::string name_ = "default";
    std::string help_ = "default help";
};

class GaugeBuilder {
 public:
    GaugeBuilder name(const std::string& name) { name_ = name; return *this; }
    GaugeBuilder help(const std::string& help) { help_ = help; return *this; }

    GaugePtr build() {
        return Gauge::create(name_, help_, [](){ return std::make_shared<GaugeValue>(); });
    }
    GaugePtr add(CollectorRegistryPtr registry = DefaultCollectorRegistry::instance()) {
        GaugePtr ptr = build();
        registry->add(ptr);
        return ptr;

    }
 private:
    std::string name_ = "default";
    std::string help_ = "default help";
};

class HistogramBuilder {
 public:
    HistogramBuilder name(const std::string& name) { name_ = name; return *this; }
    HistogramBuilder help(const std::string& help) { help_ = help; return *this; }
    HistogramBuilder buckets(const std::vector<double>& buckets) { buckets_ = buckets; return *this; }

    HistogramPtr build() {
        std::vector<double> buckets = buckets_;
        return Histogram::create(name_, help_, [buckets](){return std::make_shared<HistogramValue>(buckets); });
    }
    HistogramPtr add(CollectorRegistryPtr registry = DefaultCollectorRegistry::instance()) {
        HistogramPtr ptr = build();
        registry->add(ptr);
        return ptr;

    }
 private:
    std::string name_ = "default";
    std::string help_ = "default help";
    std::vector<double> buckets_ = { .005, .01, .025, .05, .1, .25, .5, 1, 2.5, 5, 10 };
};

class UntypedBuilder {
 public:
    UntypedBuilder name(const std::string& name) { name_ = name; return *this; }
    UntypedBuilder help(const std::string& help) { help_ = help; return *this; }

    UntypedPtr build() {
        return Untyped::create(name_, help_, [](){ return std::make_shared<UntypedValue>(); });
    }
    UntypedPtr add(CollectorRegistryPtr registry = DefaultCollectorRegistry::instance()) {
        UntypedPtr ptr = build();
        registry->add(ptr);
        return ptr;

    }
 private:
    std::string name_ = "default";
    std::string help_ = "default help";
};

} // namespace


#include <chrono>
#include <iostream>
#include <dlib/dnn.h>
#include <dlib/image_io.h>
#include <dlib/clustering.h>
#include <dlib/gui_widgets.h>
#include <dlib/image_processing.h>
#include <dlib/image_processing/frontal_face_detector.h>
#include <dlib/image_processing/render_face_detections.h>

using namespace std;
using namespace dlib;

template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual = add_prev1<block<N,BN,1,tag1<SUBNET>>>;

template <template <int,template<typename>class,int,typename> class block, int N, template<typename>class BN, typename SUBNET>
using residual_down = add_prev2<avg_pool<2,2,2,2,skip1<tag2<block<N,BN,2,tag1<SUBNET>>>>>>;

template <int N, template <typename> class BN, int stride, typename SUBNET>
using block  = BN<con<N,3,3,1,1,relu<BN<con<N,3,3,stride,stride,SUBNET>>>>>;

template <int N, typename SUBNET> using ares      = relu<residual<block,N,affine,SUBNET>>;
template <int N, typename SUBNET> using ares_down = relu<residual_down<block,N,affine,SUBNET>>;

template <typename SUBNET> using alevel0 = ares_down<256,SUBNET>;
template <typename SUBNET> using alevel1 = ares<256,ares<256,ares_down<256,SUBNET>>>;
template <typename SUBNET> using alevel2 = ares<128,ares<128,ares_down<128,SUBNET>>>;
template <typename SUBNET> using alevel3 = ares<64,ares<64,ares<64,ares_down<64,SUBNET>>>>;
template <typename SUBNET> using alevel4 = ares<32,ares<32,ares<32,SUBNET>>>;

using anet_type = loss_metric<fc_no_bias<128,avg_pool_everything<
                            alevel0<
                            alevel1<
                            alevel2<
                            alevel3<
                            alevel4<
                            max_pool<3,3,2,2,relu<affine<con<32,7,7,2,2,
                            input_rgb_image_sized<150>
                            >>>>>>>>>>>>;

int main(int argc, char const *argv[]) {
    try {
        auto tempo_total_inicio = std::chrono::high_resolution_clock::now();

        // Carrega o detector hog
        frontal_face_detector detectorHog = get_frontal_face_detector();

        // Carrega o shape predictor
        shape_predictor shapePredictor;
        deserialize("shape_predictor_68_face_landmarks.dat") >> shapePredictor;

        // Caminho da imagem 1
        string caminho_imagem_1 = "pessoas/11-12.jpg";

        // Caminho da imagem 2
        string caminho_imagem_2 = "pessoas/26-12.jpg";

        // Pega o caminho das imagens pelo argumento
        if (argc == 3) {
            caminho_imagem_1 = argv[1];
            caminho_imagem_2 = argv[2];
        }

        // Carrega a imagem 1
        array2d<rgb_pixel> imagem_1;
        load_image(imagem_1, caminho_imagem_1);
        // Converte a imagem 1 para gray
        array2d<unsigned char> imagem_cinza_1;
        assign_image(imagem_cinza_1, imagem_1);
        // Detecta as faces na imagem 1
        std::vector<rectangle> faces_detectadas_hog_1 = detectorHog(imagem_cinza_1);
        int size_1 = faces_detectadas_hog_1.size();
        if (size_1 == 0) {
            cout << "Nenhuma Face Detectada na Imagem 1" << endl;
            return 1;
        }
        // Pega a primeira face detectada
        rectangle face_1 = faces_detectadas_hog_1[0];

        // Carrega a imagem 2
        array2d<rgb_pixel> imagem_2;
        load_image(imagem_2, caminho_imagem_2);
        // Converte a imagem 2 para gray
        array2d<unsigned char> imagem_cinza_2;
        assign_image(imagem_cinza_2, imagem_2);
        // Detecta as faces na imagem 2
        std::vector<rectangle> faces_detectadas_hog_2 = detectorHog(imagem_cinza_2);
        int size_2 = faces_detectadas_hog_2.size();
        if (size_2 == 0) {
            cout << "Nenhuma Face Detectada na Imagem 2" << endl;
            return 1;
        }
        // Pega a primeira face detectada
        rectangle face_2 = faces_detectadas_hog_2[0];

        // detectando os pontos faciais da imagem 1
        full_object_detection shape_1 = shapePredictor(imagem_1, face_1);

        // detectando os pontos faciais da imagem 2
        full_object_detection shape_2 = shapePredictor(imagem_2, face_2);

        // pego apena as faces recortadas das pessoas para fazer o treinamento
        std::vector<matrix<rgb_pixel>> faces;

        // recorto a face 1
        matrix<rgb_pixel> face_chip_1;
        extract_image_chip(imagem_1, get_face_chip_details(shape_1, 150, 0.25), face_chip_1);
        faces.push_back(move(face_chip_1));

        // recorto a face 2
        matrix<rgb_pixel> face_chip_2;
        extract_image_chip(imagem_2, get_face_chip_details(shape_2, 150, 0.25), face_chip_2);
        faces.push_back(move(face_chip_2));

        // Carrega o classificador
        anet_type net;
        deserialize("dlib_face_recognition_resnet_model_v1.dat") >> net;

        // Fazendo a predição
        auto tempo_inicio_predicao = std::chrono::high_resolution_clock::now();
        std::vector<matrix<float, 0, 1>> face_descriptors = net(faces);
        auto tempo_fim_predicao = std::chrono::high_resolution_clock::now();

        if (face_descriptors.size() == 2) {
            cout << "Distancia: " << length(face_descriptors[0] - face_descriptors[1]) << endl;
        }

        // Mostra o Tempo Total
        auto tempo_total_fim = std::chrono::high_resolution_clock::now();
        double tempo_total = std::chrono::duration<double, std::milli>(tempo_total_fim - tempo_total_inicio).count();
        cout << "Tempo Total: " << to_string(tempo_total) << endl;

        // Mostra o Tempo de Predição
        double tempo_predicao = std::chrono::duration<double, std::milli>(tempo_fim_predicao - tempo_inicio_predicao).count();
        cout << "Tempo Predicao: " << to_string(tempo_predicao) << endl;

        // Mostra a Imagem 1 em uma Janela
        image_window my_window_1(imagem_1, "Imagem 1");
        my_window_1.add_overlay(faces_detectadas_hog_1, rgb_pixel(0, 255, 0));
        my_window_1.add_overlay(render_face_detections(shape_1));
        // my_window_1.wait_until_closed();

        // Mostra a Imagem 2 em uma Janela
        image_window my_window_2(imagem_2, "Imagem 2");
        my_window_2.add_overlay(faces_detectadas_hog_2, rgb_pixel(0, 255, 0));
        my_window_2.add_overlay(render_face_detections(shape_2));
        my_window_2.wait_until_closed();

    } catch (exception& e) {
        cout << "\nexception thrown!" << endl;
        cout << e.what() << endl;
        return 1;
    }
}


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_BITOFSIGN_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_BITOFSIGN_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-ieee
    This function object returns a value of the same type as
    the input with all bits set to zero
    except the sign bit which is preserved.
    This function object returns always @ref Zero for unsigned types

    @par Header <boost/simd/function/bitofsign.hpp>

    @par Example:

      @snippet bitofsign.cpp bitofsign

    @par Possible output:

      @snippet bitofsign.txt bitofsign

  **/
  Value bitofsign(Value const& x);
} }
#endif

#include <boost/simd/function/scalar/bitofsign.hpp>
#include <boost/simd/function/simd/bitofsign.hpp>

#endif


/*=========================================================================

  Program:   Small Body Geophysical Analysis
  Module:    SBGATObsRadar.hpp

  Class derived from VTK's vtkPolyDataAlgorithm by Benjamin Bercovici

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
/**
@file SBGATObsRadar.hpp
@class  SBGATObsRadar
@author Benjamin Bercovici
@author Jay McMahon
@date October 2018
@brief  Computes range/range-rate Doppler images over the surface of
provided small body
@details Computes range/range-rate Doppler images over the surface of
provided small body
@copyright MIT License, Benjamin Bercovici and Jay McMahon

*/

#ifndef SBGATObsRadar_H
#define SBGATObsRadar_H

#include <vtkFiltersCoreModule.h> // For export macro
#include <vtkPolyDataAlgorithm.h>

#include <vtkModifiedBSPTree.h>
#include <vtkImageData.h>

#include <armadillo>
#include <array>

#include <SBGATObs.hpp>

/**
Vector of vector of radar observations. Each observation is comprised of (range,range-rate,incidence)
This incidence is not strictly speaking a measured quantity but is used to penalize the binned
observations, blurring out returns collected at a high incidence
*/
typedef typename std::vector<std::vector<std::array<double, 3> > > SBGATRadarObsSequence;

class VTKFILTERSCORE_EXPORT SBGATObsRadar : public SBGATObs{
public:
  /**
   * Constructs with initial values of zero.
   */
  static SBGATObsRadar *New();

  vtkTypeMacro(SBGATObsRadar,vtkPolyDataAlgorithm);
  void PrintSelf(std::ostream& os, vtkIndent indent) override;
  void PrintHeader(std::ostream& os, vtkIndent indent) override;
  void PrintTrailer(std::ostream& os, vtkIndent indent) override;

  /**
  Collects range/range-rate samples over the surface of the specified system of small bodies at a given time after the epoch. The radar source is positionned at 1e6 * l meters from the target's center of mass, where
  l is a measure of the first object's diagonal.
  @param measurements_sequence reference to MeasurementsSequence, holding collected range/range-rate measurements at each observation time
  @param time observation timestamp
  @param N minimum number of measurements to produce over the smallest facet in the shape. The number of samples for any other facet will be equal to N * facet_surface_area / smallest_facet_surface_area
  @param radar_dir unit direction towards radar from target in inertial frame
  @param positions_vec vector of positions of each target's center-of-mass expressed in the primary's body frame
  @param velocities_vec vector of inertial velocities of each target's center-of-mass expressed in the primary's body frame
  @param mrps_vec vector of MRPs defining the inertial-to-body DCM [BN]
  @param omegas_vec vector of angular velocities of each body expressed in the inertial frame
  @param penalize_incidence if true, each measurement will be weighed by the cos(incidence) angle between
  the sampled point and the radar squared. If false, all measurements (in view of the radar and not blocked) are weighed equally
  have the same weight
  */
  void CollectMeasurements(SBGATRadarObsSequence & measurements_sequence,
  const double & time,
  const int & N,
  const arma::vec & radar_dir,
  const std::vector<arma::vec> & positions_vec,
  const std::vector<arma::vec> & velocities_vec,
  const std::vector<arma::vec> & mrps_vec,
  const std::vector<arma::vec> & omegas_vec,
  const bool & penalize_indicence);

  /**
  Bins the provided measurements sequence into a series of 2d-histogram
  Will throw an std::runtime_error exception if
  - either of the provided bin sizes are invalid (i.e <= 0)
  - the provided bin sizes yield empty histogram dimensions
  @param measurements_sequence reference to MeasurementsSequence, holding collected range/range-rate measurements at each observation time
  @param r_bin range bin size (m)
  @param rr_bin range-rate bin size (m/s)
  */
  void BinObservations(
    const SBGATRadarObsSequence & measurements_sequence,
    const double & r_bin,
    const double & rr_bin);

  /**
  Save the binned radar images to PNGs in the prescribed folder.
  The images will be normalized by the largest value in the observation sequence
  @param savepath path to folder where images will be saved (ex: "output/")
  */
  void SaveImages(std::string savepath);

  /**
  Return vector holding the computed images
  @return vector of radar images
  */
  std::vector<vtkSmartPointer<vtkImageData>> GetImages() const;

  /**
  Clears images, if any
  */

  void ClearImages() { this -> images.clear();}

protected:
  SBGATObsRadar();
  ~SBGATObsRadar() override;

  /**
  Ray traces the facets in view to the radar and measurements sequence with range/range-rate data
  if sampled point was in view of the radar
  @param measurements_sequence Sequence of measurements to add new image data to
  @param facets_in_view reference to a vector of vector holding indices of (maybe) illuminated facets for all considered bodies
  @param radar_dir radar direction expressed in inertial frame
  @param BN_dcms_vec vector holding the DCMs orienting the body frame of each body w/r to inertial
  @param positions_vec vector holding the position vector of the CM of each body w/r to the primary
  @param velocities_vec vector holding the velocities vector of the CM of each body
  @param omega_vec vector holding the angular velocities vector of each body
  */
  void reverse_ray_trace(SBGATRadarObsSequence & measurements_sequence,
    const std::vector<std::vector<int> > & facets_in_view,
    const arma::vec & radar_dir,
    const int N,
    const bool penalize_indicence,
    const std::vector<arma::mat> & BN_dcms_vec,
    const std::vector<arma::vec> & positions_vec,
    const std::vector<arma::vec> & velocities_vec,
    const std::vector<arma::vec> & omega_vec);

  std::vector<vtkSmartPointer<vtkImageData>> images;

  double max_value;

private:
  SBGATObsRadar(const SBGATObsRadar&) = delete;
  void operator=(const SBGATObsRadar&) = delete;
};

#endif


#pragma once

#include <Eigen/Dense>
#include <unsupported/Eigen/MatrixFunctions>

#include <boost/graph/graph_traits.hpp>
#include <boost/range/iterator_range.hpp>
#include <boost/range/adaptor/transformed.hpp>

#include "graphviz_interface.hpp"

#include "DiGraph.hpp"
#include "tran_mat_cell.hpp"
#include <fmt/format.h>

const size_t DEFAULT_N_SAMPLES = 200;

enum InitialBeta { ZERO, ONE, HALF, MEAN, RANDOM };

typedef std::unordered_map<std::string, std::vector<double>>
    AdjectiveResponseMap;

typedef std::vector<std::vector<std::vector<std::vector<double>>>>
    ObservedStateSequence;

typedef std::vector<std::vector<std::vector<double>>>
    PredictedObservedStateSequence;

typedef std::pair<std::tuple<std::string, int, std::string>,
                  std::tuple<std::string, int, std::string>>
    CausalFragment;

typedef std::vector<std::vector<
    std::unordered_map<std::string, std::unordered_map<std::string, double>>>>
    FormattedPredictionResult;

typedef std::tuple<std::pair<std::pair<int, int>, std::pair<int, int>>,
                   std::vector<std::string>,
                   FormattedPredictionResult>
    Prediction;

AdjectiveResponseMap construct_adjective_response_map(size_t n_kernels);

/**
 * The AnalysisGraph class is the main model/interface for Delphi.
 */
class AnalysisGraph {

  DiGraph graph;

  public:
  AnalysisGraph() {}
  Node& operator[](std::string);
  Node& operator[](int);
  Edge& edge(int, int);
  size_t num_vertices();
  size_t num_edges();
  // Manujinda: I had to move this up since I am usign this within the private:
  // block This is ugly. We need to re-factor the code to make it pretty again
  auto node_indices() {
    return boost::make_iterator_range(boost::vertices(this->graph));
  };

  auto nodes(){
    using boost::adaptors::transformed;
    return this->node_indices() |
          transformed([&](int v) -> Node& { return (*this)[v]; });
  };

  boost::range_detail::integer_iterator<unsigned long> begin() {return boost::vertices(this->graph).first;};
  boost::range_detail::integer_iterator<unsigned long> end() {return boost::vertices(this->graph).second;};

  auto successors(int i);

  // Allocate a num_verts x num_verts 2D array (std::vector of std::vectors)
  void allocate_A_beta_factors();

  void print_A_beta_factors();

  private:
  void clear_state();

  // Maps each concept name to the vertex id of the
  // vertex that concept is represented in the CAG
  // concept name --> CAG vertex id
  std::unordered_map<std::string, int> name_to_vertex = {};

  // Keeps track of indicators in CAG to ensure there are no duplicates.
  // std::vector<std::string> indicators_in_CAG;
  std::unordered_set<std::string> indicators_in_CAG;

  // A_beta_factors is a 2D array (std::vector of std::vectors) that keeps track
  // of the β factors involved with each cell of the transition matrix A.
  //
  // Accordign to our current model, which uses variables and their partial
  // derivatives with respect to each other ( x --> y, βxy = ∂y/∂x ),
  // atmost half of the transition matrix cells can be affected by βs.
  // According to the way we organize the transition matrix, the cells
  // A[row][col] where row is an even index and col is an odd index
  // are such cells.
  //
  // Each cell of matrix A_beta_factors represent all the directed paths
  // starting at the vertex equal to the column index of the matrix and
  // ending at the vertex equal to the row index of the matrix.
  //
  // Each cell of matrix A_beta_factors is an object of Tran_Mat_Cell class.
  std::vector<std::vector<std::shared_ptr<Tran_Mat_Cell>>> A_beta_factors;

  // A set of (row, column) numbers of the 2D matrix A_beta_factors
  // where the cell (row, column) depends on β factors.
  std::set<std::pair<int, int>> beta_dependent_cells;

  // Maps each β to all the transition matrix cells that are dependent on it.
  std::multimap<std::pair<int, int>, std::pair<int, int>> beta2cell;

  double t = 0.0;
  double delta_t = 1.0;

  // Latent state that is evolved by sampling.
  // Since s0 is used to represent a sequence of latent states,
  // I named this s0_original. Once things are refactored, we might be able to
  // convert this to s0
  Eigen::VectorXd s0;

  // Transition matrix that is evolved by sampling.
  // Since variable A has been already used locally in other methods,
  // I chose to name this A_orginal. After refactoring the code, we could
  // rename this to A.
  Eigen::MatrixXd A_original;

  int n_timesteps;
  int pred_timesteps;
  std::pair<std::pair<int, int>, std::pair<int, int>> training_range;

  // This is a column of the
  // this->training_latent_state_sequences
  // prediction_initial_latent_states.size() = this->res
  // TODO: If we make the code using this variable to directly fetch the values
  // from this->training_latent_state_sequences, we can get rid of this
  std::vector<Eigen::VectorXd> prediction_initial_latent_states;
  std::vector<std::string> pred_range;

  // Access this as
  // prediction_latent_state_sequences[ sample ][ time step ]
  std::vector<std::vector<Eigen::VectorXd>> predicted_latent_state_sequences;

  // Access this as
  // prediction_observed_state_sequences
  //                            [ sample ][ time step ][ vertex ][ indicator ]
  std::vector<PredictedObservedStateSequence>
      predicted_observed_state_sequences;

  PredictedObservedStateSequence test_observed_state_sequence;

  // Sampling resolution. Default is 200
  int res = DEFAULT_N_SAMPLES;

  // Keep track whether the model is trained.
  // Used to check whether there is a trained model before calling
  // generate_prediction()
  bool trained = false;

  // Access this as
  // current_latent_state
  Eigen::VectorXd current_latent_state;

  // Access this as
  // observed_state_sequence[ time step ][ vertex ][ indicator ]
  ObservedStateSequence observed_state_sequence;

  std::vector<Eigen::MatrixXd> transition_matrix_collection;

  // Remember the old β and the edge where we perturbed the β.
  // We need this to revert the system to the previous state if the proposal
  // gets rejected.
  std::pair<boost::graph_traits<DiGraph>::edge_descriptor, double>
      previous_beta;

  double log_likelihood = 0.0;
  double previous_log_likelihood = 0.0;
  bool data_heuristic = false;

  void get_subgraph(int vert,
                    std::unordered_set<int>& vertices_to_keep,
                    int cutoff,
                    bool inward);

  void get_subgraph_between(int start,
                            int end,
                            std::vector<int>& path,
                            std::unordered_set<int>& vertices_to_keep,
                            int cutoff);

  /**
   * Finds all the simple paths starting at the start vertex and
   * ending at the end vertex.
   * Uses find_all_paths_between_util() as a helper to recursively find the
   * paths
   */
  void find_all_paths_between(int start, int end, int cutoff);

  /**
   * Recursively finds all the simple paths starting at the start vertex and
   * ending at the end vertex. Used by find_all_paths_between()
   * Paths found are added to the Tran_Mat_Cell object that is tracking the
   * transition matrix cell (2*end, 2*start)
   *
   * @param start: Start vertex of the path
   * @param end  : End vertex of the path
   * @param path : A path starting at vettex start that is being explored
   *
   * @return void
   */
  void find_all_paths_between_util(int start,
                                   int end,
                                   std::vector<int>& path,
                                   int cutoff);

  /*
   ==========================================================================
   Utilities
   ==========================================================================
  */
  void set_default_initial_state();

  std::mt19937 rand_num_generator;

  // Uniform distribution used by the MCMC sampler
  std::uniform_real_distribution<double> uni_dist;

  // Normal distrubution used to perturb β
  std::normal_distribution<double> norm_dist;

  int get_vertex_id_for_concept(std::string concept, std::string caller);

  int get_degree(int vertex_id);

  void remove_node(int node_id);

  public:
  ~AnalysisGraph() {}

  /**
   * A method to construct an AnalysisGraph object given a JSON-serialized list
   * of INDRA statements.
   *
   * @param filename: The path to the file containing the JSON-serialized INDRA
   * statements.
   */
  static AnalysisGraph from_json_file(std::string filename,
                                      double belief_score_cutoff = 0.9,
                                      double grounding_score_cutoff = 0.0,
                                      std::string ontology = "WM");

  /**
   * A method to construct an AnalysisGraph object given from a std::vector of
   * ( subject, object ) pairs (Statements)
   *
   * @param statements: A std::vector of CausalFragment objects
   */
  static AnalysisGraph
  from_causal_fragments(std::vector<CausalFragment> causal_fragments);

  // TODO Change the name of this function to something better, like
  // restrict_to_subgraph_for_concept, update docstring

  /**
   * Returns a new AnaysisGraph related to the concept provided,
   * which is a subgraph of this graph.
   *
   * @param concept: The concept where the subgraph is about.
   * @param depth  : The maximum number of hops from the concept provided
   *                 to be included in the subgraph.
   * #param inward : Sets the direction of the causal influence flow to
   *                 examine.
   *                 False - (default) A subgraph rooted at the concept
   * provided.
   *                 True  - A subgraph with all the paths ending at the concept
   * provided.
   */
  AnalysisGraph get_subgraph_for_concept(std::string concept,
                                         bool inward = false,
                                         int depth = -1);

  /**
   * Returns a new AnaysisGraph related to the source concept and the target
   * concetp provided, which is a subgraph of this graph.
   * This subgraph contains all the simple directed paths of length less than
   * or equal to the provided cutoff.
   *
   * @param source_concept: The concept where the influence starts.
   * @param target_concept: The concept where the influence ends.
   * @param cutoff        : Maximum length of a directed simple path from
   *                        the source to target to be included in the
   *                        subgraph.
   */
  AnalysisGraph get_subgraph_for_concept_pair(std::string source_concept,
                                              std::string target_concept,
                                              int cutoff = -1);

  void prune(int cutoff = 2);

  void add_node(std::string concept);

  void add_edge(CausalFragment causal_fragment);
  void change_polarity_of_edge(std::string source_concept,
                               int source_polarity,
                               std::string target_concept,
                               int target_polarity);
  void remove_node(std::string concept);

  // Note:
  //      Although just calling this->remove_node(concept) within the loop
  //          for( std::string concept : concept_s )
  //      is suffifient to implement this method, it is not very efficient.
  //      It re-calculates directed simple paths for each vertex removed
  //
  //      Therefore, the code in this->remove_node() has been duplicated with
  //      slightly different flow to achive a more efficient execution.
  void remove_nodes(std::unordered_set<std::string> concepts);

  void remove_edge(std::string src, std::string tgt);

  void remove_edges(std::vector<std::pair<std::string, std::string>> edges);

  auto edges() { return boost::make_iterator_range(boost::edges(graph)); }

  /** Number of nodes in the graph */
  int num_nodes() { return boost::num_vertices(graph); }

  auto predecessors(int i) {
    return boost::make_iterator_range(boost::inv_adjacent_vertices(i, graph));
  }

  // Merge node n1 into node n2, with the option to specify relative polarity.
  // void
  // merge_nodes_old(std::string n1, std::string n2, bool same_polarity = true);

  /**
   * Merges the CAG nodes for the two concepts concept_1 and concept_2
   * with the option to specify relative polarity.
   */
  void merge_nodes(std::string concept_1,
                   std::string concept_2,
                   bool same_polarity = true);

  auto out_edges(int i) {
    return boost::make_iterator_range(boost::out_edges(i, graph));
  }

  double get_beta(std::string source_vertex_name,
                  std::string target_vertex_name) {
    // This is ∂target / ∂source
    // return this->A_original(2 * this->name_to_vertex[target_vertex_name],
    //                        2 * this->name_to_vertex[source_vertex_name] + 1);
    return this->A_original(
        2 * get_vertex_id_for_concept(target_vertex_name, "get_beta()"),
        2 * get_vertex_id_for_concept(source_vertex_name, "get_beta()") + 1);
  }

  void construct_beta_pdfs();

  AnalysisGraph
  find_all_paths_for_concept(std::string concept, int depth, bool reverse);

  /*
   * Find all the simple paths between all the paris of nodes of the graph
   */
  void find_all_paths();

  /*
   * Prints the simple paths found between all pairs of nodes of the graph
   * Groupd according to the starting and ending vertex.
   * find_all_paths() should be called before this to populate the paths
   */
  void print_all_paths();

  // Given an edge (source, target vertex ids - i.e. a β ≡ ∂target/∂source),
  // print all the transition matrix cells that are dependent on it.
  void print_cells_affected_by_beta(int source, int target);

  /*
   ==========================================================================
   Sampling and inference
   ----------------------

   This section contains code for sampling and Bayesian inference.
   ==========================================================================
  */

  // Sample elements of the stochastic transition matrix from the
  // prior distribution, based on gradable adjectives.
  void sample_initial_transition_matrix_from_prior();

  /**
   * Utility function that converts a time range given a start date and end date
   * into an integer value.
   * At the moment returns the number of months withing the time range.
   * This should be the number of traing data time points we have
   *
   * @param start_year  : Start year of the training data sequence
   * @param start_month : Start month of the training data sequence
   * @param end_year    : End year of the training data sequence
   * @param end_month   : End month of the training data sequence
   *
   * @return            : Number of months in the training data sequence
   *                      Including both start and end months
   */
  int calculate_num_timesteps(int start_year,
                              int start_month,
                              int end_year,
                              int end_month);

  /**
   * Get the observed state (values for all the indicators)
   * for a given time point from data.
   * See data.hpp::get_data_value() for missing data rules.
   * Note: units are automatically set according
   * to the parameterization of the given CAG.
   *
   * @param year    : Year of the time point data is extracted
   * @param month   : Month of the time point data is extracted
   * @param country : Country where the data is about
   * @param state   : State where the data is about
   * @param county  : County where the data is about
   *
   * @return        : Observed state std::vector for the specified location
   *                  on the specified time point.
   *                  Access it as: [ vertex id ][ indicator id ]
   */
  std::vector<std::vector<std::vector<double>>>
  get_observed_state_from_data(int year,
                               int month,
                               std::string country = "South Sudan",
                               std::string state = "",
                               std::string county = "");

  /**
   * Set the observed state sequence for a given time range from data.
   * The sequence includes both ends of the range.
   * See data.hpp::get_data_value() for missing data rules.
   * Note: units are automatically set according
   * to the parameterization of the given CAG.
   *
   * @param start_year  : Start year of the sequence of data
   * @param start_month : Start month of the sequence of data
   * @param end_year    : End year of the sequence of data
   * @param end_month   : End month of the sequenec of data
   * @param country     : Country where the data is about
   * @param state       : State where the data is about
   * @param county      : County where the data is about
   *
   */
  void
  set_observed_state_sequence_from_data(int start_year,
                                        int start_month,
                                        int end_year,
                                        int end_month,
                                        std::string country = "South Sudan",
                                        std::string state = "",
                                        std::string county = "");

  /**
   * Utility function that sets an initial latent state from observed data.
   * This is used for the inference of the transition matrix as well as the
   * training latent state sequences.
   *
   * @param timestep: Optional setting for setting the initial state to be other
   *                  than the first time step. Not currently used.
   *                  0 <= timestep < this->n_timesteps
   */
  void set_initial_latent_state_from_observed_state_sequence();

  void initialize_random_number_generator();

  void set_random_initial_latent_state();

  /**
   * To help experiment with initializing βs to differet values
   *
   * @param ib: Criteria to initialize β
   */
  void init_betas_to(InitialBeta ib = InitialBeta::MEAN);

  /**
   * Train a prediction model given a CAG with indicators
   *
   * @param start_year  : Start year of the sequence of data
   * @param start_month : Start month of the sequence of data
   * @param end_year    : End year of the sequence of data
   * @param end_month   : End month of the sequenec of data
   * @param res         : Sampling resolution. The number of samples to retain.
   * @param burn        : Number of samples to throw away. Start retaining
   *                      samples after throwing away this many samples.
   * @param country     : Country where the data is about
   * @param state       : State where the data is about
   * @param county      : county where the data is about
   * @param units       : Units for each indicator. Maps
   *                      indicator name --> unit
   * @param initial_beta: Criteria to initialize β
   *
   */
  void train_model(int start_year = 2012,
                   int start_month = 1,
                   int end_year = 2017,
                   int end_month = 12,
                   int res = 200,
                   int burn = 10000,
                   std::string country = "South Sudan",
                   std::string state = "",
                   std::string county = "",
                   std::map<std::string, std::string> units = {},
                   InitialBeta initial_beta = InitialBeta::ZERO,
                   bool use_heuristic = false);

  /**
   * Sample a collection of observed state sequences from the likelihood
   * model given a collection of transition matrices.
   *
   * @param prediction_timesteps: The number of timesteps for the prediction
   * sequences.
   * @param initial_prediction_step: The initial prediction timestep relative
   *                                 to training timesteps.
   * @param total_timesteps: Total number of timesteps from the initial
   *                         training date to the end prediction date.
   */
  void sample_predicted_latent_state_sequences(int prediction_timesteps,
                                               int initial_prediction_step,
                                               int total_timesteps);

  /** Generate predicted observed state sequenes given predicted latent state
   * sequences using the emission model
   */
  void
  generate_predicted_observed_state_sequences_from_predicted_latent_state_sequences();

  /**
   * Given a trained model, generate this->res number of
   * predicted observed state sequences.
   *
   * @param start_year  : Start year of the prediction
   *                      Should be >= the start year of training
   * @param start_month : Start month of the prediction
   *                      If training and prediction start years are equal
   *                      should be >= the start month of training
   * @param end_year    : End year of the prediction
   * @param end_month   : End month of the prediction
   *
   * @return Predicted observed state (indicator value) sequence for the
   *         prediction period including start and end time points.
   *         This is a tuple.
   *         The first element is a std::vector of std::strings with lables for
   * each time point predicted (year-month). The second element contains
   * predicted values. Access it as: [ sample number ][ time point ][ vertex
   * name ][ indicator name ]
   */
  Prediction generate_prediction(int start_year,
                                 int start_month,
                                 int end_year,
                                 int end_month);

  /**
   * Format the prediction result into a format python callers favor.
   *
   * @param pred_timestes: Number of timesteps in the predicted sequence.
   *
   * @return Re-formatted prediction result.
   *         Access it as:
   *         [ sample number ][ time point ][ vertex name ][ indicator name ]
   */

  FormattedPredictionResult format_prediction_result();

  /**
   * this->generate_prediction() must be called before callign this method.
   * Outputs raw predictions for a given indicator that were generated by
   * generate_prediction(). Each column is a time step and the rows are the
   * samples for that time step.
   *
   * @param indicator: A std::string representing the indicator variable for
   which we
   *                   want predictions for.

   * @return A this->res x this->pred_timesteps dimension 2D array
   *         (std::vector of std::vectors)
   *
  */
  std::vector<std::vector<double>> prediction_to_array(std::string indicator);

  std::vector<Eigen::VectorXd> synthetic_latent_state_sequence;
  // ObservedStateSequence synthetic_observed_state_sequence;
  bool synthetic_data_experiment = false;

  void generate_synthetic_latent_state_sequence();

  void
  generate_synthetic_observed_state_sequence_from_synthetic_latent_state_sequence();

  std::pair<PredictedObservedStateSequence, Prediction>
  test_inference_with_synthetic_data(
      int start_year = 2015,
      int start_month = 1,
      int end_year = 2015,
      int end_month = 12,
      int res = 100,
      int burn = 900,
      std::string country = "South Sudan",
      std::string state = "",
      std::string county = "",
      std::map<std::string, std::string> units = {},
      InitialBeta initial_beta = InitialBeta::HALF);

  // TODO: Need testing
  /**
   * Sample observed state std::vector.
   * This is the implementation of the emission function.
   *
   * @param latent_state: Latent state std::vector.
   *                      This has 2 * number of vertices in the CAG.
   *                      Even indices track the state of each vertex.
   *                      Odd indices track the state of the derivative.
   *
   * @return Observed state std::vector. Observed state for each indicator for
   * each vertex. Indexed by: [ vertex id ][ indicator id ]
   */
  std::vector<std::vector<double>>
  sample_observed_state(Eigen::VectorXd latent_state);

  /**
   * Find all the transition matrix (A) cells that are dependent on the β
   * attached to the provided edge and update them.
   * Acts upon this->A_original
   *
   * @param e: The directed edge ≡ β that has been perturbed
   */
  void update_transition_matrix_cells(
      boost::graph_traits<DiGraph>::edge_descriptor e);

  /**
   * Sample a new transition matrix from the proposal distribution,
   * given a current candidate transition matrix.
   * In practice, this amounts to:
   *    Selecting a random β.
   *    Perturbing it a bit.
   *    Updating all the transition matrix cells that are dependent on it.
   */
  // TODO: Need testng
  // TODO: Before calling sample_from_proposal() we must call
  // AnalysisGraph::find_all_paths()
  // TODO: Before calling sample_from_proposal(), we mush assign initial βs and
  // run Tran_Mat_Cell::compute_cell() to initialize the first transistion
  // matrix.
  // TODO: Update Tran_Mat_Cell::compute_cell() to calculate the proper value.
  // At the moment it just computes sum of length of all the paths realted to
  // this cell
  void sample_from_proposal();

  void set_current_latent_state(int ts);

  double log_normpdf(double x, double mean, double sd);

  void set_log_likelihood();

  double calculate_delta_log_prior();

  void revert_back_to_previous_state();

  /**
   * Run Bayesian inference - sample from the posterior distribution.
   */
  void sample_from_posterior();

  // ==========================================================================
  // Manipulation
  // ==========================================================================

  void
  set_indicator(std::string concept, std::string indicator, std::string source);

  void delete_indicator(std::string concept, std::string indicator);

  void delete_all_indicators(std::string concept);

  /*
  // TODO: Demosntrate how to use the Node::get_indicator() method
  // with the custom exception.
  // Not sure whether we need this method in AnalaysisGraph
  // so that python side can directly access the Indicator class
  // objects and maipulate them (we need to fiture out how to map a
  // custom class from C++ into python for this) - harder
  // or
  // mirror getter and setter methods of the Indicator class
  // in AnalysisGraph and make the python side call them - easier.
  Indicator get_indicator(std::string concept, std::string indicator) {
    try {
      return graph[name_to_vertex.at(concept)].get_indicator(indicator);
    } catch (const std::out_of_range &oor) {
      fmt::print("Error: AnalysisGraph::get_indicator()\n");
      fmt::print("\tConcept: {} is not in the CAG\n", concept);
    } catch (IndicatorNotFoundException &infe) {
      std::cerr << "Error: AnalysisGraph::get_indicator()\n"
                << "\tindicator: " << infe.what()
                << " is not attached to CAG node " << concept << std::endl;
    }
  }
  */

  void replace_indicator(std::string concept,
                         std::string indicator_old,
                         std::string indicator_new,
                         std::string source);

  /*
    ==========================================================================
    Model parameterization
    *Loren: I am going to try to port this, I'll try not to touch anything up
    top
    and only push changes that compile. If I do push a change that breaks things
    you could probably just comment out this section.*
    ==========================================================================
  */

  /**
   * Map each concept node in the AnalysisGraph instance to one or more
   * tangible quantities, known as 'indicators'.
   *
   * @param n: Int representing number of indicators to attach per node.
   * Default is 1 since our model so far is configured for only 1 indicator per
   * node.
   */
  void map_concepts_to_indicators(int n = 1);

  /**
   * Parameterize the indicators of the AnalysisGraph..
   *
   */
  void parameterize(std::string country = "South Sudan",
                    std::string state = "",
                    std::string county = "",
                    int year = 2012,
                    int month = 1,
                    std::map<std::string, std::string> units = {});

  void print_nodes();

  void print_edges();

  void print_name_to_vertex();

  std::pair<Agraph_t*, GVC_t*>
  to_agraph(bool simplified_labels =
                false, /** Whether to create simplified labels or not. */
            int label_depth =
                1 /** Depth in the ontology to which simplified labels extend */
  );

  std::string to_dot();

  void
  to_png(std::string filename = "CAG.png",
         bool simplified_labels =
             false, /** Whether to create simplified labels or not. */
         int label_depth =
             1 /** Depth in the ontology to which simplified labels extend */
  );

  void print_indicators();
};


//==================================================================================================
/**
  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
**/
//==================================================================================================
#ifndef BOOST_SIMD_DETAIL_PACK_OPERATORS_HPP_INCLUDED
#define BOOST_SIMD_DETAIL_PACK_OPERATORS_HPP_INCLUDED

#include <boost/simd/config.hpp>
#include <boost/simd/function/plus.hpp>
#include <boost/simd/function/minus.hpp>
#include <boost/simd/function/multiplies.hpp>
#include <boost/simd/function/divides.hpp>
#include <boost/simd/function/is_equal.hpp>
#include <boost/simd/function/rem.hpp>
#include <boost/simd/function/is_less.hpp>
#include <boost/simd/function/is_less_equal.hpp>
#include <boost/simd/function/is_greater.hpp>
#include <boost/simd/function/is_greater_equal.hpp>
#include <boost/simd/function/bitwise_and.hpp>
#include <boost/simd/function/bitwise_or.hpp>
#include <boost/simd/function/bitwise_xor.hpp>
#include <boost/simd/function/complement.hpp>
#include <boost/simd/function/logical_not.hpp>
#include <boost/simd/function/shift_left.hpp>
#include <boost/simd/function/shift_right.hpp>
#include <boost/simd/function/logical_and.hpp>
#include <boost/simd/function/logical_or.hpp>
#include <boost/simd/function/logical_not.hpp>
#include <boost/simd/function/rem.hpp>
#include <boost/simd/function/unary_plus.hpp>
#include <boost/simd/function/unary_minus.hpp>
#include <boost/simd/detail/nsm.hpp>
#include <boost/simd/meta/is_pack.hpp>
#include <boost/simd/logical.hpp>

namespace boost { namespace simd
{
#define BOOST_SIMD_PACK_DEFINE_BINOP(type_, op, f)                                                 \
  template <typename T, std::size_t N, typename U> BOOST_FORCEINLINE                               \
  typename std::enable_if<is_not_pack_t<U>::value && std::is_convertible<U, T>::value,             \
                          pack<type_, N>>::type                                                    \
  op(pack<T, N> const& p0, U const& s1) BOOST_NOEXCEPT_IF_EXPR(f(p0, s1))                          \
  { return f(p0, s1); }                                                                            \
                                                                                \
  template <typename T, std::size_t N, typename U> BOOST_FORCEINLINE                               \
  typename std::enable_if<is_not_pack_t<U>::value && std::is_convertible<U, T>::value,             \
                          pack<type_, N>>::type                                                    \
  op(U const& s0, pack<T, N> const& p1) BOOST_NOEXCEPT_IF_EXPR(f(s0, p1))                          \
  { return f(s0, p1); }                                                                            \
                                                                                \
  template <typename T, typename U, std::size_t N> BOOST_FORCEINLINE                               \
  auto op(pack<T, N> const& p0, pack<U, N> const& p1)                                              \
  BOOST_NOEXCEPT_DECLTYPE_BODY(f(p0, p1))                                                          \
/**/

BOOST_SIMD_PACK_DEFINE_BINOP(T, operator+, plus)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator-, minus)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator%, rem)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator/, divides)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator*, multiplies)

BOOST_SIMD_PACK_DEFINE_BINOP(T, operator&, bitwise_and)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator|, bitwise_or)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator^, bitwise_xor)

BOOST_SIMD_PACK_DEFINE_BINOP(T, operator << , shift_left)
BOOST_SIMD_PACK_DEFINE_BINOP(T, operator >> , shift_right)

#define BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(op, f)                                                \
  template <typename T, typename U, std::size_t N> BOOST_FORCEINLINE                               \
  auto op(pack<T, N> const& p0, pack<U, N> const& p1)                                              \
  BOOST_NOEXCEPT_DECLTYPE_BODY(f(p0, p1))                                                          \
                                                                                \
  template <typename T, std::size_t N, typename U> BOOST_FORCEINLINE                               \
  typename std::enable_if < is_not_pack_t<U>::value && std::is_convertible<U, T>::value            \
                          , pack<bs::as_logical_t<T>, N>                                           \
                          >::type                                                                  \
  op(pack<T, N> const& p0, U const& s1) BOOST_NOEXCEPT                                             \
  { return f(p0, s1); }                                                                            \
                                                                                \
  template <typename T, std::size_t N, typename U> BOOST_FORCEINLINE                               \
  typename std::enable_if < is_not_pack_t<U>::value && std::is_convertible<U, T>::value            \
                          , pack<bs::as_logical_t<T>, N>                                           \
                          >::type                                                                  \
  op(U const& s0, pack<T, N> const& p1) BOOST_NOEXCEPT                                             \
  { return f(s0, p1); }                                                                            \
/**/

BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator&&, logical_and)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator||, logical_or)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator<,  is_less)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator>,  is_greater)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator==, is_equal)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator<=, is_less_equal)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator>=, is_greater_equal)
BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP(operator!=, is_not_equal)

#undef BOOST_SIMD_PACK_DEFINE_BINOP
#undef BOOST_SIMD_PACK_DEFINE_LOGICAL_BINOP

  template <typename T, std::size_t N>
  BOOST_FORCEINLINE auto operator!(pack<T,N> const& a)
  BOOST_NOEXCEPT_DECLTYPE_BODY(logical_not(a))

  template <typename T, std::size_t N>
  BOOST_FORCEINLINE auto operator+(pack<T,N> const& a)
  BOOST_NOEXCEPT_DECLTYPE_BODY(unary_plus(a))

  template <typename T, std::size_t N>
  BOOST_FORCEINLINE auto operator-(pack<T,N> const& a)
  BOOST_NOEXCEPT_DECLTYPE_BODY(unary_minus(a))

  template <typename T, std::size_t N>
  BOOST_FORCEINLINE auto operator~(pack<T,N> const& a)
  BOOST_NOEXCEPT_DECLTYPE_BODY(complement(a))
} }

#endif