//  ************************************************************************************************
//
//  BornAgain: simulate and fit reflection and scattering
//
//! @file      Device/Mask/Line.cpp
//! @brief     Implements class Line.
//!
//! @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/Line.h"
#include "Base/Axis/Bin.h"
#include "Base/Math/Numeric.h"
#include <limits>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#include <boost/geometry.hpp>
#pragma clang diagnostic pop

using point_t = boost::geometry::model::d2::point_xy<double>;
using line_t = boost::geometry::model::linestring<point_t>;

Line::Line(double x1, double y1, double x2, double y2)
    : IShape2D("Line")
    , m_x1(x1)
    , m_y1(y1)
    , m_x2(x2)
    , m_y2(y2)
{
}

bool Line::contains(double x, double y) const
{
    point_t p(x, y);
    line_t line;
    line.emplace_back(m_x1, m_y1);
    line.emplace_back(m_x2, m_y2);

    double d = boost::geometry::distance(p, line);

    return d < std::numeric_limits<double>::epsilon();
}

// Calculates if line crosses the box made out of our bins.
// Ugly implementation, see discussion at http://stackoverflow.com/questions/21408977
bool Line::contains(const Bin1D& binx, const Bin1D& biny) const
{
    std::vector<point_t> box_points;
    box_points.emplace_back(binx.min(), biny.min());
    box_points.emplace_back(binx.min(), biny.max());
    box_points.emplace_back(binx.max(), biny.max());
    box_points.emplace_back(binx.max(), biny.min());
    box_points.emplace_back(binx.min(), biny.min());

    std::vector<point_t> line_points;
    line_points.emplace_back(m_x1, m_y1);
    line_points.emplace_back(m_x2, m_y2);

    return boost::geometry::intersects(line_t(box_points.begin(), box_points.end()),
                                       line_t(line_points.begin(), line_points.end()));
}

// ------------------------------------------------------------------------- //

//! @param x The value at which it crosses x-axes
VerticalLine::VerticalLine(double x)
    : IShape2D("VerticalLine")
    , m_x(x)
{
}

bool VerticalLine::contains(double x, double /*y*/) const
{
    return Numeric::almostEqual(x, m_x, 2);
}

bool VerticalLine::contains(const Bin1D& binx, const Bin1D& /*biny*/) const
{
    return m_x >= binx.min() && m_x <= binx.max();
}

// ------------------------------------------------------------------------- //

//! @param y The value at which it crosses y-axes
HorizontalLine::HorizontalLine(double y)
    : IShape2D("HorizontalLine")
    , m_y(y)
{
}

bool HorizontalLine::contains(double /*x*/, double y) const
{
    return Numeric::almostEqual(y, m_y, 2);
}

bool HorizontalLine::contains(const Bin1D& /*binx*/, const Bin1D& biny) const
{
    return m_y >= biny.min() && m_y <= biny.max();
}
