/***************************************************************************
 *   Copyright (C) 2005 by Michal Turek - Woq                              *
 *   WOQ (zavinac) seznam.cz                                               *
 *                                                                         *
 *   Program vykresluje rotujici logo knihovny SDL, ktere je nahrano       *
 *   pomoci SDL_image                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <SDL_opengl.h>
#include <SDL_image.h>


/*
 * Symbolicke konstanty
 */

#define SDL_SUBSYSTEMS SDL_INIT_VIDEO

#define WIN_FLAGS SDL_OPENGL|SDL_RESIZABLE
#define WIN_WIDTH 640
#define WIN_HEIGHT 480
#define WIN_BPP 0

#define WIN_TITLE "SDL, OpenGL a SDL_image"

// Predni a zadni orezavaci rovina pro gluPerspective()
#define NEAR_PLANE 1.0
#define FAR_PLANE 10.0

// Je-li definovano, pouzije se fsaa (karta musi podporovat GL_ARB_multisample)
// Zaroven take definuje pocet vzorku
// #define FSAA 2

// Definovat v Linuxu (aktualizuje vnitrni (SDL) velikost okna)
// Nedefinovat v MS Windows (ztrata OpenGL kontextu)
#define CALL_SETVIDEOMODE_WHEN_RESIZING

// Trocha matematiky
#define PI 3.1415926535897932384626433832795
#define DEGTORAD(deg) ((PI*(deg))/180.0)
#define RADTODEG(rad) ((180.0*(rad))/PI)


/*
 * Funkcni prototypy
 */

GLuint CreateTexture(const char* filename);	// Vytvori texturu
GLuint CreateTexture(SDL_Surface *surface);
bool SwapSurfaceRows(SDL_Surface *surface);	// Prohodi radky v obrazku
bool InitGL();					// Nastavi vlastnosti OpenGL
void ResizeGL(int width, int height);		// Nastavi OpenGL perspektivu
bool Init();					// Hlavni inicializacni funkce
void CalculateFPS();				// Vypocet FPS
void Draw();					// Rendering sceny
void Update();					// Aktualizace sceny
void Destroy();					// Deinicializace
bool ProcessEvent();				// Osetruje udalosti
int  main(int argc, char *argv[]);		// Vstup do programu


/*
 * Globalni promenne
 */

SDL_Surface *g_screen;			// Surface okna
float g_rotz = 0.0f;			// Uhel rotace okolo osy z

GLuint g_texture = 0;			// ID textury

float g_fps = 1.0f;			// FPS
Uint32 g_last_time = 0;			// Cas minuleho prekresleni (pro FPS)


/*
 * Vytvori OpenGL texturu z obrazku ulozeneho na disku;
 * vrati nulu, pokud soubor neexistuje nebo pokud neco selze
 */

GLuint CreateTexture(const char* filename)
{
	GLuint texture;
	SDL_Surface *img;

	img = IMG_Load(filename);// Nazapomenout prilinkovat SDL_image

	if(img == NULL)
	{
		fprintf(stderr, "Unable to load '%s': %s\n",
				filename, SDL_GetError());
		return 0;
	}

	texture = CreateTexture(img);
	SDL_FreeSurface(img);

	return texture;
}


/*
 * Vytvori OpenGL texturu ze SDL_Surface, v pripade neuspechu vrati nulu
 */

GLuint CreateTexture(SDL_Surface *surface)
{
	GLuint texture;		// Vysledna textura
	Uint32 saved_flags;	// Pomocne pro ulozeni flagu
	Uint8  saved_alpha;
	SDL_Surface *tmp_img;	// Obrazek pro vytvoreni textury

	// Vytvori prazdny RGB surface
	tmp_img = SDL_CreateRGBSurface(
			SDL_SWSURFACE,		// Softwarovy surface
			surface->w, surface->h,	// Sirka, vyska
			32,			// Barevna hloubka
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
			0x000000FF,		// OpenGL RGBA maska
			0x0000FF00,
			0x00FF0000,
			0xFF000000
#else
			0xFF000000,
			0x00FF0000,
			0x0000FF00,
			0x000000FF
#endif
			);

	if(tmp_img == NULL)
		return 0;

	// Ulozi atributy alfa blendingu
	saved_flags = surface->flags & (SDL_SRCALPHA|SDL_RLEACCELOK);
	saved_alpha = surface->format->alpha;

	if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA)
		SDL_SetAlpha(surface, 0, 0);

	// Zkopiruje surface do obrazku pro texturu
	SDL_Rect area;
	area.x = 0;
	area.y = 0;
	area.w = surface->w;
	area.h = surface->h;
	SDL_BlitSurface(surface, &area, tmp_img, &area);

	// Obnovi atributy alfa blendingu
	if((saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA)
		SDL_SetAlpha(surface, saved_flags, saved_alpha);

	// Prohodi radky
	if(!SwapSurfaceRows(tmp_img))
	{
		SDL_FreeSurface(tmp_img);
		return 0;
	}

	// Vytvori texturu
	glGenTextures(1, &texture);
	glBindTexture(GL_TEXTURE_2D, texture);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D,
		     0,
		     GL_RGBA,
		     surface->w, surface->h,
		     0,
		     GL_RGBA,
		     GL_UNSIGNED_BYTE,
		     tmp_img->pixels);

	SDL_FreeSurface(tmp_img);
	return texture;
}


/*
 * Prohodi radky v obrazku (prvni za posledni, druhy za predposledni atd.),
 */

bool SwapSurfaceRows(SDL_Surface *surface)
{
	// Ukazatele na prohazovane radky
	Uint8 *hi_row, *lo_row, *tmp_row;

	// Alokace pameti pro pomocny radek
	tmp_row = (Uint8 *)malloc(surface->pitch);

	if(tmp_row == NULL)
	{
		fprintf(stderr, "Unable to allocate memory.\n");
		return false;
	}

	// Ukazatele na prvni a posledni radek
	hi_row = (Uint8 *)surface->pixels;
	lo_row = hi_row + (surface->h * surface->pitch) - surface->pitch;

	if(SDL_MUSTLOCK(surface))
	{
		if(SDL_LockSurface(surface) == -1)
		{
			fprintf(stderr, "Unable to lock surface.\n");
			free(tmp_row);
			return false;
		}
	}

	for(int i = 0; i < surface->h / 2; i++)
	{
		memcpy(tmp_row, hi_row, surface->pitch);
		memcpy(hi_row, lo_row, surface->pitch);
		memcpy(lo_row, tmp_row, surface->pitch);

		hi_row += surface->pitch;// Dalsi/predchozi radek
		lo_row -= surface->pitch;
	}

	if (SDL_MUSTLOCK(surface))
	{
		SDL_UnlockSurface(surface);
	}

	free(tmp_row);// Uvolni pomocnou pamet

	return true;
}


/*
 * Inicializuje OpenGL
 */

bool InitGL()
{
	glClearColor(1.0, 1.0, 1.0, 0.5);
	glClearDepth(1.0);
	glDepthFunc(GL_LEQUAL);
	glEnable(GL_DEPTH_TEST);
	glShadeModel(GL_SMOOTH);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	// Nahraje obrazek a vytvori texturu
	if((g_texture = CreateTexture("./sdllogo.png")) == 0)
		return false;

	return true;
}


/*
 * Nastavuje perspektivu. Vola se, kdyz uzivatel roztahuje okno,
 * v parametrech nova velikost
 */

void ResizeGL(int width, int height)
{
	if(height == 0)// Deleni nulou
		height = 1;

	glViewport(0, 0, width, height);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(45.0, (GLdouble)width/(GLdouble)height,
			NEAR_PLANE, FAR_PLANE);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}


/*
 * Hlavni inicializacni funkce
 */

bool Init()
{
	if(SDL_Init(SDL_SUBSYSTEMS) == -1)	// Inicializace SDL
	{
		fprintf(stderr, "Unable to initialize SDL: %s\n",
				SDL_GetError());
		return false;
	}

	// Atributy rendering kontextu
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 24);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
	SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 0);

	SDL_GL_SetAttribute(SDL_GL_ACCUM_RED_SIZE, 0);
	SDL_GL_SetAttribute(SDL_GL_ACCUM_GREEN_SIZE, 0);
	SDL_GL_SetAttribute(SDL_GL_ACCUM_BLUE_SIZE, 0);
	SDL_GL_SetAttribute(SDL_GL_ACCUM_ALPHA_SIZE, 0);

#ifdef FSAA
	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
	SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, FSAA);
#endif

	// Vytvori okno s definovanymi vlastnostmi
	g_screen = SDL_SetVideoMode(WIN_WIDTH, WIN_HEIGHT, WIN_BPP, WIN_FLAGS);

	if(g_screen == NULL)
	{
		fprintf(stderr, "Unable to set %dx%d video: %s\n",
				WIN_WIDTH, WIN_HEIGHT, SDL_GetError());
		return false;
	}

	if(!InitGL())				// Inicializace OpenGL
		return false;

	ResizeGL(WIN_WIDTH, WIN_HEIGHT);	// Nastavi perspektivu
	SDL_WM_SetCaption(WIN_TITLE, NULL);	// Titulek okna
	g_last_time = SDL_GetTicks();		// Pro FPS

	return true;
}


/*
 * Vypocet poctu snimku za sekundu, pohyby budou nezavisle na rychlosti pocitace
 */

void CalculateFPS()
{
	// SDL_GetTicks() vraci cas v sekundach od inicializace SDL
	Uint32 miliseconds = SDL_GetTicks() - g_last_time;

	if(miliseconds == 0)// Proti deleni nulou
		miliseconds = 1;

	g_fps = 1.0f / (miliseconds / 1000.0f);
	g_last_time = SDL_GetTicks();
}


/*
 * Rendering sceny
 */

void Draw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	glTranslatef(	1.35f * (float)(cos(DEGTORAD(g_rotz))),
			0.8f * (float)(sin(DEGTORAD(g_rotz))),
			-4.0f);

	glRotatef(g_rotz, 0.0f, 0.0f, 1.0f);

	glBindTexture(GL_TEXTURE_2D, g_texture);
	glColor4f(1.0f,1.0f,1.0f,0.5f);

	glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,-1.0f, 0.0f);
		glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,-1.0f, 0.0f);
		glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f, 1.0f, 0.0f);
		glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f, 1.0f, 0.0f);
	glEnd();

	glFlush();
	SDL_GL_SwapBuffers();// Prohodi predni a zadni buffer
}


/*
 * Aktualizuje scenu
 */

void Update()
{
	g_rotz -= 90.0f / g_fps;
}


/*
 * Deinicializacni funkce
 */

void Destroy()
{
	if(glIsTexture(g_texture))
	{
		glDeleteTextures(1, &g_texture);
		g_texture = 0;
	}

	SDL_Quit();
}


/*
 * Osetruje udalosti (bude probrano nekdy v budoucnu)
 * V tomto pripade reaguje na roztahovani okna uzivatelem a ceka na klavesu ESC,
 * ktera ukonci program
 */

bool ProcessEvent()
{
	SDL_Event event;

	while(SDL_PollEvent(&event))
	{
		switch(event.type)
		{
			// Klavesnice
			case SDL_KEYDOWN:
				switch(event.key.keysym.sym)
				{
					case SDLK_ESCAPE:
						return false;
						break;

					default:
						break;
				}
				break;

			// Zmena velikosti okna
			case SDL_VIDEORESIZE:
#ifdef CALL_SETVIDEOMODE_WHEN_RESIZING
				g_screen = SDL_SetVideoMode(event.resize.w,
					event.resize.h, WIN_BPP, WIN_FLAGS);

				if(g_screen == NULL)
				{
					fprintf(stderr,
						"Unable to resize window: %s\n",
						SDL_GetError());
					return false;
				}
#endif
				ResizeGL(event.resize.w, event.resize.h);
				break;

			// Pozadavek na ukonceni
			case SDL_QUIT:
				return false;
				break;

			default:
				break;
		}
	}

	return true;
}


/*
 * Vstup do programu
 */

int main(int argc, char *argv[])
{
	printf(WIN_TITLE);
	printf("\nPress ESC key to quit.\n");

	if(!Init())				// Inicializace
	{
		Destroy();
		return 1;
	}

	bool done = false;			// Hlavni smycka programu
	while(!done)
	{
		done = !ProcessEvent();		// Osetri udalosti

		CalculateFPS();			// Vypocet FPS
		Update();			// Aktualizace sceny
		Draw();				// Rendering
	}

	Destroy();				// Deinicializace a konec
	return 0;
}