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

   This file is part of the JUCE library - "Jules' Utility Class Extensions"
   Copyright 2004-6 by Raw Material Software ltd.

  ------------------------------------------------------------------------------

   JUCE can be redistributed and/or modified under the terms of the
   GNU General Public License, as published by the Free Software Foundation;
   either version 2 of the License, or (at your option) any later version.

   JUCE 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 JUCE; if not, visit www.gnu.org/licenses or write to the
   Free Software Foundation, Inc., 59 Temple Place, Suite 330, 
   Boston, MA 02111-1307 USA

  ------------------------------------------------------------------------------

   If you'd like to release a closed-source product which uses JUCE, commercial
   licenses are also available: visit www.rawmaterialsoftware.com/juce for
   more information.

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

#include "../../../../juce_core/basics/juce_StandardHeader.h"

BEGIN_JUCE_NAMESPACE


#include "juce_PathStrokeType.h"
#include "juce_PathIterator.h"
#include "../../../../juce_core/containers/juce_VoidArray.h"

extern bool juce_lineIntersection (const float x1, const float y1,
                                   const float x2, const float y2,
                                   const float x3, const float y3,
                                   const float x4, const float y4,
                                   float& jx, float& jy);

#ifdef _MSC_VER
#pragma optimize ("t", on)
#endif


//==============================================================================
PathStrokeType::PathStrokeType (const float strokeThickness,
                                const JointStyle jointStyle_,
                                const EndCapStyle endStyle_)
    : thickness (strokeThickness),
      jointStyle (jointStyle_),
      endStyle (endStyle_)
{
}

PathStrokeType::PathStrokeType (const PathStrokeType& other)
    : thickness (other.thickness),
      jointStyle (other.jointStyle),
      endStyle (other.endStyle)
{
}

const PathStrokeType& PathStrokeType::operator= (const PathStrokeType& other)
{
    thickness = other.thickness;
    jointStyle = other.jointStyle;
    endStyle = other.endStyle;
    return *this;
}

PathStrokeType::~PathStrokeType()
{
}

//==============================================================================
static inline void perpendicularOffset (const float x1, const float y1,
                                        const float x2, const float y2,
                                        float offset,
                                        float& resultX, float& resultY)
{
    const float dx = x2 - x1;
    const float dy = y2 - y1;
    const float len = sqrtf (dx*dx + dy*dy);

    if (len == 0)
    {
        resultX = x1;
        resultY = y1;
    }
    else
    {
        offset /= len;
        resultX = x1 - dy * offset;
        resultY = y1 + dx * offset;
    }
}

// part of stroke drawing stuff
static void addEdgeAndJoint (Path& destPath,
                             const PathStrokeType::JointStyle style,
                             const float maxMiterExtensionSquared, const float width,
                             const float x1, const float y1,
                             const float x2, const float y2,
                             const float x3, const float y3,
                             const float x4, const float y4,
                             const float midX, const float midY)
{
    if (style == PathStrokeType::beveled
        || (x3 == x4 && y3 == y4)
        || (x1 == x2 && y1 == y2))
    {
        destPath.lineTo (x2, y2);
        destPath.lineTo (x3, y3);
    }
    else
    {
        float jx, jy;

        // if they intersect, use this point..
        if (juce_lineIntersection (x1, y1, x2, y2,
                                   x3, y3, x4, y4,
                                   jx, jy))
        {
            destPath.lineTo (jx, jy);
        }
        else
        {
            if (style == PathStrokeType::mitered)
            {
                const float dx = jx - x3;
                const float dy = jy - y3;

                if ((dx * dx) + (dy * dy) < maxMiterExtensionSquared)
                {
                    destPath.lineTo (jx, jy);
                }
                else
                {
                    // the end sticks out too far, so just use a blunt joint
                    destPath.lineTo (x2, y2);
                    destPath.lineTo (x3, y3);
                }
            }
            else
            {
                // curved joints
                float angle  = atan2f (x2 - midX, y2 - midY);
                float angle2 = atan2f (x3 - midX, y3 - midY);

                while (angle < angle2 - 0.01f)
                    angle2 -= float_Pi * 2.0f;

                destPath.lineTo (x2, y2);

                while (angle > angle2)
                {
                    destPath.lineTo (midX + width * sinf (angle),
                                     midY + width * cosf (angle));

                    angle -= 0.1f;
                }

                destPath.lineTo (x3, y3);
            }
        }
    }
}

static inline void addLineEnd (Path& destPath,
                               const PathStrokeType::EndCapStyle style,
                               const float x1, const float y1,
                               const float x2, const float y2,
                               const float width)
{
    if (style == PathStrokeType::butt)
    {
        destPath.lineTo (x2, y2);
    }
    else
    {
        float offx1, offy1, offx2, offy2;

        perpendicularOffset (x1, y1, x2, y2,
                             -width,
                             offx1, offy1);

        perpendicularOffset (x2, y2, x1, y1,
                             width,
                             offx2, offy2);

        if (style == PathStrokeType::square)
        {
            destPath.lineTo (offx1, offy1);
            destPath.lineTo (offx2, offy2);
            destPath.lineTo (x2, y2);
        }
        else
        {
            // rounded ends
            destPath.quadraticTo (offx1, offy1, (offx1 + offx2) * 0.5f, (offy1 + offy2) * 0.5f);
            destPath.quadraticTo (offx2, offy2, x2, y2);
        }
    }
}

void PathStrokeType::createStrokedPath (Path& destPath,
                                        const Path& sourcePath,
                                        const AffineTransform& transform) const
{
    if (thickness <= 0)
        return;

    const float maxMiterExtensionSquared = 9.0f * thickness * thickness;
    const float width = 0.5f * thickness;

    struct LineSection
    {
        float x1, y1, x2, y2;      // original line
        float lx1, ly1, lx2, ly2;  // the left-hand stroke
        float rx1, ry1, rx2, ry2;  // the right-hand stroke
        bool closesSubPath;
    };

    // Iterate the path, creating a list of the
    // left/right-hand lines along either side of it...

    PathIterator it (sourcePath, transform);

    VoidArray* currentSubPath = 0;
    LineSection* l = 0;
    VoidArray subPaths;

    while (it.next())
    {
        if (it.subPathIndex == 0)
        {
            currentSubPath = new VoidArray (128);
            subPaths.add (currentSubPath);
        }

        if (l == 0)
        {
            l = new LineSection();
            l->x1 = it.x1;
            l->y1 = it.y1;
        }

        l->x2 = it.x2;
        l->y2 = it.y2;

        const float dx = (l->x1 - l->x2);
        const float dy = (l->y1 - l->y2);

        if (it.closesSubPath || (dx*dx + dy*dy) > 2.0f)
        {
            perpendicularOffset (it.x1, it.y1,
                                 it.x2, it.y2,
                                 -width, l->lx1, l->ly1);

            perpendicularOffset (it.x2, it.y2,
                                 it.x1, it.y1,
                                 width, l->lx2, l->ly2);

            perpendicularOffset (it.x1, it.y1,
                                 it.x2, it.y2,
                                 width, l->rx2, l->ry2);

            perpendicularOffset (it.x2, it.y2,
                                 it.x1, it.y1,
                                 -width, l->rx1, l->ry1);

            l->closesSubPath = it.closesSubPath;

            currentSubPath->add (l);
            l = 0;
        }
    }

    if (l != 0)
        delete l;

    destPath.clear();
    destPath.setUsingNonZeroWinding (true);

    for (int subPathNum = subPaths.size(); --subPathNum >= 0;)
    {
        VoidArray* const currentSubPath = (VoidArray*)(subPaths.getUnchecked (subPathNum));

        if (currentSubPath->size() > 0)
        {
            const LineSection* const lastLine = (LineSection*)(currentSubPath->getLast());
            const bool isClosed = lastLine->closesSubPath;

            LineSection* l = (LineSection*)(currentSubPath->getFirst());

            float lastX1 = l->lx1;
            float lastY1 = l->ly1;
            float lastX2 = l->lx2;
            float lastY2 = l->ly2;

            if (isClosed)
            {
                destPath.startNewSubPath (lastX1, lastY1);
            }
            else
            {
                destPath.startNewSubPath (l->rx2, l->ry2);

                addLineEnd (destPath, endStyle,
                            l->rx2, l->ry2,
                            lastX1, lastY1,
                            width);
            }

            int i;
            for (i = 1; i < currentSubPath->size(); ++i)
            {
                l = (LineSection*)(currentSubPath->getUnchecked(i));

                addEdgeAndJoint (destPath, jointStyle,
                                 maxMiterExtensionSquared, width,
                                 lastX1, lastY1, lastX2, lastY2,
                                 l->lx1, l->ly1, l->lx2, l->ly2,
                                 l->x1, l->y1);

                lastX1 = l->lx1;
                lastY1 = l->ly1;
                lastX2 = l->lx2;
                lastY2 = l->ly2;
            }

            if (isClosed)
            {
                l = (LineSection*)(currentSubPath->getFirst());

                addEdgeAndJoint (destPath, jointStyle,
                                 maxMiterExtensionSquared, width,
                                 lastX1, lastY1, lastX2, lastY2,
                                 l->lx1, l->ly1, l->lx2, l->ly2,
                                 l->x1, l->y1);

                destPath.closeSubPath();
                destPath.startNewSubPath (lastLine->rx1, lastLine->ry1);
            }
            else
            {
                destPath.lineTo (lastX2, lastY2);

                addLineEnd (destPath, endStyle,
                            lastX2, lastY2,
                            l->rx1, l->ry1,
                            width);
            }

            lastX1 = lastLine->rx1;
            lastY1 = lastLine->ry1;
            lastX2 = lastLine->rx2;
            lastY2 = lastLine->ry2;

            for (i = currentSubPath->size() - 1; --i >= 0;)
            {
                l = (LineSection*)(currentSubPath->getUnchecked(i));

                addEdgeAndJoint (destPath, jointStyle,
                                 maxMiterExtensionSquared, width,
                                 lastX1, lastY1, lastX2, lastY2,
                                 l->rx1, l->ry1, l->rx2, l->ry2,
                                 l->x2, l->y2);

                lastX1 = l->rx1;
                lastY1 = l->ry1;
                lastX2 = l->rx2;
                lastY2 = l->ry2;
            }

            if (isClosed)
            {
                addEdgeAndJoint (destPath, jointStyle,
                                 maxMiterExtensionSquared, width,
                                 lastX1, lastY1, lastX2, lastY2,
                                 lastLine->rx1, lastLine->ry1, lastLine->rx2, lastLine->ry2,
                                 lastLine->x2, lastLine->y2);
            }
            else
            {
                // do the last line
                destPath.lineTo (lastX2, lastY2);
            }

            destPath.closeSubPath();

            for (i = currentSubPath->size(); --i >= 0;)
            {
                l = (LineSection*)(currentSubPath->getUnchecked(i));
                delete l;
            }
        }

        delete currentSubPath;
    }
}

const Path PathStrokeType::createDashedStroke (Path& destPath,
                                               const Path& sourcePath,
                                               const float* dashLengths,
                                               int numDashLengths,
                                               const AffineTransform& transform) const
{
    if (thickness <= 0)
        return destPath;

    // this should really be an even number..
    jassert ((numDashLengths & 1) == 0);

    Path newDestPath;
    PathIterator it (sourcePath, transform);

    bool first = true;
    int dashNum = 0;
    float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f;
    float dx = 0.0f, dy = 0.0f;

    for (;;)
    {
        const bool isSolid = ((dashNum & 1) == 0);
        pos += jmax (0.1f, dashLengths [dashNum++ % numDashLengths]);

        while (pos > lineEndPos)
        {
            if (! it.next())
            {
                if (isSolid && ! first)
                    newDestPath.lineTo (it.x2, it.y2);

                createStrokedPath (destPath, newDestPath);
                return destPath;
            }

            if (isSolid && ! first)
            {
                newDestPath.lineTo (it.x1, it.y1);
            }
            else
            {
                newDestPath.startNewSubPath (it.x1, it.y1);
                first = false;
            }

            dx = it.x2 - it.x1;
            dy = it.y2 - it.y1;
            lineLen = juce_hypotf (dx, dy);
            lineEndPos += lineLen;
        }

        const float alpha = (pos - (lineEndPos - lineLen)) / lineLen;

        if (isSolid)
            newDestPath.lineTo (it.x1 + dx * alpha, it.y1 + dy * alpha);
        else
            newDestPath.startNewSubPath (it.x1 + dx * alpha, it.y1 + dy * alpha);
    }
}

END_JUCE_NAMESPACE
