// David Eberly, Geometric Tools, Redmond WA 98052
// Copyright (c) 1998-2020
// Distributed under the Boost Software License, Version 1.0.
// https://www.boost.org/LICENSE_1_0.txt
// https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
// Version: 4.0.2019.08.13

#pragma once

#include <Mathematics/ParametricCurve.h>
#include <Mathematics/Vector2.h>
#include <Mathematics/Vector3.h>
#include <memory>

namespace WwiseGTE
{
    template <typename Real>
    class FrenetFrame2
    {
    public:
        // Construction.  The curve must persist as long as the FrenetFrame2
        // object does.
        FrenetFrame2(std::shared_ptr<ParametricCurve<2, Real>> const& curve)
            :
            mCurve(curve)
        {
        }

        // The normal is perpendicular to the tangent, rotated clockwise by
        // pi/2 radians.
        void operator()(Real t, Vector2<Real>& position, Vector2<Real>& tangent,
            Vector2<Real>& normal) const
        {
            std::array<Vector2<Real>, 2> jet;
            mCurve->Evaluate(t, 1, jet.data());
            position = jet[0];
            tangent = jet[1];
            Normalize(tangent);
            normal = Perp(tangent);
        }

        Real GetCurvature(Real t) const
        {
            std::array<Vector2<Real>, 3> jet;
            mCurve->Evaluate(t, 2, jet.data());
            Real speedSqr = Dot(jet[1], jet[1]);
            if (speedSqr > (Real)0)
            {
                Real numer = DotPerp(jet[1], jet[2]);
                Real denom = std::pow(speedSqr, (Real)1.5);
                return numer / denom;
            }
            else
            {
                // Curvature is indeterminate, just return 0.
                return (Real)0;
            }
        }

    private:
        std::shared_ptr<ParametricCurve<2, Real>> mCurve;
    };


    template <typename Real>
    class FrenetFrame3
    {
    public:
        // Construction.  The curve must persist as long as the FrenetFrame3
        // object does.
        FrenetFrame3(std::shared_ptr<ParametricCurve<3, Real>> const& curve)
            :
            mCurve(curve)
        {
        }

        // The binormal is Cross(tangent, normal).
        void operator()(Real t, Vector3<Real>& position, Vector3<Real>& tangent,
            Vector3<Real>& normal, Vector3<Real>& binormal) const
        {
            std::array<Vector3<Real>, 3> jet;
            mCurve->Evaluate(t, 2, jet.data());
            position = jet[0];
            Real VDotV = Dot(jet[1], jet[1]);
            Real VDotA = Dot(jet[1], jet[2]);
            normal = VDotV * jet[2] - VDotA * jet[1];
            Normalize(normal);
            tangent = jet[1];
            Normalize(tangent);
            binormal = Cross(tangent, normal);
        }

        Real GetCurvature(Real t) const
        {
            std::array<Vector3<Real>, 3> jet;
            mCurve->Evaluate(t, 2, jet.data());
            Real speedSqr = Dot(jet[1], jet[1]);
            if (speedSqr > (Real)0)
            {
                Real numer = Length(Cross(jet[1], jet[2]));
                Real denom = std::pow(speedSqr, (Real)1.5);
                return numer / denom;
            }
            else
            {
                // Curvature is indeterminate, just return 0.
                return (Real)0;
            }
        }

        Real GetTorsion(Real t) const
        {
            std::array<Vector3<Real>, 4> jet;
            mCurve->Evaluate(t, 3, jet.data());
            Vector3<Real> cross = Cross(jet[1], jet[2]);
            Real denom = Dot(cross, cross);
            if (denom > (Real)0)
            {
                Real numer = Dot(cross, jet[3]);
                return numer / denom;
            }
            else
            {
                // Torsion is indeterminate, just return 0.
                return (Real)0;
            }
        }

    private:
        std::shared_ptr<ParametricCurve<3, Real>> mCurve;
    };
}