/** \file category.hpp */

#pragma once

// std c++ headers
#include <type_traits>

// MTL4 headers
#include <boost/numeric/mtl/vector/dense_vector.hpp>
#include <boost/numeric/mtl/matrix/dense2D.hpp>
#include <boost/numeric/mtl/matrix/compressed2D.hpp>
#include <boost/numeric/mtl/matrix/coordinate2D.hpp>
#include <boost/numeric/mtl/matrix/morton_dense.hpp>

// AMDiS headers
#include "AMDiS_fwd.hpp"
#include "AMDiS_base.hpp"

#include "traits/traits_fwd.hpp"
#include "traits/tag.hpp"

namespace AMDiS
{
  namespace traits
  {
    // scalars
    template <class T>
    struct category<T, Requires_t<concepts::Arithmetic<T>> >
    {
      typedef tag::scalar tag;
      typedef T           value_type;
      //       typedef size_t      size_type;
    };

    // vectors
//     template <class T>
//     struct category<T, Requires_t<and_<traits::HasValueType<T>, traits::HasSizeType<T>, concepts::Vector<T>>> >
//     {
//       using tag = tag::vector;
//       using value_type = Value_t<T>;
//       using size_type  = Size_t<T>;
//     };
//
//
//     // matrices
//     template <class T>
//     struct category<T, Requires_t<and_<traits::HasValueType<T>, traits::HasSizeType<T>, concepts::Matrix<T>>> >
//     {
//       using tag = tag::matrix;
//       using value_type = Value_t<T>;
//       using size_type  = Size_t<T>;
//     };

    template <>
    struct category<AMDiS::SystemVector>
    {
      typedef tag::vector      tag;
      typedef double           value_type;
      typedef int              size_type;
    };

    template <class T>
    struct category<WorldVector<WorldVector<T>>>
    {
      typedef tag::matrix  tag;
      typedef T            value_type;
      typedef int          size_type;
    };

    template <class Matrix>
    struct category<AMDiS::SolverMatrix<Matrix>>
    {
      typedef tag::matrix  tag;
    };

    template <class T>
    struct category<const T> : category<T> {};

    // -------------------------------------------------------------------------

    template <class T, class S, class = void>
    struct IsConvertible : std::is_convertible<T, S> {};

    template <class T, class S>
    struct IsConvertible<T, S, Requires_t<and_<is_vector<T>, is_vector<S>>>>
      : std::is_convertible<Value_t<T>, Value_t<S>> {};

    template <class T, class S>
    struct IsConvertible<T, S, Requires_t<and_<is_matrix<T>, is_matrix<S>>>>
      : std::is_convertible<Value_t<T>, Value_t<S>> {};

  } // end namespace traits

} // end namespace AMDiS


// Copyright 2004 The Trustees of Indiana University.

// Use, modification and distribution is subject to the Boost Software
// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

//  Authors: Douglas Gregor
//           Andrew Lumsdaine
#ifndef BOOST_GRAPH_DIJKSTRA_TESTING_DIETMAR
#  define BOOST_GRAPH_DIJKSTRA_TESTING
#endif

#include <boost/graph/dijkstra_shortest_paths.hpp>
#include <boost/graph/dijkstra_shortest_paths_no_color_map.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/random/linear_congruential.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/random/uniform_real.hpp>
#include <boost/timer.hpp>
#include <vector>
#include <iostream>

#include <iterator>
#include <utility>
#include <boost/random/uniform_int.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/erdos_renyi_generator.hpp>
#include <boost/type_traits/is_base_and_derived.hpp>
#include <boost/type_traits/is_same.hpp>
#include <boost/detail/lightweight_test.hpp>

using namespace boost;

#ifdef BOOST_GRAPH_DIJKSTRA_TESTING_DIETMAR

struct show_events_visitor : dijkstra_visitor<>
{
  template<typename Vertex, typename Graph>
  void discover_vertex(Vertex v, const Graph&)
  {
    std::cerr << "on_discover_vertex(" << v << ")\n";
  }

  template<typename Vertex, typename Graph>
  void examine_vertex(Vertex v, const Graph&)
  {
    std::cerr << "on_discover_vertex(" << v << ")\n";
  }
};

template<typename Graph, typename Kind>
void run_test(const Graph& g, const char* name, Kind kind,
              const std::vector<double>& correct_distances)
{
  std::vector<double> distances(num_vertices(g));

  std::cout << "Running Dijkstra's with " << name << "...";
  std::cout.flush();
  timer t;
  dijkstra_heap_kind = kind;

  dijkstra_shortest_paths(g, vertex(0, g),
                          distance_map(&distances[0]).
                          visitor(show_events_visitor()));
  double run_time = t.elapsed();
  std::cout << run_time << " seconds.\n";

  BOOST_TEST(distances == correct_distances);

  if (distances != correct_distances)
    {
      std::cout << "Expected: ";
      std::copy(correct_distances.begin(), correct_distances.end(),
                std::ostream_iterator<double>(std::cout, " "));
      std::cout << "\nReceived: ";
      std::copy(distances.begin(), distances.end(),
                std::ostream_iterator<double>(std::cout, " "));
      std::cout << std::endl;
    }
}
#endif

int main(int argc, char* argv[])
{
  unsigned n = (argc > 1? lexical_cast<unsigned>(argv[1]) : 10000u);
  unsigned m = (argc > 2? lexical_cast<unsigned>(argv[2]) : 10*n);
  int seed = (argc > 3? lexical_cast<int>(argv[3]) : 1);

  // Build random graph
  typedef adjacency_list<vecS, vecS, directedS, no_property,
                         property<edge_weight_t, double> > Graph;
  std::cout << "Generating graph...";
  std::cout.flush();
  minstd_rand gen(seed);
  double p = double(m)/(double(n)*double(n));
  Graph g(erdos_renyi_iterator<minstd_rand, Graph>(gen, n, p),
          erdos_renyi_iterator<minstd_rand, Graph>(),
          n);
  std::cout << n << " vertices, " << num_edges(g) << " edges.\n";
  uniform_real<double> rand01(0.0, 1.0);
  graph_traits<Graph>::edge_iterator ei, ei_end;
  for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
    put(edge_weight, g, *ei, rand01(gen));

  std::vector<double> binary_heap_distances(n);
  std::vector<double> relaxed_heap_distances(n);
  std::vector<double> no_color_map_distances(n);

  // Run binary or d-ary heap version
  std::cout << "Running Dijkstra's with binary heap...";
  std::cout.flush();
  timer t;
#ifdef BOOST_GRAPH_DIJKSTRA_TESTING_DIETMAR
  dijkstra_heap_kind = dijkstra_binary_heap;
#else
  dijkstra_relaxed_heap = false;
#endif
  dijkstra_shortest_paths(g, vertex(0, g),
                          distance_map(
                            boost::make_iterator_property_map(
                              binary_heap_distances.begin(), get(boost::vertex_index, g))));
  double binary_heap_time = t.elapsed();
  std::cout << binary_heap_time << " seconds.\n";

  // Run relaxed heap version
#ifdef BOOST_GRAPH_DIJKSTRA_USE_RELAXED_HEAP
  std::cout << "Running Dijkstra's with relaxed heap...";
#else
  std::cout << "Running Dijkstra's with d-ary heap (d=4)...";
#endif
  std::cout.flush();
  t.restart();
#ifdef BOOST_GRAPH_DIJKSTRA_TESTING_DIETMAR
  dijkstra_heap_kind = dijkstra_relaxed_heap;
#else
  dijkstra_relaxed_heap = true;
#endif
  dijkstra_shortest_paths(g, vertex(0, g),
                          distance_map(
                            boost::make_iterator_property_map(
                              relaxed_heap_distances.begin(), get(boost::vertex_index, g))));
  double relaxed_heap_time = t.elapsed();
  std::cout << relaxed_heap_time << " seconds.\n"
            << "Speedup = " << (binary_heap_time / relaxed_heap_time) << ".\n";

  // Verify that the results are equivalent
  BOOST_TEST(binary_heap_distances == relaxed_heap_distances);

  // Run Michael's no-color-map version
#ifdef BOOST_GRAPH_DIJKSTRA_USE_RELAXED_HEAP
  std::cout << "Running Dijkstra's (no color map) with relaxed heap...";
#else
  std::cout << "Running Dijkstra's (no color map) with d-ary heap (d=4)...";
#endif
  std::cout.flush();
  t.restart();
#ifdef BOOST_GRAPH_DIJKSTRA_TESTING_DIETMAR
  dijkstra_heap_kind = dijkstra_relaxed_heap;
#else
  dijkstra_relaxed_heap = true;
#endif
  dijkstra_shortest_paths_no_color_map
    (g, vertex(0, g),
     boost::dummy_property_map(),
     boost::make_iterator_property_map(no_color_map_distances.begin(),
                                       get(boost::vertex_index, g),
                                       0.),
     get(boost::edge_weight, g),
     get(boost::vertex_index, g),
     std::less<double>(),
     boost::closed_plus<double>(),
     (std::numeric_limits<double>::max)(),
     0,
     make_dijkstra_visitor(null_visitor())
     );
  double no_color_map_time = t.elapsed();
  std::cout << no_color_map_time << " seconds.\n"
            << "Speedup = " << (binary_heap_time / no_color_map_time) << ".\n";

  // Verify that the results are equivalent
  BOOST_TEST(binary_heap_distances == no_color_map_distances);

#ifdef BOOST_GRAPH_DIJKSTRA_TESTING_DIETMAR
  run_test(g, "d-ary heap (d=2)", dijkstra_d_heap_2, binary_heap_distances);
  run_test(g, "d-ary heap (d=3)", dijkstra_d_heap_3, binary_heap_distances);
  run_test(g, "Fibonacci heap", dijkstra_fibonacci_heap, binary_heap_distances);
  run_test(g, "Lazy Fibonacci heap", dijkstra_lazy_fibonacci_heap, binary_heap_distances);
  run_test(g, "Pairing heap", dijkstra_pairing_heap, binary_heap_distances);
  run_test(g, "Splay heap", dijkstra_splay_heap, binary_heap_distances);
#endif
  return boost::report_errors();
}


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

// Copyright (c) 2019 Tinko Bartels, Berlin, Germany.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GEOMETRY_EXTENSIONS_RANDOM_UNIFORM_POINT_DISTRIBUTION_HPP
#define BOOST_GEOMETRY_EXTENSIONS_RANDOM_UNIFORM_POINT_DISTRIBUTION_HPP

#include <boost/geometry/extensions/random/strategies/agnostic/uniform_envelope_rejection.hpp>
#include <boost/geometry/extensions/random/strategies/agnostic/uniform_linear.hpp>
#include <boost/geometry/extensions/random/strategies/agnostic/uniform_point_distribution_discrete.hpp>
#include <boost/geometry/extensions/random/strategies/cartesian/uniform_point_distribution_box.hpp>
#include <boost/geometry/extensions/random/strategies/cartesian/uniform_point_distribution_segment.hpp>
#include <boost/geometry/extensions/random/strategies/spherical/edwilliams_avform_intermediate.hpp>
#include <boost/geometry/extensions/random/strategies/spherical/uniform_inverse_transform_sampling.hpp>

namespace boost { namespace geometry { namespace random
{

template
<
    typename DomainGeometry,
    typename Point = typename geometry::point_type<DomainGeometry>::type,
    typename Strategy =
        typename strategy::uniform_point_distribution::services::default_strategy
            <
                Point,
                DomainGeometry
            >
>
class uniform_point_distribution
{
public:
    typedef Point result_type;
    typedef DomainGeometry domain_type;
    class param_type {
    public:
        param_type(DomainGeometry const& domain) : m_domain(domain) {}
        param_type(param_type const& p) : m_domain(p.m_domain) {}
        DomainGeometry const& domain() const { return m_domain; }
        bool operator==(param_type const& rhs) const
        {
            return equals(m_domain, rhs.m_domain);
        }
    private:
        DomainGeometry m_domain;
    };
    uniform_point_distribution(DomainGeometry const& domain)
        : m_strategy(domain),
          m_param(domain) {}
    uniform_point_distribution()
        : m_strategy(DomainGeometry()),
          m_param(DomainGeometry()) {}
    uniform_point_distribution(param_type const& param)
        : m_strategy(param.domain()),
          m_param(param) {}
    void reset() { m_strategy.reset(); }
    param_type param() const { return m_param; }
    void param(param_type const& p)
    {
        m_param = p;
        m_strategy = Strategy(p.domain());
    }
    bool operator==(uniform_point_distribution const& rhs) const
    {
        return m_strategy.equals(m_param.domain(),
                                 rhs.domain(),
                                 rhs.m_strategy);
    }
    domain_type const& domain() const { return m_param.domain(); }
    template< typename Gen >
    result_type operator()(Gen& g)
    {
        return m_strategy.apply(g, m_param.domain());
    }
    template< typename Gen >
    result_type operator()(Gen& g, param_type const& p)
    {
        Strategy strat(p.domain());
        return strat.apply(g, p.domain());
    }
private:
    Strategy m_strategy;
    param_type m_param;
};

template
<
    typename DomainGeometry,
    typename Point = typename geometry::point_type<DomainGeometry>::type
>
inline uniform_point_distribution<DomainGeometry, Point>
    return_uniform_point_distribution(DomainGeometry const& domain)
{
    return uniform_point_distribution<DomainGeometry, Point>(domain);
}

}}} // namespace boost::geometry::random

template
<
    typename Char,
    typename Traits,
    typename Point,
    typename DomainGeometry,
    typename Strategy
>
inline std::basic_ostream<Char, Traits>& operator<<
    (
        std::basic_ostream<Char, Traits> &os,
        boost::geometry::random::uniform_point_distribution
            <
                DomainGeometry, Point, Strategy
            > const& dist
    )
{
    os << boost::geometry::wkt<DomainGeometry>(dist.domain());
    return os;
}

template
<
    typename Char,
    typename Traits,
    typename Point,
    typename DomainGeometry,
    typename Strategy
>
inline std::basic_istream<Char, Traits>& operator>>
    (
        std::basic_istream<Char, Traits> &is,
        boost::geometry::random::uniform_point_distribution
            <
                DomainGeometry, Point, Strategy
            > & dist
    )
{
    std::basic_string<Char, Traits> line;
    std::getline(is, line);
    DomainGeometry g;
    namespace bg = boost::geometry;
    typedef typename bg::random::uniform_point_distribution
        <
            DomainGeometry, Point, Strategy
        >::param_type param_type;
    bg::read_wkt<DomainGeometry>(line, g);
    dist.param( param_type(g) );
    return is;
}

#endif // BOOST_GEOMETRY_EXTENSIONS_RANDOM_UNIFORM_POINT_DISTRIBUTION_HPP


#ifndef LOSSFUNCTIONHEADERDEF
#define LOSSFUNCTIONHEADERDEF

#include <Eigen/Dense>
#include <ostream>
#include <set>
#include <stdexcept>

using namespace std;

// !This class implements a Loss Function for two partitions (clusters).
// !This is the base class. The common information for Loss functions is
// implemented here.

enum LOSS_FUNCTION {
  BINDER_LOSS,
  VARIATION_INFORMATION,
  VARIATION_INFORMATION_NORMALIZED
};

class LossFunction {
 protected:
  Eigen::VectorXi *cluster1;
  int K1;  // nº of groups in cluster1
  Eigen::VectorXi *cluster2;
  int K2;  // nº of groups in cluster2
  int N;   // nº of points

 public:
  LossFunction();
  virtual ~LossFunction() = 0;
  void SetCluster(Eigen::VectorXi cluster1_,
                  Eigen::VectorXi cluster2_);  // Populate the members
  void SetFirstCluster(Eigen::VectorXi cluster1_);
  void SetSecondCluster(Eigen::VectorXi cluster2_);
  Eigen::VectorXi *GetCluster(
      int i) const;  // return the i th cluster reference (i = 1 or 2)

  int GetNumberOfGroups(Eigen::VectorXi cluster);  // returns the nº of groups
                                                   // in a cluster (ie K)
  int ClassCounter(
      Eigen::VectorXi cluster,
      int index);  // returns how many times the group "index" appears inside
                   // "cluster" (n(a,g) in the article)
  int ClassCounterExtended(
      Eigen::VectorXi cluster1, Eigen::VectorXi cluster2, int g,
      int h);  // mutual count of how many times the group "g" and "h" appear
               // inside "cluster1" and "cluster2" simultaneously (n_{g,h} ^
               // (a,z) in the article)
  virtual double
  Loss() = 0;  // Loss Function to be implemented in the extended classes.
  string Summarize();
};

ostream &operator<<(ostream &out, LossFunction const *loss_function);

#endif