/******************************************************************************
*	File		-	physics_scene.h
*	Author		-	Joey Pollack
*	Date		-	2021/10/27 (y/m/d)
*	Mod Date	-	2021/10/27 (y/m/d)
*	Description	-	Displays a scene that tests the Box2D physics system
******************************************************************************/

#include "physics_scene.h"
#include <core/core.h>
#include <graphics/graphics.h>
#include <utils/logger.h>

namespace lunarium
{
    PhysicsScene::PhysicsScene(uint32_t logCat)
        : BaseScene(logCat), mb2World(nullptr)
    {

    }
    
    PhysicsScene::~PhysicsScene()
    {
        delete mb2World;
    }

    void PhysicsScene::OnLoad()
    {
        Logger::Info(mLogCat, "Running Physics Test Scene");

        // Comments from the docs: https://box2d.org/documentation/md__d_1__git_hub_box2d_docs_hello.html

        // Every Box2D program begins with the creation of a b2World object. 
        // b2World is the physics hub that manages memory, objects, and simulation. 
        // You can allocate the physics world on the stack, heap, or data section.

        // It is easy to create a Box2D world. First, we define the gravity vector.
        b2Vec2 gravity(0.0f, 10.0f);
        mb2World = new b2World(gravity);

        /*  CREATING THE GOUND BOX
            Bodies are built using the following steps:

            1) Define a body with position, damping, etc.
            2) Use the world object to create the body.
            3) Define fixtures with a shape, friction, density, etc.
            4) Create fixtures on the body.
        */

       // For step 1 we create the ground body. For this we need a body definition. 
       // With the body definition we specify the initial position of the ground body.
        b2BodyDef groundBodyDef;
        groundBodyDef.position.Set(60.0f, 50.0f);

        // For step 2 the body definition is passed to the world object to create the ground body. 
        // The world object does not keep a reference to the body definition. Bodies are static by default. 
        // Static bodies don't collide with other static bodies and are immovable.
        mpGroundBody = mb2World->CreateBody(&groundBodyDef);

        // For step 3 we create a ground polygon. We use the SetAsBox shortcut to form the ground polygon 
        // into a box shape, with the box centered on the origin of the parent body.
        // MORE INFO ABOUT SetAsBox() On the web page (see above)
        mpGroundBox = new b2PolygonShape;
        mpGroundBox->SetAsBox(20.0f, 5.0f);

        // We finish the ground body in step 4 by creating the shape fixture. For this step we have a shortcut. 
        // We do not have a need to alter the default fixture material properties, so we can pass the shape directly 
        // to the body without creating a fixture definition.
        mpGroundBody->CreateFixture(mpGroundBox, 0.0f);

        // Create a second ground body
        groundBodyDef.position.Set(20.0f, 70.0f);
        mpGroundBody2 = mb2World->CreateBody(&groundBodyDef);
        mpGroundBox2 = new b2PolygonShape;
        mpGroundBox2->SetAsBox(30.0f, 5.0f);
        mpGroundBody2->CreateFixture(mpGroundBox2, 0.0f);

        /////////////////////////////////////////////////////////////////////////////
        // So now we have a ground body. We can use the same technique to create a dynamic body. 
        // The main difference, besides dimensions, is that we must establish the dynamic body's mass properties.

        // First we create the body using CreateBody. By default bodies are static, so we should set the b2BodyType 
        // at construction time to make the body dynamic.
        b2BodyDef bodyDef;
        bodyDef.type = b2_dynamicBody; // Caution: You must set the body type to b2_dynamicBody if you want the body to move in response to forces.
        bodyDef.position.Set(39.0f, 4.0f);
        mpDynamicBody = mb2World->CreateBody(&bodyDef);

         
        // Next we create and attach a polygon shape using a fixture definition. First we create a box shape:
        mpDynamicBox = new b2PolygonShape;
        mpDynamicBox->SetAsBox(1.0f, 1.0f);

        // Next we create a fixture definition using the box. Notice that we set density to 1. 
        // The default density is zero. Also, the friction on the shape is set to 0.3.
        // Caution: A dynamic body should have at least one fixture with a non-zero density. Otherwise you will get strange behavior. 
        b2FixtureDef fixtureDef;
        fixtureDef.shape = mpDynamicBox;
        fixtureDef.density = 1.0f;
        fixtureDef.friction = 0.3f;

        // Using the fixture definition we can now create the fixture. This automatically updates the mass of the body. 
        // You can add as many fixtures as you like to a body. Each one contributes to the total mass.
        mpDynamicBody->CreateFixture(&fixtureDef);
    }
    
    void PhysicsScene::OnTick(double delta)
    {
        // TODO: Implement the "hello world" Box2D example and 
        //       use a scaling factor of 10 to render it
        float timeStep = 1.0f / 60.0f;
        int32 velocityIterations = 6;
        int32 positionIterations = 2;

        mb2World->Step(timeStep, velocityIterations, positionIterations);
    }
    
    void PhysicsScene::OnRender(IGraphics* g)
    {
        // This scaling is a little backwards.
        // Info about scaling factor:
        // https://box2d.org/documentation/md__d_1__git_hub_box2d_docs__f_a_q.html#autotoc_md139
        float scaleFactor = 10.0f;

        b2AABB groundbox;
        mpGroundBox->ComputeAABB(&groundbox, mpGroundBody->GetTransform(), 0);
    
        Rectangle groundRect(groundbox);
        groundRect.Scale(scaleFactor, scaleFactor);
        groundRect.X *= scaleFactor;
        groundRect.Y *= scaleFactor;
        g->DrawFilledBox(groundRect, Color(0.0f, 1.0f, 0.0f, 1.0f));

        b2AABB groundbox2;
        mpGroundBox2->ComputeAABB(&groundbox2, mpGroundBody2->GetTransform(), 0);   
        Rectangle groundRect2(groundbox2);
        groundRect2.Scale(scaleFactor, scaleFactor);
        groundRect2.X *= scaleFactor;
        groundRect2.Y *= scaleFactor;
        g->DrawFilledBox(groundRect2, Color(0.0f, 1.0f, 0.0f, 1.0f));

        b2AABB dynbox;
        b2Transform transform;
        transform.p = mpDynamicBody->GetTransform().p;
        transform.q = b2Rot(0.0f);
        mpDynamicBox->ComputeAABB(&dynbox, transform, 0);
        Rectangle dynRect(dynbox);
        float angle = mpDynamicBody->GetAngle();
        dynRect.Scale(scaleFactor, scaleFactor);
        dynRect.X *= scaleFactor;
        dynRect.Y *= scaleFactor;
        g->DrawFilledBox(dynRect, Color(0.0f, 0.0f, 1.0f, 1.0f), angle);

        // Debug info
        char str[256] = { 0 };
        sprintf(str, "GroundBox: pos: (%f, %f)  size: (%f, %f)", groundRect.X, groundRect.Y, groundRect.HalfWidth, groundRect.HalfHeight);
        g->DrawString(str, Rectangle::MakeFromTopLeft(10.0f, 10.0f, 800.0f, 30.0f), Color(0.75f, 0.85f, 0.5f, 1.0f), 0.4f);

        // sprintf(str, "GroundBox Half Size: (%f, %f)", groundRect.Width, groundRect.Height);
        // g->DrawString(str, Rectangle(10.0f, 35.0f, 800.0f, 30.0f), Color(0.75f, 0.85f, 0.5f, 1.0f), 0.4f);
        
        // sprintf(str, "DynBox: (%f, %f) (%f, %f), Angle: %f",
        // dynRect.LeftTop().x * scaleFactor, dynRect.LeftTop().y * scaleFactor,  
        // dynRect.RightBottom().x * scaleFactor, dynRect.RightBottom().y * scaleFactor, angle);
        // g->DrawString(str, Rectangle(10.0f, 60.0f, 1000.0f, 100.0f), Color(0.75f, 0.85f, 0.5f, 1.0f), 0.4);

        int ww, wh;
        Core::MainWindow().GetFramebufferSize(&ww, &wh);
        for (int i = 0; i < wh; i += 10)
        {
            sprintf(str, "%d", i);
            g->DrawString(str, Rectangle::MakeFromTopLeft(ww - 35.0f, i, 4.0f, 5.0f), Color(1.0f, 0.0f, 0.0f, 1.0f), 0.25f);
            g->DrawBox(Rectangle::MakeFromTopLeft(ww - 10.0f, i, 10.0f, 2.0f), Color(1.0f, 0.0f, 0.0f, 1.0f), 1.0f);
        }

        for (int i = 0; i < ww; i += 25)
        {
            sprintf(str, "%d", i);
            g->DrawString(str, Rectangle::MakeFromTopLeft(i, wh - 20.0f, 4.0f, 5.0f), Color(1.0f, 0.0f, 0.0f, 1.0f), 0.25f);
            g->DrawBox(Rectangle::MakeFromTopLeft(i, wh - 5.0f, 2.0f, 5.0f), Color(1.0f, 0.0f, 0.0f, 1.0f), 1.0f);
        }
    }

}