//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Mask/Polygon.cpp
//! @brief     Implements class Polygon.
//!
//! @homepage  http://www.bornagainproject.org
//! @license   GNU General Public License v3 or higher (see COPYING)
//! @copyright Forschungszentrum Jülich GmbH 2018
//! @authors   Scientific Computing Group at MLZ (see CITATION, AUTHORS)
//
//  ************************************************************************************************

#include "Device/Mask/Polygon.h"
#include "Base/Axis/Bin.h"
#include "Base/Util/Assert.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#include <boost/geometry.hpp>
#pragma clang diagnostic pop

//! The private data for polygons to hide boost dependency from the header
class PolygonPrivate {
public:
    using point_t = boost::geometry::model::d2::point_xy<double>;
    using polygon_t = boost::geometry::model::polygon<point_t>;
    PolygonPrivate(const std::vector<double>& x, const std::vector<double>& y);
    PolygonPrivate(const std::vector<std::pair<double, double>>& pts);
    polygon_t polygon;
    void get_points(std::vector<double>& xpos, std::vector<double>& ypos); // TODO const or no-&
};

PolygonPrivate::PolygonPrivate(const std::vector<double>& x, const std::vector<double>& y)
{
    ASSERT(x.size() == y.size());
    std::vector<point_t> points;
    for (size_t i = 0; i < x.size(); ++i)
        points.emplace_back(x[i], y[i]);
    boost::geometry::assign_points(polygon, points);
    boost::geometry::correct(polygon);
}

PolygonPrivate::PolygonPrivate(const std::vector<std::pair<double, double>>& pts)
{
    std::vector<point_t> points;
    points.reserve(pts.size());
    for (const std::pair<double, double>& p : pts)
        points.emplace_back(p.first, p.second);
    boost::geometry::assign_points(polygon, points);
    boost::geometry::correct(polygon);
}

void PolygonPrivate::get_points(std::vector<double>& xpos, std::vector<double>& ypos)
{
    xpos.clear();
    ypos.clear();
    for (auto& it : polygon.outer()) {
        // for vectors of x and y, extract the x/y from the point
        xpos.push_back(boost::geometry::get<0>(it));
        ypos.push_back(boost::geometry::get<1>(it));
    }
}

//  ************************************************************************************************

// IMPORTANT Input parameter is not "const reference" to be able to work from python
// (auto conversion of python list to vector<pair<double,double>>).
//! If polygon is unclosed (the last point doesn't repeat the first one), it will
//! be closed automatically.
//! @param points Two dimensional vector of (x,y) coordinates of polygon points.
Polygon::Polygon(const std::vector<std::pair<double, double>> points)
    : IShape2D("Polygon")
    , m_d(new PolygonPrivate(points))
{
}

Polygon::Polygon(const PolygonPrivate* d)
    : IShape2D("Polygon")
    , m_d(new PolygonPrivate(*d))
{
}

Polygon::~Polygon()
{
    delete m_d;
}

bool Polygon::contains(double x, double y) const
{
    // including borders
    return boost::geometry::covered_by(PolygonPrivate::point_t(x, y), m_d->polygon);
}

bool Polygon::contains(const Bin1D& binx, const Bin1D& biny) const
{
    // Test for n*n points per pixel, while waiting for a more intelligent implementation.
    const int n = 5;
    for (int ix = 0; ix < n; ++ix)
        for (int iy = 0; iy < n; ++iy)
            if (contains(binx.atFraction(ix / (n - 1.)), biny.atFraction(iy / (n - 1.))))
                return true;
    return false;
}

double Polygon::getArea() const
{
    return boost::geometry::area(m_d->polygon);
}

void Polygon::getPoints(std::vector<double>& xpos, std::vector<double>& ypos) const
{
    m_d->get_points(xpos, ypos);
}

void Polygon::print(std::ostream& ostr) const
{
    ostr << boost::geometry::wkt<PolygonPrivate::polygon_t>(m_d->polygon);
}
