#ifndef COSTMAP_2D_COSTMAP_2D_H_     // Bảo vệ tránh include file nhiều lần
#define COSTMAP_2D_COSTMAP_2D_H_

#include <vector>                     // Dùng cho std::vector
#include <queue>                      // Dùng trong các thuật toán quét ô (flood fill, BFS)
#include <boost/thread.hpp>           // Dùng mutex để đồng bộ truy cập giữa các thread (multi-thread safe)
#include <geometry_msgs/Point.h>

namespace costmap_2d                  // Mọi thứ nằm trong namespace costmap_2d
{

// Cấu trúc tiện dụng để lưu trữ cặp tọa độ (x, y) trên bản đồ (dạng ô)
struct MapLocation
{
  unsigned int x;                     // Chỉ số hàng (hoặc cột) theo trục X trong costmap
  unsigned int y;                     // Chỉ số hàng (hoặc cột) theo trục Y trong costmap
};

/**
 * @class Costmap2D
 * @brief Lớp này đại diện cho bản đồ chi phí 2D (costmap)
 * 
 * Nó ánh xạ giữa không gian thế giới (tọa độ thực, mét) và lưới ô (cells),
 * mỗi ô chứa "cost" – tức là chi phí để robot đi qua ô đó (ví dụ: 0 là free, 255 là obstacle).
 */
class Costmap2D
{
  friend class CostmapTester;         // Cho phép lớp test (gtest) truy cập vào các thành viên private
public:
  /**
   * Constructor khởi tạo bản đồ costmap
   * @param cells_size_x: số ô theo chiều X
   * @param cells_size_y: số ô theo chiều Y
   * @param resolution: kích thước 1 ô (mét / ô)
   * @param origin_x: gốc bản đồ (tọa độ thế giới) theo trục X
   * @param origin_y: gốc bản đồ (tọa độ thế giới) theo trục Y
   * @param default_value: giá trị mặc định của mỗi ô (thường là 0 = free)
   */
  Costmap2D(unsigned int cells_size_x, unsigned int cells_size_y, double resolution,
            double origin_x, double origin_y, unsigned char default_value = 0);

  /**
   * Constructor sao chép (copy constructor)
   * Dùng để tạo bản sao nhanh của một bản đồ costmap khác.
   */
  Costmap2D(const Costmap2D& map);

  /**
   * Toán tử gán (=) được overload để sao chép dữ liệu từ bản đồ khác.
   */
  Costmap2D& operator=(const Costmap2D& map);

  /**
   * Tạo bản sao từ một vùng (window) của bản đồ khác.
   * Dùng khi ta chỉ muốn sao chép một phần nhỏ của bản đồ lớn.
   */
  bool copyCostmapWindow(const Costmap2D& map, double win_origin_x, double win_origin_y, double win_size_x,
                         double win_size_y);

  /// Constructor mặc định (không khởi tạo kích thước)
  Costmap2D();

  /// Destructor
  virtual ~Costmap2D();

  /**
   * Trả về giá trị chi phí (cost) của một ô (mx, my)
   */
  unsigned char getCost(unsigned int mx, unsigned int my) const;

  /**
   * Đặt giá trị cost cho một ô (mx, my)
   */
  void setCost(unsigned int mx, unsigned int my, unsigned char cost);

  /**
   * Chuyển đổi tọa độ map (ô) → tọa độ thế giới (mét)
   * Dùng để vẽ, debug hoặc xác định vị trí thực tế của một ô.
   */
  void mapToWorld(unsigned int mx, unsigned int my, double& wx, double& wy) const;

  /**
   * Chuyển đổi tọa độ thế giới → tọa độ map (ô)
   * Nếu tọa độ nằm ngoài bản đồ → trả về false
   */
  bool worldToMap(double wx, double wy, unsigned int& mx, unsigned int& my) const;

  /**
   * Chuyển đổi mà không kiểm tra biên hợp lệ (có thể nằm ngoài bản đồ)
   */
  void worldToMapNoBounds(double wx, double wy, int& mx, int& my) const;

  /**
   * Chuyển đổi nhưng ép kết quả nằm trong biên của bản đồ (clamped)
   */
  void worldToMapEnforceBounds(double wx, double wy, int& mx, int& my) const;

  /**
   * Tính chỉ số 1D của ô từ cặp tọa độ (mx, my)
   * Công thức: index = my * size_x_ + mx
   */
  inline unsigned int getIndex(unsigned int mx, unsigned int my) const
  {
    return my * size_x_ + mx;
  }

  /**
   * Chuyển ngược lại: từ chỉ số 1D → cặp tọa độ (mx, my)
   */
  inline void indexToCells(unsigned int index, unsigned int& mx, unsigned int& my) const
  {
    my = index / size_x_;
    mx = index - (my * size_x_);
  }

  /**
   * Trả về con trỏ đến mảng dữ liệu costmap (unsigned char array)
   */
  unsigned char* getCharMap() const;

  /**
   * Trả về số ô theo trục X
   */
  unsigned int getSizeInCellsX() const;

  /**
   * Trả về số ô theo trục Y
   */
  unsigned int getSizeInCellsY() const;

  /**
   * Trả về kích thước bản đồ theo mét (theo X)
   */
  double getSizeInMetersX() const;

  /**
   * Trả về kích thước bản đồ theo mét (theo Y)
   */
  double getSizeInMetersY() const;

  /**
   * Trả về tọa độ gốc bản đồ theo trục X (tọa độ thế giới)
   */
  double getOriginX() const;

  /**
   * Trả về tọa độ gốc bản đồ theo trục Y (tọa độ thế giới)
   */
  double getOriginY() const;

  /**
   * Trả về độ phân giải (mét / ô)
   */
  double getResolution() const;

  /// Đặt giá trị mặc định (cost khi reset map)
  void setDefaultValue(unsigned char c)
  {
    default_value_ = c;
  }

  /// Trả về giá trị mặc định
  unsigned char getDefaultValue()
  {
    return default_value_;
  }

  /**
   * Đặt cost cho một vùng đa giác lồi (convex polygon)
   * Dùng để đánh dấu vùng chướng ngại vật hoặc vùng forbidden.
   */
  bool setConvexPolygonCost(const std::vector<geometry_msgs::Point>& polygon, unsigned char cost_value);

  /**
   * Lấy danh sách các ô nằm trên viền (outline) của một polygon.
   */
  void polygonOutlineCells(const std::vector<MapLocation>& polygon, std::vector<MapLocation>& polygon_cells);

  /**
   * Lấy danh sách các ô nằm bên trong polygon (điền vùng lồi).
   */
  void convexFillCells(const std::vector<MapLocation>& polygon, std::vector<MapLocation>& polygon_cells);

  /**
   * Cập nhật lại gốc bản đồ (origin) sang vị trí mới,
   * giữ lại dữ liệu cũ nếu có thể (thường dùng khi robot di chuyển xa).
   */
  virtual void updateOrigin(double new_origin_x, double new_origin_y);

  /**
   * Lưu bản đồ ra file ảnh PGM (phục vụ debug hoặc visualization)
   */
  bool saveMap(std::string file_name);

  /**
   * Thay đổi kích thước bản đồ (resize toàn bộ)
   */
  void resizeMap(unsigned int size_x, unsigned int size_y, double resolution, double origin_x,
                 double origin_y);

  /**
   * Reset toàn bộ costmap trong vùng (x0, y0) → (xn, yn)
   */
  void resetMap(unsigned int x0, unsigned int y0, unsigned int xn, unsigned int yn);

  /**
   * Chuyển khoảng cách theo mét → số ô tương ứng
   */
  unsigned int cellDistance(double world_dist);

  /**
   * Định nghĩa mutex để bảo vệ truy cập đồng thời.
   */
  typedef boost::recursive_mutex mutex_t;
  mutex_t* getMutex()
  {
    return access_;
  }

protected:
  /**
   * Hàm template copy một vùng dữ liệu từ bản đồ nguồn → bản đồ đích
   * Dữ liệu được copy bằng memcpy để đảm bảo nhanh.
   */
  template<typename data_type>
    void copyMapRegion(data_type* source_map, unsigned int sm_lower_left_x, unsigned int sm_lower_left_y,
                       unsigned int sm_size_x, data_type* dest_map, unsigned int dm_lower_left_x,
                       unsigned int dm_lower_left_y, unsigned int dm_size_x, unsigned int region_size_x,
                       unsigned int region_size_y)
    {
      // Con trỏ bắt đầu tại vị trí góc dưới trái của vùng cần copy trong source
      data_type* sm_index = source_map + (sm_lower_left_y * sm_size_x + sm_lower_left_x);
      // Con trỏ bắt đầu tại vị trí góc dưới trái trong dest
      data_type* dm_index = dest_map + (dm_lower_left_y * dm_size_x + dm_lower_left_x);

      // Lặp qua từng hàng trong vùng copy
      for (unsigned int i = 0; i < region_size_y; ++i)
      {
        // Copy nguyên hàng (memcpy nhanh hơn vòng for từng phần tử)
        memcpy(dm_index, sm_index, region_size_x * sizeof(data_type));
        sm_index += sm_size_x;  // chuyển sang hàng tiếp theo của source
        dm_index += dm_size_x;  // chuyển sang hàng tiếp theo của dest
      }
    }

  /**
   * Xóa toàn bộ dữ liệu bản đồ (free bộ nhớ)
   */
  virtual void deleteMaps();

  /**
   * Reset toàn bộ dữ liệu bản đồ về trạng thái UNKNOWN (NO_INFORMATION)
   */
  virtual void resetMaps();

  /**
   * Cấp phát và khởi tạo bộ nhớ cho costmap
   */
  virtual void initMaps(unsigned int size_x, unsigned int size_y);

  /**
   * Thuật toán raytracing Bresenham — đi từ điểm (x0,y0) → (x1,y1)
   * Mỗi bước sẽ gọi “ActionType” (một functor) để đánh dấu hoặc xử lý từng ô.
   * Thường dùng cho sensor update (quét laser).
   */
  template<class ActionType>
    inline void raytraceLine(ActionType at, unsigned int x0, unsigned int y0, unsigned int x1, unsigned int y1,
                             unsigned int max_length = UINT_MAX)
    {
      int dx = x1 - x0;                // Khoảng cách theo trục X
      int dy = y1 - y0;                // Khoảng cách theo trục Y

      unsigned int abs_dx = abs(dx);
      unsigned int abs_dy = abs(dy);

      int offset_dx = sign(dx);        // Hướng đi theo X
      int offset_dy = sign(dy) * size_x_; // Hướng đi theo Y (theo stride của hàng)

      unsigned int offset = y0 * size_x_ + x0;  // Vị trí bắt đầu trong mảng costmap

      double dist = hypot(dx, dy);              // Chiều dài đoạn thẳng
      double scale = (dist == 0.0) ? 1.0 : std::min(1.0, max_length / dist);

      // Xét hướng nào là trục chính (dominant)
      if (abs_dx >= abs_dy)
      {
        int error_y = abs_dx / 2;
        bresenham2D(at, abs_dx, abs_dy, error_y, offset_dx, offset_dy, offset, (unsigned int)(scale * abs_dx));
        return;
      }

      int error_x = abs_dy / 2;
      bresenham2D(at, abs_dy, abs_dx, error_x, offset_dy, offset_dx, offset, (unsigned int)(scale * abs_dy));
    }

private:
  /**
   * Triển khai Bresenham 2D — đi qua từng ô trên đường thẳng và gọi “at(offset)” mỗi bước.
   */
  template<class ActionType>
    inline void bresenham2D(ActionType at, unsigned int abs_da, unsigned int abs_db, int error_b, int offset_a,
                            int offset_b, unsigned int offset, unsigned int max_length)
    {
      unsigned int end = std::min(max_length, abs_da);
      for (unsigned int i = 0; i < end; ++i)
      {
        at(offset);                  // Gọi hành động (thường là đánh dấu costmap)
        offset += offset_a;          // Tiến thêm 1 bước theo hướng chính
        error_b += abs_db;
        if ((unsigned int)error_b >= abs_da)
        {
          offset += offset_b;        // Bước sang hàng tiếp theo khi sai số tích lũy đủ lớn
          error_b -= abs_da;
        }
      }
      at(offset);                    // Xử lý ô cuối cùng
    }

  /// Trả về dấu của số nguyên (x>0:1, x<0:-1)
  inline int sign(int x)
  {
    return x > 0 ? 1.0 : -1.0;
  }

  mutex_t* access_;                  // Con trỏ đến mutex (đồng bộ truy cập costmap giữa các thread)

protected:
  unsigned int size_x_;              // Số lượng ô theo trục X
  unsigned int size_y_;              // Số lượng ô theo trục Y
  double resolution_;                // Độ phân giải: mét/ô
  double origin_x_;                  // Tọa độ gốc bản đồ (theo thế giới) trục X
  double origin_y_;                  // Tọa độ gốc bản đồ (theo thế giới) trục Y
  unsigned char* costmap_;           // Mảng 1 chiều chứa giá trị cost của toàn bộ bản đồ
  unsigned char default_value_;      // Giá trị mặc định cho các ô (thường = 0 hoặc 255 nếu unknown)

  /**
   * Functor nội bộ: dùng trong raytraceLine để đánh dấu một ô.
   */
  class MarkCell
  {
  public:
    MarkCell(unsigned char* costmap, unsigned char value) :
        costmap_(costmap), value_(value)
    {
    }
    inline void operator()(unsigned int offset)
    {
      costmap_[offset] = value_;     // Gán cost của ô bằng giá trị value_
    }
  private:
    unsigned char* costmap_;
    unsigned char value_;
  };

  /**
   * Functor nội bộ: dùng để thu thập các ô nằm trên viền polygon.
   */
  class PolygonOutlineCells
  {
  public:
    PolygonOutlineCells(const Costmap2D& costmap, const unsigned char* char_map, std::vector<MapLocation>& cells) :
        costmap_(costmap), char_map_(char_map), cells_(cells)
    {
    }

    inline void operator()(unsigned int offset)
    {
      MapLocation loc;
      costmap_.indexToCells(offset, loc.x, loc.y); // Chuyển index → (x, y)
      cells_.push_back(loc);                       // Thêm ô này vào danh sách viền polygon
    }

  private:
    const Costmap2D& costmap_;
    const unsigned char* char_map_;
    std::vector<MapLocation>& cells_;
  };
};

}  // namespace costmap_2d

#endif  // COSTMAP_2D_COSTMAP_2D_H_
