/*
 * Game.cpp
 *
 *  Created on: Feb 9, 2020
 *      Author: ayoungblood
 *      Special Thanks to: Carl Birch of Let's Make Games
 *      				   Nic Allen
 *      				   Brian Lhota
 */

#include "Game.hpp"
#include "../assetmgr/TextureManager.h"
#include "../assetmgr/Map.h"
#include "../ecs/Components.h"
#include "Collision.h"
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
#include "../assetmgr/AssetManager.h"
#include "../ui/UIText.h"
#include "../ui/UINineSlice.h"
#include "../cjson/cJSON.h"
#include "../tileson/tileson.hpp"
#include <cmath>

// tmxparser::TmxMap map;
//tileson::Tileson map;
Manager manager;
UINineSlice* my9Slice;
UINineSlice* scoreboard9Slice;
UINineSlice* debugBox;

GameStateManager* Game::gsm = new GameStateManager();

SDL_Renderer* Game::renderer = nullptr;
SDL_Event Game::event;

SDL_Rect Game::camera;
SDL_Rect Game::levelMap;

AssetManager* Game::assets = new AssetManager(&manager);

bool Game::isRunning = false;
bool Game::debugMenu = false;

auto& player(manager.addEntity());
Vector2D Game::playerPosition;
Vector2D Game::pVel;

// auto& enemy(manager.addEntity());
auto& puppy(manager.addEntity());

// auto& scoreboard(manager.addEntity());
auto& uiInfo(manager.addEntity());
auto& uiJumpInfo(manager.addEntity());
auto& uiCamXInfo(manager.addEntity());
auto& uiCamYInfo(manager.addEntity());
auto& uiPlayerXInfo(manager.addEntity());
auto& uiPlayerYInfo(manager.addEntity());
auto& uiBoundary1Info(manager.addEntity());
auto& uiBoundary2Info(manager.addEntity());
auto& uiBoundary3Info(manager.addEntity());

auto& uiTextInstructions(manager.addEntity());

auto& gameScene(manager.addEntity());

bool Game::debugCollisionBoxes = false;
bool Game::gravityOnPlayer = true;
bool Game::playerIsGrounded = false;
bool Game::playerIsJumping = false;

int gScale = 0;
int last_time;
int current_time;
int diff_time;

//tson::Tileson t;

int Game::pTileX = 0;
int Game::pTileY = 0;

std::string Game::BoolToString(bool b) {
    std::string myString;
    if (b) {
        myString = "true";
    } else {
        myString = "false";
    }
    return myString;
}

Game::Game() {
	// TODO Auto-generated constructor stub

}

Game::~Game() {
	// TODO Auto-generated destructor stub
}

void Game::init(const char *title, int width, int height, bool fullscreen, int globalScale)
{
    camera = { 0, 0, width, height };
    int flags = 0;
    gScale = globalScale;
    if(fullscreen)
    {
        flags = SDL_WINDOW_FULLSCREEN;
    }

    if(SDL_Init(SDL_INIT_EVERYTHING) == 0)
    {
        window = SDL_CreateWindow(title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, width, height, flags);
        SDL_SetWindowBordered(window,SDL_FALSE);
        if(!window)
        {
            SDL_LogError(SDL_LOG_CATEGORY_APPLICATION,"Couldn't create window: %s", SDL_GetError());
        }
        renderer = SDL_CreateRenderer(window, -1, 0);
        if(!renderer)
        {
        	SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't create renderer: %s", SDL_GetError());
        	isRunning = false;
        }
        else
        {
        	SDL_SetRenderDrawColor(renderer, 255,255,255,255);
            SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1");
        	isRunning = true;
        }
        //Initialize SDL_mixer
        if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 2, 2048)<0)
        {
        	SDL_LogError(SDL_LOG_CATEGORY_ERROR, "Couldn't initialize SDL audio mixer!, Error: %s", SDL_GetError());
        }

// ---------------------------------------
//  Trying out the halsafar/libtmx-parser
/*        tmxparser::TmxReturn error;
        tmxparser::TmxMap map;
        error = tmxparser::parseFromFile("assets/maps/testmapb64.tmx", &map, "assets/textures/tiles/");
        
        if (!error)
        {
//             printf("Yay! Tile map loaded with no errors.\n");
            tmxparser::TmxLayer layer = map.layerCollection[0];
            levelMap.x = 0;
            levelMap.y = 0;
            levelMap.w = map.width*map.tileWidth*gScale;
            levelMap.h = map.height*map.tileHeight*gScale;
//             std::cout << "Map Width Tiles: " << map.width << " Map Tile Width: " << map.tileWidth <<std::endl;
        } else {
            std::cout << "Encountered error loading map file: " << error << std::endl;
        }
*/
//         std::cout << "levelMap.w - camera.w: " << levelMap.w-camera.w << std::endl;
        
        assets->AddTexture("player", "assets/textures/actors/firefighter.png");
        assets->AddTexture("font", "assets/textures/ui/ui-font-cloud-sans.png");
        assets->AddTexture("textBox", "assets/textures/ui/ui-element-cloud.png");
        assets->AddTexture("puppy","assets/textures/actors/beaglepuppy.png");
        assets->AddTexture("collider","assets/textures/ColTex.png");
        
        assets->AddMusicTrack("simonZ","assets/audio/music/sillypuppy.ogg");
        assets->AddMusicTrack("simonZ","assets/audio/music/victory.ogg");
        
        assets->AddSoundClip("bwoop","assets/audio/sfx/bwoop.wav");
        assets->AddSoundClip("bark1","assets/audio/sfx/Bark1.wav");        
        assets->AddSoundClip("bark2","assets/audio/sfx/Bark2.wav");
        
//        map = new Map("terrain",globalScale,16);
//         std::string myText = "Find lost puppies!\nThey need your help!";
        std::string myText = "Press U to Start";
        
        uiTextInstructions.addComponent<TransformComponent>(18,22,138*gScale,20*gScale,gScale);
        uiTextInstructions.addComponent<UITextComponent>("font",myText,8,12,gScale);
        uiTextInstructions.addGroup(groupUI_Layer1);
        
        SDL_Rect myDestRect = SDL_Rect();
        myDestRect.x = 12;
        myDestRect.y = 8;
        myDestRect.w = 160;
        myDestRect.h = 40;
        my9Slice = new UINineSlice("textBox");
        my9Slice->MakeSlices("textBox",32,32,14,16,14,16,myDestRect,globalScale,Game::groupUI_Layer0);
        
//         std::cout << "camera.w " << camera.w << std::endl;
        
        uiInfo.addComponent<TransformComponent>(camera.w/gScale-94,10,72*gScale,96*gScale,gScale);
//         uiInfo.addComponent<UITextComponent>("font", "CollisionHori:    Vert:    Jump:    P.y :    P.dy:    YVec:    ", 8, 12, 1);
        uiInfo.addComponent<UITextComponent>("font", "Player   PTiX:    PTiY:     P.x:    P.y :    coll:    Px2      tson:  ", 8, 12, gScale);
        uiInfo.addGroup(groupUI_Layer3);  

        uiCamXInfo.addComponent<TransformComponent>(camera.w/gScale-48,23,40*gScale,12*gScale,gScale);
        uiCamXInfo.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiCamXInfo.addGroup(groupUI_Layer3);
        
        uiCamYInfo.addComponent<TransformComponent>(camera.w/gScale-48,36,40*gScale,12*gScale,gScale);
        uiCamYInfo.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiCamYInfo.addGroup(groupUI_Layer3);
        
        uiPlayerXInfo.addComponent<TransformComponent>(camera.w/gScale-49,49,40*gScale,12*gScale,gScale);
        uiPlayerXInfo.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiPlayerXInfo.addGroup(groupUI_Layer3);
          
        uiPlayerYInfo.addComponent<TransformComponent>(camera.w/gScale-48,62,40*gScale,12*gScale,gScale);
        uiPlayerYInfo.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiPlayerYInfo.addGroup(groupUI_Layer3);
        
        uiBoundary1Info.addComponent<TransformComponent>(camera.w/gScale-48,75,128*gScale,12*gScale,gScale);
        uiBoundary1Info.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiBoundary1Info.addGroup(groupUI_Layer3);
         
        uiBoundary2Info.addComponent<TransformComponent>(camera.w/gScale-64,88,128*gScale,12*gScale,gScale);
        uiBoundary2Info.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiBoundary2Info.addGroup(groupUI_Layer3);
        
        uiBoundary3Info.addComponent<TransformComponent>(camera.w/gScale-64,101,128*gScale,12*gScale,gScale);
        uiBoundary3Info.addComponent<UITextComponent>("font", "nan", 8, 12, gScale);
        uiBoundary3Info.addGroup(groupUI_Layer3);        
        
        // debug UI box
        SDL_Rect debugBoxRect = SDL_Rect();
        debugBoxRect.x = camera.w-(100*gScale);
        debugBoxRect.y = 4*gScale;
        debugBoxRect.w = 98*gScale;
        debugBoxRect.h = 112*gScale;
        debugBox = new UINineSlice("textBox");
        debugBox->MakeSlices("textBox",32,32,14,16,14,16,debugBoxRect,1,Game::groupUI_Layer2);
        
        //ecs implementation

//         player.addComponent<TransformComponent>(860*globalScale,640*globalScale,22,42,globalScale);
        player.addComponent<TransformComponent>(150*gScale,100*gScale,22,42,globalScale,3); // 180,120
        player.addComponent<SpriteComponent>("player", SpriteComponent::spriteAnimation, "assets/textures/actors/firefighter.json");

//         player.addComponent<PlayerController>(0.0,0.0,false,false,Vector2D().Zero());
        player.addComponent<ColliderComponent>("player",16*globalScale,32*globalScale, true, 2*globalScale,10*globalScale, "collider");
        player.addComponent<KeyboardController>();
        player.addGroup(groupPlayers);
        
        gameScene.addComponent<TileMapComponent>(/*map,*/gScale,player.getComponent<TransformComponent>().position.x+player.getComponent<TransformComponent>().width/2,player.getComponent<TransformComponent>().position.y+player.getComponent<TransformComponent>().height/2); //150,100
        gameScene.addGroup(groupMap);

    // ---------------------------------------
        // Tileson tryout
        // printf("Trying out Tileson, does it work?");
        //
        //
        // //std::unique_ptr<tson::Map> map = t.parse(std::filesystem::path("../../assets/maps/testmap.json"));
        // std::unique_ptr<tson::Map> map = t.parse(tson_files::_ULTIMATE_TEST_JSON, tson_files::_ULTIMATE_TEST_JSON_SIZE);
        //
        // if(map->getStatus() == tson::ParseStatus::OK)
        // {
        //     printf("Tileson successfully parsed the tilemap");
        //     //Gets the layer called "Object Layer" from the "ultimate_demo.json map
        //     //tson::Layer *tileLayer = map->getLayer("Tile Layer 1"); //This is an Object Layer
        //
        //     //Example from a Tile Layer
        //     //I know for a fact that this is a Tile Layer, but you can check it this way to be sure.
        //     // if(tileLayer->getType() == tson::LayerType::TileLayer)
        //     // {
        //     //     //pos = position in tile units
        //     //     printf("Tileson found layer of tiles");
        //     //     /*for(auto &[pos, tileObject] : tileLayer->getTileObjects()) //Loops through absolutely all existing tileObjects
        //     //     {
        //     //         tson::Tileset *tileset = tileObject.getTile()->getTileset();
        //     //         tson::Rect drawingRect = tileObject.getDrawingRect();
        //     //         tson::Vector2f position = tileObject.getPosition();
        //     //     }*/
        //     // }
        //
        // } else {
        //     printf("Failed to load Tileson map");
        // }
        
        playerPosition = Vector2D().Zero();
        pVel = Vector2D().Zero();
        
        puppy.addComponent<TransformComponent>(1024*globalScale,210*globalScale,36,30,globalScale);
        puppy.addComponent<SpriteComponent>("puppy", SpriteComponent::spriteObject);
        puppy.addGroup(groupObjects);
        
    } else {
    		SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Couldn't Initialize SDL: %s", SDL_GetError());
            isRunning = false;
    }
}

// ====== Setup groups
//auto& tiles(manager.getGroup(Game::groupMap));
auto& players(manager.getGroup(Game::groupPlayers));
auto& colliders(manager.getGroup(Game::groupColliders));
auto& objects(manager.getGroup(Game::groupObjects));
// auto& enemies(manager.getGroup(Game::groupEnemies));
// auto& projectiles(manager.getGroup(Game::groupProjectiles));
auto& gui(manager.getGroup(Game::groupUI_Layer0));
auto& uiText(manager.getGroup(Game::groupUI_Layer1));
auto& debugBG(manager.getGroup(Game::groupUI_Layer2));
auto& debugText(manager.getGroup(Game::groupUI_Layer3));

void Game::handleEvents()
{
    SDL_PollEvent(&event);
    switch (event.type)
    {
        case SDL_QUIT:
            isRunning = false;
            break;
        default:
            break;
    }

}

void Game::update()
{
	SDL_Rect playerCol = player.getComponent<ColliderComponent>().collider;
	Vector2D playerPos = player.getComponent<TransformComponent>().position;
    
//     if (gsm->currentState == GameStateManager::ST_INIT)
//     {
//         const char* initText = "Loading...";
//         text->ParseString(initText,12,22,gScale,"init");
//         const char* titleText = "Beagle Rescue";
//         const char* gameOverText = "Game Over";
//     }
    
	if (Mix_PlayingMusic() == 0 && gsm->currentState == GameStateManager::ST_COREGAME)
	{
//		std::cout << "Play Music Now" << std::endl;
// 		Mix_PlayMusic(assets->GetMusicTrack("simonZ"), -1);
	}
	if (Mix_PlayingMusic() != 0 && gsm->currentState != GameStateManager::ST_COREGAME)
    {
        Mix_HaltMusic();
    }
//     if (Mix_PlayChannel(-1, Game::assets->GetSoundClip("bark1"),0) == 0)
//     {
//         Mix_PlayChannel(-1, Game::assets->GetSoundClip("bark1"),0);
//     }

	manager.refresh();
	manager.update();

	for (auto& c : colliders)
	{
		SDL_Rect cCol = c->getComponent<ColliderComponent>().collider;
		if(Collision::AABB(cCol, playerCol))
		{
//             if(!playerIsGrounded){       
//                 player.getComponent<SpriteComponent>().Play("Idle");                
//             }
//             playerIsGrounded = true;
//             player.getComponent<TransformComponent>().position.y = player.getComponent<TransformComponent>().lastSafePos.y;
//             gravityOnPlayer = false;
		}

	}


	
// 	Gravity
//     if (gravityOnPlayer){
//     player.getComponent<TransformComponent>().position.y += 3*gScale;
//     uiJumpInfo.getComponent<UITextComponent>().updateString("true");
//     } else {
//     uiJumpInfo.getComponent<UITextComponent>().updateString("false");
//     }
// 	for(auto& p: projectiles)
// 	{
// 		if(Collision::AABB(player.getComponent<ColliderComponent>().collider, p->getComponent<ColliderComponent>().collider))
// 		{
// 			std::cout << "Projectile hit player" << std::endl;
// 			p->destroy();
// 		}
// 	}

    auto& tileMap = gameScene.getComponent<TileMapComponent>();

	camera.x = player.getComponent<TransformComponent>().position.x - camera.w/2 + player.getComponent<TransformComponent>().width/2;
	camera.y = player.getComponent<TransformComponent>().position.y - camera.h/2 + player.getComponent<TransformComponent>().height/2;
    
    pTileX = (player.getComponent<TransformComponent>().position.x+player.getComponent<TransformComponent>().width/2)/gScale/tileMap.tileWidth;
    pTileY = (player.getComponent<TransformComponent>().position.y+player.getComponent<TransformComponent>().height/2)/gScale/tileMap.tileWidth;
    
    player.getComponent<TransformComponent>().updateTilePosition(pTileX,pTileY);
    
    uiCamXInfo.getComponent<UITextComponent>().updateString(std::to_string((int)player.getComponent<TransformComponent>().tilePos.x));
    uiCamYInfo.getComponent<UITextComponent>().updateString(std::to_string((int)player.getComponent<TransformComponent>().tilePos.y));
    int playerX = player.getComponent<TransformComponent>().position.x;
    int playerY = player.getComponent<TransformComponent>().position.y;

    uiPlayerXInfo.getComponent<UITextComponent>().updateString(std::to_string(playerX));
    uiPlayerYInfo.getComponent<UITextComponent>().updateString(std::to_string(playerY));

    int * foundBoundaries = predictCollisions();
    int max = foundBoundaries[0];
        for (int b=0;b<3;b++){
            if (foundBoundaries[b]>max){
                max = foundBoundaries[b];
            }
        }
    float desiredMovementX = -1*player.getComponent<TransformComponent>().speed*0.016;
//     int desiredMovementY = player.getComponent<TransformComponent>().velocity.y*player.getComponent<TransformComponent>().speed*player.getComponent<TransformComponent>().scale;
//     uiBoundary1Info.getComponent<UITextComponent>().updateString(std::to_string((int)foundBoundaries[0]));
//     uiBoundary1Info.getComponent<UITextComponent>().updateString(std::to_string((float)desiredMovementX));
    if (playerIsGrounded) {
        uiBoundary1Info.getComponent<UITextComponent>().updateString("yes");
    } else {
        uiBoundary1Info.getComponent<UITextComponent>().updateString("no");
    }
//     uiBoundary2Info.getComponent<UITextComponent>().updateString(std::to_string((int)foundBoundaries[1]));
    float difference = player.getComponent<TransformComponent>().position.x+desiredMovementX;
        uiBoundary2Info.getComponent<UITextComponent>().updateString(std::to_string((float)difference));
    uiBoundary3Info.getComponent<UITextComponent>().updateString(std::to_string((int)(max*gScale)));

//     uiBoundary3Info.getComponent<UITextComponent>().updateString(std::to_string((int)foundBoundaries[2]));    

    playerPosition.x = playerX;
    playerPosition.y = playerY;
    
    pVel.x = player.getComponent<TransformComponent>().velocity.x;
    pVel.y = player.getComponent<TransformComponent>().velocity.y;
    
	if(camera.x < 0)
		camera.x = 0;
	if (camera.y < 0)
		camera.y = 0;
	if (camera.x > levelMap.w-camera.w)
		camera.x = levelMap.w-camera.w;
	if (camera.y > levelMap.h-camera.h)
		camera.y = levelMap.h-camera.h;
    if (Game::debugCollisionBoxes)
    {
        for (auto& c: colliders)
        {
            c->getComponent<ColliderComponent>().hidden = false;
        }
        for (auto& p: players)
        {
            p->getComponent<ColliderComponent>().hidden = false;
        }
    } else {
        for (auto& c: colliders)
        {
            c->getComponent<ColliderComponent>().hidden = true;
        }
        for (auto& p: players)
        {
            p->getComponent<ColliderComponent>().hidden = true;
        }
    }
}

void Game::render()
{
	SDL_RenderClear(renderer);
//     if (gsm->currentState==GameStateManager::ST_COREGAME)
//     {
//       printf("Core Game state\n");  
//     }
/*    for (auto& t : tiles)
	{
		t->draw();
	} */

	/*for (auto& c : colliders)
	{
		c->draw();
	}*/

    for (auto& o : objects)
    {
        o->draw();
    }	
//     
// 	for (auto& e : enemies)
// 	{
// 		e->draw();
// 	}

	for (auto& p : players)
	{
		p->draw();
	}
    if (gsm->currentState==GameStateManager::ST_TITLESCREEN||gsm->currentState==GameStateManager::ST_INIT||gsm->currentState==GameStateManager::ST_GAMEOVER){
        for (auto& guiElement : gui)
        {
            guiElement->draw();
        }
        for (auto& text : uiText)
        {
            text->draw();
        }
    }
    if (debugMenu)
    {
        for (auto& guiElement : debugBG)
        {
            guiElement->draw();
        }
        for (auto& text : debugText)
        {
            text->draw();
        }
    }
    
    SDL_RenderPresent(renderer);
}

void Game::clean()
{
	SDL_DestroyWindow(window);
	SDL_DestroyRenderer(renderer);
	IMG_Quit();
	Mix_Quit();
	SDL_Quit();
	printf("Game Cleaned\n");
}

void Game::drawLine(Vector2D srcpt, Vector2D destpt, int red, int green, int blue)
{
    SDL_SetRenderDrawColor(renderer, red, green, blue, 200);
    SDL_RenderDrawLine(renderer, srcpt.x, srcpt.y, destpt.x, destpt.y);
}

int * Game::predictCollisions(){
    static int boundaries[3];
//     ===== LEFT =====
    /*if (player.getComponent<TransformComponent>().velocity.x<0){
//           ====== For Each Row ====
        int i = 0;
               for (int r=Game::pTileY-1;r<=Game::pTileY+1;r++){
//               ====== For Each Tile (Column) =====
                if(r<0){r=0;}
                    for (int c=Game::pTileX;c>Game::pTileX-Game::camera.w/gameScene.getComponent<TileMapComponent>().tileWidth;c--){
                        if(c<0){c=0;}
                        if(gameScene.getComponent<TileMapComponent>().colliders[r][c]>0){
                            boundaries[i] = c*gameScene.getComponent<TileMapComponent>().tileWidth+gameScene.getComponent<TileMapComponent>().tileWidth;
                            i++;
                            break;
                        }
                    }
                }
    }*/
    /*
//     ====== RIGHT ====
    if (player.getComponent<TransformComponent>().velocity.x>0){
//           ====== For Each Row ====
        int i = 0;
               for (int r=Game::pTileY-1;r<=Game::pTileY+1;r++){
                   if(r<0){r=0;}
//               ====== For Each Tile (Column) =====    
                    for (int c=Game::pTileX;c<Game::pTileX+Game::camera.w/gameScene.getComponent<TileMapComponent>().tileWidth;c++){
                        if(c<0){c=0;}
                        if(gameScene.getComponent<TileMapComponent>().colliders[r][c]>0){
                            boundaries[i] = c*gameScene.getComponent<TileMapComponent>().tileWidth;
                            i++;
                            break;
                        }
                    }
                }    
    }
//     ===== UP ====
    if (player.getComponent<TransformComponent>().velocity.y<0){
//           ====== For Each Column ====
        int i = 0;
               for (int c=Game::pTileX-1;c<=Game::pTileX+1;c++){
                   if(c<0){c=0;}
//               ====== For Each Tile (Row) =====    
                    for (int r=Game::pTileY;r>Game::pTileY-Game::camera.h/gameScene.getComponent<TileMapComponent>().tileWidth;r--){
                        if(r<0){r=0;}
                        if(gameScene.getComponent<TileMapComponent>().colliders[r][c]>0){
                            boundaries[i] = r*gameScene.getComponent<TileMapComponent>().tileWidth+gameScene.getComponent<TileMapComponent>().tileWidth;
                            i++;
                            break;
                        }
                    }
                }    
        
    }
//   ===== DOWN ====
    if (player.getComponent<TransformComponent>().velocity.y>0){
//           ====== For Each Column ====
        int i = 0;
               for (int c=Game::pTileX-1;c<=Game::pTileX+1;c++){
                   if(c<0){c=0;}
//               ====== For Each Tile (Row) =====    
                    for (int r=Game::pTileY;r<Game::pTileY+Game::camera.h/gameScene.getComponent<TileMapComponent>().tileWidth;r++){
                        if(r<0){r=0;}
                        if(gameScene.getComponent<TileMapComponent>().colliders[r][c]>0){
                            boundaries[i] = r*gameScene.getComponent<TileMapComponent>().tileWidth+gameScene.getComponent<TileMapComponent>().tileWidth;
                            i++;
                            break;
                        }
                    }
                }    
    }
    */
    return boundaries;
}
