/*
 * JamSpookGame.cpp
 *
 *  Created on: Aug 8, 2019
 *      Author: fredrick
 */

#include "JamSpookGame.h"

namespace JamSpook {

JamSpookGame::JamSpookGame():
        ecos::core::Game()
{


    Log::write(LogLevel::info, "Game - Constructor");
    setGameState(ecos::core::GameState::Launching);

    // Create cycle regulation system, to cap cycles per second and the the computer rest if has time
    shared_ptr<CycleRegulationSystem> cycleRegulationSystem = make_shared<CycleRegulationSystem>();
    addSystem(cycleRegulationSystem);

    // Create asset subssystem for file change listening
    const string assetPath = getRealPath(getWorkingPath() + "/assets");
    shared_ptr<AssetFileSystem> assetFileSystem = make_shared<AssetFileSystem>(assetPath);
    addSystem(assetFileSystem);

    // Create asset system
    shared_ptr<AssetSystem> assetSystem = make_shared<AssetSystem>(this, assetFileSystem);
    addSystem(assetSystem);

    // Create window
    Log::write(LogLevel::info, "Game - Constructor - Creating window");
    mWindow = make_shared<Window>("BouncyBalls", 1024, 1024, Window::WindowMode::window);
    mWindow->hide();

    // Create renderer
    mRenderer = make_shared<GL4Renderer>();

    // Create physics system
    addSystem(make_shared<PhysicsSystem>());

    // Add physics related asset loaders
    assetSystem->addLoader(make_shared<CollisionShapeLoader>(assetPath), "CollisionShapeLoader");

    // Create animation system
    addSystem(make_shared<AnimationSystem>());

    // Create interaction system
    addSystem(make_shared<InteractionSystem>());
    shared_ptr<InteractionSystem> interactionSystem = dynamic_pointer_cast<InteractionSystem>(findSystem(IDCache::get("InteractionSystem")));
    mCallbackId = interactionSystem->ecos::core::BroadcastObservable<const SysCall>::
            addCallback(bind(&JamSpook::JamSpookGame::onSysCall, this, std::placeholders::_1));
    mInputDeviceCallbackId = interactionSystem->ecos::core::BroadcastObservable<const InputDeviceState>::
            addCallback(bind(&JamSpook::JamSpookGame::onInputDeviceStateChange, this, std::placeholders::_1));

    // Create graphics system
    shared_ptr<GraphicsSystem> graphicsSystem = make_shared<GraphicsSystem>(mWindow,
                                                                            dynamic_pointer_cast<Renderer>(mRenderer));
    addSystem(graphicsSystem);

    // Create asset subsystem for image loading
    addSystem(make_shared<DevILSystem>());

    // Add graphics related asset loaders
    assetSystem->addLoader(make_shared<MeshLoader>(assetPath), "MeshLoader");
    assetSystem->addLoader(make_shared<ShaderLoader>(assetPath), "ShaderLoader");
    assetSystem->addLoader(make_shared<ShaderProgramLoader>(assetPath), "ShaderProgramLoader");
    assetSystem->addLoader(make_shared<TextureFileLoader>(assetPath), "TextureFileLoader");
    assetSystem->addLoader(make_shared<TextureBlankLoader>(assetPath), "TextureBlankLoader");

    // Add render layer compositor shader
    ShaderProgramFactory* shaderProgramFactory = new ShaderProgramFactory(assetSystem);
    vector<shared_ptr<ShaderAsset> > compositorShaders;
    compositorShaders.push_back(make_shared<ShaderAsset>("compositor-vertex.glsl", "compositor-vertex-shader", DataManagementMode::LoadOnAccessAndKeep, ShaderType::VertexShader));
    assetSystem->addAsset(compositorShaders.back());
    compositorShaders.push_back(make_shared<ShaderAsset>("compositor-fragment.glsl", "compositor-fragment-shader", DataManagementMode::LoadOnAccessAndKeep, ShaderType::FragmentShader));
    assetSystem->addAsset(compositorShaders.back());
    shared_ptr<ShaderProgram> compositorShaderProgram = shaderProgramFactory->createShaderProgram("compositor-shaderProgram", compositorShaders);

    delete shaderProgramFactory;
    graphicsSystem->setCompositorShader(compositorShaderProgram);

    // Create the main camera
    CameraFactory* camFactory = new CameraFactory();
//    mCamera = camFactory->createOrthogonal(vec3(0.0f, 0.0f, 50.0f),
//                                           vec3(0.0f, 0.0f, 0.0f), // might be looking away from center
//                                           vec4(-(tilesX/2.0f),     // Left plane
//                                                (tilesX/2.0f),      // Right plane
//                                                (tilesY/2.0f),       // Top plane,
//                                                -(tilesY/2.0f)),     // Bottom plane
//                                           0.01f,
//                                           100.0f);

    // 16.0f/9.0f,

//    mCamera = camFactory->createPerspective(vec3(-40.0f, 40.0f, 40.0f),
    mCamera = camFactory->createPerspective(vec3(-60.0f, 40.0f, 60.0f),
                                            vec3(0.0f, -20.0f, 0.0f),
                                            vec3(0.0f, 1.0f, 0.0f),
                                            90.0f/2,
                                            static_cast<float>(mWindow->getWindowSize().x)/mWindow->getWindowSize().y,
                                            0.1f,
                                            1000.0f);

    delete camFactory;
    mRenderer->setCamera(mCamera);

    // Create render layers
    // Add render layers
    RenderLayerFactory* renderLayerFactory = new RenderLayerFactory(assetSystem, mWindow);
//    graphicsSystem->addRenderLayer(renderLayerFactory->createRenderLayer("rl-bg", mCamera));
    graphicsSystem->addRenderLayer(renderLayerFactory->createRenderLayer("rl-game", mCamera));
    graphicsSystem->addRenderLayer(renderLayerFactory->createRenderLayer("rl-gui", mCamera));
    delete renderLayerFactory;

    // Add Sound system
    addSystem(make_shared<SoundSystem>(assetSystem));
    assetSystem->addLoader(make_shared<SoundEffectLoader>(assetPath), "SoundEffectLoader");

    // Show window
    mWindow->show();
//    SDL_ShowCursor(SDL_DISABLE);
    mWindow->setWindowMode(Window::WindowMode::window);
}

JamSpookGame::~JamSpookGame()
{
    Log::write(LogLevel::info, "Game - Destructor.");

    // Hide window
    mWindow->hide();

    // Destroy all entities
    SceneGraph::clear();

    // Remove SysCall callback
    dynamic_pointer_cast<InteractionSystem>(findSystem(IDCache::get("InteractionSystem")))->ecos::core::BroadcastObservable<const SysCall>::removeCallback(mCallbackId);
    dynamic_pointer_cast<InteractionSystem>(findSystem(IDCache::get("InteractionSystem")))->ecos::core::BroadcastObservable<const InputDeviceState>::removeCallback(mInputDeviceCallbackId);

    // Destroy window
    mWindow.reset();
}

void JamSpookGame::load()
{
    Log::write(LogLevel::info, "Game - Load.");

    // Add entities
    setGameState(ecos::core::GameState::Loading);

    // Preload assets
    dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem")))->preload();

    // Set game
    set();
}

void JamSpookGame::update()
{
    Log::write(LogLevel::trace, "Game - Update.");
    updateSystems();
}

void JamSpookGame::set()
{
    Log::write(LogLevel::info, "Game - set.");

    // Clear scene
    SceneGraph::clear();

    // Prepare scene
    shared_ptr<GraphicsSystem> graphicsSystem = dynamic_pointer_cast<GraphicsSystem>(findSystem(IDCache::get("GraphicsSystem")));
    shared_ptr<RenderLayer> renderLayerGame = graphicsSystem->getRenderLayer(IDCache::get("rl-game"));

//      // Add lighting
//    unique_ptr<LightingFactory> lightingFactoryGame = make_unique<LightingFactory>(dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
//            graphicsSystem,
//            dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
//            dynamic_pointer_cast<InteractionSystem>(findSystem(IDCache::get("InteractionSystem"))));
//
//    lightingFactoryGame->createPointLight(vec3(10, 10, 0),
//                                          vec3(1.0f, 0.93f, 0.68f),
//                                          .6f,
//                                          renderLayerGame);
//    lightingFactoryGame->createPointLight(vec3(-10, 2, 0),
//                                          vec3(0.78f, 0.82f, 1.0f),
//                                          .6f,
//                                          renderLayerGame);

//   lightingFactoryGame->createSun(100.0f,
//                                  vec3(0,0,0),
//                                  vec3(1,0,0),
//                                  1.0f,
//                                  renderLayerGame);


//    lightingFactoryGame->createDirectionalLight(vec3(10, 10, 0),
//                                            vec3(-90, 0, 0),
//                                            vec3(1,0,0),
//                                            1.0f,
//                                            renderLayerGame);
//    lightingFactoryGame->createDirectionalLight(vec3(-10, 10, 0),
//                                            vec3(0, 0, 0),
//                                            vec3(0,1,0),
//                                            1.0f,
//                                            renderLayerGame);
//    lightingFactoryGame->createDirectionalLight(vec3(-10, 10, 0),
//                                            vec3(0, 0, 0),
//                                            vec3(0,0,1),
//                                            1.0f,
//                                            renderLayerGame);
//
//    unique_ptr<LightingFactory> lightingFactoryGUI = make_unique<LightingFactory>(dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
//            graphicsSystem,
//            dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
//            renderLayerGui,
//            dynamic_pointer_cast<InteractionSystem>(findSystem(IDCache::get("InteractionSystem"))));
//    lightingFactoryGUI->createDirectionalLight(vec3(10, 10, 0),
//                                            vec3(0, 0, 0),
//                                            vec3(1,0,0),
//                                            1.0f,
//                                            renderLayerGui);
//    lightingFactoryGUI->createDirectionalLight(vec3(-10, 10, 0),
//                                            vec3(0, 0, 0),
//                                            vec3(0,1,0),
//                                            1.0f,
//                                            renderLayerGui);



    // Note: this was enabled
//
//    // Add entities
//    FloorFactory* floorFactory = new FloorFactory(dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
//                                                  graphicsSystem,
//                                                  dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
//                                                  renderLayerGame);
//    floorFactory->createFloor(vec3(0,-5,0));
//    floorFactory->createStaticFloor(vec3(0,-8,0));
//    delete floorFactory;
//

    // Create 2 jump180s
    unique_ptr<Jump180Factory> jump180Factory = make_unique<Jump180Factory>(
            dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
            graphicsSystem,
            dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
            renderLayerGame);

    jump180Factory->createJump180(vec3(0, 0, 0), vec3(0, 0, 0));
    jump180Factory->createJump180(vec3(18,-10, 12), vec3(0, 180, 0));

    jump180Factory.reset();

    // Create the goal
    unique_ptr<GoalFactory> goalFactory = make_unique<GoalFactory>(
            dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
            graphicsSystem,
            dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
            renderLayerGame);

    goalFactory->createGoal(vec3(0, -25, -10));

    goalFactory.reset();

    // Create the ball
    unique_ptr<BallFactory> ballFactory = make_unique<BallFactory>(dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
                                               graphicsSystem,
                                               dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
                                               renderLayerGame);

    ballFactory->createBall(vec3(0, 3, 0),  // position
                            1.0f,           // size
                            10.0f,         // size*2
                            0.1f,           // restitution
                            "base",         // ballName
                            vec3(1,1,1));   // lightColor

    ballFactory.reset();

    // Set game state
    setGameState(ecos::core::GameState::InGame);
}

void JamSpookGame::reset()
{
    // Clear scene
    SceneGraph::clear();

    // Reset data
    // ...set variables

    // Prepare scene
    set();
}

void JamSpookGame::onSysCall(const SysCall sysCall)
{
    Log::write(LogLevel::info, "Game - onSysCall()");
    switch (sysCall)
    {
        case SysCall::exit:
        {
            setGameState(ecos::core::GameState::Closing);
            break;
        }
    }
}

void JamSpookGame::onInputDeviceStateChange(const InputDeviceState inputDeviceState)
{
//    if (inputDeviceState.anyKey != ButtonState::neutral ||
//        inputDeviceState.anyMouseButton != ButtonState::neutral)
//    {
//        // Close the game
//        setGameState(ecos::core::GameState::Closing);
//    }
}

} // namespace JamSpook
