#include <stdio.h>
#include <time.h>
#include "main.h"
#include "text.h"
#include "sync_table.h"

void loadConfig();

Fader g_blackFade;
TextureManager g_textureManager;
SoundManager g_soundManager;
MusicManager g_musicManager;
SpriteManager g_spriteManager;
ParticleManager g_particleManager;
DataManager g_dataManager;

sf::RenderTexture rtBuffer1;
sf::Sprite buffer1;
sf::RenderTexture rtBuffer2;
sf::Sprite buffer2;
sf::RenderTexture rtBuffer3;
sf::Sprite buffer3;

sf::Shader shader_shatter;
sf::Shader shader_rainbow;

int scene00(float delta, sf::RenderTarget* target);

//int scene = -666;
int scene = 0;

bool run = true;
bool keyb_sync = false;
float sync_time = 0;
std::string windowTitle = "Papa Keno - YAWD";
bool fullscreen = false;
bool vsync = true;
int fps = 0;
bool showFps = false;
sf::RenderWindow* renderWindow;

void loadConfig() {
	JSON config;
	std::ifstream file("config.json");
	if(!file.is_open()) {
		std::cerr << "Config.json missing." << std::endl;
		return;	
	}

	try {
		file >> config;
	}
	catch (JSON::exception& e) {
		std::cerr << "config.json: Error while parsing JSON." << std::endl << e.what() << std::endl;
		return;
	}

	file.close();

	if (config.contains("fullscreen")) {
		fullscreen = config["fullscreen"];
	}	
	if (config.contains("sound") && !config["sound"]) {
		g_soundManager.mute();
	}
	if (config.contains("vsync")) {
		vsync = config["vsync"];
	}
	if (config.contains("show_fps")) {
		showFps = config["show_fps"];
	}

	#ifdef VERBOSE_LOG
	std::cout << "config.json: Loaded configuration." << std::endl;
	#endif

}

	float gen_var = 0.5f;

int main() {
	loadConfig();

	sf::RenderWindow window;
	renderWindow = &window;

	if (fullscreen) {
		window.create(sf::VideoMode(1920, 1080), windowTitle, sf::Style::Fullscreen);
	} else {
		window.create(sf::VideoMode(1920, 1080), windowTitle);
	}
	
	window.setFramerateLimit(60);
	window.setVerticalSyncEnabled(vsync);
	window.setActive(true);
	window.setMouseCursorVisible(false);
	window.clear(sf::Color(0, 0, 0));
	window.display();

	rtBuffer1.create(1920, 1080);
	buffer1.setTexture(rtBuffer1.getTexture());

	rtBuffer2.create(1920, 1080);
	buffer2.setTexture(rtBuffer2.getTexture());

	rtBuffer3.create(1920, 1080);
	buffer3.setTexture(rtBuffer3.getTexture());

	g_textureManager.loadFromFile("assets/textures.json");

	g_spriteManager.loadFromFile("assets/sprites.json");

	g_soundManager.loadFromFile("assets/sounds.json");

	g_musicManager.loadFromFile("assets/music.json");

	g_particleManager.loadFromFile("assets/particles.json");
	
	g_dataManager.loadFromFile("assets/data.json");

	g_blackFade.setSprite(g_spriteManager.getSprite("black"));

	if (!shader_shatter.loadFromFile("assets/shaders/scattershot.frag", sf::Shader::Fragment)) {
		std::cerr << "Failed to load shader scattershot\n";
	}

	if(!shader_rainbow.loadFromFile("assets/shaders/passthrough.vert", "assets/shaders/peephole.frag")) {
		std::cerr << "Unable to load the peephole shader :(" << std::endl;
		return 0;
	}
	shader_rainbow.setUniform("currentTexture", sf::Shader::CurrentTexture);
	shader_rainbow.setUniform("bgTexture", *g_textureManager.getTexture("rainbow"));	


	sf::Clock clock;
	float delta;

	bool holdingSpace = false;

	clock.restart();	

	while (run) {
		delta = clock.restart().asSeconds();

		rtBuffer1.clear(sf::Color(0, 0, 0, 0));
		rtBuffer2.clear(sf::Color(0, 0, 0, 0));
		rtBuffer3.clear(sf::Color(0, 0, 0, 0));

		if(keyb_sync) {
			sync_time += delta;			
		}

		if(sf::Keyboard::isKeyPressed(sf::Keyboard::Escape)) {
			run = false;
		}

		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
			gen_var -= 1 * delta;
			std::cout << gen_var << std::endl;
		}

		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) {
			gen_var += 1 * delta;
			std::cout << gen_var << std::endl;
		}

		if(sf::Keyboard::isKeyPressed(sf::Keyboard::Space) && !holdingSpace) {
			std::cout << "@" << sync_time << std::endl;
			keyb_sync = true;
			holdingSpace = true;
		}

		if(!sf::Keyboard::isKeyPressed(sf::Keyboard::Space) && holdingSpace) {
			holdingSpace = false;
		}
		
		sf::Event event;
		while (window.pollEvent(event)) {
			if (event.type == sf::Event::Closed) {
				run = false;
			}                            
		}

		g_blackFade.update(delta);
		renderWindow->clear(sf::Color(0, 0, 0, 0));

		switch(scene) {			
			case -666:
				//scene = sceneTest(delta, renderWindow);
			break;
			case 0:
				scene = scene00(delta, renderWindow);
			break;
			case -1:
				run = false;
			break;
		}

    	
		g_blackFade.draw(renderWindow); 
    	renderWindow->display();
	}

	window.close();
	return EXIT_SUCCESS;
}

float sineBetween(float min, float max, float t) {
    float halfRange = (max - min) / 2;
    return min + halfRange + sin(t) * halfRange;
}

float cosBetween(float min, float max, float t) {
    float halfRange = (max - min) / 2;
    return min + halfRange + cos(t) * halfRange;
}

sf::IntRect wallRect(0, 0, 2200, 1238);

float WALLSPEED = 200.f;

const int EFFECT_SCATTER = 1;
const int EFFECT_ROTATE_IN = 2;
const int EFFECT_RAINBOW = 3;

class Decal {
public:
	float t;
	float x;
	float y;
	float delay;
	float scale;
	float rot;
	float alpha;
	int effect;
	bool active;

	sf::Sprite* sprite;
	Tween scaleT;
	Tween rotT;
	Tween alphaT;
	Tween yT;

	Decal(sf::Sprite* sprite, float x, float y, float delay, int effect, bool fadeIn = false, bool rotateIn = false, bool scaleIn = false) {
		this->sprite = sprite;
		this->x = x;
		this->y = y;

		this->delay = delay;
		this->alpha = 1;
		this->rot = 0;
		this->scale = 1;

		this->active = false;

		this->t = 0;

		this->effect = effect;

		if(fadeIn) {
			alphaT.setup(0, 1, 0.45, Tween::LINEAR, 0);
			alphaT.setActive();
		}

		if(rotateIn) {
			rotT.setup(-20, 0, 0.45, Tween::EASE_OUT_BACK, 0);
			rotT.setActive();
		}

		if(scaleIn) {
			scaleT.setup(0, 1, 0.45, Tween::EASE_OUT_BACK, 0);
		}
	}

	void update(float delta) {

		this->x -= WALLSPEED * delta;		

		if(this->x < 960 && !this->active) {
			this->active = true;
		}

		if(!this->active) {
			return;
		}

		delay -= delta;
		if(delay > 0) {
			return;
		}

		if(alphaT.isActive()) {
			alphaT.update(delta);
			this->alpha = alphaT.getValue();
		} 

		if(rotT.isActive()) {
			rotT.update(delta);
			this->rot = rotT.getValue();
		}

		if(scaleT.isActive()) {
			scaleT.update(delta);
			this->scale = scaleT.getValue();
		}

		

		this->sprite->setPosition(this->x, this->y);
		this->sprite->setColor(sf::Color(255, 255, 255, this->alpha * 255));
		this->sprite->setRotation(this->rot);
		this->sprite->setScale(scale, scale);

		this->t += delta;
	}

	void draw(sf::RenderTarget* target) {

		if(!this->active) {
			return;
		}

		if(delay > 0) {
			return;
		}

		switch(effect) {
			case EFFECT_SCATTER:
				shader_shatter.setUniform("image", *this->sprite->getTexture());
				target->draw(*this->sprite, &shader_shatter );
			break;

			case EFFECT_RAINBOW:
				target->draw(*this->sprite, &shader_rainbow );
			break;

			default:
				target->draw(*this->sprite);
			break;
		}
		
	}
};

std::vector<Decal*> decals;

void createDecalsFromData(std::vector<DataEntry> data) {
	std::cout << "Creating decals ..." << std::endl;

	int i = 0;

	for (const auto& e : data) {

		sf::Sprite* sprite = g_spriteManager.getSprite(e.spriteId);
		float height = sprite->getGlobalBounds().height;

		std::cout << e.spriteId << "    height: " << height << std::endl;

		bool scale = false;
		if(i%3) scale = true;

		int effect = 0;

		if(
			e.spriteId == "another" ||
			e.spriteId == "brickwall-t" ||
			e.spriteId == "demo" ||
			e.spriteId == "yet" ||
			e.spriteId == "shane" ||
			e.spriteId == "creativity" ||
			e.spriteId == "identity" ||
			e.spriteId == "text1" ||
			e.spriteId == "text2" ||
			e.spriteId == "text3"		
		) {
			effect = EFFECT_RAINBOW;
		}

		if(
			e.spriteId == "flower" ||
			e.spriteId == "finger" ||
			e.spriteId == "facerape" || 
			e.spriteId == "hypen2"
		) {
			effect = EFFECT_SCATTER;
		}

		decals.push_back(new Decal( sprite, e.x, e.y - (height/2) , 0.f, effect, true, true, scale ));

		std::cout << "Created decal " << e.spriteId << std::endl;

		i++;
    }

	std::cout << "----------------" << std::endl;

	//bajdecals.push_back(new Decal(a, 0, 100, 1.0f, EFFECT_ROTATE_IN, true));
}

int scene00(float delta, sf::RenderTarget* target) {
	static int state = 0;
	static float time = 0;
	static bool first = true;	
	static sf::Sprite* brickwall = nullptr;
	static sf::Sprite* yawd = nullptr;
	static float wallScroll = 0.0f;
	static float wallRot = 0.0f;
	
	time += delta;

	// std::cout << time << std::endl;

	static bool timetoquit = false;

	if(time > 115 && !timetoquit) {
		timetoquit = true;
		g_blackFade.fadeTo(4.0f);
	}

	if(time > 121 && timetoquit) {
		return -1;
	}

	if(first) {

		g_blackFade.fadeFrom(2.0f);

		g_musicManager.playSong("song");

		first = false;
		brickwall = g_spriteManager.getSprite("brickwall");
		//yawd = g_spriteManager.getSprite("yawd");

		createDecalsFromData(g_dataManager.data);

		brickwall->setOrigin(2200/2, 1238/2);
		brickwall->setPosition(2200/2, 1238/2);
				
		buffer1.setOrigin(1920 / 2, 1080 / 2);
		buffer1.setPosition(1920 / 2, 1080 / 2);
		buffer1.setScale(1.2f, 1.2f);

		//shader_shatter.setUniform("image", *g_textureManager.getTexture("yawd"));

		//decals.push_back(new Decal(yawd, -1920, 0, 1.0f, EFFECT_SCATTER, false));		
	}

	wallScroll += WALLSPEED * delta;

	wallRot = sineBetween(-5, 5, time);

	buffer1.setRotation( wallRot );
	
	wallRect.left = wallScroll;
    brickwall->setTextureRect(wallRect);

	rtBuffer1.draw(*brickwall);

	shader_shatter.setUniform("iTime", sineBetween(0.75, 1, time * 8));
	shader_rainbow.setUniform("xOffset", time);
	shader_rainbow.setUniform("yOffset", time);

	for (auto& d : decals) {
		d->update(delta);
		d->draw(&rtBuffer1);
	}

	rtBuffer1.display();

	target->draw(buffer1);

	return 0;
}

