//
// Lol Engine
//
// Copyright: (c) 2010-2013 Sam Hocevar <sam@hocevar.net>
//            (c) 2010-2013 Benjamin "Touky" Huet <huet.benjamin@gmail.com>
//   This program is free software; you can redistribute it and/or
//   modify it under the terms of the Do What The Fuck You Want To
//   Public License, Version 2, as published by Sam Hocevar. See
//   http://www.wtfpl.net/ for more details.
//

#pragma once

//
// Various geometry functions
// --------------------------
//

#include <lol/base/enum.h>

#include <cmath>
#include <cstdio>
#include <algorithm>
#include <stdint.h>

namespace lol
{

//AxisBase --------------------------------------------------------------------
    struct AxisBase : public StructSafeEnum
{
    enum Type
    {
        X = 0, Y, Z, MAX, XY = 2, XYZ = 3,
    };
protected:
    virtual bool BuildEnumMap(map<int64_t, String>& enum_map)
    {
        enum_map[X]   = "X";
        enum_map[Y]   = "Y";
        enum_map[Z]   = "Z";
        enum_map[MAX] = "MAX";
        enum_map[XY]  = "XY";
        enum_map[XYZ] = "XYZ";
        return true;
    }
};
typedef SafeEnum<AxisBase> Axis;

//DirectionBase ---------------------------------------------------------------
struct DirectionBase : public StructSafeEnum
{
    enum Type
    {
        Up = 0, Down, Left, Right, MAX,
    };
protected:
    virtual bool BuildEnumMap(map<int64_t, String>& enum_map)
    {
        enum_map[Up]    = "Up";
        enum_map[Down]  = "Down";
        enum_map[Left]  = "Left";
        enum_map[Right] = "Right";
        enum_map[MAX]   = "MAX";
        return true;
    }
};
typedef SafeEnum<DirectionBase> Direction;

//LOL_BOX_TYPEDEFS ------------------------------------------------------------
#define LOL_BOX_TYPEDEFS(tname, suffix) \
    template <typename T> struct tname; \
    typedef tname<float> suffix; \
    typedef tname<double> d##suffix; \
    typedef tname<int32_t> i##suffix; \
    typedef tname<uint32_t> u##suffix;

LOL_BOX_TYPEDEFS(Box2, box2)
LOL_BOX_TYPEDEFS(Box3, box3)

#undef LOL_BOX_TYPEDEFS

/*
 * 2D boxes
 */

template <typename T> struct Box2
{
    inline Box2()
      : A(T(0)),
        B(T(0))
    {}

    inline Box2(vec_t<T,2> const &a, vec_t<T,2> const &b)
      : A(a),
        B(b)
    {}

    inline Box2(T const &ax, T const &ay, T const &bx, T const &by)
      : A(ax, ay),
        B(bx, by)
    {}

    Box2<T> operator +(vec_t<T,2> const &v) const
    {
        return Box2<T>(A + v, B + v);
    }

    Box2<T> &operator +=(vec_t<T,2> const &v)
    {
        return *this = *this + v;
    }

    Box2<T> operator -(vec_t<T,2> const &v) const
    {
        return Box2<T>(A - v, B - v);
    }

    Box2<T> &operator -=(vec_t<T,2> const &v)
    {
        return *this = *this - v;
    }

    Box2<T> operator *(vec_t<T,2> const &v) const
    {
        return Box2<T>(A * v, B * v);
    }

    Box2<T> &operator *=(vec_t<T,2> const &v)
    {
        return *this = *this * v;
    }

    bool operator ==(Box2<T> const &box)
    {
        return A == box.A && B == box.B;
    }

    bool operator !=(Box2<T> const &box)
    {
        return A != box.A || B != box.B;
    }

    vec_t<T,2> A, B;
};

/*
 * 3D boxes
 */

template <typename T> struct Box3
{
    inline Box3()
      : A(T(0)),
        B(T(0))
    {}

    inline Box3(vec_t<T,3> const &a, vec_t<T,3> const &b)
      : A(a),
        B(b)
    {}

    inline Box3(T const &ax, T const &ay, T const &az,
                T const &bx, T const &by, T const &bz)
      : A(ax, ay, az),
        B(bx, by, bz)
    {}

    Box3<T> operator +(vec_t<T,3> const &v) const
    {
        return Box3<T>(A + v, B + v);
    }

    Box3<T> &operator +=(vec_t<T,3> const &v)
    {
        return *this = *this + v;
    }

    Box3<T> operator -(vec_t<T,3> const &v) const
    {
        return Box3<T>(A - v, B - v);
    }

    Box3<T> &operator -=(vec_t<T,3> const &v)
    {
        return *this = *this - v;
    }

    Box3<T> operator *(vec_t<T,3> const &v) const
    {
        return Box3<T>(A * v, B * v);
    }

    Box3<T> &operator *=(vec_t<T,3> const &v)
    {
        return *this = *this * v;
    }

    bool operator ==(Box3<T> const &box)
    {
        return A == box.A && B == box.B;
    }

    bool operator !=(Box3<T> const &box)
    {
        return A != box.A || B != box.B;
    }

    vec_t<T,3> A, B;
};

/*
 * Helper geometry functions
 */
class TestEpsilon
{
private:
    float           m_epsilon;
    float           m_value;
public:
    TestEpsilon()   { m_value = 0.f; m_epsilon = .0001f; }
    static float Get();
    static void Set(float epsilon=.0001f);
    static const TestEpsilon& F(float value);
private:
    float Minus() const;
    float Plus()  const;
public:
    bool operator==(float value) const;
    bool operator!=(float value) const;
    bool operator<(float value)  const;
    bool operator<=(float value) const;
    bool operator>(float value)  const;
    bool operator>=(float value) const;
};
bool operator==(float value, const TestEpsilon& epsilon);
bool operator!=(float value, const TestEpsilon& epsilon);
bool operator<(float value, const TestEpsilon& epsilon);
bool operator<=(float value, const TestEpsilon& epsilon);
bool operator>(float value, const TestEpsilon& epsilon);
bool operator>=(float value, const TestEpsilon& epsilon);

//--
static inline bool TestAABBVsAABB(box2 const &b1, box2 const &b2)
{
    vec2 c  = 0.5f * ((b1.A + b1.B) - (b2.A + b2.B));
    vec2 e1 = 0.5f *  (b1.B - b1.A);
    vec2 e2 = 0.5f *  (b2.B - b2.A);

    return abs(c.x) <= e1.x + e2.x
        && abs(c.y) <= e1.y + e2.y;
}
static inline bool TestAABBVsPoint(box2 const &b1, vec2 const &p)
{
    return TestAABBVsAABB(b1, box2(p, p));
}

static inline bool TestAABBVsAABB(box3 const &b1, box3 const &b2)
{
    vec3 c =  0.5f * ((b1.A + b1.B) - (b2.A + b2.B));
    vec3 e1 = 0.5f *  (b1.B - b1.A);
    vec3 e2 = 0.5f *  (b2.B - b2.A);

    return abs(c.x) <= e1.x + e2.x
        && abs(c.y) <= e1.y + e2.y
        && abs(c.z) <= e1.z + e2.z;
}
static inline bool TestAABBVsPoint(box3 const &b1, vec3 const &p)
{
    return TestAABBVsAABB(b1, box3(p, p));
}

bool TestTriangleVsTriangle(vec3 const &v00, vec3 const &v01, vec3 const &v02,
                           vec3 const &v10, vec3 const &v11, vec3 const &v12,
                           vec3 &ip00, vec3 &ip10);
bool TestRayVsTriangleSide(vec3 const &v0, vec3 const &v1, vec3 const &v2,
                          vec3 const &ip0, vec3 const &ip1,
                          vec3 &iV0, int &iIdx0, vec3 &iV1, int &iIdx1);
bool TestRayVsTriangle(vec3 const &ray_point, vec3 const &ray_dir,
                      vec3 const &tri_p0, vec3 const &tri_p1, vec3 const &tri_p2,
                      vec3 &vi);

//RayIntersect ----------------------------------------------------------------
struct RayIntersectBase : public StructSafeEnum
{
    enum Type
    {
        Nothing,
        All,
        None,
        P0,
        P1,
    };
    //LOL_DECLARE_ENUM_METHODS(RayIntersectBase)
protected:
    virtual bool BuildEnumMap(map<int64_t, String>& enum_map)
    {
        enum_map[Nothing] = "Nothing";
        enum_map[All] = "All";
        enum_map[None] = "None";
        enum_map[P0] = "P0";
        enum_map[P1] = "P1";
        return true;
    }
};
typedef SafeEnum<RayIntersectBase> RayIntersect;

#define RAY_ISECT_NOTHING   0
#define RAY_ISECT_ALL       1
#define RAY_ISECT_NONE      2
#define RAY_ISECT_P0        3
#define RAY_ISECT_P1        4
int TestRayVsRay(vec3 const &ray_p00, vec3 const &ray_p01,
                 vec3 const &ray_p10, vec3 const &ray_p11,
                 vec3 &isec_p);
bool TestPointVsFrustum(const vec3& point, const mat4& frustum, vec3* result_point=nullptr);

//Ray/Plane : Normal must be given normalized. returns 1 if succeeded.
template <typename TV>
bool TestRayVsPlane(const TV &ray_p0,  const TV &ray_p1,
                    const TV &plane_p, const TV &plane_n,
                          TV &isec_p,  bool test_line_only=false)
{
    TV ray_dir = ray_p1 - ray_p0;
    float d = dot(ray_dir, plane_n);

    if (d > -TestEpsilon::Get() && d < TestEpsilon::Get())
        return false;

    TV o2p1 = ray_p1 - plane_p;
    TV o2p0 = ray_p0 - plane_p;

    if (!test_line_only)
    {
        d = dot(o2p1, plane_n);
        d *= dot(o2p0, plane_n);

        //point are on the same side, so ray can intersect.
        if (d > .0f)
            return false;
    }

    float t = (dot(ProjectPointOnPlane(ray_p0, plane_p, plane_n) - ray_p0, plane_n)) / dot(ray_dir, plane_n);

    if (!test_line_only && (t < -TestEpsilon::Get() || t > 1.f + TestEpsilon::Get()))
        return false;

    isec_p = ray_p0 + t * ray_dir;
    return true;
}

//PlaneIntersectionBase -------------------------------------------------------
struct PlaneIntersectionBase : public StructSafeEnum
{
    /* A safe enum for Primitive edge face. */
    enum Type
    {
        Back, Front, Plane,
    };
protected:
    virtual bool BuildEnumMap(map<int64_t, String>& enum_map)
    {
        enum_map[Back]  = "Back";
        enum_map[Front] = "Front";
        enum_map[Plane] = "Plane";
        return true;
    }
};
typedef SafeEnum<PlaneIntersectionBase> PlaneIntersection;

//Point/Plane : Normal must be given normalized.
template <typename TV>
PlaneIntersection TestPointVsPlane(const TV &point, const TV &plane_p, const TV &plane_n)
{
    float d = dot(normalize(point - plane_p), plane_n);
    if (d > TestEpsilon::Get())
        return PlaneIntersection::Front;
    else if (d < -TestEpsilon::Get())
        return PlaneIntersection::Back;
    else
        return PlaneIntersection::Plane;
}

/* Project point on plane */
template <typename TV>
TV ProjectPointOnPlane(TV const &p, TV const &origin, TV const &normal)
{
    return p - dot(p - origin, normal) * normal;
}

/* Project point on line */
template <typename TV>
TV ProjectPointOnRay(TV const &p, TV const &origin, TV const &direction)
{
    return origin + direction * dot(p - origin, direction);
}

/* Distance from point to plane */
template <typename TV>
float PointDistToPlane(TV const &p, TV const &origin, TV const &normal)
{
    return abs(dot(p - origin, normal));
}

/* Distance from point to segment */
template <typename TV>
float PointDistToSegment(TV const &p, TV const &a, TV const &b)
{
    float d2 = sqlength(b - a);
    float u = d2 ? dot(p - a, b - a) / d2 : 0.0f;
    return distance(p, mix(a, b, clamp(u, 0.0f, 1.0f)));
}

} /* namespace lol */

