/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2022 - Raw Material Software Limited

   JUCE is an open source library subject to commercial or open-source
   licensing.

   By using JUCE, you agree to the terms of both the JUCE 7 End-User License
   Agreement and JUCE Privacy Policy.

   End User License Agreement: www.juce.com/juce-7-licence
   Privacy Policy: www.juce.com/juce-privacy-policy

   Or: You may also use this code under the terms of the GPL v3 (see
   www.gnu.org/licenses).

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

RelativePointPath::RelativePointPath()
    : usesNonZeroWinding (true),
      containsDynamicPoints (false)
{
}

RelativePointPath::RelativePointPath (const RelativePointPath& other)
    : usesNonZeroWinding (true),
      containsDynamicPoints (false)
{
    for (int i = 0; i < other.elements.size(); ++i)
        elements.add (other.elements.getUnchecked(i)->clone());
}

RelativePointPath::RelativePointPath (const Path& path)
    : usesNonZeroWinding (path.isUsingNonZeroWinding()),
      containsDynamicPoints (false)
{
    for (Path::Iterator i (path); i.next();)
    {
        switch (i.elementType)
        {
            case Path::Iterator::startNewSubPath:   elements.add (new StartSubPath (RelativePoint (i.x1, i.y1))); break;
            case Path::Iterator::lineTo:            elements.add (new LineTo (RelativePoint (i.x1, i.y1))); break;
            case Path::Iterator::quadraticTo:       elements.add (new QuadraticTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2))); break;
            case Path::Iterator::cubicTo:           elements.add (new CubicTo (RelativePoint (i.x1, i.y1), RelativePoint (i.x2, i.y2), RelativePoint (i.x3, i.y3))); break;
            case Path::Iterator::closePath:         elements.add (new CloseSubPath()); break;
            default:                                jassertfalse; break;
        }
    }
}

RelativePointPath::~RelativePointPath()
{
}

bool RelativePointPath::operator== (const RelativePointPath& other) const noexcept
{
    if (elements.size() != other.elements.size()
         || usesNonZeroWinding != other.usesNonZeroWinding
         || containsDynamicPoints != other.containsDynamicPoints)
        return false;

    for (int i = 0; i < elements.size(); ++i)
    {
        ElementBase* const e1 = elements.getUnchecked(i);
        ElementBase* const e2 = other.elements.getUnchecked(i);

        if (e1->type != e2->type)
            return false;

        int numPoints1, numPoints2;
        const RelativePoint* const points1 = e1->getControlPoints (numPoints1);
        const RelativePoint* const points2 = e2->getControlPoints (numPoints2);

        jassert (numPoints1 == numPoints2);

        for (int j = numPoints1; --j >= 0;)
            if (points1[j] != points2[j])
                return false;
    }

    return true;
}

bool RelativePointPath::operator!= (const RelativePointPath& other) const noexcept
{
    return ! operator== (other);
}

void RelativePointPath::swapWith (RelativePointPath& other) noexcept
{
    elements.swapWith (other.elements);
    std::swap (usesNonZeroWinding, other.usesNonZeroWinding);
    std::swap (containsDynamicPoints, other.containsDynamicPoints);
}

void RelativePointPath::createPath (Path& path, Expression::Scope* scope) const
{
    for (int i = 0; i < elements.size(); ++i)
        elements.getUnchecked(i)->addToPath (path, scope);
}

bool RelativePointPath::containsAnyDynamicPoints() const
{
    return containsDynamicPoints;
}

void RelativePointPath::addElement (ElementBase* newElement)
{
    if (newElement != nullptr)
    {
        elements.add (newElement);
        containsDynamicPoints = containsDynamicPoints || newElement->isDynamic();
    }
}

//==============================================================================
RelativePointPath::ElementBase::ElementBase (const ElementType type_) : type (type_)
{
}

bool RelativePointPath::ElementBase::isDynamic()
{
    int numPoints;
    const RelativePoint* const points = getControlPoints (numPoints);

    for (int i = numPoints; --i >= 0;)
        if (points[i].isDynamic())
            return true;

    return false;
}

//==============================================================================
RelativePointPath::StartSubPath::StartSubPath (const RelativePoint& pos)
    : ElementBase (startSubPathElement), startPos (pos)
{
}

void RelativePointPath::StartSubPath::addToPath (Path& path, Expression::Scope* scope) const
{
    path.startNewSubPath (startPos.resolve (scope));
}

RelativePoint* RelativePointPath::StartSubPath::getControlPoints (int& numPoints)
{
    numPoints = 1;
    return &startPos;
}

RelativePointPath::ElementBase* RelativePointPath::StartSubPath::clone() const
{
    return new StartSubPath (startPos);
}

//==============================================================================
RelativePointPath::CloseSubPath::CloseSubPath()
    : ElementBase (closeSubPathElement)
{
}

void RelativePointPath::CloseSubPath::addToPath (Path& path, Expression::Scope*) const
{
    path.closeSubPath();
}

RelativePoint* RelativePointPath::CloseSubPath::getControlPoints (int& numPoints)
{
    numPoints = 0;
    return nullptr;
}

RelativePointPath::ElementBase* RelativePointPath::CloseSubPath::clone() const
{
    return new CloseSubPath();
}

//==============================================================================
RelativePointPath::LineTo::LineTo (const RelativePoint& endPoint_)
    : ElementBase (lineToElement), endPoint (endPoint_)
{
}

void RelativePointPath::LineTo::addToPath (Path& path, Expression::Scope* scope) const
{
    path.lineTo (endPoint.resolve (scope));
}

RelativePoint* RelativePointPath::LineTo::getControlPoints (int& numPoints)
{
    numPoints = 1;
    return &endPoint;
}

RelativePointPath::ElementBase* RelativePointPath::LineTo::clone() const
{
    return new LineTo (endPoint);
}

//==============================================================================
RelativePointPath::QuadraticTo::QuadraticTo (const RelativePoint& controlPoint, const RelativePoint& endPoint)
    : ElementBase (quadraticToElement)
{
    controlPoints[0] = controlPoint;
    controlPoints[1] = endPoint;
}

void RelativePointPath::QuadraticTo::addToPath (Path& path, Expression::Scope* scope) const
{
    path.quadraticTo (controlPoints[0].resolve (scope),
                      controlPoints[1].resolve (scope));
}

RelativePoint* RelativePointPath::QuadraticTo::getControlPoints (int& numPoints)
{
    numPoints = 2;
    return controlPoints;
}

RelativePointPath::ElementBase* RelativePointPath::QuadraticTo::clone() const
{
    return new QuadraticTo (controlPoints[0], controlPoints[1]);
}


//==============================================================================
RelativePointPath::CubicTo::CubicTo (const RelativePoint& controlPoint1, const RelativePoint& controlPoint2, const RelativePoint& endPoint)
    : ElementBase (cubicToElement)
{
    controlPoints[0] = controlPoint1;
    controlPoints[1] = controlPoint2;
    controlPoints[2] = endPoint;
}

void RelativePointPath::CubicTo::addToPath (Path& path, Expression::Scope* scope) const
{
    path.cubicTo (controlPoints[0].resolve (scope),
                  controlPoints[1].resolve (scope),
                  controlPoints[2].resolve (scope));
}

RelativePoint* RelativePointPath::CubicTo::getControlPoints (int& numPoints)
{
    numPoints = 3;
    return controlPoints;
}

RelativePointPath::ElementBase* RelativePointPath::CubicTo::clone() const
{
    return new CubicTo (controlPoints[0], controlPoints[1], controlPoints[2]);
}

} // namespace juce
