// Written for LD38 by DusteD
// License: WTFPL
// For fun, it's okay if it's bad.

#include <iostream>
#include <SDL2/SDL.h>
#include <SDL2/SDL_image.h>

#include "ball.hpp"

typedef struct
{
    int screenWidth;
    int screenHeight;
    bool fullscreen;
    bool clear;
    bool pause;
    bool cheat;
} config_t;

// Draw score
const int charw = 24;
const int chard = 6;
const int charh = 37;
void drawNum(SDL_Renderer *ren, SDL_Texture *texNums, char *numStr, int len, int x, int y)
{
    for (int i = 0; i < len; i++)
    {
	if (numStr[i] >= '0' && numStr[i] <= '9')
	{
	    int num = numStr[i] - 48;

	    SDL_Rect srcNumRect = {
		charw * num, 0, charw, charh};
	    SDL_Rect dstNumRect = {
		x + (charw + chard) * i, y,
		charw, charh};
	    SDL_RenderCopy(ren, texNums, &srcNumRect, &dstNumRect);
	}
    }
}

int main(int, char **)
{

	std::cout <<std::endl<< "Bounceball v 0.1" << std::endl << "Copyleft WTFPL 2017 DusteD" << std::endl << "For LudumDare 38, Enjoy!" << std::endl<< std::endl;
    config_t config;

    config.fullscreen = true;
    config.screenWidth = 1024;
    config.screenHeight = 768;
    config.clear = true;
    config.pause = false;
    config.cheat = false;
    int sdlFullscreenFlag = 0;

    // Initialize SDL2
    if (SDL_Init(SDL_INIT_VIDEO) != 0)
    {
	std::cout << "SDL_Init Error: " << SDL_GetError() << std::endl;
	return 1;
    }

    // If configured to fullscreen, get the current desktop resolution, and use that instead.
    if (config.fullscreen)
    {
	SDL_DisplayMode modeInfo;
	SDL_GetCurrentDisplayMode(0, &modeInfo);
	config.screenWidth = modeInfo.w;
	config.screenHeight = modeInfo.h;
	sdlFullscreenFlag = SDL_WINDOW_FULLSCREEN_DESKTOP;
    }

    // Initialize the SDL_Image library for loading PNG files
    if ((IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG) != IMG_INIT_PNG)
    {
	std::cout << "IMG_Init error:" << IMG_GetError() << std::endl;
	return 1;
    }

    // Create a window
    SDL_Window *win = SDL_CreateWindow("Bounce!", 100, 100, config.screenWidth, config.screenHeight, SDL_WINDOW_SHOWN | sdlFullscreenFlag);
    if (win == nullptr)
    {
	std::cout << "SDL_CreateWindow Error: " << SDL_GetError() << std::endl;
	return 1;
    }

    // Create a renderer (That's what draws stuff in the window)
    SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
    if (ren == nullptr)
    {
	std::cout << "SDL_CreateRenderer Error: " << SDL_GetError() << std::endl;
	return 1;
    }

    SDL_Surface *img = IMG_Load("./d/ball.png");
    SDL_Surface *imgb = IMG_Load("./d/plr.png");

    SDL_Surface *imgStart = IMG_Load("./d/start.png");
    SDL_Texture *texStart = SDL_CreateTextureFromSurface(ren, imgStart);
    SDL_Surface *imgEnd = IMG_Load("./d/end.png");
    SDL_Texture *texEnd = SDL_CreateTextureFromSurface(ren, imgEnd);
    SDL_Surface *imgQuit = IMG_Load("./d/quit.png");
    SDL_Texture *texQuit = SDL_CreateTextureFromSurface(ren, imgQuit);
    SDL_Surface *imgNums = IMG_Load("./d/nums.png");
    SDL_Texture *texNums = SDL_CreateTextureFromSurface(ren, imgNums);
    SDL_Surface *imgShld = IMG_Load("./d/shld.png");
    SDL_Texture *texShld = SDL_CreateTextureFromSurface(ren, imgShld);
    SDL_Surface *imgRemBall = IMG_Load("./d/remball.png");
    SDL_Texture *texRemBall = SDL_CreateTextureFromSurface(ren, imgRemBall);
    SDL_Surface *imgNom = IMG_Load("./d/nom.png");
    SDL_Texture *texNom = SDL_CreateTextureFromSurface(ren, imgNom);

    // Make the image into a texture, suitable for rendering with our renderer
    SDL_Texture *tex = SDL_CreateTextureFromSurface(ren, img);

    SDL_Texture *texb = SDL_CreateTextureFromSurface(ren, imgb);

    const int numBalls = 5;
    Ball *balls[numBalls];
    for (int i = 0; i < numBalls; ++i)
    {
	balls[i] = new Ball(tex, img->clip_rect, 100 + (i * (img->clip_rect.w * 2)), 100, 20.8 + i, 2 + (i / 2.0));
    }

    Ball *plr = new Ball(texb, imgb->clip_rect, 200, 400, 0, 0);

    Ball *remBall = new Ball(texRemBall, imgRemBall->clip_rect, 400, 200, 7, 2);
    Ball *shld = new Ball(texShld, imgShld->clip_rect, 200, 500, 4, 5);
    Ball *shldIco = new Ball(texShld, imgShld->clip_rect, 0, 0, 0, 0);

    const int numNoms = 25;
    Ball *noms[numNoms];
    int nomCountDowns[numNoms];
    int nomsAge[numNoms];
    for (int i = 0; i < numNoms; i++)
    {
	noms[i] = new Ball(texNom, imgNom->clip_rect, 0, 0, 0, 0);
    }

    SDL_Event event;

    // This is the main loop
    bool run = true;
    SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
    //	SDL_GL_SetSwapInterval(0);
    float speed = 0.05;
    const float MAX_VEL = 10.0;
    const float DAMP = 1.08;
    const float INC = 2.0;
    int impulseX = 0;
    int impulseY = 0;

    float dangerLevel = 0.0;

    SDL_Texture *scrTex = SDL_CreateTexture(ren, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, config.screenWidth, config.screenHeight);

    SDL_Rect overlay = {
	.x = 0,
	.y = 0,
	.w = config.screenWidth,
	.h = config.screenHeight};

    enum
    {
	GAME_START,
	GAME_RUN,
	GAME_OVER,
	GAME_QUIT
    };

    uint32_t gameBeginTime;
    uint32_t gameTime;
    int state = GAME_START;
    char *score = (char *)malloc(128);
    char *time = (char *)malloc(128);
    char *best = (char *)malloc(256);

    uint32_t bestGameTime = 0;
    int bestScore = 0;

    score[0] = 0;
    time[0] = 0;
    best[0] = 0;

    SDL_Rect msgMid = {
	.x = (int)(config.screenWidth / 2.0 - imgStart->clip_rect.w / 2),
	.y = (int)(config.screenHeight / 2.0 - imgStart->clip_rect.h / 2),
	.w = imgStart->clip_rect.w,
	.h = imgStart->clip_rect.h};

    int plScore = 0;
    uint32_t lastGameTime = 0;
    uint32_t stillTime = 0;
    int bonus = 0;

    int tempBallReductionCountDown[numBalls];
    int numShields = 0;

    int nextShld = 12;
    int nextRemBall = 0;

    int shldAge = 0;
    int nextBallAge = 0;

    int cheat = 0;

    SDL_ShowCursor(SDL_DISABLE);
    while (run)
    {

	SDL_SetRenderTarget(ren, NULL);
	switch (state)
	{
	case GAME_START:
	    SDL_RenderCopy(ren, texStart, NULL, &msgMid);
	    gameBeginTime = SDL_GetTicks();
	    lastGameTime = 0;
	    for (int i = 0; i < numBalls; i++)
	    {
		balls[i]->posx = 100 + (i * (balls[i]->rect.w * 2));
		balls[i]->posy = 100;
		balls[i]->velx = 20.8 + i;
		balls[i]->vely = 2 + (i / 2.0);
	    }
	    plr->velx = 0;
	    plr->vely = 0;
	    plr->posx = 300;
	    plr->posy = 400;
	    if (impulseX || impulseY)
	    {
		state = GAME_RUN;
		plScore = 0;
		score[0] = '0';
		score[1] = 0;
		stillTime = 0;
		for (int i = 0; i < numBalls; i++)
		{
		    tempBallReductionCountDown[i] = 0;
		}

		remBall->posx = 400;
		remBall->posy = 200;
		shld->posx = 200;
		shld->posy = 500;

		numShields = 0;
		nextShld = 2;
		shldAge = 0;
		nextBallAge = 0;
		nextRemBall = 1;
		std::srand(42);
		for (int i = 0; i < numNoms; i++)
		{
		    nomCountDowns[i] = std::rand() % 90;
		}
	    }
	    break;
	case GAME_RUN:

	    gameTime = SDL_GetTicks() - gameBeginTime;
	    if (gameTime > lastGameTime + 1000)
	    {
		lastGameTime = gameTime;
		plScore += 10;

		for (int i = 0; i < numBalls; i++)
		{
		    if (tempBallReductionCountDown[i])
		    {
			tempBallReductionCountDown[i]--;
		    }
		}

		for (int i = 0; i < numNoms; i++)
		{
		    if (nomCountDowns[i])
		    {
			nomCountDowns[i]--;
			if (!nomCountDowns[i])
			{
			    noms[i]->velx = std::rand() % 30 - 15;
			    noms[i]->vely = std::rand() % 30 - 15;
			    noms[i]->posx = std::rand() % config.screenWidth;
			    noms[i]->posy = std::rand() % config.screenHeight;
			    nomsAge[i] = 0;
			}
		    }
		    else
		    {
			nomsAge[i]++;
			if (nomsAge[i] > 4)
			{
			    nomCountDowns[i] = std::rand() % 90 + 1;
			}
		    }
		}

		stillTime += 1;
		bonus = 0;
		if (stillTime > 5)
		    bonus += 20;
		if (stillTime > 8)
		    bonus += 10;
		if (stillTime > 10)
		    bonus += 8;
		if (stillTime > 12)
		    bonus += 11;
		if (stillTime > 18)
		    bonus += 25;
		if (stillTime > 20)
		    bonus += 50;
		if (stillTime > 25)
		    bonus += 1;
		plScore += bonus;

		if (nextShld)
		{
		    nextShld--;
		    if (!nextShld)
		    {
			shld->posx = std::rand() % config.screenWidth;
			shld->posy = std::rand() % config.screenHeight;
			shldAge = 0;
		    }
		}
		else
		{
		    shldAge++;
		    if (shldAge > 6)
		    {
			nextShld = 6;
		    }
		}

		if (nextRemBall)
		{
		    nextRemBall--;
		    if (!nextRemBall)
		    {
			remBall->posx = std::rand() % config.screenWidth;
			remBall->posy = std::rand() % config.screenHeight;
			nextBallAge = 0;
		    }
		}
		else
		{
		    nextBallAge++;
		    if (nextBallAge > 5)
		    {
			nextRemBall = 4;
		    }
		}
	    }
	    speed = 0.045 + (gameTime / 1000) / (60 * 15.0);
	    break;
	case GAME_OVER:
	    SDL_RenderCopy(ren, texEnd, NULL, &msgMid);
	    if (impulseX || impulseY)
	    {
		state = GAME_START;
		impulseX = 0;
		impulseY = 0;
	    }
	    break;
	case GAME_QUIT:
	    SDL_RenderCopy(ren, texQuit, NULL, &msgMid);
	    if (impulseX || impulseY)
	    {
		state = GAME_START;
	    }
	    break;
	}
	// Handle events
	while (SDL_PollEvent(&event))
	{
	    switch (event.type)
	    {
	    case SDL_KEYDOWN:
		switch (event.key.keysym.sym)
		{
		case SDLK_ESCAPE:
		case SDLK_q:
		    if (state == GAME_QUIT)
			run = false;
		    else
			state = GAME_QUIT;
		    break;
		case SDLK_c:
		    config.clear = !config.clear;
		    break;
		case SDLK_p:
		    if (config.cheat)
			config.pause = !config.pause;
		    break;
		case SDLK_UP:
		case SDLK_w:
		    impulseY = -1;
		    break;
		case SDLK_DOWN:
		case SDLK_s:
		    impulseY = 1;
		    break;

		case SDLK_LEFT:
		case SDLK_a:
		    impulseX = -1;
		    break;
		case SDLK_RIGHT:
		case SDLK_d:
		    impulseX = 1;
		    break;
		case SDLK_l:
		    if (cheat == 0)
		    {
			cheat++;
		    }
		    else
			cheat = 0;

		    break;
		case SDLK_u:
		    if (cheat == 1)
		    {
			cheat++;
		    }
		    else
			cheat = 0;

		    break;
		case SDLK_k:
		    if (cheat == 2)
		    {
			cheat++;
		    }
		    else
			cheat = 0;

		    break;
		case SDLK_e:
		    if (cheat == 3)
		    {
			config.cheat = true;
		    }
		    else
			cheat = 0;
		    break;
		}
		break;
	    case SDL_KEYUP:
		switch (event.key.keysym.sym)
		{
		case SDLK_UP:
		case SDLK_w:
		    if (impulseY < 0)
			impulseY = 0;
		    break;
		case SDLK_DOWN:
		case SDLK_s:
		    if (impulseY > 0)
			impulseY = 0;
		    break;

		case SDLK_LEFT:
		case SDLK_a:
		    if (impulseX < 0)
			impulseX = 0;
		    break;
		case SDLK_RIGHT:
		case SDLK_d:
		    if (impulseX > 0)
			impulseX = 0;
		    break;
		}
		break;
	    case SDL_QUIT:
		run = false;
		break;
	    }
	}

	if (state == GAME_RUN)
	{

	    SDL_SetRenderTarget(ren, scrTex);

	    if (dangerLevel > 0.1)
	    {
		SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_MOD);
		int lvl = (15.0 * dangerLevel);
		SDL_SetRenderDrawColor(ren, 255 - lvl, 0, 0, 1);
	    }
	    else
	    {
		SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
		SDL_SetRenderDrawColor(ren, 0, 0, 0, 16);
	    }
	    SDL_RenderFillRect(ren, &overlay);

	    // Run behaviour
	    if (!config.pause)
	    {
		for (int i = 0; i < numBalls; i++)
		{
		    if (tempBallReductionCountDown[i])
			continue;
		    balls[i]->move(speed);
		    balls[i]->reactInsideBox(0, 0, config.screenWidth, config.screenHeight);
		}
	    }

	    if (impulseX < 0)
	    {
		plr->velx -= INC;
		if (plr->velx < -MAX_VEL)
		    plr->velx = -MAX_VEL;
	    }
	    else if (impulseX > 0)
	    {
		plr->velx += INC;
		if (plr->velx > MAX_VEL)
		    plr->velx = MAX_VEL;
	    }
	    else
	    {
		plr->velx /= DAMP;
	    }

	    if (impulseY < 0)
	    {
		plr->vely -= INC;
		if (plr->vely < -MAX_VEL)
		    plr->vely = -MAX_VEL;
	    }
	    else if (impulseY > 0)
	    {
		plr->vely += INC;
		if (plr->vely > MAX_VEL)
		    plr->vely = MAX_VEL;
	    }
	    else
	    {
		plr->vely /= DAMP;
	    }

	    if (impulseX || impulseY)
	    {
		stillTime = 0;
	    }

	    plr->reactInsideBox(0, 0, config.screenWidth, config.screenHeight);
	    plr->move(1.0);

	    //Draw
	    for (int i = 0; i < numBalls; i++)
	    {
		if (!tempBallReductionCountDown[i])
		    balls[i]->draw(ren);
		else
		{
		    //FIsker
		    SDL_SetTextureAlphaMod(tex, 4);
		    balls[i]->draw(ren);
		    SDL_SetTextureAlphaMod(tex, 255);
		}
	    }
	    //Powerups
	    if (!nextShld)
	    {
		shld->reactInsideBox(0, 0, config.screenWidth, config.screenHeight);
		shld->move(0.015);
		shld->draw(ren);
		if (shld->distTo(plr) < shld->rect.w / 2 + plr->rect.w / 2)
		{
		    SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
		    SDL_SetRenderDrawColor(ren, 0, 255, 0, 64);
		    SDL_RenderFillRect(ren, &overlay);
		    numShields++;
		    nextShld = std::rand() % 30 + 5;
		}
	    }

	    if (!nextRemBall)
	    {
		remBall->reactInsideBox(0, 0, config.screenWidth, config.screenHeight);
		remBall->move(0.0015);
		remBall->draw(ren);
		if (remBall->distTo(plr) < remBall->rect.w / 2 + plr->rect.w / 2)
		{
		    SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
		    SDL_SetRenderDrawColor(ren, 0, 255, 0, 32);
		    SDL_RenderFillRect(ren, &overlay);
		    nextRemBall = std::rand() % 30 + 5;
		    for (int i = 0; i < numBalls; i++)
		    {
			if (!tempBallReductionCountDown[i])
			{
			    tempBallReductionCountDown[i] = 15 - i;
			    break;
			}
		    }
		}
	    }

	    //noms
	    for (int i = 0; i < numNoms; i++)
	    {
		if (!nomCountDowns[i])
		{
		    noms[i]->move(0.025);
		    noms[i]->reactInsideBox(0, 0, config.screenWidth, config.screenHeight);
		    noms[i]->draw(ren);
		    if (noms[i]->distTo(plr) < noms[i]->rect.w / 2 + plr->rect.w / 2)
		    {
			SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
			SDL_SetRenderDrawColor(ren, 0, 255, 0, 16);
			SDL_RenderFillRect(ren, &overlay);
			nomCountDowns[i] = std::rand() % 100 + 2;
			plScore += 250;
		    }
		}
	    }

	    plr->draw(ren);
	    if (numShields)
	    {
		shldIco->posx = plr->posx + plr->rect.w / 2.0 - shldIco->rect.w / 2.0;
		shldIco->posy = plr->posy + plr->rect.w / 2.0 - shldIco->rect.w / 2.0;

		shldIco->draw(ren);
	    }
	    dangerLevel = 0;
	    for (int i = 0; i < numBalls; i++)
	    {

		if (tempBallReductionCountDown[i])
		    continue;

		float pd = balls[i]->distTo(plr);
		if (pd < balls[i]->rect.w / 2.0 + plr->rect.w / 2.0 + 25.0)
		{
		    float crit = balls[i]->rect.w / 2.0 + plr->rect.w / 2.0;
		    float dl = (1.0 / (pd)) * crit;

		    plScore += dl * 10;

		    if (dl > dangerLevel)
			dangerLevel = dl;

		    if (pd < crit - 20)
		    {
			if (numShields)
			{
			    if (!config.cheat)
				numShields--;
			    SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
			    SDL_SetRenderDrawColor(ren, 255, 240, 200, 10);
			    SDL_RenderFillRect(ren, &overlay);

			    //find angle between them
			    float xa = plr->posx - balls[i]->posx;
			    float ya = plr->posy - balls[i]->posy;

			    float ra = std::atan2(ya, xa);

			    xa = cos(ra);
			    ya = sin(ra);

			    float dist = pd - (balls[i]->rect.w / 2.0 + plr->rect.w / 2.0 + 10);

			    balls[i]->posx += xa * (dist / 2.0);
			    balls[i]->posy += ya * (dist / 2.0);
			    plr->posx += -xa * (dist / 2.0);
			    plr->posy += -ya * (dist / 2.0);

			    balls[i]->velx = -xa * 25;
			    balls[i]->vely = -ya * 25;
			    plr->velx = xa * 5;
			    plr->vely = ya * 5;
			}
			else
			{
			    state = GAME_OVER;
			    impulseX = 0;
			    impulseY = 0;

			    SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_BLEND);
			    SDL_SetRenderDrawColor(ren, 255, 90, 0, 128);
			    SDL_RenderFillRect(ren, &overlay);

			    SDL_SetRenderDrawBlendMode(ren, SDL_BLENDMODE_MOD);
			    SDL_SetRenderDrawColor(ren, 255, 0, 0, 255);
			    SDL_RenderFillRect(ren, &overlay);
			}
		    }
		}

		for (int ii = i + 1; ii < numBalls; ii++)
		{

		    float dist = balls[i]->distTo(balls[ii]);
		    if (dist <= balls[i]->rect.w)
		    {
			//find angle between them
			float xa = balls[ii]->posx - balls[i]->posx;
			float ya = balls[ii]->posy - balls[i]->posy;

			float ra = std::atan2(ya, xa);

			xa = cos(ra);
			ya = sin(ra);

			dist -= balls[i]->rect.w + 10;

			balls[i]->posx += xa * (dist / 2.0);
			balls[i]->posy += ya * (dist / 2.0);
			balls[ii]->posx += -xa * (dist / 2.0);
			balls[ii]->posy += -ya * (dist / 2.0);

			balls[i]->velx = -xa * 25;
			balls[i]->vely = -ya * 25;
			balls[ii]->velx = xa * 25;
			balls[ii]->vely = ya * 25;
		    }
		}
	    }

	    if (bonus)
	    {
		sprintf(score, "%i", bonus);
		int len = strlen(score);
		drawNum(ren, texNums, score, len, 0, 10);
	    }

	    //Update the screen
	    SDL_SetRenderTarget(ren, NULL);
	    SDL_RenderCopy(ren, scrTex, NULL, NULL);

	    sprintf(score, "%i", plScore);
	    int len = strlen(score);
	    int startPosX = config.screenWidth / 2.0 - (len * charw + chard) / 2.0;
	    drawNum(ren, texNums, score, len, startPosX, 10);

	    SDL_SetTextureAlphaMod(texNums, 64);

	    sprintf(time, "%i %i", lastGameTime / 1000 / 60, (lastGameTime / 1000) % 60);
	    drawNum(ren, texNums, time, strlen(time), 10, config.screenHeight - 10 - charh);
	}

	if (lastGameTime > bestGameTime)
	{
	    bestGameTime = lastGameTime;
	    bestScore = plScore;
	    sprintf(best, "%i %i  %i", bestGameTime / 1000 / 60, (bestGameTime / 1000) % 60, bestScore);
	}

	int len = strlen(best);
	int startPosX = config.screenWidth - 10 - len * (charw + chard);
	drawNum(ren, texNums, best, len, startPosX, config.screenHeight - 10 - charh);

	SDL_SetTextureAlphaMod(texNums, 255);

	SDL_RenderPresent(ren);
    }

    SDL_ShowCursor(SDL_ENABLE);

    for (int i = 0; i < numBalls; i++)
    {
	delete balls[i];
    }

    delete plr;
    delete shld;
    delete shldIco;
    delete remBall;

    SDL_FreeSurface(img);
    SDL_FreeSurface(imgb);
    SDL_DestroyTexture(tex);
    SDL_DestroyTexture(texb);

    SDL_FreeSurface(imgStart);
    SDL_FreeSurface(imgEnd);
    SDL_FreeSurface(imgQuit);
    SDL_FreeSurface(imgNums);
    SDL_FreeSurface(imgShld);
    SDL_FreeSurface(imgRemBall);
    SDL_FreeSurface(imgNom);

    SDL_DestroyTexture(texStart);
    SDL_DestroyTexture(texEnd);
    SDL_DestroyTexture(texQuit);
    SDL_DestroyTexture(texNums);
    SDL_DestroyTexture(texNom);

    SDL_DestroyTexture(texShld);
    SDL_DestroyTexture(texRemBall);

    SDL_DestroyRenderer(ren);
    SDL_DestroyWindow(win);
    IMG_Quit();
    SDL_Quit();

	std::cout <<"Your best round: " << (int)(bestGameTime/1000) << " seconds with " << bestScore << " points!" << std::endl << "Thanks for playing!" <<std::endl<< "Best Regards," << std::endl<<"  DusteD ( dusted.dk via HTTP and Gopher )"<< std::endl;

    return 0;
}
