#pragma once

#ifdef USE_FIBONACCI_HEAP
#include <boost/heap/fibonacci_heap.hpp>
#endif

#include <boost/functional/hash.hpp>
#include <boost/heap/d_ary_heap.hpp>

#include <unordered_map>
#include <unordered_set>

// #include "neighbor.hpp"
// #include "planresult.hpp"
#include "a_star.hpp"

namespace libMultiRobotPlanning {

/*!
  \example sipp.cpp Simple example using a 2D grid world and
  up/down/left/right
  actions
*/

/*! \brief SIPP Algorithm to find the shortest path with dynamic obstacles

This class implements the SIPP algorithm. SIPP is an informed search algorithm
that finds the shortest path for a given map and dynamic a-priori known
obstacles.
It can use a heuristic that needs to be admissible.

Details of the algorithm can be found in the following paper:\n
Mike Phillips and Maxim Likhachev:\n
"SIPP:  Safe  Interval  Path  Planning  for  Dynamic  Environments". IEEE
International Conference on Robotics and Automation (ICRA), 2011\n
https://doi.org/10.1109/ICRA.2011.5980306

This class can either use a fibonacci heap, or a d-ary heap. The latter is the
default. Define "USE_FIBONACCI_HEAP" to use the fibonacci heap instead.

\tparam State Custom state for the search. Needs to be copy'able
\tparam Location Custom location type for the search. Needs to be copy'able
\tparam Cost Custom Cost type (integer or floating point types)
\tparam Environment This class needs to provide the custom A* logic. In
    particular, it needs to support the following functions:
  - `Cost admissibleHeuristic(const State& s)`\n
    This function can return 0 if no suitable heuristic is available.

  - `bool isSolution(const State& s)`\n
    Return true if the given state is a goal state.

  - `void getNeighbors(const State& s, std::vector<Neighbor<State, Action,
   int> >& neighbors)`\n
    Fill the list of neighboring state for the given state s.

  - `void onExpandNode(const State& s, int fScore, int gScore)`\n
    This function is called on every expansion and can be used for statistical
purposes.

  - `void onDiscover(const State& s, int fScore, int gScore)`\n
    This function is called on every node discovery and can be used for
   statistical purposes.
*/
template <typename State, typename Location, typename Action, typename Cost,
          typename Environment>
class SIPP {
 public:
  struct interval {
    interval(Cost start, Cost end) : start(start), end(end) {}

    Cost start;
    Cost end;

    friend bool operator<(const interval& a, const interval& b) {
      return a.start < b.start;
    }
  };

 public:
   struct edgeCollision{
 	  edgeCollision(Cost t, Action action) : t(t), action(action){}

 	Cost t;
 	Action action;
 	friend bool operator==(const edgeCollision& a, const edgeCollision& b){
 		return (a.t == b.t) && (a.action == b.action);
 	}
  };

 public:
  SIPP(Environment& environment) : m_env(environment), m_astar(m_env) {}

  void setCollisionIntervals(const Location& location,
                             const std::vector<interval>& intervals) {
    m_env.setCollisionIntervals(location, intervals);
  }

  void setEdgeCollisions(const Location& location,
                             const std::vector<edgeCollision>& ec) {
    m_env.setEdgeCollisions(location, ec);
  }

  bool mightHaveSolution(const State& goal) {
    return m_env.mightHaveSolution(goal);
  }

  bool search(const State& startState, const Action& waitAction,
              PlanResult<State, Action, Cost>& solution, Cost startTime = 0) {
    PlanResult<SIPPState, SIPPAction, Cost> astarsolution;
    solution.cost = 0;
    solution.fmin = 0;
    solution.actions.clear();
    solution.states.clear();
    size_t interval;
    if (!m_env.findSafeInterval(startState, startTime, interval)) {
      return false;
    }
    bool success = m_astar.search(SIPPState(startState, interval),
                                  astarsolution, startTime);
    solution.cost = astarsolution.cost - startTime;
    solution.fmin = astarsolution.fmin;
    for (size_t i = 0; i < astarsolution.actions.size(); ++i) {
      Cost waitTime =
          astarsolution.actions[i].second - astarsolution.actions[i].first.time;
      if (waitTime == 0) {
        solution.states.push_back(
            std::make_pair<>(astarsolution.states[i].first.state,
                             astarsolution.states[i].second));
        solution.actions.push_back(
            std::make_pair<>(astarsolution.actions[i].first.action,
                             astarsolution.actions[i].second));
      } else {
        // additional wait action before
        solution.states.push_back(
            std::make_pair<>(astarsolution.states[i].first.state,
                             astarsolution.states[i].second));
        solution.actions.push_back(std::make_pair<>(waitAction, waitTime));
        solution.states.push_back(
            std::make_pair<>(astarsolution.states[i].first.state,
                             astarsolution.states[i].second + waitTime));
        solution.actions.push_back(
            std::make_pair<>(astarsolution.actions[i].first.action,
                             astarsolution.actions[i].first.time));
      }
    }
    solution.states.push_back(
        std::make_pair<>(astarsolution.states.back().first.state,
                         astarsolution.states.back().second));

    return success;
  }

 private:
  // public:
  struct SIPPState {
    SIPPState(const State& state, size_t interval)
        : state(state), interval(interval) {}
    SIPPState(const State& state, size_t interval, unsigned int dir)
        : state(state), interval(interval), dir(dir) {}

    bool operator==(const SIPPState& other) const {
      return std::tie(state, interval) == std::tie(other.state, other.interval);
    }

    friend std::ostream& operator<<(std::ostream& os, const SIPPState& s) {
      return os << "(" << s.state << "," << s.interval << ")";
    }

    State state;
    unsigned int dir;
    size_t interval;
  };

  struct SIPPStateHasher {
    size_t operator()(const SIPPState& s) const {
      size_t seed = 0;
      boost::hash_combine(seed, std::hash<State>()(s.state));
      boost::hash_combine(seed, s.interval);
      return seed;
    }
  };

  struct SIPPAction {
    SIPPAction(const Action& action, Cost time) : action(action), time(time) {}

    Action action;
    Cost time;
  };

  // private:
  struct SIPPEnvironment {
    SIPPEnvironment(Environment& env) : m_env(env) {}

    Cost admissibleHeuristic(const SIPPState& s) {
      return m_env.admissibleHeuristic(s.state);
    }

    bool mightHaveSolution(const State& goal) {
      const auto& si = safeIntervals(m_env.getLocation(goal));
      return m_env.isSolution(goal) && !si.empty() &&
             si.back().end == std::numeric_limits<Cost>::max();
    }

    bool isSolution(const SIPPState& s) {
      return m_env.isSolution(s.state) &&
             safeIntervals(m_env.getLocation(s.state)).at(s.interval).end ==
                 std::numeric_limits<Cost>::max();
    }

    void getNeighbors(
        const SIPPState& s,
        std::vector<Neighbor<SIPPState, SIPPAction, Cost> >& neighbors) {
      std::vector<Neighbor<State, Action, Cost> > motions;
// 	  std::cout << "Current state ---------------------------------------GScore " << m_lastGScore << " " << s.state.x << " " << s.state.y << " dir " << s.dir << " \n";

      m_env.getNeighbors(s.state, motions);
      for (const auto& m : motions) {
    	  m_env.num_generation++;
        // std::cout << "gN " << m.state << std::endl;
        Cost m_time = m.cost;
        // std::cout << m_lastGScore;
        Cost start_t = m_lastGScore + m_time;
        Cost end_t =
            safeIntervals(m_env.getLocation(s.state)).at(s.interval).end;

        const auto& sis = safeIntervals(m_env.getLocation(m.state));
        for (size_t i = 0; i < sis.size(); ++i) {
          const interval& si = sis[i];
//          std::cout << m.state.x << " " << m.state.y << "  i " << i << ": " << si.start << " , " << si.end << " " << start_t << " " << end_t<< " \n";
          // std::endl;
          if (si.start - m_time > end_t || si.end < start_t) {
            continue;
          }
//          std::cout << " --------------\n";
          int t;
          unsigned int dir_1 = 0x00;
          Action a_temp;
          if(m.action == Action::Left) {a_temp = Action::Right;dir_1 = 0x01;}
          else if(m.action == Action::Right) {a_temp = Action::Left; dir_1 = 0x02;}
          else if(m.action == Action::Up) {a_temp = Action::Down;dir_1 = 0x04;}
          else if(m.action == Action::Down){ a_temp = Action::Up; dir_1 = 0x08;}

          if (m_env.isCommandValid(s.state, m.state, m.action, m_lastGScore,
                                   end_t, si.start, si.end, t)
        		  && !IsEdgeCollisions(m.state, edgeCollision(t - 1, a_temp))) {
            // std::cout << "  gN: " << m.state << "," << i << "," << t << ","
            // << m_lastGScore << std::endl;
            neighbors.emplace_back(Neighbor<SIPPState, SIPPAction, Cost>(
                SIPPState(m.state, i, dir_1), SIPPAction(m.action, m.cost),
                t - m_lastGScore));
//               std::cout << "Successor : " << m.state.x << " " << m.state.y <<" Cost " << m.cost  << " dir " << dir_1 << " Gscore " << t << " \n";
          }
        }
      }
    }

    void onExpandNode(const SIPPState& s, Cost fScore, Cost gScore) {
      // const auto& interval =
      // safeIntervals(m_env.getLocation(s.state)).at(s.interval);
      // std::cout << "expand: " << s.state << "," << interval.start << " to "
      // << interval.end << "(g: " << gScore << " f: " << fScore << ")" <<
      // std::endl;
      // This is called before getNeighbors(). We use the callback to find the
      // current cost (=time) of the expanded node
      m_env.num_expansion++;
      m_lastGScore = gScore;
      m_env.onExpandNode(s.state, fScore, gScore);
    }

    void onDiscover(const SIPPState& s, Cost fScore, Cost gScore) {
      // const auto& interval =
      // safeIntervals(m_env.getLocation(s.state)).at(s.interval);
      // std::cout << "discover: " << s.state << "," << interval.start << " to "
      // << interval.end << std::endl;
      m_env.onDiscover(s.state, fScore, gScore);
    }

    void setCollisionIntervals(const Location& location,
                               const std::vector<interval>& intervals) {
      m_safeIntervals.erase(location);
      std::vector<interval> sortedIntervals(intervals);
      std::sort(sortedIntervals.begin(), sortedIntervals.end());

      // std::cout << location << ": " << std::endl;
      if (intervals.size() > 0) {
        m_safeIntervals[location];  // create empty safe interval
        int start = 0;
        int lastEnd = 0;
        for (const auto& interval : sortedIntervals) {
          // std::cout << "  ci: " << interval.start << " - " << interval.end <<
          // std::endl;
          assert(interval.start <= interval.end);
          assert(start <= interval.start);
          // if (start + 1 != interval.start - 1) {
          // std::cout << start << "," << interval.start << std::endl;
          // assert(start + 1 < interval.start - 1);
          if (start <= interval.start - 1) {
            m_safeIntervals[location].push_back({start, interval.start - 1});
          }
          // }
          start = interval.end + 1;
          lastEnd = interval.end;
        }
        if (lastEnd < std::numeric_limits<int>::max()) {
          // assert(start < std::numeric_limits<int>::max());
          m_safeIntervals[location].push_back(
              {start, std::numeric_limits<int>::max()});
        }
      }

      // auto iter = m_safeIntervals.find(location);
      // if (iter != m_safeIntervals.end()) {
      //   for (const auto& si : iter->second) {
      //     std::cout << "  si: " << si.start << " - " << si.end << std::endl;
      //   }
      // }
    }

    bool findSafeInterval(const State& state, Cost time, size_t& interval) {
      const auto& si = safeIntervals(m_env.getLocation(state));
      for (size_t idx = 0; idx < si.size(); ++idx) {
        if (si[idx].start <= time && si[idx].end >= time) {
          interval = idx;
          return true;
        }
      }
      return false;
    }

    void setEdgeCollisions(const Location& location,
  		  	  	  	  	const std::vector<edgeCollision>& edge_collision) {
  	  m_edgeCollision.erase(location);
  	  if(edge_collision.size() > 0){
  		  m_edgeCollision[location];
  		  for(const auto& ec : edge_collision){
  			  m_edgeCollision[location].push_back(ec);
  		  }
  	  }
    }

    bool IsEdgeCollisions(const Location& location, const edgeCollision& ec){
//    		std::cout << location.x << " " << location.y << " " << ec.t << " " << ec.action << std::endl;
//    		return false;
    		const auto iter = m_edgeCollision.find(location);
    		if(iter == m_edgeCollision.end()) return false;
    		if((iter->second).size() == 0) return false;
    		for (auto& cec : (iter->second)){
    			if( cec == ec ){
    				return true;
    			}
    		}
    		return false;
    }

   private:
    const std::vector<interval>& safeIntervals(const Location& location) {
      static std::vector<interval> defaultInterval(
          1, {0, std::numeric_limits<Cost>::max()});
      const auto iter = m_safeIntervals.find(location);
      if (iter == m_safeIntervals.end()) {
        return defaultInterval;
      }
      return iter->second;
    }

   private:
    Environment& m_env;
    Cost m_lastGScore;
    std::unordered_map<Location, std::vector<interval> > m_safeIntervals;
    std::unordered_map<Location, std::vector<edgeCollision>> m_edgeCollision;
  };

 private:
  SIPPEnvironment m_env;
  AStar<SIPPState, SIPPAction, Cost, SIPPEnvironment, SIPPStateHasher> m_astar;
};

}  // namespace libMultiRobotPlanning


/* Copyright (c) 2016 - 2020, the adamantine authors.
 *
 * This file is subject to the Modified BSD License and may not be distributed
 * without copyright and license information. Please refer to the file LICENSE
 * for the text and further information on this license.
 */

#ifndef THERMAL_PHYSICS_TEMPLATES_HH
#define THERMAL_PHYSICS_TEMPLATES_HH

#include <ThermalOperator.hh>
#ifdef ADAMANTINE_HAVE_CUDA
#include <ThermalOperatorDevice.hh>
#endif
#include <ThermalPhysics.hh>

#include <deal.II/dofs/dof_tools.h>
#include <deal.II/fe/fe_values.h>
#include <deal.II/grid/filtered_iterator.h>
#include <deal.II/lac/precondition.h>
#include <deal.II/lac/solver_gmres.h>

#include <algorithm>

namespace adamantine
{
namespace
{
template <int dim, int fe_degree, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value,
              int> = 0>
dealii::LA::distributed::Vector<double, MemorySpaceType> vmult_and_scale(
    std::shared_ptr<ThermalOperatorBase<dim, MemorySpaceType>> thermal_operator,
    dealii::LA::distributed::Vector<double, MemorySpaceType> const &y,
    dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> &value,
    std::vector<Timer> &timers)
{
  // Apply the Thermal Operator.
  thermal_operator->vmult_add(value, y);

  // Multiply by the inverse of the mass matrix.
  value.scale(*thermal_operator->get_inverse_mass_matrix());

  timers[evol_time_eval_th_ph].stop();

  return value;
}

template <int dim, int fe_degree, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value,
              int> = 0>
void init_dof_vector(
    dealii::DoFHandler<dim> const &dof_handler, dealii::FE_Q<dim> const &fe,
    double const value,
    dealii::LinearAlgebra::distributed::Vector<double, MemorySpaceType> &vector)
{
  dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> dummy;
  dealii::QGauss<dim> quadrature(1);
  dealii::FEValues<dim> fe_values(fe, quadrature, dealii::update_values);
  unsigned int const dofs_per_cell = fe.dofs_per_cell;
  std::vector<dealii::types::global_dof_index> local_dof_indices(dofs_per_cell);
  dealii::IndexSet local_elements = vector.locally_owned_elements();
  for (auto cell :
       dealii::filter_iterators(dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    fe_values.reinit(cell);
    cell->get_dof_indices(local_dof_indices);
    for (auto const dof_index : local_dof_indices)
    {
      if (local_elements.is_element(dof_index) == true)
        vector[dof_index] = value;
    }
  }
}

#ifdef ADAMANTINE_HAVE_CUDA
template <int dim, int fe_degree, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::CUDA>::value,
              int> = 0>
dealii::LA::distributed::Vector<double, MemorySpaceType> vmult_and_scale(
    std::shared_ptr<ThermalOperatorBase<dim, MemorySpaceType>> thermal_operator,
    dealii::LA::distributed::Vector<double, MemorySpaceType> const &y,
    dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> &value,
    std::vector<Timer> &timers)
{
  dealii::LA::distributed::Vector<double, MemorySpaceType> value_dev(
      value.get_partitioner());
  value_dev.import(value, dealii::VectorOperation::insert);

  // Apply the Thermal Operator.
  thermal_operator->vmult_add(value_dev, y);

  // Multiply by the inverse of the mass matrix.
  value_dev.scale(*thermal_operator->get_inverse_mass_matrix());

  timers[evol_time_eval_th_ph].stop();

  return value_dev;
}

template <int dim, int fe_degree, typename MemorySpaceType,
          std::enable_if_t<
              std::is_same<MemorySpaceType, dealii::MemorySpace::CUDA>::value,
              int> = 0>
void init_dof_vector(
    dealii::DoFHandler<dim> const &dof_handler, dealii::FE_Q<dim> const &fe,
    double const value,
    dealii::LinearAlgebra::distributed::Vector<double, MemorySpaceType> &vector)
{
  dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host>
      vector_host(vector.get_partitioner());
  dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> dummy;

  dealii::QGauss<dim> quadrature(1);
  dealii::FEValues<dim> fe_values(fe, quadrature, dealii::update_values);
  unsigned int const dofs_per_cell = fe.dofs_per_cell;
  std::vector<dealii::types::global_dof_index> local_dof_indices(dofs_per_cell);
  dealii::IndexSet local_elements = vector.locally_owned_elements();
  for (auto cell :
       dealii::filter_iterators(dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    fe_values.reinit(cell);
    cell->get_dof_indices(local_dof_indices);
    for (auto const dof_index : local_dof_indices)
    {
      if (local_elements.is_element(dof_index) == true)
        vector_host[dof_index] = value;
    }
  }
  vector.import(vector_host, dealii::VectorOperation::insert);
}
#endif
} // namespace

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
ThermalPhysics<dim, fe_degree, MemorySpaceType, QuadratureType>::ThermalPhysics(
    MPI_Comm const &communicator, boost::property_tree::ptree const &database,
    Geometry<dim> &geometry)
    : _embedded_method(false), _implicit_method(false), _geometry(geometry),
      _fe(fe_degree), _dof_handler(_geometry.get_triangulation()),
      _quadrature(fe_degree + 1)
{
  // Create the material properties
  boost::property_tree::ptree const &material_database =
      database.get_child("materials");
  _material_properties.reset(new MaterialProperty<dim>(
      communicator, _geometry.get_triangulation(), material_database));

  // Create the electron beams
  boost::property_tree::ptree const &source_database =
      database.get_child("sources");
  unsigned int const n_beams = source_database.get<unsigned int>("n_beams");
  _electron_beams.resize(n_beams);
  for (unsigned int i = 0; i < n_beams; ++i)
  {
    boost::property_tree::ptree const &beam_database =
        source_database.get_child("beam_" + std::to_string(i));
    _electron_beams[i] = std::make_unique<ElectronBeam<dim>>(beam_database);
    _electron_beams[i]->set_max_height(_geometry.get_max_height());
  }

  // Create the thermal operator
  if (std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value)
    _thermal_operator =
        std::make_shared<ThermalOperator<dim, fe_degree, MemorySpaceType>>(
            communicator, _material_properties);
#ifdef ADAMANTINE_HAVE_CUDA
  else
    _thermal_operator = std::make_shared<
        ThermalOperatorDevice<dim, fe_degree, MemorySpaceType>>(
        communicator, _material_properties);
#endif

  // Create the time stepping scheme
  boost::property_tree::ptree const &time_stepping_database =
      database.get_child("time_stepping");
  std::string method = time_stepping_database.get<std::string>("method");
  std::transform(method.begin(), method.end(), method.begin(),
                 [](unsigned char c) { return std::tolower(c); });
  if (method.compare("forward_euler") == 0)
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ExplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::FORWARD_EULER);
  else if (method.compare("rk_third_order") == 0)
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ExplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::RK_THIRD_ORDER);
  else if (method.compare("rk_fourth_order") == 0)
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ExplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::RK_CLASSIC_FOURTH_ORDER);
  else if (method.compare("heun_euler") == 0)
  {
    _time_stepping = std::make_unique<
        dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector>>(
        dealii::TimeStepping::HEUN_EULER);
    _embedded_method = true;
  }
  else if (method.compare("bogacki_shampine") == 0)
  {
    _time_stepping = std::make_unique<
        dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector>>(
        dealii::TimeStepping::BOGACKI_SHAMPINE);
    _embedded_method = true;
  }
  else if (method.compare("dopri") == 0)
  {
    _time_stepping = std::make_unique<
        dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector>>(
        dealii::TimeStepping::DOPRI);
    _embedded_method = true;
  }
  else if (method.compare("fehlberg") == 0)
  {
    _time_stepping = std::make_unique<
        dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector>>(
        dealii::TimeStepping::FEHLBERG);
    _embedded_method = true;
  }
  else if (method.compare("cash_karp") == 0)
  {
    _time_stepping = std::make_unique<
        dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector>>(
        dealii::TimeStepping::CASH_KARP);
    _embedded_method = true;
  }
  else if (method.compare("backward_euler") == 0)
  {
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ImplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::BACKWARD_EULER);
    _implicit_method = true;
  }
  else if (method.compare("implicit_midpoint") == 0)
  {
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ImplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::IMPLICIT_MIDPOINT);
    _implicit_method = true;
  }
  else if (method.compare("crank_nicolson") == 0)
  {
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ImplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::CRANK_NICOLSON);
    _implicit_method = true;
  }
  else if (method.compare("sdirk2") == 0)
  {
    _time_stepping =
        std::make_unique<dealii::TimeStepping::ImplicitRungeKutta<LA_Vector>>(
            dealii::TimeStepping::SDIRK_TWO_STAGES);
    _implicit_method = true;
  }

  if (_embedded_method == true)
  {
    double coarsen_param =
        time_stepping_database.get("coarsening_parameter", 1.2);
    double refine_param = time_stepping_database.get("refining_parameter", 0.8);
    double min_delta = time_stepping_database.get("min_time_step", 1e-14);
    double max_delta = time_stepping_database.get("max_time_step", 1e100);
    double refine_tol = time_stepping_database.get("refining_tolerance", 1e-8);
    double coarsen_tol =
        time_stepping_database.get("coarsening_tolerance", 1e-12);
    dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector> *embedded_rk =
        static_cast<
            dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector> *>(
            _time_stepping.get());
    embedded_rk->set_time_adaptation_parameters(coarsen_param, refine_param,
                                                min_delta, max_delta,
                                                refine_tol, coarsen_tol);
  }

  // If the time stepping scheme is implicit, set the parameters for the solver
  // and create the implicit operator.
  if (_implicit_method == true)
  {
    _max_iter = time_stepping_database.get("max_iteration", 1000);
    _tolerance = time_stepping_database.get("tolerance", 1e-12);
    _right_preconditioning =
        time_stepping_database.get("right_preconditioning", false);
    _max_n_tmp_vectors = time_stepping_database.get("n_tmp_vectors", 30);
    unsigned int newton_max_iter =
        time_stepping_database.get("newton_max_iteration", 100);
    double newton_tolerance =
        time_stepping_database.get("newton_tolerance", 1e-6);
    dealii::TimeStepping::ImplicitRungeKutta<LA_Vector> *implicit_rk =
        static_cast<dealii::TimeStepping::ImplicitRungeKutta<LA_Vector> *>(
            _time_stepping.get());
    implicit_rk->set_newton_solver_parameters(newton_max_iter,
                                              newton_tolerance);

    bool jfnk = time_stepping_database.get("jfnk", false);
    _implicit_operator = std::make_unique<ImplicitOperator<MemorySpaceType>>(
        _thermal_operator, jfnk);
  }
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
void ThermalPhysics<dim, fe_degree, MemorySpaceType,
                    QuadratureType>::setup_dofs()
{
  _dof_handler.distribute_dofs(_fe);
  dealii::IndexSet locally_relevant_dofs;
  dealii::DoFTools::extract_locally_relevant_dofs(_dof_handler,
                                                  locally_relevant_dofs);
  _affine_constraints.clear();
  _affine_constraints.reinit(locally_relevant_dofs);
  dealii::DoFTools::make_hanging_node_constraints(_dof_handler,
                                                  _affine_constraints);
  _affine_constraints.close();

  _thermal_operator->reinit(_dof_handler, _affine_constraints, _quadrature);
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
void ThermalPhysics<dim, fe_degree, MemorySpaceType,
                    QuadratureType>::compute_inverse_mass_matrix()
{
  _thermal_operator->compute_inverse_mass_matrix(_dof_handler,
                                                 _affine_constraints);
  if (_implicit_method == true)
    _implicit_operator->set_inverse_mass_matrix(
        _thermal_operator->get_inverse_mass_matrix());
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
double ThermalPhysics<dim, fe_degree, MemorySpaceType, QuadratureType>::
    evolve_one_time_step(
        double t, double delta_t,
        dealii::LA::distributed::Vector<double, MemorySpaceType> &solution,
        std::vector<Timer> &timers)
{
  auto eval = [&](double const t, LA_Vector const &y) {
    return evaluate_thermal_physics(t, y, timers);
  };
  auto id_m_Jinv = [&](double const t, double const tau, LA_Vector const &y) {
    return id_minus_tau_J_inverse(t, tau, y, timers);
  };

  double time = _time_stepping->evolve_one_time_step(eval, id_m_Jinv, t,
                                                     delta_t, solution);

  // If the method is embedded, get the next time step. Otherwise, just use the
  // current time step.
  if (_embedded_method == false)
    _delta_t_guess = delta_t;
  else
  {
    dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector> *embedded_rk =
        static_cast<
            dealii::TimeStepping::EmbeddedExplicitRungeKutta<LA_Vector> *>(
            _time_stepping.get());
    _delta_t_guess = embedded_rk->get_status().delta_t_guess;
  }

  // Return the time at the end of the time step. This may be different than
  // t+delta_t for embedded methods.
  return time;
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
void ThermalPhysics<dim, fe_degree, MemorySpaceType, QuadratureType>::
    initialize_dof_vector(
        dealii::LA::distributed::Vector<double, MemorySpaceType> &vector) const
{
  _thermal_operator->initialize_dof_vector(vector);
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
void ThermalPhysics<dim, fe_degree, MemorySpaceType, QuadratureType>::
    initialize_dof_vector(
        double const value,
        dealii::LA::distributed::Vector<double, MemorySpaceType> &vector) const
{
  // Resize the vector
  _thermal_operator->initialize_dof_vector(vector);

  init_dof_vector<dim, fe_degree, MemorySpaceType>(_dof_handler, _fe, value,
                                                   vector);
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
dealii::LA::distributed::Vector<double, MemorySpaceType>
ThermalPhysics<dim, fe_degree, MemorySpaceType, QuadratureType>::
    evaluate_thermal_physics(
        double const t,
        dealii::LA::distributed::Vector<double, MemorySpaceType> const &y,
        std::vector<Timer> &timers) const
{
  timers[evol_time_eval_mat_prop].start();
  if (std::is_same<MemorySpaceType, dealii::MemorySpace::Host>::value)
    _thermal_operator->evaluate_material_properties(y);
  else
  {
    // TODO do this on the GPU
    dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> y_host(
        y.get_partitioner());
    y_host.import(y, dealii::VectorOperation::insert);
    _thermal_operator->evaluate_material_properties(y_host);
  }
  timers[evol_time_eval_mat_prop].stop();

  timers[evol_time_eval_th_ph].start();
  dealii::LA::distributed::Vector<double, dealii::MemorySpace::Host> source(
      y.get_partitioner());
  source = 0.;

  // Compute the source term.
  for (auto &beam : _electron_beams)
    beam->set_time(t);
  dealii::QGauss<dim> source_quadrature(fe_degree + 1);
  dealii::FEValues<dim> fe_values(_fe, source_quadrature,
                                  dealii::update_quadrature_points |
                                      dealii::update_values |
                                      dealii::update_JxW_values);
  unsigned int const dofs_per_cell = _fe.dofs_per_cell;
  unsigned int const n_q_points = source_quadrature.size();
  std::vector<dealii::types::global_dof_index> local_dof_indices(dofs_per_cell);
  dealii::Vector<double> cell_source(dofs_per_cell);

  for (auto cell :
       dealii::filter_iterators(_dof_handler.active_cell_iterators(),
                                dealii::IteratorFilters::LocallyOwnedCell()))
  {
    cell_source = 0.;
    fe_values.reinit(cell);
    double const inv_rho_cp = _thermal_operator->get_inv_rho_cp(cell);

    for (unsigned int i = 0; i < dofs_per_cell; ++i)
    {
      for (unsigned int q = 0; q < n_q_points; ++q)
      {
        double quad_pt_source = 0.;
        dealii::Point<dim> const &q_point = fe_values.quadrature_point(q);
        for (auto &beam : _electron_beams)
          quad_pt_source += beam->value(q_point);

        cell_source[i] += inv_rho_cp * quad_pt_source *
                          fe_values.shape_value(i, q) * fe_values.JxW(q);
      }
    }
    cell->get_dof_indices(local_dof_indices);
    _affine_constraints.distribute_local_to_global(cell_source,
                                                   local_dof_indices, source);
  }

  return vmult_and_scale<dim, fe_degree, MemorySpaceType>(_thermal_operator, y,
                                                          source, timers);
}

template <int dim, int fe_degree, typename MemorySpaceType,
          typename QuadratureType>
dealii::LA::distributed::Vector<double, MemorySpaceType>
ThermalPhysics<dim, fe_degree, MemorySpaceType, QuadratureType>::
    id_minus_tau_J_inverse(
        double const /*t*/, double const tau,
        dealii::LA::distributed::Vector<double, MemorySpaceType> const &y,
        std::vector<Timer> &timers) const
{
  timers[evol_time_J_inv].start();
  _implicit_operator->set_tau(tau);
  dealii::LA::distributed::Vector<double, MemorySpaceType> solution(
      y.get_partitioner());

  // TODO Add a geometric multigrid preconditioner.
  dealii::PreconditionIdentity preconditioner;

  dealii::SolverControl solver_control(_max_iter, _tolerance * y.l2_norm());
  // We need to inverse (I - tau M^{-1} J). While M^{-1} and J are SPD,
  // (I - tau M^{-1} J) is symmetric indefinite in the general case.
  typename dealii::SolverGMRES<
      dealii::LA::distributed::Vector<double, MemorySpaceType>>::AdditionalData
      additional_data(_max_n_tmp_vectors, _right_preconditioning);
  dealii::SolverGMRES<dealii::LA::distributed::Vector<double, MemorySpaceType>>
      solver(solver_control, additional_data);
  solver.solve(*_implicit_operator, solution, y, preconditioner);

  timers[evol_time_J_inv].stop();

  return solution;
}
} // namespace adamantine

#endif


// Copyright 2015-2022 The ALMA Project Developers
//
// 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.

#pragma once

/// @file
/// Classes and functions used to manipulate grids in reciprocal space.

#include <boost/mpi.hpp>
#include <structures.hpp>
#include <dynamical_matrix.hpp>

namespace alma {
/// Convenient shorthand for an array of four indices.
using Tetrahedron = std::array<std::size_t, 4>;
/// Convenient shorthand for an array of three indices.
using Triangle = std::array<std::size_t, 3>;

/// Objects of this class represent a regular grid with the Gamma
/// point in one corner.
class Gamma_grid {
public:
    /// Size of the grid along the first reciprocal axis.
    const int na;
    /// Size of the grid along the second reciprocal axis.
    const int nb;
    /// Size of the grid along the third reciprocal axis.
    const int nc;
    /// Total number of q points in the grid.
    const std::size_t nqpoints;
    /// Reciprocal lattice basis vectors.
    const Eigen::MatrixXd rlattvec;
    /// Side vectors of each element in reciprocal space.
    const Eigen::MatrixXd dq;
    /// Constructor: initialize all internal variables and compute
    /// the spectrum at each q point. Do not correct the dynamical
    /// matrix for the effect of long-range interactions.
    Gamma_grid(const Crystal_structure& poscar,
               const Symmetry_operations& symms,
               const Harmonic_ifcs& force_constants,
               int _na,
               int _nb,
               int _nc);

    /// Constructor: initialize all internal variables and compute
    /// the spectrum at each q point. Correct the dynamical
    /// matrix for the effect of long-range interactions.
    Gamma_grid(const Crystal_structure& poscar,
               const Symmetry_operations& symms,
               const Harmonic_ifcs& force_constants,
               const Dielectric_parameters& born,
               int _na,
               int _nb,
               int _nc);

    /// Set the three lowest frequencies at Gamma to zero.
    void enforce_asr() {
        this->spectrum[0].omega.head(3).fill(0.);
    }

    /// Return the index of a q point identified by its position
    /// along the three axes.
    ///
    /// Indices are interpreted modulo-{na, nb, nc}, so even
    /// negative values are accepted.
    /// @param[in] indices - positions of the q point
    /// along the three axes
    /// @return the index of the q point in this grid
    std::size_t three_to_one(const std::array<int, 3>& indices) const {
        auto ia = python_mod(indices[0], this->na);
        auto ib = python_mod(indices[1], this->nb);
        auto ic = python_mod(indices[2], this->nc);

        return ic + nc * (ib + nb * ia);
    }

    /// Return the coordinates of a q point identified by its
    /// index.
    ///
    /// @param[in] index - index of the q point
    /// @return - an array with the positions of the q point
    /// along the three axes.
    std::array<int, 3> one_to_three(std::size_t iq) const {
        std::array<int, 3> nruter;
        nruter[2] = iq % nc;
        iq /= nc;
        nruter[1] = iq % nb;
        nruter[0] = iq / nb;
        return nruter;
    }

    /// @return the number of q-point equivalence classes in the grid.
    std::size_t get_nequivalences() const {
        return this->equivalences.size();
    }

    /// Access the harmonic properties at a point in the grid.
    ///
    /// The index is interpreted using modular arithmetic,
    /// so even negative values are accepted.
    /// @param[in] iq - index of the q point
    /// @return the harmonic properties at the q point.
    const Spectrum_at_point& get_spectrum_at_q(int iq) const {
        std::size_t index = python_mod(iq, this->nqpoints);

        return this->spectrum[index];
    }

    /// Return the cardinal of an equivalence class.
    ///
    /// @param[in] ic - index of the equivalence class.
    /// @return the number of points in an equivalence class.
    std::size_t get_cardinal(std::size_t ic) const {
        if (ic > this->equivalences.size())
            throw value_error("wrong equivalence class index");
        return this->equivalences[ic].size();
    }

    /// Return a representative of an equivalence class.
    ///
    /// The method is guaranteed to always return the same point.
    /// @param[in] ic - index of the equivalence class.
    /// @return a representative of the equivalence class.
    std::size_t get_representative(std::size_t ic) const {
        if (ic > this->equivalences.size())
            throw value_error("wrong equivalence class index");
        return this->equivalences[ic][0];
    }

    /// Return the elements in an equivalence class.
    ///
    /// @param[in] ic - index of the equivalence class.
    /// @return a vector with the elements in the equivalence
    /// class.
    std::vector<std::size_t> get_equivalence(std::size_t ic) const {
        if (ic > this->equivalences.size())
            throw value_error("wrong equivalence class index");
        return this->equivalences[ic];
    }

    /// Return the Cartesian coordinates of a q point.
    ///
    /// @param[in] iq - a q point index
    /// @return three Cartesian coordinates
    Eigen::VectorXd get_q(std::size_t iq) const {
        std::size_t index = python_mod(iq, this->nqpoints);

        return this->cpos.col(index);
    }

    /// Return the base broadening (without any prefactor) for a
    /// mode.
    ///
    /// @param[in] v - group velocity, or difference
    /// of group velocities in the case of three-phonon processes
    /// @return - the standard deviation of a Gaussian
    double base_sigma(const Eigen::VectorXd& v) const {
        return (v.transpose() * this->dq).norm() / std::sqrt(12.);
    }

    /// Very basic constructor that builds a stub object. Useful
    /// for deserialization or for obtaining an equivalences vector
    /// without computing the spectrum.
    Gamma_grid(const Crystal_structure& poscar,
               const Symmetry_operations& symms,
               int _na,
               int _nb,
               int _nc);

    /// Find the index of the polar opposite q point.
    ///
    /// @param[in] q - a q point
    /// @return the index of the polar opposite of q
    std::size_t polar_opposite(std::size_t q) {
        auto indices = this->one_to_three(q);

        return this->three_to_one({{-indices[0], -indices[1], -indices[2]}});
    }

    /// Return the images of a q point through all the symmetry
    /// operations, including inversions.
    ///
    /// @param[in] q - a q point
    /// @return a vector of q point indices. Elements with even indices
    /// correspond to operations in the space group,
    /// while elements with odd indices compound them with time
    /// reversal.
    std::vector<size_t> equivalent_qpoints(std::size_t original) const {
        std::size_t index = python_mod(original, this->nqpoints);
        return this->symmetry_map[index];
    }

    /// Find all q-point pairs equivalent to the input.
    ///
    /// Given a pair of indices, obtain all equivalent pairs
    /// after looking the up in the symmetry_map.
    /// @param[in] pair a pair of q point indices
    /// @return a vector of pairs, including the input
    std::vector<std::array<std::size_t, 2>> equivalent_qpairs(
        const std::array<std::size_t, 2>& original) const;

    /// Find all q-point triplets equivalent to the input.
    ///
    /// Given a triplet of indices, obtain all equivalent triplets
    /// after looking the up in the symmetry_map.
    /// @param[in] a triplet of q point indices
    /// @return a vector of triplets, including the input
    std::vector<std::array<std::size_t, 3>> equivalent_qtriplets(
        const std::array<std::size_t, 3>& original) const;

    /// Decompose the q-th microcell in five tetrahedra.
    ///
    /// @param[in] q - the index of the q point
    /// @return a vector of five Tetrahedron objects
    std::vector<Tetrahedron> get_tetrahedra(std::size_t q) const {
        // Find out the indices of the eight corners of the
        // microcell.
        std::size_t i000 = q;
        auto indices = this->one_to_three(i000);
        std::size_t i001 =
            this->three_to_one({{indices[0], indices[1], indices[2] + 1}});
        std::size_t i010 =
            this->three_to_one({{indices[0], indices[1] + 1, indices[2]}});
        std::size_t i011 =
            this->three_to_one({{indices[0], indices[1] + 1, indices[2] + 1}});
        std::size_t i100 =
            this->three_to_one({{indices[0] + 1, indices[1], indices[2]}});
        std::size_t i101 =
            this->three_to_one({{indices[0] + 1, indices[1], indices[2] + 1}});
        std::size_t i110 =
            this->three_to_one({{indices[0] + 1, indices[1] + 1, indices[2]}});
        std::size_t i111 = this->three_to_one(
            {{indices[0] + 1, indices[1] + 1, indices[2] + 1}});

        // Build the four equivalent tetrahedra.
        std::vector<Tetrahedron> nruter;
        nruter.reserve(5);
        nruter.emplace_back(Tetrahedron({{i000, i001, i010, i100}}));
        nruter.emplace_back(Tetrahedron({{i110, i111, i100, i010}}));
        nruter.emplace_back(Tetrahedron({{i101, i100, i111, i001}}));
        nruter.emplace_back(Tetrahedron({{i011, i010, i001, i111}}));
        // And the central, inequivalent one.
        nruter.emplace_back(Tetrahedron({{i010, i100, i111, i001}}));
        return nruter;
    }

    /// @return a vector of triangle objects with each containing three
    /// indices
    std::vector<Triangle> get_triangles(std::size_t ia) const;

    /// @return the index of the representative of the equivalence
    /// class
    /// of the provided q-point
    std::size_t getParentIdx(std::size_t iq) const;

    /// @return the index of the symmetry operation that maps the
    /// provided q-point
    /// to the representative of the equivalence class to which it
    /// belongs
    std::size_t getSymIdxToParent(std::size_t iq) const;

    /// @return the grid's symmetry map
    std::vector<std::vector<std::size_t>> getSymmetryMap() {
        return this->symmetry_map;
    }

    /// Remove the component of a Cartesian vector which does transform as a
    /// q-point in the grid.
    ///
    /// @param[in] iq - index of the q point
    /// @param[in] symms - set of symmetry operations of the crystal
    /// @param[in] x - vector in Cartesian coordinates. Several vectors can
    /// be provided by making v a matrix and each vector a column
    /// @return the symmetrized version of x
    template <typename T>
    auto copy_symmetry(std::size_t iq,
                       const Symmetry_operations& symms,
                       const Eigen::MatrixBase<T>& x) const
        -> Eigen::Matrix<typename T::Scalar, Eigen::Dynamic, Eigen::Dynamic> {
        std::size_t index = python_mod(iq, this->nqpoints);
        std::size_t nops = symms.get_nsym();

        typename T::PlainObject nruter(x);
        nruter.fill(0.);
        std::size_t nfound = 0;

        for (std::size_t iop = 0; iop < nops; ++iop) {
            if (this->symmetry_map[index][2 * iop] == index) {
                nruter += symms.rotate_v(x, iop, true);
                ++nfound;
            }
        }
        nruter /= nfound;
        return nruter;
    }

private:
    friend void save_bulk_hdf5(const char* filename,
                               const std::string& description,
                               const Crystal_structure& cell,
                               const Symmetry_operations& symmetries,
                               const Gamma_grid& grid,
                               const std::vector<Threeph_process>& processes,
                               const boost::mpi::communicator& comm);

    friend std::tuple<std::string,
                      std::unique_ptr<Crystal_structure>,
                      std::unique_ptr<Symmetry_operations>,
                      std::unique_ptr<Gamma_grid>,
                      std::unique_ptr<std::vector<Threeph_process>>>
    load_bulk_hdf5(const char* filename, const boost::mpi::communicator& comm);

    /// Cartesian coordinates of each q point.
    Eigen::MatrixXd cpos;
    /// Harmonic properties at each q point.
    std::vector<Spectrum_at_point> spectrum;
    /// Vector of equivalence classes. Two q points in the same
    /// equivalence class are related by a symmetry operation.
    /// Note that both space group symmetries and time reversal
    /// symmetry are taken into account.
    std::vector<std::vector<std::size_t>> equivalences;
    /// Detailed map between q points through the symmetry
    /// operations.
    std::vector<std::vector<std::size_t>> symmetry_map;
    /// Initialize the Cartesian coordinates of the q points.
    /// @param[in] poscar - a description of the crystal structure
    void initialize_cpos();

    /// Compute the spectrum at a subset of the q points.
    ///
    /// That subset is selected on the basis of this process'
    /// position in an MPI communicator.
    /// @param[in] factory - object in charge of building the
    /// dynamical matrix at each point.
    /// @param[in] symms - set of symmetry operations of the crystal
    /// @param[in] communicator - MPI communicator to use
    std::vector<Spectrum_at_point> compute_my_spectrum(
        const Dynamical_matrix_builder& factory,
        const Symmetry_operations& symms,
        const boost::mpi::communicator& communicator);

    /// Fill the equivalences vector.
    ///
    /// @param[in] symms - the set of symmetry operations to try.
    void fill_equivalences(const Symmetry_operations& symms);

    /// Parentlookup table: stores for each q-point the index of
    /// the representative of the equivalence class to which it
    /// belongs.
    std::vector<std::size_t> parentlookup;

    /// Fill the parentlookup table
    void fill_parentlookup();

    /// Fill the symmetry_map vector.
    ///
    /// @param[in] symms - the set of symmetry operations to try.
    void fill_map(const Symmetry_operations& symms);
};
} // namespace alma


/*
    Copyright (c) 2014, Philipp Krähenbühl
    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 Stanford University 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 Philipp Krähenbühl ''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 Philipp Krähenbühl 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.
*/
#include "util/win_util.h"
#include "gradient.h"
#include "filter.h"
#include <stdexcept>
#include <iostream>
#include <Eigen/Core>
using namespace Eigen;

static const float * acosTable(){
	static float table[2001];
	float * r = table+1000;
	for( int i=-1000; i<1001; i++ )
		r[i] = acos( i / 1000. );
	return r;
}
static void computeGradientOriAndMag( float * g, float * o, const float * gx, const float * gy, int N, int C ) {
	const float * acost = acosTable();
	Map<const ArrayXXf> mgx( gx, C, N ), mgy( gy, C, N );
	ArrayXXf mag = (mgx*mgx+mgy*mgy);
	for( int i=0; i<N; i++ ) {
		int j;
		float m = mag.col(i).maxCoeff(&j);
		float gm = std::max((float)sqrt(m),1e-10f);
		float cm = mgx(j,i) / gm;
		if( mgy(j,i) <= -0 ) cm = -cm;
		if( cm > 1 )  cm = 1;
		if( cm < -1 ) cm = -1;

		o[i] = acost[(int)(cm*1000)];
		g[i] = gm;
	}
}
static void computeGradientMag( float * g, const float * gx, const float * gy, int N, int C ) {
	Map<const ArrayXXf> mgx( gx, C, N ), mgy( gy, C, N );
	Map<VectorXf>(g,N) = (mgx*mgx+mgy*mgy).colwise().maxCoeff().sqrt();
}
template <int BINS>
static void computeGradHist( Image & hist, const RMatrixXf & gm, const RMatrixXf & go, int nori ) {
	const int W = gm.cols(), H = gm.rows();
	const int Wb = W/BINS, Hb = H/BINS;
	const int W0 = Wb*BINS, H0 = Hb*BINS;
	hist.create( Wb, Hb, nori );
	hist = 0;
	for(int j = 0; j < H0; j++){
		float * phist = hist.data() + (j/BINS)*Wb*nori;
		for(int i = 0; i < W0; phist+=nori){
			for(int k = 0; k < BINS && i<W; i++, k++){
				float o = go(j,i) / M_PI;
				unsigned int o0 = o*nori;
				float w = o*nori - o0;
				if( o0 >= nori )
					o0 = 0;
				unsigned int o1 = o0+1;
				if( o1 >= nori )
					o1 = 0;
				phist[o0] += (1-w)*gm(j,i) / (BINS*BINS);
				phist[o1] += w*gm(j,i) / (BINS*BINS);
			}
		}
	}
}
void gradientHist( Image & hist, const RMatrixXf & gm, const RMatrixXf & go, int nori, int nbins) {
	switch(nbins){
		case 1: return computeGradHist<1>(hist, gm, go, nori);
		case 2: return computeGradHist<2>(hist, gm, go, nori);
		case 3: return computeGradHist<3>(hist, gm, go, nori);
		case 4: return computeGradHist<4>(hist, gm, go, nori);
		case 5: return computeGradHist<5>(hist, gm, go, nori);
		case 6: return computeGradHist<6>(hist, gm, go, nori);
		case 7: return computeGradHist<7>(hist, gm, go, nori);
		case 8: return computeGradHist<8>(hist, gm, go, nori);
		default: throw std::invalid_argument("Bin size too large!");
	}
}
static void diff( float * r, const float * a, const float * b, int N, float w=1.0 ) {
	Map<VectorXf>( r, N ) = w*(Map<VectorXf>( (float*)a, N ) - Map<VectorXf>( (float*)b, N ));
}
void gradient( Image & gx, Image & gy, const Image & im ) {
	const int W = im.W(), H = im.H(), C = im.C();
	gx.create( W, H, C );
	gy.create( W, H, C );
	for( int j=0; j<H; j++ ) {
		float * pgx = gx.data()+j*W*C;
		float * pgy = gy.data()+j*W*C;
		const float * pim = im.data()+j*W*C;
		// Compute the x gradient
		for( int c=0; c<C; c++ ) {
			pgx[c        ] = (float)pim[C      +c]-(float)pim[c];
			pgx[(W-1)*C+c] = (float)pim[(W-1)*C+c]-(float)pim[(W-2)*C+c];
		}
		diff( pgx+C, pim+2*C, pim, (W-2)*C, 0.5 );
		// Compute the y gradient
		if(j==0)
			diff( pgy, pim+W*C, pim, W*C );
		else if( j==H-1 )
			diff( pgy, pim, pim-W*C, W*C );
		else
			diff( pgy, pim+W*C, pim-W*C, W*C, 0.5 );
	}
}
void gradientMagAndOri( RMatrixXf & gm, RMatrixXf & go, const Image & im, int norm_rad, float norm_const ) {
	Image gx, gy;
	gradient( gx, gy, im );
	const int W = im.W(), H = im.H(), C = im.C();
	go = RMatrixXf::Zero(H, W);
	gm = RMatrixXf::Zero(H, W);
	computeGradientOriAndMag( gm.data(), go.data(), gx.data(), gy.data(), W*H, C );

	if( norm_rad>0 ) {
		float * tmp = gx.data();
		tentFilter( tmp, gm.data(), W, H, 1, norm_rad );
		for( int i=0; i<W*H; i++ )
			gm.data()[i] /= tmp[i] + norm_const;
	}
}
RMatrixXf gradientMag( const Image & im, int norm_rad, float norm_const ) {
	Image gx, gy;
	gradient( gx, gy, im );
	const int W = im.W(), H = im.H(), C = im.C();
	RMatrixXf gm(H, W);
	computeGradientMag( gm.data(), gx.data(), gy.data(), W*H, C );

	if( norm_rad>0 ) {
		float * tmp = gx.data();
		tentFilter( tmp, gm.data(), W, H, 1, norm_rad );
		for( int i=0; i<W*H; i++ )
			gm.data()[i] /= tmp[i] + norm_const;
	}
	return gm;
}


#include <CGAL/Three/Polyhedron_demo_plugin_helper.h>
#include <CGAL/Three/Three.h>
#include <QApplication>
#include <QObject>
#include <QAction>
#include <QMainWindow>
#include <QInputDialog>
#include <QColorDialog>
#include <QPalette>
#include <QColor>
#include <QStyleFactory>
#include <QMessageBox>

#include <CGAL/boost/graph/helpers.h>
#include <CGAL/Polygon_mesh_processing/compute_normal.h>
#include <CGAL/Heat_method_3/Surface_mesh_geodesic_distances_3.h>

#include "Scene_points_with_normal_item.h"

#include "Messages_interface.h"
#include "Scene_surface_mesh_item.h"
#include "Color_ramp.h"
#include <boost/unordered_map.hpp>
#include "ui_Display_property.h"
#include "id_printing.h"
#include "Scene.h"
#include "triangulate_primitive.h"
#include <CGAL/Buffer_for_vao.h>
#include <CGAL/Three/Triangle_container.h>
#include <CGAL/Dynamic_property_map.h>

#define ARBITRARY_DBL_MIN 1.0E-30
#define ARBITRARY_DBL_MAX 1.0E+30

//Item for heat values
typedef CGAL::Three::Triangle_container Tri;
typedef CGAL::Three::Viewer_interface VI;

class Scene_heat_item
    : public CGAL::Three::Scene_item_rendering_helper
{
  Q_OBJECT

public:
  Scene_heat_item(Scene_surface_mesh_item* item)
    :sm(item->face_graph()), parent(item)
  {
    setTriangleContainer(0, new Triangle_container(VI::PROGRAM_HEAT_INTENSITY,
                                                   true));
    setRenderingMode(Gouraud);
  }
  Scene_item* clone() const Q_DECL_OVERRIDE {return nullptr;}
  QString toolTip() const Q_DECL_OVERRIDE{return QString(); }
  void select(double orig_x,
             double orig_y,
             double orig_z,
             double dir_x,
             double dir_y,
             double dir_z) Q_DECL_OVERRIDE
  {
    parent->select( orig_x, orig_y, orig_z,
                    dir_x, dir_y, dir_z);
  }

  void initializeBuffers(CGAL::Three::Viewer_interface *viewer) const Q_DECL_OVERRIDE
  {
    getTriangleContainer(0)->initializeBuffers(viewer);
    getTriangleContainer(0)->setIdxSize(nb_idx);
    verts.resize(0);
    normals .resize(0);
    colors.resize(0);
    idx.clear();
    idx.shrink_to_fit();
    colors.shrink_to_fit();
    verts.shrink_to_fit();
    normals.shrink_to_fit();
  }

  void draw(CGAL::Three::Viewer_interface *viewer) const Q_DECL_OVERRIDE
  {
    if(!visible())
      return;
    if(!isInit(viewer))
      initGL(viewer);
    if ( getBuffersFilled() &&
         ! getBuffersInit(viewer))
    {
      initializeBuffers(viewer);
      setBuffersInit(viewer, true);
    }
    if(!getBuffersFilled())
    {
      computeElements();
      initializeBuffers(viewer);
    }

    getTriangleContainer(0)->setAlpha(1.0f);
    getTriangleContainer(0)->draw(viewer, false);
  }
  void compute_bbox() const Q_DECL_OVERRIDE
  {
    SMesh::Property_map<vertex_descriptor, Point_3> pprop = sm->points();
    CGAL::Bbox_3 bbox ;

    for(vertex_descriptor vd :vertices(*sm))
    {
      bbox = bbox + pprop[vd].bbox();
    }
    _bbox = Bbox(bbox.xmin(),bbox.ymin(),bbox.zmin(),
                 bbox.xmax(),bbox.ymax(),bbox.zmax());
    is_bbox_computed = true;
  }
  Scene_item::Bbox bbox() const Q_DECL_OVERRIDE {
    if(!is_bbox_computed)
      compute_bbox();
    is_bbox_computed = true;
    return _bbox;
  }

  ~Scene_heat_item(){}
  virtual bool supportsRenderingMode(RenderingMode m) const Q_DECL_OVERRIDE { return m==Gouraud; }
  virtual void invalidateOpenGLBuffers() Q_DECL_OVERRIDE
  {

    setBuffersFilled(false);
    compute_bbox();
    getTriangleContainer(0)->reset_vbos(NOT_INSTANCED);
    is_bbox_computed = false;
  }
  void triangulate_convex_facet(face_descriptor fd,
                                boost::property_map< SMesh, boost::vertex_index_t >::type *im) const
  {
    const CGAL::qglviewer::Vec v_offset = static_cast<CGAL::Three::Viewer_interface*>(CGAL::QGLViewer::QGLViewerPool().first())->offset();
    EPICK::Vector_3 offset = EPICK::Vector_3(v_offset.x, v_offset.y, v_offset.z);

    EPICK::Point_3 p0,p1,p2;
    SMesh::Halfedge_around_face_circulator he(halfedge(fd, *sm), *sm);
    SMesh::Halfedge_around_face_circulator he_end = he;

    while(next(*he, *sm) != prev(*he_end, *sm))
    {
      ++he;
      vertex_descriptor v0(target(*he_end, *sm)),
          v1(target(*he, *sm)),
          v2(target(next(*he, *sm), *sm));
      p0 = sm->point(v0) + offset;
      p1 = sm->point(v1) + offset;
      p2 = sm->point(v2) + offset;
      idx.push_back((*im)[v0]);
      idx.push_back((*im)[v1]);
      idx.push_back((*im)[v2]);
    }
  }
  void triangulate_facet(face_descriptor fd,
                         SMesh::Property_map<face_descriptor, EPICK::Vector_3> *fnormals,
                         boost::property_map< SMesh, boost::vertex_index_t >::type *im) const
  {
    //Computes the normal of the facet
    EPICK::Vector_3 normal = get(*fnormals, fd);

    //check if normal contains NaN values
    if (normal.x() != normal.x() || normal.y() != normal.y() || normal.z() != normal.z())
    {
      qDebug()<<"Warning : normal is not valid. Facet not displayed";
      return;
    }

    typedef FacetTriangulator<SMesh, EPICK, boost::graph_traits<SMesh>::vertex_descriptor> FT;
    const CGAL::qglviewer::Vec off = static_cast<CGAL::Three::Viewer_interface*>(CGAL::QGLViewer::QGLViewerPool().first())->offset();
    EPICK::Vector_3 offset(off.x,off.y,off.z);
    FT triangulation(fd,normal,sm, offset);
    //iterates on the internal faces
    for(FT::CDT::Finite_faces_iterator
        ffit = triangulation.cdt->finite_faces_begin(),
        end = triangulation.cdt->finite_faces_end();
        ffit != end; ++ffit)
    {
      if(ffit->info().is_external)
        continue;
      //add the vertices to the positions
      //adds the vertices, normals and colors to the appropriate vectors
      //adds the indices to the appropriate vector
      idx.push_back((*im)[triangulation.v2v[ffit->vertex(0)]]);
      idx.push_back((*im)[triangulation.v2v[ffit->vertex(1)]]);
      idx.push_back((*im)[triangulation.v2v[ffit->vertex(2)]]);
    }
  }

  void computeElements() const Q_DECL_OVERRIDE
  {
    typedef EPICK::Point_3 Point;
    QApplication::setOverrideCursor(Qt::WaitCursor);
    const CGAL::qglviewer::Vec o = static_cast<CGAL::Three::Viewer_interface*>(CGAL::QGLViewer::QGLViewerPool().first())->offset();
    EPICK::Vector_3 offset(o.x, o.y, o.z);
    SMesh::Property_map<vertex_descriptor, SMesh::Point> positions =
        sm->points();
    SMesh::Property_map<vertex_descriptor, EPICK::Vector_3 > vnormals =
        sm->property_map<vertex_descriptor, EPICK::Vector_3 >("v:normal").first;
    SMesh::Property_map<face_descriptor, EPICK::Vector_3 > fnormals =
        sm->property_map<face_descriptor, EPICK::Vector_3 >("f:normal").first;
    typedef boost::graph_traits<SMesh>::face_descriptor face_descriptor;
    typedef boost::graph_traits<SMesh>::halfedge_descriptor halfedge_descriptor;
    typedef boost::graph_traits<SMesh>::vertex_descriptor vertex_descriptor;
    SMesh::Property_map<vertex_descriptor, CGAL::Color> vcolors =
        sm->property_map<vertex_descriptor, CGAL::Color >("v:color").first;
    SMesh::Property_map<vertex_descriptor, float> vdist=
        sm->property_map<vertex_descriptor, float >("v:dist").first;
    typedef CGAL::Buffer_for_vao<float, unsigned int> CPF;
    verts.clear();
    normals.clear();
    idx.clear();
    colors.clear();
    boost::property_map< SMesh, boost::vertex_index_t >::type
        im = get(boost::vertex_index, *sm);

    idx.reserve(num_faces(*sm) * 3);
    for(face_descriptor fd : faces(*sm))
    {
      if(is_triangle(halfedge(fd,*sm),*sm))
      {
        for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, *sm),*sm))
        {
          idx.push_back(source(hd, *sm));
        }
      }
      else
      {
        std::vector<Point> facet_points;
        for(halfedge_descriptor hd : halfedges_around_face(halfedge(fd, *sm),*sm))
        {
          facet_points.push_back(positions[target(hd, *sm)]);
        }
        bool is_convex = CPF::is_facet_convex(facet_points, fnormals[fd]);

        if(is_convex && is_quad(halfedge(fd,*sm),*sm) )
        {
          halfedge_descriptor hd = halfedge(fd,*sm);
          //1st half
          idx.push_back(source(hd, *sm));
          idx.push_back(source(next(hd, *sm), *sm));
          idx.push_back(source(next(next(hd, *sm), *sm), *sm));

          //2nd half
          idx.push_back(source(hd, *sm));
          idx.push_back(source(next(next(hd, *sm), *sm), *sm));
          idx.push_back(source(prev(hd, *sm), *sm));
        }
        else if(is_convex)
        {
          triangulate_convex_facet(fd, &im);
        }
        else
        {
          triangulate_facet(fd, &fnormals, &im);
        }
      }
    }
    for(vertex_descriptor vd : vertices(*sm))
    {
      CGAL::Color c = vcolors[vd];
      colors.push_back((float)c.red()/255);
      colors.push_back((float)c.green()/255);
      colors.push_back((float)c.blue()/255);

      Point p = positions[vd] + offset;
      CPF::add_point_in_buffer(p, verts);
      EPICK::Vector_3 n = vnormals[vd];
      CPF::add_normal_in_buffer(n, normals);
      heat_values.push_back(vdist[vd]);
    }
    nb_idx = idx.size();
    getTriangleContainer(0)->allocate(Tri::Vertex_indices, idx.data(),
                                      static_cast<int>(idx.size()*sizeof(unsigned int)));
    getTriangleContainer(0)->allocate(Tri::Smooth_vertices, verts.data(),
                                      static_cast<int>(num_vertices(*sm)*3*sizeof(float)));

    getTriangleContainer(0)->allocate(Tri::Smooth_normals, normals.data(),
                                      static_cast<int>(num_vertices(*sm)*3*sizeof(float)));
    getTriangleContainer(0)->allocate(Tri::VColors, colors.data(),
                                      static_cast<int>(colors.size()*sizeof(float)));
    getTriangleContainer(0)->allocate(Tri::Distances, heat_values.data(),
                                      static_cast<int>(heat_values.size()*sizeof(float)));
    compute_bbox();
    setBuffersFilled(true);
     QApplication::restoreOverrideCursor();
  }

  bool isEmpty() const Q_DECL_OVERRIDE {return false;}
  SMesh *face_graph() { return sm;}
  Scene_surface_mesh_item* getParent() { return parent; }

private:
  SMesh* sm;
  Scene_surface_mesh_item* parent;
  mutable std::vector<float> normals;
  mutable std::vector<unsigned int> idx;
  mutable std::vector<float> verts;
  mutable std::vector<float> colors;
  mutable std::vector<float> heat_values;
  mutable std::size_t nb_idx;
}; // end class Scene_heat_item

class DockWidget :
    public QDockWidget,
    public Ui::DisplayPropertyWidget
{
public:
  DockWidget(QString name, QWidget *parent)
    :QDockWidget(name,parent)
  {
    setupUi(this);
  }
};

typedef boost::graph_traits<SMesh>::halfedge_descriptor halfedge_descriptor;
typedef boost::graph_traits<SMesh>::face_descriptor face_descriptor;
CGAL::Three::Viewer_interface* (&getActiveViewer)() = CGAL::Three::Three::activeViewer;
class DisplayPropertyPlugin :
    public QObject,
    public CGAL::Three::Polyhedron_demo_plugin_helper
{
  Q_OBJECT
  Q_INTERFACES(CGAL::Three::Polyhedron_demo_plugin_interface)
  Q_PLUGIN_METADATA(IID "com.geometryfactory.PolyhedronDemo.PluginInterface/1.0")
  typedef SMesh::Property_map<boost::graph_traits<SMesh>::vertex_descriptor, double> Vertex_distance_map;
  typedef CGAL::Heat_method_3::Surface_mesh_geodesic_distances_3<SMesh> Heat_method;
  typedef CGAL::Heat_method_3::Surface_mesh_geodesic_distances_3<SMesh, CGAL::Heat_method_3::Intrinsic_Delaunay> Heat_method_idt;
  typedef CGAL::dynamic_vertex_property_t<bool>                        Vertex_source_tag;
  typedef boost::property_map<SMesh, Vertex_source_tag>::type Vertex_source_map;

public:

  bool applicable(QAction*) const Q_DECL_OVERRIDE
  {
    CGAL::Three::Scene_item* item = scene->item(scene->mainSelectionIndex());
    return qobject_cast<Scene_surface_mesh_item*>(item);
  }

  QList<QAction*> actions() const Q_DECL_OVERRIDE
  {
    return _actions;
  }

  QColor textColor(const QColor& color)
  {
    QColor text_color (255, 255, 255);
    if (color.red() * 0.299 + color.green() * 0.587 + color.blue() * 0.114 > 128)
      text_color = QColor (0, 0, 0);
    return text_color;
  }

  void init(QMainWindow* mw, CGAL::Three::Scene_interface* sc, Messages_interface*) Q_DECL_OVERRIDE
  {
    this->scene = sc;
    this->mw = mw;
    this->current_item = NULL;

    QAction *actionDisplayAngles= new QAction(QString("Display Properties"), mw);
    QAction *actionHeatMethod= new QAction(QString("Heat Method"), mw);
    actionHeatMethod->setProperty("submenuName", "Color");

    rm = 1.0;
    rM = 0.0;
    gm = 0.0;
    gM = 1.0;
    bm = 0.0;
    bM = 0.0;
    actionDisplayAngles->setProperty("submenuName", "Color");

    if(actionDisplayAngles) {
      connect(actionDisplayAngles, SIGNAL(triggered()),
              this, SLOT(openDialog()));
      if(actionHeatMethod)
      {
        connect(actionHeatMethod, &QAction::triggered,
                this, [this](){
          this->dock_widget->propertyBox->setCurrentIndex(2);
          this->dock_widget->show();
        });
      }
      _actions << actionDisplayAngles;
      _actions << actionHeatMethod;

    }
    dock_widget = new DockWidget("Property Displaying", mw);
    dock_widget->setVisible(false);
    addDockWidget(dock_widget);
    QPalette palette(Qt::red);
    dock_widget->minColorButton->setPalette(palette);
    dock_widget->minColorButton->setStyle(QStyleFactory::create("Fusion"));
    dock_widget->minColorButton->update();

    palette = QPalette(Qt::green);
    dock_widget->maxColorButton->setPalette(palette);
    dock_widget->maxColorButton->setStyle(QStyleFactory::create("Fusion"));
    dock_widget->maxColorButton->update();
    connect(dock_widget->colorizeButton, SIGNAL(clicked(bool)),
            this, SLOT(colorize()));

    connect(dock_widget->propertyBox, SIGNAL(currentIndexChanged(int)),
            this, SLOT(on_propertyBox_currentIndexChanged(int)));
    connect(dock_widget->zoomToMinButton, &QPushButton::pressed,
            this, &DisplayPropertyPlugin::on_zoomToMinButton_pressed);
    connect(dock_widget->zoomToMaxButton, &QPushButton::pressed,
            this, &DisplayPropertyPlugin::on_zoomToMaxButton_pressed);
    connect(dock_widget->minColorButton, &QPushButton::pressed,
            this, [this]()
    {
      QColor minColor = QColorDialog::getColor();
      if (!minColor.isValid())
      {
        return;
      }

      rm = minColor.redF();
      gm = minColor.greenF();
      bm = minColor.blueF();
      QPalette palette(minColor);
      dock_widget->minColorButton->setPalette(palette);
      dock_widget->minColorButton->update();
      replaceRamp();
    });
    connect(dock_widget->maxColorButton, &QPushButton::pressed,
            this, [this]()
    {
      QColor maxColor = QColorDialog::getColor();
      if(!maxColor.isValid())
        return;
      QPalette palette(maxColor);
      rM = maxColor.redF();
      gM = maxColor.greenF();
      bM = maxColor.blueF();

      dock_widget->maxColorButton->setPalette(palette);
      dock_widget->maxColorButton->update();
      replaceRamp();
    });

    connect(dock_widget->sourcePointsButton, SIGNAL(toggled(bool)),
            this, SLOT(on_sourcePointsButton_toggled(bool)));
    connect(dock_widget->deleteButton, &QPushButton::clicked,
            this, &DisplayPropertyPlugin::delete_group);

    connect(dock_widget->resetButton, &QPushButton::pressed,
            this, &DisplayPropertyPlugin::resetRampExtremas);

    dock_widget->zoomToMaxButton->setEnabled(false);
    dock_widget->zoomToMinButton->setEnabled(false);
    Scene* scene_obj =static_cast<Scene*>(scene);
    connect(scene_obj, &Scene::itemIndexSelected,
            this, &DisplayPropertyPlugin::enableButtons);
    on_propertyBox_currentIndexChanged(0);

  }
private Q_SLOTS:
  void openDialog()
  {
    if(dock_widget->isVisible()) { dock_widget->hide(); }
    else{
      replaceRamp();
      dock_widget->show();
      dock_widget->raise(); }
  }

  void resetRampExtremas()
  {
    Scene_surface_mesh_item* item =
        qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
    if(!item)
      return;
    QApplication::setOverrideCursor(Qt::WaitCursor);
    item->face_graph()->collect_garbage();
    bool ok;
    switch(dock_widget->propertyBox->currentIndex())
    {
    case 0:
      ok = resetAngles(item);
      break;
    default:
      ok = resetScaledJacobian(item);
      break;
    }
    QApplication::restoreOverrideCursor();
    if(!ok)
      QMessageBox::warning(mw, "Error", "You must first run colorize once to initialize the values.");
  }

  void colorize()
  {
    Scene_heat_item* h_item = nullptr;
    Scene_surface_mesh_item* item =
        qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
    if(!item)
    {
      h_item = qobject_cast<Scene_heat_item*>(scene->item(scene->mainSelectionIndex()));
      if(!h_item)
        return;
      item = h_item->getParent();
    }
    QApplication::setOverrideCursor(Qt::WaitCursor);

    replaceRamp();
    item->face_graph()->collect_garbage();

    switch(dock_widget->propertyBox->currentIndex()){
    case 0:
      displayAngles(item);
      break;
      case 1:
        displayScaledJacobian(item);
        break;
    case 2:
      if(!displayHeatIntensity(item))
        return;
      item->setRenderingMode(Gouraud);
      break;
    default:  // Heat Method (Intrinsic Delaunay)
      if(!displayHeatIntensity(item, true))
        return;
      item->setRenderingMode(Gouraud);
      break;
    }

    connect(item, &Scene_surface_mesh_item::itemChanged,
            this, [item](){
      bool does_exist;
      SMesh::Property_map<face_descriptor, double> pmap;
      boost::tie(pmap, does_exist) =
          item->face_graph()->property_map<face_descriptor,double>("f:jacobian");
      if(does_exist)
        item->face_graph()->remove_property_map(pmap);
      boost::tie(pmap, does_exist) =
          item->face_graph()->property_map<face_descriptor,double>("f:angle");
      if(does_exist)
        item->face_graph()->remove_property_map(pmap);
    });
    QApplication::restoreOverrideCursor();
    item->invalidateOpenGLBuffers();
    item->redraw();
    if(dock_widget->propertyBox->currentIndex() != 2){
      dock_widget->zoomToMinButton->setEnabled(true);
      dock_widget->zoomToMaxButton->setEnabled(true);}
  }

  void enableButtons()
  {
    Scene_surface_mesh_item* item =
        qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
    if(! item )
    {
      dock_widget->zoomToMinButton->setEnabled(false);
      dock_widget->zoomToMaxButton->setEnabled(false);
    }

    switch(dock_widget->propertyBox->currentIndex())
    {
    case 0:
      dock_widget->zoomToMinButton->setEnabled(angles_max.count(item)>0 );
      dock_widget->zoomToMaxButton->setEnabled(angles_max.count(item)>0 );
      break;
    case 1:
      dock_widget->zoomToMinButton->setEnabled(jacobian_max.count(item)>0);
      dock_widget->zoomToMaxButton->setEnabled(jacobian_max.count(item)>0);
      break;
    default:
      break;
    }
  }

  void resetProperty()
  {
    Scene_surface_mesh_item* item =
        qobject_cast<Scene_surface_mesh_item*>(sender());
    if(!item)
      return;
    SMesh& smesh = *item->face_graph();
    SMesh::Property_map<face_descriptor, double> jacobians;
    bool found;
    boost::tie(jacobians, found) = smesh.property_map<face_descriptor,double>("f:jacobian");
    if(found)
    {
      smesh.remove_property_map(jacobians);
    }
    SMesh::Property_map<face_descriptor, double> angles;
    boost::tie(angles, found) = smesh.property_map<face_descriptor,double>("f:angle");
    if(found)
    {
      smesh.remove_property_map(angles);
    }
  }

  void displayScaledJacobian(Scene_surface_mesh_item* item)
  {

    SMesh& smesh = *item->face_graph();
    //compute and store the jacobian per face
    bool non_init;
    SMesh::Property_map<face_descriptor, double> fjacobian;
    boost::tie(fjacobian, non_init) = smesh.add_property_map<face_descriptor, double>("f:jacobian", 0);
    if(non_init)
    {
      double res_min = ARBITRARY_DBL_MAX,
          res_max = -ARBITRARY_DBL_MAX;
      SMesh::Face_index min_index, max_index;
      for(boost::graph_traits<SMesh>::face_iterator fit = faces(smesh).begin();
          fit != faces(smesh).end();
          ++fit)
      {
        fjacobian[*fit] = scaled_jacobian(*fit, smesh);
        if(fjacobian[*fit] > res_max)
        {
          res_max = fjacobian[*fit];
          max_index = *fit;
        }
        if(fjacobian[*fit] < res_min)
        {
          res_min = fjacobian[*fit];
          min_index = *fit;
        }
      }
      jacobian_min.erase(item);
      jacobian_min.insert(std::make_pair(item, std::make_pair(res_min, min_index)));
      jacobian_max.erase(item);
      jacobian_max.insert(std::make_pair(item, std::make_pair(res_max, max_index)));
      connect(item, &Scene_surface_mesh_item::itemChanged,
              this, &DisplayPropertyPlugin::resetProperty);
    }
    //scale a color ramp between min and max
    double max = maxBox;
    double min = minBox;
    //fill f:color pmap
    SMesh::Property_map<face_descriptor, CGAL::Color> fcolors =
        smesh.add_property_map<face_descriptor, CGAL::Color >("f:color", CGAL::Color()).first;
    for(boost::graph_traits<SMesh>::face_iterator fit = faces(smesh).begin();
        fit != faces(smesh).end();
        ++fit)
    {
      if(min == max)
        --min;
      double f = (fjacobian[*fit]-min)/(max-min);
      if(f<min)
        f = min;
      if(f>max)
        f = max;
      CGAL::Color color(
            255*color_ramp.r(f),
            255*color_ramp.g(f),
            255*color_ramp.b(f));
      fcolors[*fit] = color;
    }
  }

  bool resetScaledJacobian(Scene_surface_mesh_item* item)
  {
    SMesh& smesh = *item->face_graph();
    if(!smesh.property_map<face_descriptor, double>("f:jacobian").second)
    {
      return false;
    }
    dock_widget->minBox->setValue(jacobian_min[item].first-0.01);
    dock_widget->maxBox->setValue(jacobian_max[item].first);
    return true;
  }

  void displayAngles(Scene_surface_mesh_item* item)
  {
    SMesh& smesh = *item->face_graph();
    typedef boost::property_map<SMesh, boost::vertex_point_t>::type PMap;
    PMap pmap = get(boost::vertex_point, smesh);
    //compute and store smallest angle per face
    bool non_init;
    SMesh::Property_map<face_descriptor, double> fangle;
    boost::tie(fangle, non_init) = smesh.add_property_map<face_descriptor, double>("f:angle", 0);
    if(non_init)
    {
      double res_min = ARBITRARY_DBL_MAX,
          res_max = -ARBITRARY_DBL_MAX;
      SMesh::Face_index index_min, index_max;
      for(boost::graph_traits<SMesh>::face_iterator fit = faces(smesh).begin();
          fit != faces(smesh).end();
          ++fit)
      {
        bool is_face_triangle = is_triangle(halfedge(*fit, smesh), smesh);
        bool normal_is_ok = true;
        EPICK::Vector_3 normal(0,0,0);

        EPICK::Orientation orientation = CGAL::POSITIVE;
        if(!is_face_triangle)
        {
          face_descriptor f = *fit;
          CGAL::Halfedge_around_face_circulator<SMesh>
              he(halfedge(f, smesh), smesh),
              he_end(he);
          do{
            normal_is_ok = true;

            //Initializes the facet orientation

            EPICK::Point_3 S,T;
            T = get(pmap, source(*he, smesh));
            S = get(pmap, target(*he, smesh));
            EPICK::Vector_3 V1((T-S).x(), (T-S).y(), (T-S).z());
            S = get(pmap,source(next(*he,smesh), smesh));
            T = get(pmap, target(next(*he,smesh), smesh));
            EPICK::Vector_3 V2((T-S).x(), (T-S).y(), (T-S).z());

            if(normal == EPICK::Vector_3(0,0,0))
              normal_is_ok = false;
            {
              normal = CGAL::cross_product(V1, V2);
            }
            if(normal_is_ok)
            {
              orientation = EPICK::Orientation_3()(V1, V2, normal);
              if( orientation == CGAL::COPLANAR )
                normal_is_ok = false;
            }
          }while( ++he != he_end && !normal_is_ok);
        }

        std::vector<float> local_angles;
        local_angles.reserve(degree(*fit, smesh));
        for(halfedge_descriptor hd :
                      halfedges_around_face(halfedge(*fit, smesh),smesh))
        {
          halfedge_descriptor hdn = next(hd, smesh);
          EPICK::Vector_3 v1(get(pmap, source(hd, smesh)), get(pmap, target(hd, smesh))),
              v2(get(pmap, target(hdn, smesh)), get(pmap, source(hdn, smesh)));
          float norm1(CGAL::approximate_sqrt(v1.squared_length())), norm2(CGAL::approximate_sqrt(v2.squared_length()));
          float dot_prod = v1*v2;
          float angle = std::acos(dot_prod/(norm1*norm2));
          if(is_face_triangle || !normal_is_ok)
            local_angles.push_back(angle * 180/CGAL_PI);
          else
          {
            bool is_convex = true;
            EPICK::Orientation res = EPICK::Orientation_3()(v1, v2, normal) ;
            if(res!= orientation && res != CGAL::ZERO)
              is_convex = false;
            local_angles.push_back(is_convex ? angle * 180/CGAL_PI : 360 - angle * 180/CGAL_PI );
          }
        }
        std::sort(local_angles.begin(), local_angles.end());
        fangle[*fit]=local_angles.front();

        if(fangle[*fit] > res_max)
        {
          res_max = fangle[*fit];
          index_max = *fit;
        }
        if(fangle[*fit] < res_min)
        {
          res_min = fangle[*fit];
          index_min = *fit;
        }
      }
      angles_min.erase(item);
      angles_min.insert(std::make_pair(item, std::make_pair(res_min, index_min)));
      angles_max.erase(item);
      angles_max.insert(std::make_pair(item, std::make_pair(res_max, index_max)));

      connect(item, &Scene_surface_mesh_item::itemChanged,
              this, &DisplayPropertyPlugin::resetProperty);
    }
    //scale a color ramp between min and max

    float max = maxBox;
    float min = minBox;

    //fill f:color pmap
    SMesh::Property_map<face_descriptor, CGAL::Color> fcolors =
        smesh.add_property_map<face_descriptor, CGAL::Color >("f:color", CGAL::Color()).first;
    for(boost::graph_traits<SMesh>::face_iterator fit = faces(smesh).begin();
        fit != faces(smesh).end();
        ++fit)
    {
      if(min == max)
        --min;
      float f = (fangle[*fit]-min)/(max-min);
      if(f<0)
        f = 0;
      if(f>1)
        f = 1;
      CGAL::Color color(
            255*color_ramp.r(f),
            255*color_ramp.g(f),
            255*color_ramp.b(f));
      fcolors[*fit] = color;
    }
  }

  bool resetAngles(Scene_surface_mesh_item* item)
  {
    SMesh& smesh = *item->face_graph();
    if(!smesh.property_map<face_descriptor, double>("f:angle").second)
    {
      return false;
    }
    dock_widget->minBox->setValue(angles_min[item].first);
    dock_widget->maxBox->setValue(angles_max[item].first);
    return true;
  }

  // AF: This function gets called when we click on the button "Colorize"
  bool displayHeatIntensity(Scene_surface_mesh_item* item, bool iDT = false)
  {
    SMesh& mesh = *item->face_graph();
    bool found = is_source.find(item) != is_source.end();
    if(!found
       || ! source_points
       || source_points->point_set()->is_empty())
    {
      QApplication::restoreOverrideCursor();
      QMessageBox::warning(mw, "Warning","Source vertices are needed for this property.");
      return false;
    }
    if(!is_triangle_mesh(mesh))
    {
      QApplication::restoreOverrideCursor();
      QMessageBox::warning(mw,"Error","The mesh must be triangulated.");
      return false;
    }
    Heat_method * hm = NULL;
    Heat_method_idt * hm_idt = NULL;
    SMesh::Property_map<vertex_descriptor, double> heat_intensity =
      mesh.add_property_map<vertex_descriptor, double>("v:heat_intensity", 0).first;
    if(! iDT){
      if(mesh_heat_method_map.find(item) != mesh_heat_method_map.end()){
        hm = mesh_heat_method_map[item];
      }else {
        hm = new Heat_method(mesh);
        mesh_heat_method_map[item] = hm;
      }
      connect(item, &Scene_surface_mesh_item::aboutToBeDestroyed,
              [this,item](){
                auto it =  mesh_heat_method_map.find(item);
                delete it->second;
                mesh_heat_method_map.erase(it);
              }
              );
    } else {
      if(mesh_heat_method_idt_map.find(item) != mesh_heat_method_idt_map.end()){
        hm_idt = mesh_heat_method_idt_map[item];
      }else {
        hm_idt = new Heat_method_idt(mesh);
        mesh_heat_method_idt_map[item] = hm_idt;
      }
      connect(item, &Scene_surface_mesh_item::aboutToBeDestroyed,
              [this,item](){
                auto it = mesh_heat_method_idt_map.find(item);
                if(it == mesh_heat_method_idt_map.end())
                  return;
                Heat_method_idt *hm_idt = it->second;
                delete hm_idt;
                mesh_heat_method_idt_map.erase(it);
              }
              );
    }

    for(vertex_descriptor vd : vertices(mesh)){
      if(get(is_source[item], vd)){
        if(iDT){
          hm_idt->add_source(vd);
        } else
          hm->add_source(vd);
      }
      else
      {
        if(iDT){
          hm_idt->remove_source(vd);
        } else
          hm->remove_source(vd);
      }
    }

    if(iDT){
      hm_idt->estimate_geodesic_distances(heat_intensity);
    }else{
      hm->estimate_geodesic_distances(heat_intensity);
    }

    double max = 0;
    double min = (std::numeric_limits<double>::max)();

    for(vertex_descriptor vd : vertices(mesh)){
      double hi = heat_intensity[vd];
      if(hi < min)
        min = hi;
      if(hi > max)
        max = hi;
    }
    color_ramp = Color_ramp(rm, rM, gm, gM, bm, bM);
    dock_widget->minBox->setValue(min);
    dock_widget->maxBox->setValue(max);

    //}
    SMesh::Property_map<vertex_descriptor, CGAL::Color> vcolors =
        mesh.add_property_map<vertex_descriptor, CGAL::Color >("v:color", CGAL::Color()).first;
    SMesh::Property_map<vertex_descriptor, float> vdist=
        mesh.add_property_map<vertex_descriptor, float >("v:dist", 0.0).first;
    for(boost::graph_traits<SMesh>::vertex_iterator vit = vertices(mesh).begin();
        vit != vertices(mesh).end();
        ++vit)
    {
      double h =(heat_intensity[*vit]-min)/(max-min);
      CGAL::Color color(
            255*color_ramp.r(h),
            255*color_ramp.g(h),
            255*color_ramp.b(h));
      vcolors[*vit] = color;
      vdist[*vit]=h;
    }
    Scene_group_item* group;
    if(mesh_heat_item_map.find(item) != mesh_heat_item_map.end())
    {
      group = mesh_heat_item_map[item]->parentGroup();
      group->unlockChild(mesh_heat_item_map[item]);
      scene->erase(scene->item_id(mesh_heat_item_map[item]));
    }
    else
    {
      group = new Scene_group_item("Heat Visualization");
      group->setProperty("heat_group", true);
      scene->addItem(group);
      scene->changeGroup(item, group);
      scene->changeGroup(source_points, group);
      group->lockChild(item);
      group->lockChild(source_points);
      dock_widget->deleteButton->setEnabled(true);
      connect(group, &Scene_group_item::aboutToBeDestroyed,
              this, [this](){
        this->dock_widget->deleteButton->setEnabled(false);
      });
    }
    mesh_heat_item_map[item] = new Scene_heat_item(item);
    mesh_heat_item_map[item]->setName(tr("%1 heat").arg(item->name()));
    scene->addItem(mesh_heat_item_map[item]);
    scene->changeGroup(mesh_heat_item_map[item], group);
    group->lockChild(mesh_heat_item_map[item]);
    item->setVisible(false);
    displayLegend();
    if(dock_widget->sourcePointsButton->isChecked())
      dock_widget->sourcePointsButton->toggle();
    return true;
  }

  void replaceRamp()
  {
    color_ramp = Color_ramp(rm, rM, gm, gM, bm, bM);
    displayLegend();
    minBox = dock_widget->minBox->value();
    maxBox = dock_widget->maxBox->value();
  }

  void on_propertyBox_currentIndexChanged(int)
  {
    switch(dock_widget->propertyBox->currentIndex())
    {
    case 0:
    {
      dock_widget->groupBox->  setEnabled(true);
      dock_widget->groupBox_3->setEnabled(true);

      dock_widget->sourcePointsButton->setEnabled(false);

      dock_widget->minBox->setMinimum(0);
      dock_widget->minBox->setMaximum(360);
      dock_widget->minBox->setValue(0);

      dock_widget->maxBox->setMinimum(0);
      dock_widget->maxBox->setMaximum(360);
      Scene_surface_mesh_item* item =
          qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
      if(! item )
        dock_widget->maxBox->setValue(180);
      else if(is_triangle_mesh(*item->face_graph()))
        dock_widget->maxBox->setValue(60);
      else if(is_quad_mesh(*item->face_graph()))
        dock_widget->maxBox->setValue(90);
      break;
    }
    case 1:
      dock_widget->groupBox->  setEnabled(true);
      dock_widget->groupBox_3->setEnabled(true);
      dock_widget->sourcePointsButton->setEnabled(false);

      dock_widget->minBox->setMinimum(-1000);
      dock_widget->minBox->setMaximum(1000);
      dock_widget->minBox->setValue(0);

      dock_widget->maxBox->setMinimum(-1000);
      dock_widget->maxBox->setMaximum(1000);
      dock_widget->maxBox->setValue(2);
      break;
    default:
      dock_widget->maxBox->setMinimum(0);
      dock_widget->maxBox->setMaximum(99999999);
      dock_widget->groupBox->  setEnabled(false);
      dock_widget->groupBox_3->setEnabled(false);
      dock_widget->sourcePointsButton->setEnabled(true);

    }
    replaceRamp();
    enableButtons();
  }

  void closure()Q_DECL_OVERRIDE
  {
    dock_widget->hide();
  }

  void on_zoomToMinButton_pressed()
  {
    Scene_surface_mesh_item* item =
        qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
    if(!item)
      return;
    face_descriptor dummy_fd;
    Point_3 dummy_p;
    switch(dock_widget->propertyBox->currentIndex())
    {
    case 0:
    {
      ::zoomToId(*item->face_graph(),
                 QString("f%1").arg(angles_min[item].second),
                 getActiveViewer(),
                 dummy_fd,
                 dummy_p);
    }
      break;
    case 1:
    {
      ::zoomToId(*item->face_graph(),
                 QString("f%1").arg(jacobian_min[item].second),
                 getActiveViewer(),
                 dummy_fd,
                 dummy_p);
    }
      break;
    default:
      break;
    }
  }

  void on_zoomToMaxButton_pressed()
  {
    Scene_surface_mesh_item* item =
        qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
    if(!item)
      return;
    face_descriptor dummy_fd;
    Point_3 dummy_p;
    switch(dock_widget->propertyBox->currentIndex())
    {
    case 0:
    {
      ::zoomToId(*item->face_graph(),
                 QString("f%1").arg(angles_max[item].second),
                 getActiveViewer(),
                 dummy_fd,
                 dummy_p);
    }
      break;
    case 1:
    {
      ::zoomToId(*item->face_graph(),
                 QString("f%1").arg(jacobian_max[item].second),
                 getActiveViewer(),
                 dummy_fd,
                 dummy_p);
    }
      break;
    default:
      break;
    }
  }

  void delete_group()
  {
    Scene_item* item = scene->item(scene->selectionIndices().first());
    Scene_group_item* group = qobject_cast<Scene_group_item*>(item);
    if(!group || !group->property("heat_group").toBool())
      return;
    for(auto child_id : group->getChildren())
    {
      if(Scene_surface_mesh_item* child = qobject_cast<Scene_surface_mesh_item*>(scene->item(child_id))){
        group->unlockChild(child);
         group->removeChild(child);
         scene->addChild(child);
         child->setVisible(true);
         child->resetColors();
        break;
      }
    }
    scene->erase(scene->item_id(group));

  }

  void on_sourcePointsButton_toggled(bool b)
  {
    if(b)
    {
      Scene_heat_item* h_item = nullptr;
      Scene_surface_mesh_item* item =
          qobject_cast<Scene_surface_mesh_item*>(scene->item(scene->mainSelectionIndex()));
      if(!item)
      {
        h_item = qobject_cast<Scene_heat_item*>(scene->item(scene->mainSelectionIndex()));
        if(h_item)
          item = h_item->getParent();
      }
      if(!item)
      {
        QMessageBox::warning(mw, "Warning", "You must select a Surface_mesh_item to make this work. Aborting.");
        dock_widget->sourcePointsButton->setChecked(false);
        return;
      }
      current_item = item;
      connect(current_item, &Scene_surface_mesh_item::aboutToBeDestroyed,
              this, [this]()
      {
        dock_widget->sourcePointsButton->setChecked(false);
      });
      if(mesh_sources_map.find(item) == mesh_sources_map.end())
      {
        source_points = new Scene_points_with_normal_item();
        source_points->setName(QString("Source vertices for %1").arg(current_item->name()));
        source_points->setColor(QColor(Qt::red));
        source_points->setPointSize(5);
        scene->addItem(source_points);
        connect(source_points, &Scene_points_with_normal_item::aboutToBeDestroyed,
                [this](){
          boost::unordered_map<Scene_surface_mesh_item*, Scene_points_with_normal_item*>::iterator it;
          for(it = mesh_sources_map.begin();
              it != mesh_sources_map.end();
              ++it)
          {
            if(it->second == source_points)
            {
              mesh_sources_map.erase(it);
              break;
            }
          }
        });
      mesh_sources_map[current_item] = source_points;
      }
      else
      {
        source_points=mesh_sources_map[current_item];
      }
      connect(item, SIGNAL(selected_vertex(void*)), this, SLOT(on_vertex_selected(void*)));
      bool non_init = is_source.find(item) == is_source.end();
      if(non_init)
      {
        Vertex_source_map map = get(Vertex_source_tag(), *item->face_graph());
        is_source.insert(std::make_pair(item, map));
        connect(item, &Scene_surface_mesh_item::itemChanged,
                this, &DisplayPropertyPlugin::resetProperty);
        connect(item, &Scene_surface_mesh_item::aboutToBeDestroyed,
                [this, item](){
          if(is_source.find(item) != is_source.end())
          {
            is_source.erase(item);
          }
        });
      }
    }
    else
    {
      if(!current_item)
        return;
      disconnect(current_item, SIGNAL(selected_vertex(void*)), this, SLOT(on_vertex_selected(void*)));
      current_item = NULL;
    }
  }

  void on_vertex_selected(void* void_ptr)
  {
    typedef boost::graph_traits<SMesh>::vertices_size_type size_type;
    size_type h = static_cast<size_type>(reinterpret_cast<std::size_t>(void_ptr));
    vertex_descriptor vd = static_cast<vertex_descriptor>(h) ;
    bool found = is_source.find(current_item) != is_source.end();
    if(found)
    {
      if(!get(is_source[current_item], vd))
      {
        put(is_source[current_item], vd, true);
        source_points->point_set()->insert(current_item->face_graph()->point(vd));
      }
      else
      {
        put(is_source[current_item], vd, false);
        Point_set::iterator it;
        for(it = source_points->point_set()->begin(); it != source_points->point_set()->end(); ++it)
          if(source_points->point_set()->point(*it) == current_item->face_graph()->point(vd))
          {
            source_points->point_set()->remove(it);
            source_points->point_set()->collect_garbage();
            break;
          }
      }
    }

   source_points->invalidateOpenGLBuffers();
   source_points->itemChanged();
  }
private:
  void displayLegend()
  {
    // Create an legend_ and display it
    const int height = 256;
    const int width = 90;
    const int cell_width = width/3;
    const int top_margin = 5;
    const int left_margin = 5;
    const int drawing_height = height - top_margin * 2;
    const int text_height = 20;

    legend_ = QPixmap(width, height + text_height);
    legend_.fill(QColor(200, 200, 200));

    QPainter painter(&legend_);
    painter.setPen(Qt::black);
    painter.setBrush(QColor(200, 200, 200));

    // Build legend_ data
    double min_value(dock_widget->minBox->value()),
        max_value(dock_widget->maxBox->value());
    std::vector<double> graduations(100);
    for(int i=0; i<100; ++i)
      graduations[i] = i/100.0;

    // draw
    int i=0;
    for (std::vector<double>::iterator it = graduations.begin(), end = graduations.end();
         it != end; ++it, i+=2)
    {
      QColor color(255*color_ramp.r(*it),
                   255*color_ramp.g(*it),
                   255*color_ramp.b(*it));
      painter.fillRect(left_margin,
                       drawing_height - top_margin - i,
                       cell_width,
                       2,
                       color);
    }

    // draw right vertical line
    painter.setPen(Qt::blue);

    painter.drawLine(QPoint(left_margin + cell_width+10, drawing_height - top_margin),
                     QPoint(left_margin + cell_width+10,
                            drawing_height - top_margin - static_cast<int>(graduations.size())*2));

    // draw min value and max value
    painter.setPen(Qt::blue);
    QRect min_text_rect(left_margin + cell_width+10,drawing_height - top_margin,
                        50, text_height);
    painter.drawText(min_text_rect, Qt::AlignCenter, tr("%1").arg(min_value, 0, 'f', 1));

    QRect max_text_rect(left_margin + cell_width+10, drawing_height - top_margin - 200,
                        50, text_height);
    painter.drawText(max_text_rect, Qt::AlignCenter, tr("%1").arg(max_value, 0, 'f', 1));

    dock_widget->legendLabel->setPixmap(legend_);
  }
  double scaled_jacobian(const face_descriptor& f , const SMesh &mesh);
  QList<QAction*> _actions;
  Color_ramp color_ramp;
  DockWidget* dock_widget;
  double rm;
  double rM;
  double gm;
  double gM;
  double bm;
  double bM;
  boost::unordered_map<Scene_surface_mesh_item*, std::pair<double, SMesh::Face_index> > jacobian_min;
  boost::unordered_map<Scene_surface_mesh_item*, std::pair<double, SMesh::Face_index> > jacobian_max;

  boost::unordered_map<Scene_surface_mesh_item*, std::pair<double, SMesh::Face_index> > angles_min;
  boost::unordered_map<Scene_surface_mesh_item*, std::pair<double, SMesh::Face_index> > angles_max;
  boost::unordered_map<Scene_surface_mesh_item*, Vertex_source_map> is_source;

  double minBox;
  double maxBox;
  QPixmap legend_;

  Scene_surface_mesh_item* current_item;
  Scene_points_with_normal_item* source_points;
  boost::unordered_map<Scene_surface_mesh_item*, Scene_points_with_normal_item*> mesh_sources_map;
  boost::unordered_map<Scene_surface_mesh_item*, Scene_heat_item*> mesh_heat_item_map;

  boost::unordered_map<Scene_surface_mesh_item*, Heat_method*> mesh_heat_method_map;
  boost::unordered_map<Scene_surface_mesh_item*, Heat_method_idt*> mesh_heat_method_idt_map;
};

  /// Code based on the verdict module of vtk

  /*=========================================================================
  Copyright (c) 2006 Sandia Corporation.
  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.
=========================================================================*/

  double DisplayPropertyPlugin::scaled_jacobian( const face_descriptor& f , const SMesh& mesh)
  {
    boost::property_map<SMesh, boost::vertex_point_t>::type
        pmap = get(boost::vertex_point, mesh);
    std::vector<double> corner_areas(degree(f, mesh));
    std::vector<EPICK::Vector_3> edges;
    for(halfedge_descriptor hd : CGAL::halfedges_around_face(halfedge(f, mesh), mesh))
    {
      edges.push_back(EPICK::Vector_3(get(pmap, source(hd, mesh)), get(pmap, target(hd, mesh))));
    }
    std::vector<EPICK::Vector_3> corner_normals;
    for(std::size_t i = 0; i < edges.size(); ++i)
    {
      corner_normals.push_back(CGAL::cross_product(edges[i], edges[(i+1)%(edges.size())]));
    }

    EPICK::Vector_3 unit_center_normal = CGAL::Polygon_mesh_processing::compute_face_normal(f, mesh);
    unit_center_normal *= 1.0/CGAL::approximate_sqrt(unit_center_normal.squared_length());

    for(std::size_t i = 0; i < corner_areas.size(); ++i)
    {
      corner_areas[i] =  unit_center_normal*corner_normals[i];
    }
    std::vector<double> length;
    for(std::size_t i=0; i<edges.size(); ++i)
    {
      length.push_back(CGAL::approximate_sqrt(edges[i].squared_length()));
      if( length[i] < ARBITRARY_DBL_MIN)
        return 0.0;
    }
    double min_scaled_jac = ARBITRARY_DBL_MAX;
    for(std::size_t i=0; i<edges.size(); ++i)
    {
      double scaled_jac = corner_areas[i] / (length[i] * length[(i+edges.size()-1)%(edges.size())]);
      min_scaled_jac = (std::min)( scaled_jac, min_scaled_jac );
    }

    if( min_scaled_jac > 0 )
      return (double) (std::min)( min_scaled_jac, ARBITRARY_DBL_MAX );
    return (double) (std::max)( min_scaled_jac, -ARBITRARY_DBL_MAX );

  }

#include "Display_property_plugin.moc"