#pragma once

#include <QPoint>
#include <cstddef>
#include <limits>
#include <vector>

namespace sme::mesh {

struct LineError {
  double total = std::numeric_limits<double>::max();
  double average = std::numeric_limits<double>::max();
};

/**
 * @brief Simplify a line using Visvalingam-Whyatt polyline simplification
 *
 * Given an ordered set of pixels that form a line or a closed loop, construct
 * an n-point approximation to the line.
 *
 * @note Each point in the line is ranked according to the size of the triangle
 * formed by itself and its two nearest neighbours, and points are removed in
 * reverse order of importance. See
 * [10.1179/000870493786962263](https://www.tandfonline.com/doi/abs/10.1179/000870493786962263)
 * for more details.
 */
class LineSimplifier {
private:
  std::vector<QPoint> vertices;
  std::size_t minNumPoints;
  std::vector<std::size_t> priorities;
  [[nodiscard]] LineError getLineError(const std::vector<QPoint> &line) const;
  bool valid{true};
  bool closedLoop{false};

public:
  /**
   * @brief Construct a simplified line given a maximum allowed error
   *
   * @param[out] line the simplified lines
   * @param[in] allowedError the maximum allowed error
   */
  void getSimplifiedLine(std::vector<QPoint> &line,
                         const LineError &allowedError = {0.0, 0.5}) const;
  /**
   * @brief Construct a simplified line given a maximum number of points
   *
   * @param[out] line the simplified lines
   * @param[in] nPoints the maximum allowed number of points
   */
  void getSimplifiedLine(std::vector<QPoint> &line, std::size_t nPoints) const;
  /**
   * @brief The original un-simplified line
   */
  [[nodiscard]] const std::vector<QPoint> &getAllVertices() const;
  /**
   * @brief The number of points in the original un-simplified line
   */
  [[nodiscard]] std::size_t maxPoints() const;
  /**
   * @brief Is the line valid
   */
  [[nodiscard]] bool isValid() const;
  /**
   * @brief Is the line a closed loop
   */
  [[nodiscard]] bool isLoop() const;
  /**
   * @brief Construct a simplified version of the supplied line
   *
   * @param[in] points the ordered points that define the line
   * @param[in] isClosedLoop true if the line represents a closed loop, i.e. the
   *    last point implicitly connects to the first point
   */
  explicit LineSimplifier(const std::vector<QPoint> &points,
                          bool isClosedLoop = false);
};

} // namespace sme::mesh
