/* osgEarth
 * Copyright 2008-2012 Pelican Mapping
 * MIT License
 */
#pragma once

#include <osgEarth/Common>
#include <osgEarth/Geometry>
#include <osg/Quat>
#include <osg/Vec3>
#include <osg/Vec4>
#include <osg/BoundingBox>
#include <osg/Array>
#include <osg/Geometry>
#include <osg/Matrix>

#include <iterator>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace osgEarth
{
    struct Line2d;
    struct Segment2d;
    struct Ray2d;
    struct Segment3d;

    /** Infinite 2D line. */
    struct OSGEARTH_EXPORT Line2d
    {
        osg::Vec3d _a, _b; // two points on the line (Z ignored)
        Line2d() { }
        Line2d(const osg::Vec3d& a, const osg::Vec3d& b) : _a(a), _b(b) { }
        Line2d(const osg::Vec2d& a, const osg::Vec2d& b) : _a(a.x(), a.y(), 0), _b(b.x(), b.y(), 0) { }
        Line2d(const osg::Vec4d& a, const osg::Vec4d& b) : _a(a.x()/a.w(), a.y()/a.w(), a.z()/a.w()), _b(b.x()/b.w(), b.y()/b.w(), b.z()/b.w()) { }
        Line2d(const Line2d& rhs) : _a(rhs._a), _b(rhs._b) { }
        bool intersect( const Line2d&    rhs, osg::Vec2d& out ) const;
        bool intersect( const Ray2d&     rhs, osg::Vec2d& out ) const;
        bool intersect( const Segment2d& rhs, osg::Vec2d& out ) const;
        bool intersect( const Line2d&    rhs, osg::Vec3d& out ) const;
        bool intersect( const Ray2d&     rhs, osg::Vec3d& out ) const;
        bool intersect( const Segment2d& rhs, osg::Vec3d& out ) const;
        bool intersect( const Line2d&    rhs, osg::Vec4d& out ) const;
        bool isPointOnLeft( const osg::Vec2d& p ) const;
        bool isPointOnLeft( const osg::Vec3d& p ) const;
    };
    
    //// rotate a Line
    //inline Line2d operator* (const osg::Quat& q, const Line2d& rhs) {
    //    return Line( q * rhs._a, q * rhs._b );
    //}

    /** Endpoint and a direction vector */
    struct OSGEARTH_EXPORT Ray2d
    {
        osg::Vec3d _a;  // endpoint
        osg::Vec3d _dv; // directional vector
        Ray2d() { }
        Ray2d(const osg::Vec3d& a, const osg::Vec3d& dv) : _a(a), _dv(dv) { }
        Ray2d(const osg::Vec2d& a, const osg::Vec2d& dv) : _a(a.x(), a.y(), 0), _dv(dv.x(), dv.y(), 0) { }
        Ray2d(const Ray2d& rhs) : _a(rhs._a), _dv(rhs._dv) { }
        bool intersect( const Line2d&    rhs, osg::Vec2d& out ) const;
        bool intersect( const Ray2d&     rhs, osg::Vec2d& out ) const;
        bool intersect( const Segment2d& rhs, osg::Vec2d& out ) const;
        bool intersect( const Line2d&    rhs, osg::Vec3d& out ) const;
        bool intersect( const Ray2d&     rhs, osg::Vec3d& out ) const;
        bool intersect( const Segment2d& rhs, osg::Vec3d& out ) const;
        bool isPointOnLeft( const osg::Vec2d& p ) const;
        bool isPointOnLeft( const osg::Vec3d& p ) const;
        double angle(const Segment2d& rhs) const;
    };
    
    //// rotate a Ray
    //inline Ray operator* (const osg::Quat& q, const Ray& rhs) {
    //    return Ray( q * rhs._a, q * rhs._dv );
    //}

    /** Finite line between two endpoints */
    struct OSGEARTH_EXPORT Segment2d
    {
        osg::Vec3d _a, _b; // endpoints
        Segment2d() { }
        Segment2d(const osg::Vec3d& a, const osg::Vec3d& b) : _a(a), _b(b) { }
        Segment2d(const Segment2d& rhs) : _a(rhs._a), _b(rhs._b) { }
        bool intersect( const Line2d&    rhs, osg::Vec2d& out ) const;
        bool intersect( const Ray2d&     rhs, osg::Vec2d& out ) const;
        bool intersect( const Segment2d& rhs, osg::Vec2d& out ) const;
        bool intersect( const Line2d&    rhs, osg::Vec3d& out ) const;
        bool intersect( const Ray2d&     rhs, osg::Vec3d& out ) const;
        bool intersect( const Segment2d& rhs, osg::Vec3d& out ) const;
        bool isPointOnLeft( const osg::Vec2d& p ) const;
        bool isPointOnLeft( const osg::Vec3d& p ) const;
        Segment3d unrotateTo3D(const osg::Quat& q) const;
        double angle(const Segment2d& rhs) const;
        // Distance of point to segment; left side is positive
        double leftDistanceXY( const osg::Vec3d&p ) const
        {
            osg::Vec2d base(_b.x() - _a.x(), _b.y() - _a.y());
            // 2D cross product, 2 * area of triangle
            double cross = base.x() * (p.y() - _a.y()) - base.y() * (p.x() - _a.x());
            return cross / base.length();
        }
        double squaredDistanceTo(const osg::Vec3d& point) const;
        osg::Vec3d closestPointTo(const osg::Vec3d& point) const;
    };

    struct OSGEARTH_EXPORT Segment3d
    {
        osg::Vec3d _a, _b; // endpoints
        Segment3d() { }
        Segment3d(const osg::Vec3d& a, const osg::Vec3d& b) : _a(a), _b(b) { }
        Segment3d(const Segment3d& rhs) : _a(rhs._a), _b(rhs._b) { }
        Segment3d(const Segment2d& seg2d, const osg::Vec3& planeNormal);
        Segment2d rotateTo2D(const osg::Quat& q) { return Segment2d(q*_a, q*_b); }
    };
    
    //// rotate a Segment
    //inline Segment operator* (const osg::Quat& q, const Segment& rhs) {
    //    return Segment( q * rhs._a, q * rhs._b );
    //}

    /** 2D traingle with CCW winding. */
    struct OSGEARTH_EXPORT Triangle2d
    {
        osg::Vec3d _a, _b, _c;
        Triangle2d(const osg::Vec3d& a, const osg::Vec3d& b, const osg::Vec3d& c) : _a(a), _b(b), _c(c) { }
        bool contains(const osg::Vec3d& p) const;
    };

    // Work in progress. We want to use Ray without doing the work to
    // fully support the other types.

    struct Line;
    struct Ray;

#if 0
    /** Infinite 3D line. */
    struct Line
    {
        osg::Vec3d _a, _b; // two points on the line
        Line(const osg::Vec3d& a, const osg::Vec3d& b) : _a(a), _b(b) { }
        Line(const Line& rhs) : _a(rhs._a), _b(rhs._b) { }
        bool intersectXY( const Line&    rhs, osg::Vec3d& out ) const;
        bool intersectXY( const Ray&     rhs, osg::Vec3d& out ) const;
        bool intersectXY( const Segment& rhs, osg::Vec3d& out ) const;
    };
    
    // rotate a Line
    inline Line operator* (const osg::Quat& q, const Line& rhs) {
        return Line( q * rhs._a, q * rhs._b );
    }
#endif

    /** Endpoint and a direction vector */
    struct Ray
    {
        osg::Vec3d _a;  // endpoint
        osg::Vec3d _dv; // directional vector
        Ray(const osg::Vec3d& a, const osg::Vec3d& dv) : _a(a), _dv(dv) { }
        Ray(const Ray& rhs) : _a(rhs._a), _dv(rhs._dv) { }
#if 0
        bool intersectXY( const Line&    rhs, osg::Vec3d& out ) const;
        bool intersectXY( const Ray&     rhs, osg::Vec3d& out ) const;
        bool intersectXY( const Segment& rhs, osg::Vec3d& out ) const;
        bool isPointOnLeftXY( const osg::Vec3d& p ) const;
#endif
    };
    
    // rotate a Ray
    inline Ray operator* (const osg::Quat& q, const Ray& rhs) {
        return Ray( q * rhs._a, q * rhs._dv );
    }

#if 0
    /** Finite line between two endpoints */
    // This will need a new name before it's enabled; conflicts with a
    // type in osgEarth/Geometry.
    struct Segment
    {
        osg::Vec3d _a, _b; // endpoints
        Segment(const osg::Vec3d& a, const osg::Vec3d& b) : _a(a), _b(b) { }
        Segment(const Segment& rhs) : _a(rhs._a), _b(rhs._b) { }
        bool intersectXY( const Line&    rhs, osg::Vec3d& out ) const;
        bool intersectXY( const Ray&     rhs, osg::Vec3d& out ) const;
        bool intersectXY( const Segment& rhs, osg::Vec3d& out ) const;
        bool isPointOnLeftXY( const osg::Vec3d& p ) const;
    };
    
    // rotate a Segment
    inline Segment operator* (const osg::Quat& q, const Segment& rhs) {
        return Segment( q * rhs._a, q * rhs._b );
    }
#endif

    // Utility functions for arrays of points treated as a 2d polygon
    OSGEARTH_EXPORT osg::BoundingBoxd polygonBBox2d(const osg::Vec3dArray& points);

    template<typename Iterator>
    osg::BoundingBoxd polygonBBox2d(Iterator begin, Iterator end)
{
    osg::BoundingBoxd result(DBL_MAX, DBL_MAX, DBL_MAX, -DBL_MAX, -DBL_MAX, -DBL_MAX);
    for (Iterator itr = begin; itr != end; ++itr)
    {
        result.xMin() = std::min(result.xMin(), (*itr).x());
        result.xMax() = std::max(result.xMax(), (*itr).x());
        result.yMin() = std::min(result.yMin(), (*itr).y());
        result.yMax() = std::max(result.yMax(), (*itr).y());
    }
    return result;
}
    OSGEARTH_EXPORT bool pointInPoly2d(const osg::Vec3d& pt, const Polygon& polyPoints,
                                       double tolerance = 0.0);
    OSGEARTH_EXPORT bool pointInPoly2d(const osg::Vec3d& pt, const osg::Geometry* polyPoints,
                                       float tolerance = 0.0f);

    // Winding number test; see http://geomalgorithms.com/a03-_inclusion.html
    template<typename Pt, typename PtItr>
    bool
    pointInPoly2d(const Pt& pt, const PtItr& begin, const PtItr& end, double tolerance = 0.0)
    {
        int windingNum = 0;

        for (PtItr itr = begin; itr != end; ++itr)
        {
            Segment2d seg = (std::next(itr) == end
                             ? Segment2d(*itr, *begin)
                             : Segment2d(*itr, *std::next(itr)));
            // if the segment is horizontal, then the "is left" test
            // isn't meaningful. We count the point as in if it's on or
            // to the left of the segment.


            if (seg._a.y() == seg._b.y() && fabs(pt.y() - seg._a.y()) <= tolerance)
            {
                if (pt.x() < seg._a.x() || pt.x() < seg._b.x())
                {
                    windingNum++;
                }
            }
            else if (seg._a.y() <= pt.y())
            {
                if (seg._b.y() > pt.y())
                {
                    double dist = seg.leftDistanceXY(pt);
                    if (dist > -tolerance)
                    {
                        windingNum++;
                    }
                }
            }
            else if (seg._b.y() <= pt.y())
            {
                double dist = seg.leftDistanceXY(pt);
                if (dist < tolerance)
                {
                    windingNum--;
                }
            }
        }
        return windingNum != 0;
    }

    template<typename T>
    inline T deg2rad(T v) {
        return v * static_cast<T>(M_PI)/static_cast<T>(180.0);
    }

    template<typename T>
    inline T rad2deg(T v) {
        return v * static_cast<T>(180.0) / static_cast<T>(M_PI);
    }

    template<typename T>
    inline T step(const T& edge, const T& x)
    {
        return x < edge ? static_cast<T>(0.0) : static_cast<T>(1.0);
    }
    
    template<typename T>
    inline T clamp(const T& x, const T& lo, const T& hi)
    {
        return x<lo ? lo : x>hi ? hi : x;
    }

    template<typename T>
    inline T lerpstep(T lo, T hi, T x)
    {
        if (x <= lo) return static_cast<T>(0.0);
        else if (x >= hi) return static_cast<T>(1.0);
        else return (x - lo) / (hi - lo);
    }

    template<typename T>
    inline T smoothstep(T lo, T hi, T x)
    {
        T t = clamp((x - lo) / (hi - lo), static_cast<T>(0.0), static_cast<T>(1.0));
        return t * t*(static_cast<T>(3.0) - static_cast<T>(2.0)*t);
    }

    // move closer to one
    template<typename T>
    inline T harden(T x)
    {
        return static_cast<T>(1.0) - (static_cast<T>(1.0) - x)*(static_cast<T>(1.0) - x);
    }
    template<typename T>
    inline T decel(T x) { return harden(x); }

    // move closer to zero
    template<typename T>
    inline T soften(T x)
    {
        return x * x;
    }
    template<typename T>
    inline T accel(T x) { return soften(x); }

    template<typename T>
    inline T threshold(T x, T thresh, T buf)
    {
        if (x < thresh - buf) return static_cast<T>(0.0);
        else if (x > thresh + buf) return static_cast<T>(1.0);
        else return clamp((x - (thresh - buf)) / (buf*static_cast<T>(2.0)), static_cast<T>(0.0), static_cast<T>(1.0));
    }

    template<typename T>
    inline T fract(T x)
    {
        return x - floor(x);
    }

    template<typename T>
    inline double unitremap(T a, T lo, T hi)
    {
        return clamp((a - lo) / (hi - lo), static_cast<T>(0.0), static_cast<T>(1.0));
    }

    template<typename T>
    inline T mix(const T& a, const T& b, float c)
    {
        return a * (1.0 - c) + b * c;
    }

    template<typename T>
    inline double dot2D(const T& a, const T& b)
    {        
        return a.x()*b.x() + a.y()*b.y();
    }

    template<typename T>
    inline double dot3D(const T& a, const T& b)
    {
        return a.x()*b.x() + a.y()*b.y() + a.z()*b.z();
    }

    template<typename A, typename B>
    inline double distanceSquared2D(const A& a, const B& b)
    {
        return 
            (b.x() - a.x())*(b.x() - a.x()) + 
            (b.y() - a.y())*(b.y() - a.y());
    }

    template<typename A, typename B>
    inline double distanceSquared3D(const A& a, const B& b)
    {
        return
            (b.x() - a.x())*(b.x() - a.x()) +
            (b.y() - a.y())*(b.y() - a.y()) +
            (b.z() - a.z())*(b.z() - a.z());
    }

    template<typename A, typename B>
    inline double distance2D(const A& a, const B& b)
    {
        return sqrt(distanceSquared2D(a, b));
    }

    template<typename A, typename B>
    inline double distance3D(const A& a, const B& b)
    {
        return sqrt(distanceSquared3D(a, b));
    }
    template<typename T>
    inline T normalize(const T& a)
    {
        T temp = a;
        temp.normalize();
        return temp;
    }
    // Newton-Raphson solver
    template<typename Func, typename FuncDeriv>
    double solve(Func func, FuncDeriv deriv, double guess, double tolerance, bool& valid, int maxIterations = 16)
    {
        double xn = guess;
        for (int i = 0; i <= maxIterations; ++i)
        {
            double f = func(xn);
            if (fabs(f) <= tolerance)
            {
                valid = true;
                return xn;
            }
            xn = xn - f / deriv(xn);
        }
        valid = false;
        return xn;
    }

    // Courtesy of stackoverflow, return -1, 0, +1 based on the sign
    // of a number
    template<typename T>
    int sgn(T val)
    {
        return (T(0) < val) - (val < T(0));
    }
    // Bisection solver, useful when the derivative of a function is
    // unknown or expensive to calculate.
    // x0 and x1 are initial guesses surrounding the root. The signs
    // of func(x0) and func(x1) should be different; otherwise we bail
    // immediately.
    template<typename Func>
    double solveBisect(const Func& func, double x0, double x1, double tolerance, int maxIterations = 8)
    {
        double f0 = func(x0);
        double f1 =func(x1);
        if (sgn(f0) == sgn(f1))
        {
            return x0;
        }
        double midPoint = 0.0;
        for (int i = 0; i < maxIterations; ++i)
        {
            midPoint = (x0 + x1) / 2.0;
            double fMidpoint = func(midPoint);
            if (fabs(fMidpoint) <= tolerance)
            {
                return midPoint;
            }
            else if (sgn(f0) == sgn(fMidpoint))
            {
                x0 = midPoint;
                f0 = fMidpoint;
            }
            else
            {
                x1 = midPoint;
                f1 = fMidpoint;
            }
        }
        return midPoint;
    }

    // Project osg::Vec3 a onto b.
    template<typename VecType>
    VecType vecProjection(const VecType& a, const VecType& b)
    {
        return b * ((a * b) / (b * b));
    }

    // Project osg::Vec3 a onto the plane perpendicular to b.
    template<typename VecType>
    VecType vecRejection(const VecType& a, const VecType& b)
    {
        return a - vecProjection(a, b);
    }

    // Round integral x to the nearest multiple of "multiple" greater than or equal to x
    template<typename T>
    T align(T x, T multiple) {
        T isPositive = (T)(x >= 0);
        return ((x + isPositive * (multiple - 1)) / multiple) * multiple;
    }

    // equal within a default threshold
    template<typename T>
    bool equivalent(T x, T y) {
        return osg::equivalent(x, y);
    }

    // equal within a threshold
    template<typename T>
    bool equivalent(T x, T y, T epsilon) {
        return osg::equivalent(x, y, epsilon);
    }

    inline int nextPowerOf2(int x)
    {
        --x;
        x |= x >> 1;
        x |= x >> 2;
        x |= x >> 4;
        x |= x >> 8;
        x |= x >> 16;
        return x + 1;
    }

    template<typename T>
    inline osg::Vec4f mult(const osg::Vec4f& in, T scalar)
    {
        return osg::Vec4f(in.x()*scalar, in.y()*scalar, in.z()*scalar, in.w()*scalar);
    }

    // Adapted from Boost - see boost license
    // https://www.boost.org/users/license.html
    template <typename T> inline std::size_t hash_value_unsigned(T val) {
        const int size_t_bits = std::numeric_limits<std::size_t>::digits;
        const int length = (std::numeric_limits<T>::digits - 1) / size_t_bits;
        std::size_t seed = 0;
        for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits)
            seed ^= (std::size_t) (val >> i) + (seed<<6) + (seed>>2);
        seed ^= (std::size_t) val + (seed<<6) + (seed>>2);
        return seed;
    }

    inline std::size_t hash_value_unsigned(bool val) {
        return hash_value_unsigned((unsigned)val ? 0x1111111 : 0x2222222);
    }

    template<typename T>
    inline std::size_t hash_value_unsigned(const optional<T>& val) {
        if (val.isSet())
            return hash_value_unsigned(0x3333333u, val.get());
        else
            return (std::size_t)0;
    }

    template <typename A, typename B> inline std::size_t hash_value_unsigned(A a, B b) {
        std::size_t seed = hash_value_unsigned(a);
        seed ^= hash_value_unsigned(b) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        return seed;
    }

    template <typename A, typename B, typename C> inline std::size_t hash_value_unsigned(A a, B b, C c) {
        std::size_t seed = hash_value_unsigned(a);
        seed ^= hash_value_unsigned(b) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        seed ^= hash_value_unsigned(c) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        return seed;
    }

    template <typename A, typename B, typename C, typename D> inline std::size_t hash_value_unsigned(A a, B b, C c, D d) {
        std::size_t seed = hash_value_unsigned(a);
        seed ^= hash_value_unsigned(b) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        seed ^= hash_value_unsigned(c) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        seed ^= hash_value_unsigned(d) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        return seed;
    }

    template <typename A, typename B, typename C, typename D, typename E> inline std::size_t hash_value_unsigned(A a, B b, C c, D d, E e) {
        std::size_t seed = hash_value_unsigned(a);
        seed ^= hash_value_unsigned(b) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        seed ^= hash_value_unsigned(c) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        seed ^= hash_value_unsigned(d) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        seed ^= hash_value_unsigned(e) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        return seed;
    }

    // https://github.com/matteo65/mzHash64 (Public Domain)
    inline std::size_t hash_string(const std::string& input, std::size_t seed = 0) {
        auto hash = seed ^ 0xD45E69F901E72147L;
        auto len = input.length();
        for (std::size_t i = 0; i < len; ++i) {
            hash = 0x3631754B22FF2D5CL * (i + input[i]) ^ (hash << 2) ^ (hash >> 2);
        }
        return hash;
    };

    inline osg::Matrix makeBasis(const osg::Vec3d& pos, const osg::Vec3d& normal)
    {
        auto N = normal; N.normalize();
        auto temp = fabs(normal.z() < 0.999) ? osg::Vec3d(0, 0, 1) : osg::Vec3d(0, 1, 0);
        auto T = temp ^ N; T.normalize();
        auto B = N ^ T; B.normalize();
        return osg::Matrix(
            T.x(), T.y(), T.z(), 0.0,
            B.x(), B.y(), B.z(), 0.0,
            N.x(), N.y(), N.z(), 0.0,
            pos.x(), pos.y(), pos.z(), 1.0);
    }

    /**
     * Utilities to manipulate a projection matrix.
     * These functions will automatically handle standard OGL versus Reverse-Z
     * projection matrices.
     */
    struct OSGEARTH_EXPORT ProjectionMatrix
    {
        //! Projection matrix type
        using Type = enum {
            STANDARD,
            REVERSE_Z,
            UNKNOWN
        };

        //! True for an orthographic projection matrix
        static inline bool isOrtho(const osg::Matrix& m) {
            return !m.isIdentity() && m(3, 3) > 0.0;
        }

        //! True for a perspective projection matrix
        static inline bool isPerspective(const osg::Matrix& m) {
            return m(3, 3) == 0.0;
        }

        //! Detected type of the projection matrix
        static Type getType(const osg::Matrix& m);

        //! Construct a perspective matrix, deriving the type from the
        //! existing values in the matrix if possible
        static void setPerspective(
            osg::Matrix& m,
            double vfov, double ar, double N, double F,
            Type = UNKNOWN);

        //! Extract values from a perspective matrix
        static bool getPerspective(
            const osg::Matrix& m,
            double& vfov, double& ar, double& N, double& F);

        //! Extract raw frustum values from a perspective matrix
        static bool getPerspective(
            const osg::Matrix& m,
            double& L, double& R, double& B, double& T, double& N, double& F);

        //! Construct an orthographc matrix, deriving the type from the
        //! existing values in the matrix if possible
        static void setOrtho(
            osg::Matrix& m,
            double L, double R, double B, double T, double N, double F,
            Type = UNKNOWN);

        //! Extract frustum values from an orthographic matrix
        static bool getOrtho(
            const osg::Matrix& m,
            double& L, double& R, double& B, double& T, double& N, double& F);
    };
}
