// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "cubicsegment.h"

#include <qmath.h>
#include <QtDebug>


namespace QmlDesigner {

CubicSegment::CubicSegment() = default;

CubicSegment CubicSegment::create()
{
    CubicSegment cubicSegment;
    cubicSegment.d = new CubicSegmentData;

    return cubicSegment;
}

void CubicSegment::setModelNode(const ModelNode &modelNode)
{
    d->modelNode = modelNode;
}

ModelNode CubicSegment::modelNode() const
{
    return d->modelNode;
}

void CubicSegment::setFirstControlPoint(const ControlPoint &firstControlPoint)
{
    d->firstControllPoint = firstControlPoint;
}

void CubicSegment::setFirstControlPoint(double x, double y)
{
    d->firstControllPoint.setX(x);
    d->firstControllPoint.setY(y);
}

void CubicSegment::setFirstControlPoint(const QPointF &coordiante)
{
    d->firstControllPoint.setCoordinate(coordiante);
}

void CubicSegment::setSecondControlPoint(const ControlPoint &secondControlPoint)
{
    d->secondControllPoint = secondControlPoint;
    d->secondControllPoint.setPathElementModelNode(d->modelNode);
    d->secondControllPoint.setPointType(FirstControlPoint);
}

void CubicSegment::setSecondControlPoint(double x, double y)
{
    d->secondControllPoint.setX(x);
    d->secondControllPoint.setY(y);
    d->secondControllPoint.setPathElementModelNode(d->modelNode);
    d->secondControllPoint.setPointType(FirstControlPoint);
}

void CubicSegment::setSecondControlPoint(const QPointF &coordiante)
{
    d->secondControllPoint.setCoordinate(coordiante);
    d->secondControllPoint.setPathElementModelNode(d->modelNode);
    d->secondControllPoint.setPointType(FirstControlPoint);
}

void CubicSegment::setThirdControlPoint(const ControlPoint &thirdControlPoint)
{
    d->thirdControllPoint = thirdControlPoint;
    d->thirdControllPoint.setPathElementModelNode(d->modelNode);
    d->thirdControllPoint.setPointType(SecondControlPoint);
}

void CubicSegment::setThirdControlPoint(double x, double y)
{
    d->thirdControllPoint.setX(x);
    d->thirdControllPoint.setY(y);
    d->thirdControllPoint.setPathElementModelNode(d->modelNode);
    d->thirdControllPoint.setPointType(SecondControlPoint);
}

void CubicSegment::setThirdControlPoint(const QPointF &coordiante)
{
    d->thirdControllPoint.setCoordinate(coordiante);
    d->thirdControllPoint.setPathElementModelNode(d->modelNode);
    d->thirdControllPoint.setPointType(SecondControlPoint);
}

void CubicSegment::setFourthControlPoint(const ControlPoint &fourthControlPoint)
{
    d->fourthControllPoint = fourthControlPoint;
    d->fourthControllPoint.setPathElementModelNode(d->modelNode);
    d->fourthControllPoint.setPointType(EndPoint);
}

void CubicSegment::setFourthControlPoint(double x, double y)
{
    d->fourthControllPoint.setX(x);
    d->fourthControllPoint.setY(y);
    d->fourthControllPoint.setPathElementModelNode(d->modelNode);
    d->fourthControllPoint.setPointType(EndPoint);
}

void CubicSegment::setFourthControlPoint(const QPointF &coordiante)
{
    d->fourthControllPoint.setCoordinate(coordiante);
    d->fourthControllPoint.setPathElementModelNode(d->modelNode);
    d->fourthControllPoint.setPointType(EndPoint);
}

void CubicSegment::setAttributes(const QMap<QString, QVariant> &attributes)
{
    d->attributes = attributes;
}

void CubicSegment::setPercent(double percent)
{
    d->percent = percent;
}

ControlPoint CubicSegment::firstControlPoint() const
{
    return d->firstControllPoint;
}

ControlPoint CubicSegment::secondControlPoint() const
{
    return d->secondControllPoint;
}

ControlPoint CubicSegment::thirdControlPoint() const
{
    return d->thirdControllPoint;
}

ControlPoint CubicSegment::fourthControlPoint() const
{
    return d->fourthControllPoint;
}

const QMap<QString, QVariant> CubicSegment::attributes() const
{
    return d->attributes;
}

double CubicSegment::percent() const
{
    return d->percent;
}

QList<ControlPoint> CubicSegment::controlPoints() const
{
    QList<ControlPoint> controlPointList;

    controlPointList.reserve(4);

    controlPointList.append(firstControlPoint());
    controlPointList.append(secondControlPoint());
    controlPointList.append(thirdControlPoint());
    controlPointList.append(fourthControlPoint());

    return controlPointList;
}

double CubicSegment::firstControlX() const
{
    return firstControlPoint().coordinate().x();
}

double CubicSegment::firstControlY() const
{
    return firstControlPoint().coordinate().y();
}

double CubicSegment::secondControlX() const
{
    return secondControlPoint().coordinate().x();
}

double CubicSegment::secondControlY() const
{
    return secondControlPoint().coordinate().y();
}

double CubicSegment::thirdControlX() const
{
    return thirdControlPoint().coordinate().x();
}

double CubicSegment::thirdControlY() const
{
    return thirdControlPoint().coordinate().y();
}

double CubicSegment::fourthControlX() const
{
    return fourthControlPoint().coordinate().x();
}

double CubicSegment::fourthControlY() const
{
    return fourthControlPoint().coordinate().y();
}

double CubicSegment::quadraticControlX() const
{
    return -0.25 * firstControlX() + 0.75 * secondControlX() + 0.75 * thirdControlX() - 0.25 * fourthControlX();
}

double CubicSegment::quadraticControlY() const
{
    return -0.25 * firstControlY() + 0.75 * secondControlY() + 0.75 * thirdControlY() - 0.25 * fourthControlY();
}

bool CubicSegment::isValid() const
{
    return d.data();
}

bool CubicSegment::canBeConvertedToLine() const
{
    return canBeConvertedToQuad()
            && qFuzzyIsNull(((3. * d->firstControllPoint.coordinate())
                             - (6. * d->secondControllPoint.coordinate())
                             + (3. * d->thirdControllPoint.coordinate())).manhattanLength());;
}

bool CubicSegment::canBeConvertedToQuad() const
{
    return qFuzzyIsNull(((3. * d->secondControllPoint.coordinate())
                         - (3 * d->thirdControllPoint.coordinate())
                         + d->fourthControllPoint.coordinate()
                         - d->firstControllPoint.coordinate()).manhattanLength());
}

QPointF CubicSegment::sample(double t) const
{
    return qPow(1.-t, 3.) * firstControlPoint().coordinate()
            + 3 * qPow(1.-t, 2.) * t * secondControlPoint().coordinate()
            + 3 * qPow(t, 2.) * (1. - t) * thirdControlPoint().coordinate()
            + qPow(t, 3.) * fourthControlPoint().coordinate();
}

double CubicSegment::minimumDistance(const QPointF &pickPoint, double &tReturnValue) const
{
    double actualMinimumDistance = 10000000.;
    const int tMax = 10;
    for (int t = 0; t <= tMax; ++t) {
        const QPointF samplePoint = sample(double(t) / tMax);
        const QPointF distanceVector = pickPoint - samplePoint;
        if (distanceVector.manhattanLength() < actualMinimumDistance) {
            actualMinimumDistance = distanceVector.manhattanLength();
            tReturnValue = t;
        }
    }
    return actualMinimumDistance;
}

static QPointF interpolatedPoint(double t, const QPointF &firstPoint, const QPointF &secondPoint)
{
    return (secondPoint - firstPoint) * t + firstPoint;
}

QPair<CubicSegment, CubicSegment> CubicSegment::split(double t)
{
    // first pass
    QPointF secondPointFirstSegment = interpolatedPoint(t, firstControlPoint().coordinate(), secondControlPoint().coordinate());
    QPointF firstIntermediatPoint = interpolatedPoint(t, secondControlPoint().coordinate(), thirdControlPoint().coordinate());
    QPointF thirdPointSecondSegment = interpolatedPoint(t, thirdControlPoint().coordinate(), fourthControlPoint().coordinate());

    // second pass
    QPointF thirdPointFirstSegment = interpolatedPoint(t, secondPointFirstSegment, firstIntermediatPoint);
    QPointF secondPointSecondSegment = interpolatedPoint(t, firstIntermediatPoint, thirdPointSecondSegment);

    // third pass
    QPointF midPoint = interpolatedPoint(t, thirdPointFirstSegment, secondPointSecondSegment);
    ControlPoint midControlPoint(midPoint);


    CubicSegment firstCubicSegment = CubicSegment::create();
    firstCubicSegment.setFirstControlPoint(firstControlPoint().coordinate());
    firstCubicSegment.setSecondControlPoint(secondPointFirstSegment);
    firstCubicSegment.setThirdControlPoint(thirdPointFirstSegment);
    firstCubicSegment.setFourthControlPoint(midControlPoint);

    CubicSegment secondCubicSegment =  CubicSegment::create();
    secondCubicSegment.setFirstControlPoint(midControlPoint);
    secondCubicSegment.setSecondControlPoint(secondPointSecondSegment);
    secondCubicSegment.setThirdControlPoint(thirdPointSecondSegment);
    secondCubicSegment.setFourthControlPoint(fourthControlPoint().coordinate());

    qDebug() << firstCubicSegment << secondCubicSegment;

    return {firstCubicSegment, secondCubicSegment};
}

void CubicSegment::makeStraightLine()
{
    QPointF lineVector = fourthControlPoint().coordinate() - firstControlPoint().coordinate();
    QPointF newSecondControlPoint = firstControlPoint().coordinate() + (lineVector * 0.3);
    QPointF newThirdControlPoint = fourthControlPoint().coordinate() - (lineVector * 0.3);
    setSecondControlPoint(newSecondControlPoint);
    setThirdControlPoint(newThirdControlPoint);
}

void CubicSegment::updateModelNode()
{
    firstControlPoint().updateModelNode();
    secondControlPoint().updateModelNode();
    thirdControlPoint().updateModelNode();
    fourthControlPoint().updateModelNode();
}

CubicSegmentData::CubicSegmentData()
    : firstControllPoint(0., 0.),
      secondControllPoint(0., 0.),
      thirdControllPoint(0., 0.),
      fourthControllPoint(0., 0.),
      percent(-1.0)
{
}

bool operator ==(const CubicSegment& firstCubicSegment, const CubicSegment& secondCubicSegment)
{
    return firstCubicSegment.d.data() == secondCubicSegment.d.data();
}

QDebug operator<<(QDebug debug, const CubicSegment &cubicSegment)
{
    if (cubicSegment.isValid()) {
        debug.nospace() << "CubicSegment("
                << cubicSegment.firstControlPoint() << ", "
                << cubicSegment.secondControlPoint() << ", "
                << cubicSegment.thirdControlPoint() << ", "
                << cubicSegment.fourthControlPoint() << ')';
    } else {
        debug.nospace() << "CubicSegment(invalid)";
    }

    return debug.space();
}
} // namespace QmlDesigner
