/* +------------------------------------------------------------------------+
   |                     Mobile Robot Programming Toolkit (MRPT)            |
   |                          https://www.mrpt.org/                         |
   |                                                                        |
   | Copyright (c) 2005-2024, Individual contributors, see AUTHORS file     |
   | See: https://www.mrpt.org/Authors - All rights reserved.               |
   | Released under BSD License. See: https://www.mrpt.org/License          |
   +------------------------------------------------------------------------+ */

#pragma once

#include <mrpt/config/CLoadableOptions.h>
#include <mrpt/containers/CDynamicGrid.h>
#include <mrpt/img/color_maps.h>
#include <mrpt/maps/CHeightGridMap2D_Base.h>
#include <mrpt/maps/CMetricMap.h>
#include <mrpt/obs/obs_frwds.h>
#include <mrpt/poses/poses_frwds.h>
#include <mrpt/serialization/CSerializable.h>
#include <mrpt/typemeta/TEnumType.h>

namespace mrpt
{
namespace maps
{
/** The contents of each cell in a CHeightGridMap2D map */
struct THeightGridmapCell
{
  /** The current average height (in meters) */
  float h{};
  /** The current standard deviation of the height (in meters) */
  float var{};
  /** Auxiliary variable for storing the incremental mean value (in meters).
   */
  float u{};
  /** Auxiliary (in meters) */
  float v{};
  /** [For mrSimpleAverage model] The accumulated weight: initially zero if
   * un-observed, increased by one for each observation */
  uint32_t w{};

  THeightGridmapCell() = default;
};

/** Digital Elevation Model (DEM), a mesh or grid representation of a surface
 * which keeps the estimated height for each (x,y) location.
 *  Important implemented features are the insertion of 2D laser scans (from
 * arbitrary 6D poses) and the exportation as 3D scenes.
 *
 * Each cell contains the up-to-date average height from measured falling in
 * that cell. Algorithms that can be used:
 *   - mrSimpleAverage: Each cell only stores the current average value.
 *
 *  This class implements generic version of
 * mrpt::maps::CMetric::insertObservation() accepting these types of sensory
 * data:
 *   - mrpt::obs::CObservation2DRangeScan: 2D range scans
 *   - mrpt::obs::CObservationVelodyneScan
 *
 * \ingroup mrpt_maps_grp
 */
class CHeightGridMap2D :
    public mrpt::maps::CMetricMap,
    public mrpt::containers::CDynamicGrid<THeightGridmapCell>,
    public CHeightGridMap2D_Base
{
  DEFINE_SERIALIZABLE(CHeightGridMap2D, mrpt::maps)
 public:
  /** Calls the base CMetricMap::clear
   * Declared here to avoid ambiguity between the two clear() in both base
   * classes.
   */
  inline void clear() { CMetricMap::clear(); }
  float cell2float(const THeightGridmapCell& c) const override { return float(c.h); }

  /** Returns a short description of the map. */
  std::string asString() const override
  {
    return mrpt::format(
        "HeightGridMap2D, extending from (%f,%f) to (%f,%f), resolution=%f", getXMin(), getYMin(),
        getXMax(), getYMax(), getResolution());
  }

  /** The type of map representation to be used.
   *  See mrpt::maps::CHeightGridMap2D for discussion.
   */
  enum TMapRepresentation
  {
    mrSimpleAverage = 0
  };

  /** Constructor */
  CHeightGridMap2D(
      TMapRepresentation mapType = mrSimpleAverage,
      double x_min = -2,
      double x_max = 2,
      double y_min = -2,
      double y_max = 2,
      double resolution = 0.1);

  /** Returns true if the map is empty/no observation has been inserted. */
  bool isEmpty() const override;

  /** Parameters related with inserting observations into the map */
  struct TInsertionOptions : public mrpt::config::CLoadableOptions
  {
    /** Default values loader */
    TInsertionOptions();

    void loadFromConfigFile(
        const mrpt::config::CConfigFileBase& source,
        const std::string& section) override;                 // See base docs
    void dumpToTextStream(std::ostream& out) const override;  // See base docs

    /** Whether to perform filtering by z-coordinate (default=false):
     * coordinates are always RELATIVE to the robot for this filter.vvv */
    bool filterByHeight{false};
    /** Only when filterByHeight is true: coordinates are always RELATIVE to
     * the robot for this filter. */
    float z_min{-0.5}, z_max{0.5};

    mrpt::img::TColormap colorMap{mrpt::img::cmJET};
  } insertionOptions;

  /** See docs in base class: in this class it always returns 0 */
  float compute3DMatchingRatio(
      const mrpt::maps::CMetricMap* otherMap,
      const mrpt::poses::CPose3D& otherMapPose,
      const TMatchingRatioParams& params) const override;

  void saveMetricMapRepresentationToFile(
      const std::string& filNamePrefix) const override;  // See base class docs

  /** Returns a 3D object representing the map: by default, it will be a
   * mrpt::opengl::CMesh object, unless
   *   it is specified otherwise in
   * mrpt::global_settings::HEIGHTGRIDMAP_EXPORT3D_AS_MESH */
  void getVisualizationInto(mrpt::opengl::CSetOfObjects& outObj) const override;

  /** Return the type of the gas distribution map, according to parameters
   * passed on construction */
  TMapRepresentation getMapType();

  /** Return the number of cells with at least one height data inserted. */
  size_t countObservedCells() const;

  bool insertIndividualPoint(
      const double x,
      const double y,
      const double z,
      const CHeightGridMap2D_Base::TPointInsertParams& params =
          CHeightGridMap2D_Base::TPointInsertParams()) override;
  double dem_get_resolution() const override;
  size_t dem_get_size_x() const override;
  size_t dem_get_size_y() const override;
  bool dem_get_z_by_cell(size_t cx, size_t cy, double& z_out) const override;
  bool dem_get_z(const double x, const double y, double& z_out) const override;
  void dem_update_map() override;

  /** The map representation type of this map */
  TMapRepresentation m_mapType;

  // See docs in base class
  void internal_clear() override;
  bool internal_insertObservation(
      const mrpt::obs::CObservation& obs,
      const std::optional<const mrpt::poses::CPose3D>& robotPose = std::nullopt) override;
  double internal_computeObservationLikelihood(
      const mrpt::obs::CObservation& obs, const mrpt::poses::CPose3D& takenFrom) const override;

  MAP_DEFINITION_START(CHeightGridMap2D)
  /** See CHeightGridMap2D::CHeightGridMap2D */
  double min_x{-2}, max_x{2}, min_y{-2}, max_y{2}, resolution{0.10f};
  /** The kind of map representation (see CHeightGridMap2D::CHeightGridMap2D)
   */
  mrpt::maps::CHeightGridMap2D::TMapRepresentation mapType{CHeightGridMap2D::mrSimpleAverage};
  mrpt::maps::CHeightGridMap2D::TInsertionOptions insertionOpts;
  MAP_DEFINITION_END(CHeightGridMap2D)
};

}  // namespace maps

namespace global_settings
{
/** If set to true (default), mrpt::maps::CHeightGridMap2D will be exported as a
 *opengl::CMesh, otherwise, as a opengl::CPointCloudColoured
 * Affects to:
 *		- CHeightGridMap2D::getAs3DObject
 */
void HEIGHTGRIDMAP_EXPORT3D_AS_MESH(bool value);
bool HEIGHTGRIDMAP_EXPORT3D_AS_MESH();
}  // namespace global_settings
}  // namespace mrpt

MRPT_ENUM_TYPE_BEGIN(maps::CHeightGridMap2D::TMapRepresentation)
MRPT_FILL_ENUM_MEMBER(maps::CHeightGridMap2D::TMapRepresentation, mrSimpleAverage);
MRPT_ENUM_TYPE_END()
