/************************************************************************
 **
 **  @file   vgrainline.cpp
 **  @author Roman Telezhynskyi <dismine(at)gmail.com>
 **  @date   27 4, 2023
 **
 **  @brief
 **  @copyright
 **  This source code is part of the Valentina project, a pattern making
 **  program, whose allow create and modeling patterns of clothing.
 **  Copyright (C) 2023 Valentina project
 **  <https://gitlab.com/smart-pattern/valentina> All Rights Reserved.
 **
 **  Valentina is free software: you can redistribute it and/or modify
 **  it under the terms of the GNU General Public License as published by
 **  the Free Software Foundation, either version 3 of the License, or
 **  (at your option) any later version.
 **
 **  Valentina is distributed in the hope that it will be useful,
 **  but WITHOUT ANY WARRANTY; without even the implied warranty of
 **  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 **  GNU General Public License for more details.
 **
 **  You should have received a copy of the GNU General Public License
 **  along with Valentina.  If not, see <http://www.gnu.org/licenses/>.
 **
 *************************************************************************/
#include "vpiecegrainline.h"
#include "../vgeometry/vabstractcurve.h"
#include "compatibility.h"
#include "qmath.h"
#include "vpiecegrainline_p.h"

#include <QPolygonF>
#include <QTransform>
#include <algorithm>

namespace
{
constexpr qreal arrowAngle = M_PI / 9;
constexpr int arrowLength = 15;
} // namespace

// VPieceGrainlinePrivate
//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainlinePrivate::MainLine(const QPointF &p1, qreal length, qreal angle) -> QLineF
{
    QPointF const pt2(p1.x() + length * cos(angle), p1.y() - length * sin(angle));
    return {p1, pt2};
}

// VPieceGrainline
//---------------------------------------------------------------------------------------------------------------------
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wnoexcept")

VPieceGrainline::VPieceGrainline()
  : d(new VPieceGrainlinePrivate)
{
}

QT_WARNING_POP

//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::~VPieceGrainline() = default;

//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::VPieceGrainline(const QLineF &mainLine, GrainlineArrowDirection arrowType)
  : d(new VPieceGrainlinePrivate(mainLine, arrowType))
{
}

//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::VPieceGrainline(const QPointF &p1, qreal length, qreal angle, GrainlineArrowDirection arrowType)
  : d(new VPieceGrainlinePrivate(VPieceGrainlinePrivate::MainLine(p1, length, angle), arrowType))
{
}

//---------------------------------------------------------------------------------------------------------------------
COPY_CONSTRUCTOR_IMPL(VPieceGrainline)

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::operator=(const VPieceGrainline &grainline) -> VPieceGrainline &
{
    if (&grainline == this)
    {
        return *this;
    }
    d = grainline.d;
    return *this;
}

//---------------------------------------------------------------------------------------------------------------------
VPieceGrainline::VPieceGrainline(VPieceGrainline &&grainline) noexcept
  : d(std::move(grainline.d))
{
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::operator=(VPieceGrainline &&grainline) noexcept -> VPieceGrainline &
{
    std::swap(d, grainline.d);
    return *this;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::GetMainLine() const -> QLineF
{
    return d->m_mainLine;
}

//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetMainLine(const QLineF &mainLine)
{
    d->m_mainLine = mainLine;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::GetArrowType() const -> GrainlineArrowDirection
{
    return d->m_arrowType;
}

//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetArrowType(GrainlineArrowDirection arrowType)
{
    d->m_arrowType = arrowType;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsEnabled() const -> bool
{
    return d->m_enabled;
}

//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetEnabled(bool enabled)
{
    d->m_enabled = enabled;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsVisible() const -> bool
{
    return d->m_visible;
}

//---------------------------------------------------------------------------------------------------------------------
void VPieceGrainline::SetVisible(bool visible)
{
    d->m_visible = visible;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::SecondaryLine() const -> QLineF
{
    const QLineF mainLine = GetMainLine();
    const QPointF center = mainLine.center();
    QTransform t;
    t.translate(center.x(), center.y());
    t.rotate(90);
    t.translate(-center.x(), -center.y());
    return t.map(mainLine);
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsFourWays() const -> bool
{
    return d->m_arrowType == GrainlineArrowDirection::fourWays ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpLeft ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpRight ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowUpEnabled() const -> bool
{
    return d->m_arrowType == GrainlineArrowDirection::oneWayUp ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpDown ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpLeft ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
           d->m_arrowType == GrainlineArrowDirection::fourWays;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowDownEnabled() const -> bool
{
    return d->m_arrowType == GrainlineArrowDirection::oneWayDown ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpDown ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight ||
           d->m_arrowType == GrainlineArrowDirection::fourWays;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowLeftEnabled() const -> bool
{
    return d->m_arrowType == GrainlineArrowDirection::fourWays ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpLeft ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownLeft ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsArrowRightEnabled() const -> bool
{
    return d->m_arrowType == GrainlineArrowDirection::fourWays ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysUpRight ||
           d->m_arrowType == GrainlineArrowDirection::twoWaysDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpDownRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysUpLeftRight ||
           d->m_arrowType == GrainlineArrowDirection::threeWaysDownLeftRight;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowUp() const -> QPolygonF
{
    const QLineF mainLine = GetMainLine();
    const qreal rotation = M_PI + qDegreesToRadians(mainLine.angle());
    const QPointF pt = mainLine.p2();

    return {
        {pt,
         QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
         QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
         pt}};
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowDown() const -> QPolygonF
{
    const QLineF mainLine = GetMainLine();
    const qreal rotation = qDegreesToRadians(mainLine.angle());
    const QPointF pt = mainLine.p1();

    return {
        {pt,
         QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
         QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
         pt}};
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowLeft() const -> QPolygonF
{
    const qreal rotation = 3 * M_PI / 2 + qDegreesToRadians(GetMainLine().angle());
    const QPointF pt = SecondaryLine().p1();

    return {
        {pt,
         QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
         QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
         pt}};
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::ArrowRight() const -> QPolygonF
{
    const qreal rotation = M_PI / 2 + qDegreesToRadians(GetMainLine().angle());
    const QPointF pt = SecondaryLine().p2();

    return {
        {pt,
         QPointF(pt.x() + arrowLength * cos(rotation + arrowAngle), pt.y() - arrowLength * sin(rotation + arrowAngle)),
         QPointF(pt.x() + arrowLength * cos(rotation - arrowAngle), pt.y() - arrowLength * sin(rotation - arrowAngle)),
         pt}};
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::Shape() const -> GrainlineShape
{
    const QLineF mainLine = GetMainLine();
    if (mainLine.isNull())
    {
        return {};
    }

    // main arrow
    QVector<QPointF> arrow1;

    if (IsArrowDownEnabled())
    {
        arrow1 << ArrowDown();
    }
    else
    {
        arrow1 << mainLine.p1();
    }

    if (IsArrowUpEnabled())
    {
        arrow1 << ArrowUp();
    }
    else
    {
        arrow1 << mainLine.p2();
    }

    if (IsFourWays())
    {
        // secondary arrow
        QVector<QPointF> arrow2;
        const QLineF secondaryLine = SecondaryLine();

        if (IsArrowLeftEnabled())
        {
            arrow2 << ArrowLeft();
        }
        else
        {
            arrow2 << secondaryLine.p1();
        }

        if (IsArrowRightEnabled())
        {
            arrow2 << ArrowRight();
        }
        else
        {
            arrow2 << secondaryLine.p2();
        }

        return {arrow1, arrow2};
    }

    return {arrow1};
}

//---------------------------------------------------------------------------------------------------------------------
/**
 * @brief VPieceGrainline::IsContained checks, if all ends of the grainline, starting at pt, are contained in
 * parent widget.
 * @param boundingRect bounding rect of piece
 * @param dX horizontal translation needed to put the arrow inside parent item
 * @param dY vertical translation needed to put the arrow inside parent item
 * @return true, if all ends of the grainline, starting at pt, are contained in the bounding rect of piece and
 * false otherwise.
 */
auto VPieceGrainline::IsContained(const QRectF &boundingRect, qreal &dX, qreal &dY) const -> bool
{
    dX = 0;
    dY = 0;

    const QLineF mainLine = GetMainLine();
    QVector<QPointF> apt = {mainLine.p1(), mainLine.p2()};
    if (IsFourWays())
    {
        const QLineF secondaryLine = SecondaryLine();
        apt.append(secondaryLine.p1());
        apt.append(secondaryLine.p2());
    }

    // single point differences
    qreal dPtX;
    qreal dPtY;
    bool bInside = true;

    for (auto item : apt)
    {
        dPtX = 0;
        dPtY = 0;
        if (boundingRect.contains(item))
        {
            continue;
        }

        if (item.x() < boundingRect.left())
        {
            dPtX = boundingRect.left() - item.x();
        }
        else if (item.x() > boundingRect.right())
        {
            dPtX = boundingRect.right() - item.x();
        }
        if (item.y() < boundingRect.top())
        {
            dPtY = boundingRect.top() - item.y();
        }
        else if (item.y() > boundingRect.bottom())
        {
            dPtY = boundingRect.bottom() - item.y();
        }

        if (fabs(dPtX) > fabs(dX))
        {
            dX = dPtX;
        }
        if (fabs(dPtY) > fabs(dY))
        {
            dY = dPtY;
        }

        bInside = false;
    }
    return bInside;
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsPositionValid(const QVector<QPointF> &contourPoints) const -> bool
{
    QVector<QLineF> grainLine;
    QLineF const mainLine = GetMainLine();
    if (IsFourWays())
    {
        grainLine = {mainLine, SecondaryLine()};
    }

    grainLine = {mainLine};

    for (auto line : qAsConst(grainLine))
    {
        QVector<QPointF> const points = VAbstractCurve::CurveIntersectLine(contourPoints, line);
        for (const auto &point : points)
        {
            if (not VFuzzyComparePoints(line.p1(), point) && not VFuzzyComparePoints(line.p2(), point))
            {
                return false;
            }
        }
    }

    QPainterPath grainLinePath;
    for (auto line : qAsConst(grainLine))
    {
        grainLinePath.addPath(VGObject::PainterPath(QVector<QPointF>{line.p1(), line.p2()}));
    }
    const QPainterPath contourPath = VGObject::PainterPath(contourPoints);
    return contourPath.contains(grainLinePath);
}

//---------------------------------------------------------------------------------------------------------------------
auto VPieceGrainline::IsShapeValid() const -> bool
{
    GrainlineShape const shape = Shape();
    return std::all_of(shape.cbegin(), shape.cend(), [](const auto &subShape) { return not subShape.isEmpty(); });
}

// Friend functions
//---------------------------------------------------------------------------------------------------------------------
auto operator<<(QDataStream &dataStream, const VPieceGrainline &grainline) -> QDataStream &
{
    dataStream << *grainline.d;
    return dataStream;
}

//---------------------------------------------------------------------------------------------------------------------
auto operator>>(QDataStream &dataStream, VPieceGrainline &grainline) -> QDataStream &
{
    dataStream >> *grainline.d;
    return dataStream;
}