#include "Engine.hpp"
#include "Sprite.hpp"
#include "TileMap.hpp"
#include "ControllerComponent.hpp"
#include "IPhysicsComponent.hpp"
#include "RigidbodyComponent.hpp"
#include "ProjectileComponent.hpp"
#include "HealthComponent.h"
// I recommend a map for filling in the resource manager
#include <map>
#include <string>
#include <memory>
#include <iterator>

// Try toggling this number!
#define CHARACTERS 1

Vec2D cellSize = Vec2D(64, 64);

// Global array to create our characters
// SpriteComponent characterSpriteComponent[CHARACTERS];

// Initialization function
// Returns a true or false value based on successful completion of setup.
// Takes in dimensions of window.
Engine::Engine()
{
}

// Proper shutdown and destroy initialized objects
Engine::~Engine()
{
}

// Return Input
void Engine::Input(bool *quit, bool *runnin)
{
    // Event handler that handles various events in SDL
    // that are related to input and output
    SDL_Event e;
    // Enable text input
    SDL_StartTextInput();
    // Handle events on queue
    while (SDL_PollEvent(&e) != 0)
    {
        mGUI->Input(e);

        // User posts an event to quit
        // An example is hitting the "x" in the corner of the window.
        if (e.type == SDL_QUIT)
        {
            *quit = true;
        }
    }
}

// Update SDL
void Engine::Update()
{
    static float frame = 0;

    // Increment the frame that
    // the sprite is playing
    frame += 0.25f;

    SDL_Event e;
    SDL_PollEvent(&e);

    // Vector storing messages from components meant to be broadcast to all other objects
    std::vector<std::string> broadcastList;

    // iterate through each of our characters
    // for (int i = 0; i < CHARACTERS; i++)
    // {
    //     characterSpriteComponent[i].Update(e, frame);
    // }

    for (int i = 0; i < m_gameobjects.size(); i++)
    {
        m_gameobjects[i]->Update(e, (int)frame, broadcastList);
    }
    mPhysics->Update();
}

// Render
// The render function gets called once per loop
void Engine::Render()
{
    // Set the color of the empty framebuffer
    mRenderer->SetRenderDrawColor(110, 130, 170, 0xFF);
    // Clear the screen to the color of the empty framebuffer
    mRenderer->RenderClear();

    // NOTE: We have to dynamic_cast here, because we do not exactly know the type
    //       of the renderer that is going to 'render' our character.
    //       Polymorphism is being used for our renderer, and we need to thus
    //       determine using dynamic_cast if we can make a successful cast to find
    //       a matching member function that can retrieve a 'renderer' suitable.
    // NOTE: The very fact that I am storing an SDL_Renderer* below completely
    //       defeats the purpose of using the dynamic_cast.
    //       The whole point of the dynamic_cast is so that at run-time we can retrieve a
    //       renderer to draw our characters.
    //       That said, it's just more readable for you to see the dynamic cast here.
    // NOTE: If dynamic_cast fails, you can check at run-time and handle through either an
    //       assertion or exception the behavior.
    SDL_Renderer *renderer = dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer();

    // Render each of the character(s)
    // for (int i = 0; i < CHARACTERS; i++)
    // {
    //     characterSpriteComponent[i].Render(renderer);
    // }
    //   characterSpriteComponent->Render(renderer);
    // Render the tile map
    // myTileMapComponent->Render(renderer);

    for (int i = 0; i < m_gameobjects.size(); i++)
    {
        m_gameobjects[i]->Render(renderer);
    }
    mGUI->RenderEditor(mRenderer, &runGame);
    // Flip the buffer to render
    mRenderer->RenderPresent();
}

// Loops forever!
void Engine::MainGameLoop()
{
    // Wait until we start playing the game to load up all the objects into the physics engine
    InitializePhysicsSubsystem();

    // Main loop flag
    // If this is quit = 'true' then the program terminates.
    bool quit = false;

    // While application is running
    while (!quit)
    {
        Uint32 startTicks = SDL_GetTicks();
        // Get user input
        Input(&quit, &runGame);

        if (runGame)
        {
            InitializePhysicsSubsystem();
            Update();
        }

        Render();

        Uint32 endTicks = SDL_GetTicks();
        float frameTime = (endTicks - startTicks) / 1000.0f;

        SDL_Delay(floor(16.666f - frameTime));
        endTicks = SDL_GetTicks();
        frameTime = (endTicks - startTicks) / 1000.0f;
    }
    // Disable text input
    SDL_StopTextInput();
}

void Engine::Start()
{
    // Report which subsystems are being initialized
    if (mRenderer != nullptr)
    {
        std::cout << "Initializing Graphics Subsystem\n";
    }
    else
    {
        std::cout << "No Graphics Subsystem initialized\n";
    }

    GameObject *player = new GameObject("Player");

    TransformComponent *playerLocation = new TransformComponent(0, 0);
    SpriteComponent *playerSprite = new SpriteComponent(playerLocation);
    RigidbodyComponent *playerRigidBody = new RigidbodyComponent(playerLocation, cellSize, Vec2D(0, 0), 1, false, false);
    ControllerComponent *playerController = new ControllerComponent(playerLocation, Vec2D(10, 10));

    playerSprite->LoadImage("./Assets/images/Froggo-SheetV2.bmp", dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer(), 32, 32, 14, Vec2D(128, 128));
    player->AddComponent(playerLocation);
    player->AddComponent(playerSprite);
    player->AddComponent(playerRigidBody);
    player->AddComponent(playerController);

    // Setup our TileMapComponent
    // This tile map is 20x11 in our game
    // It is using a 'reference' tilemap with 8x8 tiles
    // that are each 64x64 pixels.
    TileMapComponent *myTileMapComponent = new TileMapComponent("./Assets/images/TileMapFrog.bmp", 5, 5, 64, 64, 20, 12, dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer(), &m_gameobjects);
    // Generate a a simple tilemap
    myTileMapComponent->GenerateSimpleMap();
    // Print out the map to the console
    // so we can see what was created.
    myTileMapComponent->PrintMap();

    GameObject *tileMap = new GameObject("TileMap");
    tileMap->AddComponent(myTileMapComponent);

    GameObject *cloud1 = new GameObject("Cloud1");
    TransformComponent *cloud1Location = new TransformComponent(750, 125);
    SpriteComponent *cloud1Sprite = new SpriteComponent(cloud1Location);
    cloud1Sprite->LoadImage("./Assets/images/Cloud.bmp", dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer(), 512, 256, 1, Vec2D(512, 256));

    cloud1->AddComponent(cloud1Location);
    cloud1->AddComponent(cloud1Sprite);

    GameObject *cloud2 = new GameObject("Cloud2");
    TransformComponent *cloud2Location = new TransformComponent(50, 275);
    SpriteComponent *cloud2Sprite = new SpriteComponent(cloud2Location);
    cloud2Sprite->LoadImage("./Assets/images/Cloud.bmp", dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer(), 512, 256, 1, Vec2D(512, 256));

    cloud2->AddComponent(cloud2Location);
    cloud2->AddComponent(cloud2Sprite);

    GameObject *cloud3 = new GameObject("Cloud3");
    TransformComponent *cloud3Location = new TransformComponent(425, 75);
    SpriteComponent *cloud3Sprite = new SpriteComponent(cloud3Location);
    cloud3Sprite->LoadImage("./Assets/images/Cloud.bmp", dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer(), 512, 256, 1, Vec2D(256, 128));

    cloud3->AddComponent(cloud3Location);
    cloud3->AddComponent(cloud3Sprite);

    m_gameobjects.push_back(cloud1);
    m_gameobjects.push_back(cloud2);
    m_gameobjects.push_back(cloud3);
    m_gameobjects.push_back(createFly(playerLocation, Vec2D(800, 20)));
    m_gameobjects.push_back(tileMap);
    m_gameobjects.push_back(player);
}

GameObject *Engine::createFly(TransformComponent *player, Vec2D spawnPos)
{
    GameObject *fly = new GameObject("Fly #1");

    TransformComponent *flyLocation = new TransformComponent(spawnPos.x, spawnPos.y);
    SpriteComponent *flySprite = new SpriteComponent(flyLocation);
    Component *flyHitbox = new RigidbodyComponent(flyLocation, Vec2D(64, 64), Vec2D(0, 0), 1000.0f, false, false);
    flySprite->LoadImage("./Assets/images/Fly-Sheet.bmp", dynamic_cast<SDLGraphicsEngineRenderer *>(mRenderer)->GetRenderer(), 32, 32, 12, Vec2D(64, 64));
    ProjectileComponent *flyProjectile = new ProjectileComponent(flyLocation, player, true, 1);

    fly->AddComponent(flyLocation);
    fly->AddComponent(flySprite);
    fly->AddComponent(flyHitbox);
    fly->AddComponent(flyProjectile);

    return fly;
}

void Engine::Shutdown()
{
    // Shut down our Tile Systems
    if (nullptr != mRenderer)
    {
        delete mRenderer;
    }

    // Destroy our gmae objects
    for (int i = 0; i < m_gameobjects.size(); i++)
    {
        delete &m_gameobjects[i];
    }
}

void Engine::InitializeGraphicsSubSystem()
{
    // Setup our Renderer
    mRenderer = new SDLGraphicsEngineRenderer(1280, 720);
    if (nullptr == mRenderer)
    {
        exit(1); // Terminate program if renderer
        // cannot be created.
        // (Optional) TODO:   Could put an error
        //                    messeage, or try to
        //                    reinitialize the engine
        //                    with a different render
    }
}

void Engine::InitializePhysicsSubsystem()
{
    // Find all Physics components and send them to the physics engine
    mPhysics = new PhysicsEngine();
    for (GameObject *g : m_gameobjects)
    {
        for (Component *c : g->ListComponents())
        {
            // NOTE: this will break if the Physics tag is ever applied to something that isn't an IPhysicsComponent
            // I'll try to find a better way but I think this will be future proof as long as we're careful
            if (strcmp(c->ComponentTypeName(), "Rigidbody Component") == 0)
            {
                mPhysics->AddComponent(dynamic_cast<IPhysicsComponent *>(c));
            }
        }
    }
}

void Engine::InitializeGUI()
{
    mGUI = new GameMakerGUI(mRenderer->GetWindow(), mRenderer->GetRenderer(), &m_gameobjects);
}
