/*
 * 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
    // Resolution (256x224 scaled to whatever)
    Log::write(LogLevel::info, "Game - Constructor - Creating window");
    mWindow = make_shared<Window>("Spooky Game", 480, 272, 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");
    assetSystem->addLoader(make_shared<StaticCollisionShapeLoader>(assetPath), "StaticCollisionShapeLoader");

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

    // Create camera system
    addSystem(make_shared<CameraSystem>());

    // 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(1.0f, 100.0f, 0.0f),
    mCamera = camFactory->createPerspective(vec3(0.0f, 0.0f, 0.0f),   // The follow cam implementation will change the position during runtime.
                                            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);


    mCameraBackground = camFactory->createOrthogonal(translate(mat4(1), vec3(0, 0, -1)),
                                                     vec4(10,10,10,10),
                                                     0.1f,
                                                     20);

    mCameraEndScene = camFactory->createPerspective(vec3(1010.0f, 4.0f, 1010.0f),
                                                    vec3(0.0f, 0.0f, 0.0f),
                                                    vec3(0.0f, 1.0f, 0.0f),
                                                    90.0f/2,
                                                    static_cast<float>(mWindow->getWindowSize().x)/mWindow->getWindowSize().y,
                                                    0.1f,
                                                    100.0f);

    delete camFactory;
    mRenderer->setCamera(mCamera);
//    mRenderer->setCamera(mCameraEndScene);

    // Create render layers
    // Add render layers
    RenderLayerFactory* renderLayerFactory = new RenderLayerFactory(assetSystem, mWindow);
    graphicsSystem->addRenderLayer(renderLayerFactory->createRenderLayer("rl-bg", mCameraBackground));
    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> renderLayerBackground = graphicsSystem->getRenderLayer(IDCache::get("rl-bg"));
    shared_ptr<RenderLayer> renderLayerGame = graphicsSystem->getRenderLayer(IDCache::get("rl-game"));

    // Create lighting
    unique_ptr<LightingFactory> lightingFactory = 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"))));

    lightingFactory->createSun(10.0f,
            vec3(45,-45,0),
            vec3(1,1,1),
            0.5f,
            renderLayerGame);

    lightingFactory.reset();
//    // Create background
//    unique_ptr<SpriteFactory> bakgroundSpriteFactory = make_unique<SpriteFactory>(
//                dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
//                graphicsSystem,
//                renderLayerBackground);
//
//    bakgroundSpriteFactory->createBackground("sky-plane", "sky-diffuse.png");
//
//    bakgroundSpriteFactory.reset();
//
//    // Create background
//    unique_ptr<SpriteFactory> spriteFactory = make_unique<SpriteFactory>(
//                dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
//                graphicsSystem,
//                renderLayerGame);
//
////    bakgroundSpriteFactory->createBackground("ball-test-plane", "ball-diffuse.png");
//    spriteFactory->createPlane("water", "water-diffuse.png", vec3(0, 4, 0), vec3(90, 0, 0), vec3(100, 100, 0));
//
//    spriteFactory.reset();

    // 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, -1, 0), vec3(0, 0, 0));
    jump180Factory->createJump180(vec3(25,-16, 14), 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(-1, -31, -12));
//    goalFactory->createGoal(vec3(0, 0, 0));

    goalFactory.reset();

    // Create the track pieces
    unique_ptr<TrackPieceFactory> trackPieceFactory = make_unique<TrackPieceFactory>(
            dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
            graphicsSystem,
            dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
            renderLayerGame);

//    trackPieceFactory->createRoad();
//    trackPieceFactory->createTunnel();
//    trackPieceFactory->createMountain();

//    trackPieceFactory->createStaticPlatform();

    trackPieceFactory->createGhost("SadGhost",
                                  "ghost-white-diffuse.png",
                                  vec3(20, -15, 10),
                                  quat(vec3(radians(0.0f), radians(45.0f), radians(0.0f))),
                                  5.0f);

    trackPieceFactory->createGhost("AngryGhost",
                                  "ghost-red-diffuse.png",
                                  vec3(-8, -25, -18),
                                  quat(vec3(radians(0.0f), radians(15.0f), radians(0.0f))),
                                  5.0f);





    // End scene entities
    trackPieceFactory->createGhost("SadGhost",
                                  "ghost-white-diffuse.png",
                                  vec3(1005, 4, 1001),
                                  quat(vec3(radians(0.0f), radians(45.0f), radians(0.0f))));

    trackPieceFactory->createSign("ThankYouSign",
                                  "sign-thanks-diffuse.png",
                                  vec3(994, 0, 1000),
                                  quat(vec3(radians(0.0f), radians(90.0f), radians(0.0f))));

    trackPieceFactory.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();

    // Create the player kart
    shared_ptr<KartFactory> kartFactory = make_unique<KartFactory>(
            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"))),
            dynamic_pointer_cast<AnimationSystem>(findSystem(IDCache::get("AnimationSystem"))),
            renderLayerGame);

    shared_ptr<Entity> kartEntity = kartFactory->createKart(
            vec3(0, 3, 0),  // position
            vec3(0, 180, 0)); // euler degrees rotation, XYZ



//    kartFactory.reset();

    // Create the follow camera, and lock it to the kart
    unique_ptr<CameraFactory> cameraFactory = make_unique<CameraFactory>();

    cameraFactory->createActiveFollowCamera("followKartCamera",
                                            mCamera,
                                            kartEntity,
                                            vec3(0 ,25 ,25),
                                            vec3(0, 5, 0),
                                            bvec3(0, 0, 0));

    shared_ptr<Entity> endSceneCamera = cameraFactory->createStaticCamera("endSceneCamera",
                                                       mCameraEndScene);

    cameraFactory.reset();


    // Create the reset trigger
    unique_ptr<ResetTriggerFactory> resetTriggerFactory = make_unique<ResetTriggerFactory>(
            dynamic_pointer_cast<AssetSystem>(findSystem(IDCache::get("AssetSystem"))),
            graphicsSystem,
            dynamic_pointer_cast<PhysicsSystem>(findSystem(IDCache::get("PhysicsSystem"))),
            renderLayerGame);

    shared_ptr<Entity> endGameEntity = resetTriggerFactory->
            createResetTrigger(vec3(-1, -31, -12),
                               vec3(1));

//    shared_ptr<Entity> endGameEntity = resetTriggerFactory->
//            createResetTrigger(vec3(0, 0, 0),
//                               vec3(1));

    // Add the leaking ICCResponse component to // ContactWithPlayer
    weak_ptr<GraphicsSystem> wGraphicsSystem = graphicsSystem;
    weak_ptr<Camera> wEndSceneCamera = mCameraEndScene;
    endGameEntity->addComponent(make_shared<ICCResponseComponent>(endGameEntity, [wGraphicsSystem, wEndSceneCamera, kartFactory] (shared_ptr<Message> message) {
        if (message->getMessageType() == IDCache::get("ContactWithPlayer"))
        {
            Log::write(LogLevel::debug, "endGameEntity - ContactWithPlayer");
            shared_ptr<GraphicsSystem> graphicsSystem = getShared(wGraphicsSystem);
            shared_ptr<RenderLayer> renderLayerGame = graphicsSystem->getRenderLayer(IDCache::get("rl-game"));
            renderLayerGame->setCamera(getShared(wEndSceneCamera));

            // Create the end scene rotating kart
            shared_ptr<Entity> rotatingKartEntity = kartFactory->createRotatingKart(vec3(1000, 0, 1000));
        }
    }));

    resetTriggerFactory.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
