/*    This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT.
 *    See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details.
 *    Author(s):       Siargey Kachanovich
 *
 *    Copyright (C) 2019 Inria
 *
 *    Modification(s):
 *      - YYYY/MM Author: Description of the modification
 */

#ifndef CELL_COMPLEX_H_
#define CELL_COMPLEX_H_

#include <Eigen/Dense>

#include <vector>
#include <map>
#include <utility>  // for std::make_pair

#include <gudhi/IO/output_debug_traces_to_html.h>  // for DEBUG_TRACES
#include <gudhi/Permutahedral_representation/Simplex_comparator.h>
#include <gudhi/Coxeter_triangulation/Cell_complex/Hasse_diagram_cell.h>  // for Hasse_cell

namespace Gudhi {

namespace coxeter_triangulation {

/** \class Cell_complex
 *  \brief A class that constructs the cell complex from the output provided by the class
 *   \ref Gudhi::coxeter_triangulation::Manifold_tracing.
 * 
 *  The use and interfaces of this cell complex is limited to the \ref coxeter_triangulation implementation.
 *
 *  \tparam Out_simplex_map_ The type of a map from a simplex type that is a
 *   model of SimplexInCoxeterTriangulation to Eigen::VectorXd.
 */
template <class Out_simplex_map_>
class Cell_complex {
 public:
  /** \brief Type of a simplex in the ambient triangulation.
   *  Is a model of the concept SimplexInCoxeterTriangulation.
   */
  using Simplex_handle = typename Out_simplex_map_::key_type;
  /** \brief Type of a cell in the cell complex.
   *  Always is Gudhi::Hasse_cell from the Hasse diagram module.
   *  The additional information is the boolean that is true if and only if the cell lies
   *  on the boundary.
   */
  using Hasse_cell = Gudhi::Hasse_diagram::Hasse_diagram_cell<int, double, bool>;
  /** \brief Type of a map from permutahedral representations of simplices in the
   *  ambient triangulation to the corresponding cells in the cell complex of some
   *  specific dimension.
   */
  using Simplex_cell_map = std::map<Simplex_handle, Hasse_cell*, Simplex_comparator<Simplex_handle> >;
  /** \brief Type of a vector of maps from permutahedral representations of simplices in the
   *  ambient triangulation to the corresponding cells in the cell complex of various dimensions.
   */
  using Simplex_cell_maps = std::vector<Simplex_cell_map>;

  /** \brief Type of a map from cells in the cell complex to the permutahedral representations
   *  of the corresponding simplices in the ambient triangulation.
   */
  using Cell_simplex_map = std::map<Hasse_cell*, Simplex_handle>;

  /** \brief Type of a map from vertex cells in the cell complex to the permutahedral representations
   *  of their Cartesian coordinates.
   */
  using Cell_point_map = std::map<Hasse_cell*, Eigen::VectorXd>;

 private:
  Hasse_cell* insert_cell(const Simplex_handle& simplex, std::size_t cell_d, bool is_boundary) {
    Simplex_cell_maps& simplex_cell_maps = (is_boundary ? boundary_simplex_cell_maps_ : interior_simplex_cell_maps_);
#ifdef DEBUG_TRACES
    CC_detail_list& cc_detail_list =
        (is_boundary ? cc_boundary_detail_lists[cell_d] : cc_interior_detail_lists[cell_d]);
    cc_detail_list.emplace_back(simplex);
#endif
    Simplex_cell_map& simplex_cell_map = simplex_cell_maps[cell_d];
    auto map_it = simplex_cell_map.find(simplex);
    if (map_it == simplex_cell_map.end()) {
      hasse_cells_.push_back(new Hasse_cell(is_boundary, cell_d));
      Hasse_cell* new_cell = hasse_cells_.back();
      simplex_cell_map.emplace(simplex, new_cell);
      cell_simplex_map_.emplace(new_cell, simplex);
#ifdef DEBUG_TRACES
      cc_detail_list.back().status_ = CC_detail_info::Result_type::inserted;
#endif
      return new_cell;
    }
#ifdef DEBUG_TRACES
    CC_detail_info& cc_info = cc_detail_list.back();
    cc_info.trigger_ = to_string(map_it->first);
    cc_info.status_ = CC_detail_info::Result_type::self;
#endif
    return map_it->second;
  }

  void expand_level(std::size_t cell_d) {
    bool is_manifold_with_boundary = boundary_simplex_cell_maps_.size() > 0;
    for (auto& sc_pair : interior_simplex_cell_maps_[cell_d - 1]) {
      const Simplex_handle& simplex = sc_pair.first;
      Hasse_cell* cell = sc_pair.second;
      for (Simplex_handle coface : simplex.coface_range(cod_d_ + cell_d)) {
        Hasse_cell* new_cell = insert_cell(coface, cell_d, false);
        new_cell->get_boundary().emplace_back(cell, 1);
      }
    }

    if (is_manifold_with_boundary) {
      for (auto& sc_pair : boundary_simplex_cell_maps_[cell_d - 1]) {
        const Simplex_handle& simplex = sc_pair.first;
        Hasse_cell* cell = sc_pair.second;
        if (cell_d != intr_d_)
          for (Simplex_handle coface : simplex.coface_range(cod_d_ + cell_d + 1)) {
            Hasse_cell* new_cell = insert_cell(coface, cell_d, true);
            new_cell->get_boundary().emplace_back(cell, 1);
          }
        auto map_it = interior_simplex_cell_maps_[cell_d].find(simplex);
        if (map_it == interior_simplex_cell_maps_[cell_d].end())
          std::cerr << "Cell_complex::expand_level error: A boundary cell does not have an interior counterpart.\n";
        else {
          Hasse_cell* i_cell = map_it->second;
          i_cell->get_boundary().emplace_back(cell, 1);
        }
      }
    }
  }

  void construct_complex_(const Out_simplex_map_& out_simplex_map) {
#ifdef DEBUG_TRACES
    cc_interior_summary_lists.resize(interior_simplex_cell_maps_.size());
    cc_interior_prejoin_lists.resize(interior_simplex_cell_maps_.size());
    cc_interior_detail_lists.resize(interior_simplex_cell_maps_.size());
#endif
    for (auto& os_pair : out_simplex_map) {
      const Simplex_handle& simplex = os_pair.first;
      const Eigen::VectorXd& point = os_pair.second;
      Hasse_cell* new_cell = insert_cell(simplex, 0, false);
      cell_point_map_.emplace(new_cell, point);
    }
    for (std::size_t cell_d = 1;
         cell_d < interior_simplex_cell_maps_.size() && !interior_simplex_cell_maps_[cell_d - 1].empty(); ++cell_d) {
      expand_level(cell_d);
    }
  }

  void construct_complex_(const Out_simplex_map_& interior_simplex_map, const Out_simplex_map_& boundary_simplex_map) {
#ifdef DEBUG_TRACES
    cc_interior_summary_lists.resize(interior_simplex_cell_maps_.size());
    cc_interior_prejoin_lists.resize(interior_simplex_cell_maps_.size());
    cc_interior_detail_lists.resize(interior_simplex_cell_maps_.size());
    cc_boundary_summary_lists.resize(boundary_simplex_cell_maps_.size());
    cc_boundary_prejoin_lists.resize(boundary_simplex_cell_maps_.size());
    cc_boundary_detail_lists.resize(boundary_simplex_cell_maps_.size());
#endif
    for (auto& os_pair : boundary_simplex_map) {
      const Simplex_handle& simplex = os_pair.first;
      const Eigen::VectorXd& point = os_pair.second;
      Hasse_cell* new_cell = insert_cell(simplex, 0, true);
      cell_point_map_.emplace(new_cell, point);
    }
    for (auto& os_pair : interior_simplex_map) {
      const Simplex_handle& simplex = os_pair.first;
      const Eigen::VectorXd& point = os_pair.second;
      Hasse_cell* new_cell = insert_cell(simplex, 0, false);
      cell_point_map_.emplace(new_cell, point);
    }
#ifdef DEBUG_TRACES
    for (const auto& sc_pair : interior_simplex_cell_maps_[0])
      cc_interior_summary_lists[0].push_back(CC_summary_info(sc_pair));
    for (const auto& sc_pair : boundary_simplex_cell_maps_[0])
      cc_boundary_summary_lists[0].push_back(CC_summary_info(sc_pair));
#endif

    for (std::size_t cell_d = 1;
         cell_d < interior_simplex_cell_maps_.size() && !interior_simplex_cell_maps_[cell_d - 1].empty(); ++cell_d) {
      expand_level(cell_d);

#ifdef DEBUG_TRACES
      for (const auto& sc_pair : interior_simplex_cell_maps_[cell_d])
        cc_interior_summary_lists[cell_d].push_back(CC_summary_info(sc_pair));
      if (cell_d < boundary_simplex_cell_maps_.size())
        for (const auto& sc_pair : boundary_simplex_cell_maps_[cell_d])
          cc_boundary_summary_lists[cell_d].push_back(CC_summary_info(sc_pair));
#endif
    }
  }

 public:
  /**
   * \brief Constructs the the cell complex that approximates an \f$m\f$-dimensional manifold
   *  without boundary embedded in the \f$ d \f$-dimensional Euclidean space
   *  from the output of the class Gudhi::Manifold_tracing.
   *
   * \param[in] out_simplex_map A map from simplices of dimension \f$(d-m)\f$
   * in the ambient triangulation that intersect the relative interior of the manifold
   * to the intersection points.
   */
  void construct_complex(const Out_simplex_map_& out_simplex_map) {
    interior_simplex_cell_maps_.resize(intr_d_ + 1);
    if (!out_simplex_map.empty()) cod_d_ = out_simplex_map.begin()->first.dimension();
    construct_complex_(out_simplex_map);
  }

  /**
   * \brief Constructs the skeleton of the cell complex that approximates
   *  an \f$m\f$-dimensional manifold without boundary embedded
   *  in the \f$d\f$-dimensional Euclidean space
   *  up to a limit dimension from the output of the class Gudhi::Manifold_tracing.
   *
   * \param[in] out_simplex_map A map from simplices of dimension \f$(d-m)\f$
   * in the ambient triangulation that intersect the relative interior of the manifold
   * to the intersection points.
   * \param[in] limit_dimension The dimension of the constructed skeleton.
   */
  void construct_complex(const Out_simplex_map_& out_simplex_map, std::size_t limit_dimension) {
    interior_simplex_cell_maps_.resize(limit_dimension + 1);
    if (!out_simplex_map.empty()) cod_d_ = out_simplex_map.begin()->first.dimension();
    construct_complex_(out_simplex_map);
  }

  /**
   * \brief Constructs the the cell complex that approximates an \f$m\f$-dimensional manifold
   *  with boundary embedded in the \f$ d \f$-dimensional Euclidean space
   *  from the output of the class Gudhi::Manifold_tracing.
   *
   * \param[in] interior_simplex_map A map from simplices of dimension \f$(d-m)\f$
   * in the ambient triangulation that intersect the relative interior of the manifold
   * to the intersection points.
   * \param[in] boundary_simplex_map A map from simplices of dimension \f$(d-m+1)\f$
   * in the ambient triangulation that intersect the boundary of the manifold
   * to the intersection points.
   */
  void construct_complex(const Out_simplex_map_& interior_simplex_map, const Out_simplex_map_& boundary_simplex_map) {
    interior_simplex_cell_maps_.resize(intr_d_ + 1);
    boundary_simplex_cell_maps_.resize(intr_d_);
    if (!interior_simplex_map.empty()) cod_d_ = interior_simplex_map.begin()->first.dimension();
    construct_complex_(interior_simplex_map, boundary_simplex_map);
  }

  /**
   * \brief Constructs the skeleton of the cell complex that approximates
   *  an \f$m\f$-dimensional manifold with boundary embedded
   *  in the \f$d\f$-dimensional Euclidean space
   *  up to a limit dimension from the output of the class Gudhi::Manifold_tracing.
   *
   * \param[in] interior_simplex_map A map from simplices of dimension \f$(d-m)\f$
   * in the ambient triangulation that intersect the relative interior of the manifold
   * to the intersection points.
   * \param[in] boundary_simplex_map A map from simplices of dimension \f$(d-m+1)\f$
   * in the ambient triangulation that intersect the boundary of the manifold
   * to the intersection points.
   * \param[in] limit_dimension The dimension of the constructed skeleton.
   */
  void construct_complex(const Out_simplex_map_& interior_simplex_map, const Out_simplex_map_& boundary_simplex_map,
                         std::size_t limit_dimension) {
    interior_simplex_cell_maps_.resize(limit_dimension + 1);
    boundary_simplex_cell_maps_.resize(limit_dimension);
    if (!interior_simplex_map.empty()) cod_d_ = interior_simplex_map.begin()->first.dimension();
    construct_complex_(interior_simplex_map, boundary_simplex_map);
  }

  /**
   * \brief Returns the dimension of the cell complex.
   */
  std::size_t intrinsic_dimension() const { return intr_d_; }

  /**
   * \brief Returns a vector of maps from the cells of various dimensions in the interior
   *  of the cell complex of type Gudhi::Hasse_cell to the permutahedral representations
   *  of the corresponding simplices in the ambient triangulation.
   */
  const Simplex_cell_maps& interior_simplex_cell_maps() const { return interior_simplex_cell_maps_; }

  /**
   * \brief Returns a vector of maps from the cells of various dimensions on the boundary
   *  of the cell complex of type Gudhi::Hasse_cell to the permutahedral representations
   *  of the corresponding simplices in the ambient triangulation.
   */
  const Simplex_cell_maps& boundary_simplex_cell_maps() const { return boundary_simplex_cell_maps_; }

  /**
   * \brief Returns a map from the cells of a given dimension in the interior
   *  of the cell complex of type Gudhi::Hasse_cell to the permutahedral representations
   *  of the corresponding simplices in the ambient triangulation.
   *
   * \param[in] cell_d The dimension of the cells.
   */
  const Simplex_cell_map& interior_simplex_cell_map(std::size_t cell_d) const {
    return interior_simplex_cell_maps_[cell_d];
  }

  /**
   * \brief Returns a map from the cells of a given dimension on the boundary
   *  of the cell complex of type Gudhi::Hasse_cell to the permutahedral representations
   *  of the corresponding simplices in the ambient triangulation.
   *
   * \param[in] cell_d The dimension of the cells.
   */
  const Simplex_cell_map& boundary_simplex_cell_map(std::size_t cell_d) const {
    return boundary_simplex_cell_maps_[cell_d];
  }

  /**
   * \brief Returns a map from the cells in the cell complex of type Gudhi::Hasse_cell
   *  to the permutahedral representations of the corresponding simplices in the
   *  ambient triangulation.
   */
  const Cell_simplex_map& cell_simplex_map() const { return cell_simplex_map_; }

  /**
   * \brief Returns a map from the vertex cells in the cell complex of type Gudhi::Hasse_cell
   *  to their Cartesian coordinates.
   */
  const Cell_point_map& cell_point_map() const { return cell_point_map_; }

  /**
   * \brief Constructor for the class Cell_complex.
   *
   * \param[in] intrinsic_dimension The dimension of the cell complex.
   */
  Cell_complex(std::size_t intrinsic_dimension) : intr_d_(intrinsic_dimension) {}

  ~Cell_complex() {
    for (Hasse_cell* hs_ptr : hasse_cells_) delete hs_ptr;
  }

 private:
  std::size_t intr_d_, cod_d_;
  Simplex_cell_maps interior_simplex_cell_maps_, boundary_simplex_cell_maps_;
  Cell_simplex_map cell_simplex_map_;
  Cell_point_map cell_point_map_;
  std::vector<Hasse_cell*> hasse_cells_;
};

}  // namespace coxeter_triangulation

}  // namespace Gudhi

#endif
