//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------

#include "graphics/dgl.h"
#include "2d/scene/DebugDraw.h"

// Debug Profiling.
#include "debug/profiler.h"

//-----------------------------------------------------------------------------

void DebugDraw::DrawAABB( const b2AABB& aabb, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawAABB);

    // Calculate AABB vertices.
    b2Vec2 aabbVertex[4];
    aabbVertex[0].Set(aabb.lowerBound.x, aabb.lowerBound.y);
    aabbVertex[1].Set(aabb.upperBound.x, aabb.lowerBound.y);
    aabbVertex[2].Set(aabb.upperBound.x, aabb.upperBound.y);
    aabbVertex[3].Set(aabb.lowerBound.x, aabb.upperBound.y);

    DrawPolygon( aabbVertex, 4, color );
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawOOBB( const b2Vec2* pOOBB, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawOOBB);

    DrawPolygon( pOOBB, 4, color );
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawAsleep( const b2Vec2* pOOBB, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawAsleep);

    DrawSegment( pOOBB[0], pOOBB[2], color );
    DrawSegment( pOOBB[1], pOOBB[3], color );
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawCollisionShapes( const b2Transform& xf, b2Body* pBody )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawCollisionShapes);

    // Iterate fixtures.
    for ( b2Fixture* pFixture = pBody->GetFixtureList(); pFixture; pFixture = pFixture->GetNext() )
    {
        // Inactive fixture.
        if ( pBody->IsActive() == false )
        {
            DrawShape(pFixture, xf, ColorF(0.5f, 0.5f, 0.5f));
        }
        // Active static fixture.
        else if ( pBody->GetType() == b2_staticBody )
        {
            DrawShape(pFixture, xf, ColorF(0.5f, 0.9f, 0.5f));
        }
        // Active kinematic fixture.
        else if ( pBody->GetType() == b2_kinematicBody )
        {
            DrawShape(pFixture, xf, ColorF(0.5f, 0.5f, 0.9f));
        }
        // Active, asleep dynamic fixture.
        else if ( pBody->IsAwake() == false )
        {
            DrawShape(pFixture, xf, ColorF(0.6f, 0.6f, 0.2f));
        }
        // Active, awake dynamic fixture.
        else
        {
            DrawShape(pFixture, xf, ColorF(0.9f, 0.9f, 0.2f));
        }
    }
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawSortPoint( const b2Vec2& worldPosition, const b2Vec2& size, const b2Vec2& localSortPoint )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawSortPoint);

    // Calculate sort point marker size based upon the object size.
    const b2Vec2 halfSize( size.x * 0.5f, size.y * 0.5f );
    const b2Vec2 objectSize( size.x * 0.2f, size.y * 0.2f );
    const F32 markerSize = getMin(objectSize.x, objectSize.y);

    // Calculate world sort point.
    b2Vec2 worldPoint( worldPosition.x + (localSortPoint.x  * halfSize.x), worldPosition.y + (localSortPoint.y  * halfSize.y) );

    // Calculate The Sort Point "X". 
    b2Vec2 worldSortPoint[4];
    worldSortPoint[0].Set( worldPoint.x - markerSize, worldPoint.y - markerSize );
    worldSortPoint[1].Set( worldPoint.x + markerSize, worldPoint.y + markerSize );
    worldSortPoint[2].Set( worldPoint.x - markerSize, worldPoint.y + markerSize );
    worldSortPoint[3].Set( worldPoint.x + markerSize, worldPoint.y - markerSize );

    DrawSegment( worldSortPoint[0], worldSortPoint[1], ColorF( 0.0f, 1.0f, 0.8f ) );
    DrawSegment( worldSortPoint[2], worldSortPoint[3], ColorF( 0.0f, 1.0f, 0.8f ) );
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawShape( b2Fixture* fixture, const b2Transform& xf, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawShape);

    switch (fixture->GetType())
    {
    case b2Shape::e_circle:
        {
            b2CircleShape* circle = (b2CircleShape*)fixture->GetShape();

            b2Vec2 center = b2Mul(xf, circle->m_p);
            float32 radius = circle->m_radius;
            b2Vec2 axis = b2Mul(xf.q, b2Vec2(0.0f, 1.0f));

            DrawSolidCircle(center, radius, axis, color);
        }
        break;

    case b2Shape::e_edge:
        {
            b2EdgeShape* edge = (b2EdgeShape*)fixture->GetShape();
            b2Vec2 v1 = b2Mul(xf, edge->m_vertex1);
            b2Vec2 v2 = b2Mul(xf, edge->m_vertex2);
            DrawSegment(v1, v2, color);
        }
        break;

    case b2Shape::e_chain:
        {
            b2ChainShape* chain = (b2ChainShape*)fixture->GetShape();
            int32 count = chain->m_count;
            const b2Vec2* vertices = chain->m_vertices;

            b2Vec2 v1 = b2Mul(xf, vertices[0]);
            for (int32 i = 1; i < count; ++i)
            {
                b2Vec2 v2 = b2Mul(xf, vertices[i]);
                DrawSegment(v1, v2, color);
                DrawCircle(v1, 0.05f, color);
                v1 = v2;
            }
        }
        break;

    case b2Shape::e_polygon:
        {
            b2PolygonShape* poly = (b2PolygonShape*)fixture->GetShape();
            int32 vertexCount = poly->m_count;
            b2Assert(vertexCount <= b2_maxPolygonVertices);
            b2Vec2 vertices[b2_maxPolygonVertices];

            for (int32 i = 0; i < vertexCount; ++i)
            {
                vertices[i] = b2Mul(xf, poly->m_vertices[i]);
            }

            DrawSolidPolygon(vertices, vertexCount, color);
        }
        break;
            
    default:
        break;
    }
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawJoints( b2World* pWorld )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_Joints);

    // Draw all joints in world.
    for ( b2Joint* pJoint = pWorld->GetJointList(); pJoint; pJoint = pJoint->GetNext() )
    {
        // Fetch joint configuration.
        b2Body* pBodyA = pJoint->GetBodyA();
        b2Body* pBodyB = pJoint->GetBodyB();
        const b2Transform& xf1 = pBodyA->GetTransform();
        const b2Transform& xf2 = pBodyB->GetTransform();
        b2Vec2 x1 = xf1.p;
        b2Vec2 x2 = xf2.p;
        b2Vec2 p1 = pJoint->GetAnchorA();
        b2Vec2 p2 = pJoint->GetAnchorB();

        ColorF color( 0.5f, 0.8f, 0.8f );

        switch ( pJoint->GetType() )
        {
            // Distance joint.
            case e_distanceJoint:
                DrawSegment(p1, p2, color);
                break;

            // Pullet joint.
            case e_pulleyJoint:
                {
                    b2PulleyJoint* pPulley = (b2PulleyJoint*)pJoint;
                    b2Vec2 s1 = pPulley->GetGroundAnchorA();
                    b2Vec2 s2 = pPulley->GetGroundAnchorB();
                    DrawSegment( s1, p1, color );
                    DrawSegment( s2, p2, color );
                    DrawSegment( s1, s2, color );
                }
                break;
                
            case e_mouseJoint:
                {
                    b2MouseJoint* pMouseJoint = (b2MouseJoint*)pJoint;
                    
                    b2Vec2 p1 = pBodyB->GetPosition();
                    p1 = pMouseJoint->GetAnchorB();
                    b2Vec2 p2 = pMouseJoint->GetTarget();
                    DrawSegment( p1, p2, color );
                }
                break;

            // All other joints.
            default:
                DrawSegment( x1, p1, color );
                DrawSegment( p1, p2, color );
                DrawSegment( x2, p2, color );
        }
    }
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawPolygon( const b2Vec2* vertices, int32 vertexCount, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawPolygon);

    glColor3f(color.red, color.green, color.blue);
    glBegin(GL_LINE_LOOP);
    for (int32 i = 0; i < vertexCount; ++i)
    {
        glVertex2f(vertices[i].x, vertices[i].y);
    }
    glEnd();
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawSolidPolygon( const b2Vec2* vertices, int32 vertexCount, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawSolidPolygon);

    glEnable(GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glColor4f(0.5f * color.red, 0.5f * color.green, 0.5f * color.blue, 0.15f);
    glBegin(GL_TRIANGLE_FAN);
    for (int32 i = 0; i < vertexCount; ++i)
    {
        glVertex2f(vertices[i].x, vertices[i].y);
    }
    glEnd();
    glDisable(GL_BLEND);

    glColor4f(color.red, color.green, color.blue, 1.0f);
    glBegin(GL_LINE_LOOP);
    for (int32 i = 0; i < vertexCount; ++i)
    {
        glVertex2f(vertices[i].x, vertices[i].y);
    }
    glEnd();
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawCircle( const b2Vec2& center, float32 radius, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawCircle);

    const float32 k_segments = 16.0f;
    const float32 k_increment = 2.0f * b2_pi / k_segments;
    float32 theta = 0.0f;
    glColor3f(color.red, color.green, color.blue);
    glBegin(GL_LINE_LOOP);
    for (int32 i = 0; i < k_segments; ++i)
    {
        b2Vec2 v = center + radius * b2Vec2(cosf(theta), sinf(theta));
        glVertex2f(v.x, v.y);
        theta += k_increment;
    }
    glEnd();
}
    
//-----------------------------------------------------------------------------

void DebugDraw::DrawSolidCircle( const b2Vec2& center, float32 radius, const b2Vec2& axis, const ColorF& color )
{
    // Debug Profiling.
    PROFILE_SCOPE(DebugDraw_DrawSolidCircle);

    const float32 k_segments = 12.0f;
    const float32 k_increment = 2.0f * b2_pi / k_segments;
    float32 theta = 0.0f;
    glEnable(GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    glColor4f(0.5f * color.red, 0.5f * color.green, 0.5f * color.blue, 0.15f);
    glBegin(GL_TRIANGLE_FAN);
    for (int32 i = 0; i < k_segments; ++i)
    {
        b2Vec2 v = center + radius * b2Vec2(cosf(theta), sinf(theta));
        glVertex2f(v.x, v.y);
        theta += k_increment;
    }
    glEnd();
    glDisable(GL_BLEND);

    theta = 0.0f;
    glColor4f(color.red, color.green, color.blue, 1.0f);
    glBegin(GL_LINE_LOOP);
    for (int32 i = 0; i < k_segments; ++i)
    {
        b2Vec2 v = center + radius * b2Vec2(cosf(theta), sinf(theta));
        glVertex2f(v.x, v.y);
        theta += k_increment;
    }
    glEnd();

    b2Vec2 p = center + radius * axis;
    glBegin(GL_LINES);
    glVertex2f(center.x, center.y);
    glVertex2f(p.x, p.y);
    glEnd();
}
    
//-----------------------------------------------------------------------------

void DebugDraw::DrawSegment( const b2Vec2& p1, const b2Vec2& p2, const ColorF& color )
{
    glColor3f(color.red, color.green, color.blue);
    glBegin(GL_LINES);
    glVertex2f(p1.x, p1.y);
    glVertex2f(p2.x, p2.y);
    glEnd();
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawTransform( const b2Transform& xf )
{
    b2Vec2 p1 = xf.p, p2;
    const float32 k_axisScale = 0.4f;
    glBegin(GL_LINES);
    
    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex2f(p1.x, p1.y);
    p2 = p1 + k_axisScale * xf.q.GetXAxis();
    glVertex2f(p2.x, p2.y);

    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex2f(p1.x, p1.y);
    p2 = p1 + k_axisScale * xf.q.GetYAxis();
    glVertex2f(p2.x, p2.y);

    glEnd();
}

//-----------------------------------------------------------------------------

void DebugDraw::DrawPoint( const b2Vec2& p, float32 size, const ColorF& color )
{
    glPointSize(size);
    glBegin(GL_POINTS);
    glColor3f(color.red, color.green, color.blue);
    glVertex2f(p.x, p.y);
    glEnd();
    glPointSize(1.0f);
}

