/*
 *  jigzo, an OpenGL jigsaw game
 *  
 *  version 0.6.1
 *
 *  http://www.resorama.com/jigzo/
 *  
 *  Copyright (C) 2005-2008  Maarten de Boer <maarten@resorama.com>
 * 
 *  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., 51 Franklin Street, Fifth Floor, Boston, MA
 *  02110-1301, USA.
 * 
 */

/*
float BG_R = float(0x35)/255.;
float BG_G = float(0x90)/255.;
float BG_B = float(0xbc)/255.;
*/
float BG_R = float(0x99)/255.;
float BG_G = float(0x00)/255.;
float BG_B = float(0x00)/255.;

bool rotateEnabled = false;
bool soundEnabled = false;
bool soundFailed = false;
#define INTROIMGW 420 
#define INTROIMGH 300

#include <cstdio>
#include <cstring>
#include <cmath>
#include <list>
#include <vector>
#include <fstream>
#include <string>
#include <sstream>

#ifdef WIN32
#include <io.h>
#else
// mkdir, dirent
#include <sys/stat.h>
#include <sys/types.h>
#endif
#include <dirent.h>

#include "Screen.hxx"
#include "Sprite.hxx"
#include <SDL.h>

#ifdef ENABLE_SOUND
#include <SDL_mixer.h>
Mix_Chunk *mixChunkConnect = NULL;
Mix_Chunk *mixChunkFinish = NULL;
#endif

#include "Loader.hxx"

int  tolerance = 1;
const char* toleranceString[3] = {
			"  Easy","Normal","  Hard"};
float toleranceAngle[3] = {4.*M_PI/36.,2.*M_PI/36.,M_PI/36.};
float toleranceLength[3] = {16,8,4};


Rect rotateOptionRect;
Rect soundOptionRect;
Rect toleranceOptionRect;

int INTERPOLATION = GL_NEAREST;

void Assign(Texture& texture,RGBA& rgba,GLint param)
{
	texture.Bind();

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, param);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, param);

	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, rgba.Width(), rgba.Height(),
					 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba.Data());

	texture.rect = Rect(0,0,rgba.Width(),rgba.Height());
}

void SetSound(bool enable)
{
	int audio_rate = 44100;
	Uint16 audio_format = AUDIO_S16;
	int audio_channels = 2;
#ifdef WIN32
	int audio_buffers = 4096;
#else
	int audio_buffers = 1024;
#endif

	if (enable==soundEnabled) return;
	soundEnabled = enable;
#ifdef ENABLE_SOUND
	if (soundEnabled)
	{
		if (Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers))
		{
			printf("Unable to open audio... continuing without.\n");
			soundFailed = true;
		}else{
			mixChunkConnect= Mix_LoadWAV("sound/connect.wav");
			mixChunkFinish= Mix_LoadWAV("sound/finish.wav");
		}
	}
	else
	{
		Mix_CloseAudio();
		Mix_FreeChunk(mixChunkConnect);
		Mix_FreeChunk(mixChunkFinish);
		mixChunkConnect = NULL;
		mixChunkFinish = NULL;
	}
}
#endif


using namespace std;

string puzzlename;

Uint32 fadestart = 0;
Uint32 fotofade = 0;
bool fadein = 0;

enum Mode {
	start_intro,
	intro,
	end_intro,
	start_browse,
	browse,
	end_browse,
	start_play,
	play,
	end_play,
	end
};

Mode mode,next_mode,next_next_mode;

Sprite *image = 0;
Sprite *arrow = 0;
Sprite *leave= 0;

Sprite *chars[128];

int mouseX = 0,mouseY = 0;

class PuzzlePieceSprite **grid = 0;
int ncolumns = 0;
int nrows = 0;

GLuint global_gl_texture;

typedef std::list < class PreviewSprite * >PreviewSpriteList;
typedef std::list < class PuzzlePieceSprite * >PuzzlePieceSpriteList;

PreviewSpriteList previews;
PuzzlePieceSpriteList puzzlePieces;
PuzzlePieceSpriteList drawOrder;
PuzzlePieceSprite *grab = 0;

int grabdx,grabdy;
float rotateX,rotateY;
float grabangle;
int lastSingleGroup = 0;

int nextGroup = 0;

float aw, ah;
Texture *texture;

bool drawFoto = 0;
Uint32 prevticks = 0;
float fade = 0.;


void FadeIn(void)
{
	fadestart = SDL_GetTicks();
	fadein = true;
}

void FadeOutTo(Mode _next_mode,Mode _next_next_mode)
{
	fadestart = SDL_GetTicks();
	fadein = false;
	next_mode = _next_mode;
	next_next_mode = _next_next_mode;
}

class PreviewSprite:public Sprite
{
public:
	string name;
	int npieces; // used for sorting
	PreviewSprite(const string & _name):Sprite(), name(_name)
	{}
};

class FolderSprite:public Sprite
{
public:
	int id;
	
	FolderSprite(int i):id(i)
	{
	}
};

vector<FolderSprite*> folders;

class PuzzlePieceSprite:public Sprite
{
public:
	int id;

	int group;
	bool grouped;
	int cx, cy;
	float tx, ty;
	Uint32 fadestart;
	float angle; // -180-180

	PuzzlePieceSprite(int _id):Sprite(), id(_id)
	{
		group = 0;
		grouped = false;
		fadestart = 0;
		angle = 0;
		if (rotateEnabled)
		angle = float(rand()%72)/72.f * 2.f*M_PI;
	}

	void RotatedDraw(void)
	{
		Draw(angle);
	}
};

void GrabToFront(void)
{
	std::list < PuzzlePieceSprite * >::iterator it, end;

	for (it = drawOrder.begin(); it != drawOrder.end();) {
		PuzzlePieceSprite *piece = *it;
		it++;
		if (grab != 0 && piece->group == grab->group) {
			if (piece->fadestart==0) piece->fadestart = SDL_GetTicks();
		}
	}
}

int NextGroup(void)
{
	return nextGroup++;
}

bool Finished(void)
{
	std::list < PuzzlePieceSprite * >::iterator it;
	int g = -1;
	for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++)
	{
		if (g == -1)
			g = (*it)->group;
		if (fabs((*it)->angle) > 0.00001)
		{
			return false;
		}
		if ((*it)->group != g)
		{
			return false;
		}
	}
	return true;
}

void JoinGroups(int idA, int idB)
{
	if (idA == idB) return;
	std::list < PuzzlePieceSprite * >::iterator it;

	for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
		if ((*it)->group == idB)
		{
			(*it)->group = idA;
			(*it)->grouped = true;
		}
	}
	if (idA <= lastSingleGroup) {
		int idC = NextGroup();
		for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
			if ((*it)->group == idA)
			{
				(*it)->group = idC;
				(*it)->grouped = true;
			}
		}
	}
}

bool GroupNeighbours(int a, int b)
{
	std::list < PuzzlePieceSprite * >::iterator it;

	for (int j = 0; j < nrows; j++) {
		for (int i = 0; i < ncolumns; i++) {
			if (grid[i + j * ncolumns]->group == a) {
				if (i != 0 && grid[i - 1 + j * ncolumns]->group == b)
					return 1;
				if (i != ncolumns - 1
						&& grid[i + 1 + j * ncolumns]->group == b)
					return 1;
				if (j != 0 && grid[i + (j - 1) * ncolumns]->group == b)
					return 1;
				if (j != nrows - 1
						&& grid[i + (j + 1) * ncolumns]->group == b)
					return 1;
			}
		}
	}
	return 0;
}

class PreviewFolder
{
	public:
		string directory;
		int offset;

		PreviewFolder(const string& d,int o):directory(d),offset(o)
		{
		}

		void Read(void);
};

vector<PreviewFolder*> previewfolders;
int currentPreviewFolder = 0;
int nextPreviewFolder = 0;

void FreeImages(void)
{
	delete arrow;
	arrow = 0;
	delete leave;
	leave = 0;
	vector<FolderSprite*>::const_iterator it;
	for (it = folders.begin(); it!= folders.end(); it++)
	{
		delete (*it);
	}
	folders.clear();
}

void FreePreviews(void)
{
	{
		std::list < PreviewSprite * >::iterator it;
		for (it = previews.begin(); it != previews.end(); it++) {
			delete (*it);
		}
		previews.clear();
	}
	{
		std::vector < PreviewFolder* >::iterator it;
		for (it = previewfolders.begin(); it != previewfolders.end(); it++)
		{
			delete (*it);
		}
		previewfolders.clear();
	}
	FreeImages();
	delete texture;
	texture = 0;
}

void FreeChars(void)
{
	for (int i = 0; i < 128; i++) {
		if (chars[i])
		{
			delete chars[i];
			chars[i] = 0;
		}

		chars[i] = 0;
	}
	std::list < PuzzlePieceSprite * >::iterator it;
	for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
		PuzzlePieceSprite* crop = *it;
		delete crop;
	}
	puzzlePieces.clear();
	drawOrder.clear();
	FreeImages();
	delete texture;
	texture = 0;
}

void FreePuzzle(void)
{
	std::list < PuzzlePieceSprite * >::iterator it;
	for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
		PuzzlePieceSprite* crop = *it;
		delete crop;
	}
	puzzlePieces.clear();
	drawOrder.clear();
	delete image;
	image = 0;
	FreeImages();
	delete [] grid;
	grid = 0;
	delete texture;
	texture = 0;
}

class TmpDir
{
public:
	string _path;
	TmpDir()
	{
		stringstream ss;
#ifdef WIN32
		ss << "tmpfiles/";
		_path = ss.str();
		mkdir(_path.c_str());
#else
		ss << "/tmp/jigzo." << getpid() << "/";
		_path = ss.str();
		mkdir(_path.c_str(),0755);
#endif
	}
	~TmpDir()
	{
		DIR* dir = opendir(_path.c_str());
		struct dirent* dirent;
		while ((dirent = readdir(dir)))
		{
			string n(dirent->d_name);
			if (n!="." && n!="..")
			{
				n = _path+n;
				remove(n.c_str());
			}
		}
		remove(_path.c_str());
		closedir(dir);
	}
	const string& Path() { return _path; }
};

class PuzzleFile
{
public:
	TmpDir tmpdir;
	string jpg;
	string png;
	string thumbJpg;
	string map;

	const string& Jpg(void) { return jpg; }
	const string& Png(void) { return png; }
	const string& ThumbJpg(void) { return thumbJpg; }
	const string& Map(void) { return map; }

	PuzzleFile(const string& pzl)
	{
		std::ifstream f(pzl.c_str(), std::ios::binary);
		char tmp[4096];
		int chucklen;
		f.getline(tmp, 256 ,'\n'); // PzlF
		if (string(tmp)!="PzlF") {
			fprintf(stderr,"%s is not a valid file\n",pzl.c_str());
		}
		f.getline(tmp, 256 ,'\n'); // version
		if (string(tmp)!="0.1") {
			fprintf(stderr,"%s is not the right version\n",pzl.c_str());
		}
		f.getline(tmp, 256 ,'\n'); // name
		f.getline(tmp, 256 ,'\n'); // URL
		while (!f.eof())
		{
			f.getline(tmp, 256, '\n'); // filename
			if (!f.eof())
			{
				string filename(tmpdir.Path() + tmp);
				if (filename.substr(filename.length()-6)=="-t.jpg")
					thumbJpg = filename;
				else
				{
					string ext = filename.substr(filename.length()-4);
					if (ext==".png")
						png = filename;
					else if (ext==".jpg")
						jpg = filename;
					else if (ext==".map")
						map = filename;
					else
						throw "ERROR PARSING PUZZLE FILE";
				}

				std::ofstream of(filename.c_str(), std::ios::binary);
				f.getline(tmp, 256); // length
				string s(tmp);
				stringstream ss(s);
				ss >> chucklen;
				int n = chucklen;
				while (n)
				{
					int m = n;
					if (m>4096) m = 4096;
					f.read(tmp,m);
					of.write(tmp,m);
					n -= m;
				}
			}
		}
	}
};

void LoadImages(RGBA& rgba,int nFolders)
{
	int offsetX = 0;
	int offsetY = 0;
	{
		PngLoader pngLoader;
		RGBA arrowRGBA;
		pngLoader.Load("image/arrow.png",arrowRGBA);
		rgba.Paste(arrowRGBA,rgba.Width()-arrowRGBA.Width(),
					  rgba.Height()-arrowRGBA.Height());
		arrow = new Sprite;
		arrow->CreateTextureMap(texture,
										Rect(
											rgba.Width()-arrowRGBA.Width(),
											rgba.Height()-arrowRGBA.Height(),
											arrowRGBA.Width(),
											arrowRGBA.Height()));
		arrow->Move(Point(mouseX, mouseY));
		offsetX = arrowRGBA.Width();
	}
	{
		PngLoader pngLoader;
		RGBA leaveRGBA;
		pngLoader.Load("image/leave.png",leaveRGBA);
		rgba.Paste(leaveRGBA,rgba.Width()-offsetX-leaveRGBA.Width(),
					  rgba.Height()-offsetY-leaveRGBA.Height());
		leave = new Sprite;
		leave->CreateTextureMap(texture,
										Rect(
											rgba.Width()-offsetX-leaveRGBA.Width(),
											rgba.Height()-offsetY-leaveRGBA.Height(),
											leaveRGBA.Width(),
											leaveRGBA.Height()));
		leave->Move(Point(1024-4-leave->GetRect().w,768-leave->GetRect().h));
		offsetX += leaveRGBA.Width();
	}
	if (nFolders)
	{
		PngLoader pngLoader;
		RGBA folderRGBA;
		pngLoader.Load("image/folder.png",folderRGBA);
		rgba.Paste(folderRGBA,rgba.Width()-offsetX-folderRGBA.Width(),
					  rgba.Height()-offsetY-folderRGBA.Height());
		for (int i = 0;i<nFolders; i++)
		{
		  	FolderSprite* folder = new FolderSprite(i);
			folder->CreateTextureMap(texture,
										Rect(
											rgba.Width()-offsetX-folderRGBA.Width(),
											rgba.Height()-offsetY-folderRGBA.Height(),
											folderRGBA.Width(),
											folderRGBA.Height()));
			folder->Move(Point(4+i*(4+folder->GetRect().w),768-folder->GetRect().h));
			folders.push_back(folder);
		}
	}
}

struct sort_previews:public binary_function<PreviewSprite*,PreviewSprite*,bool>
{
	bool operator()(PreviewSprite* a,PreviewSprite* b)
	{
		return a->npieces < b->npieces;
	}
};

int CountPieces(const string& map)
{
	int n = 0;
	std::fstream f(map.c_str(), std::ios::in);
	char tmp[256];
	while (!f.eof()) {
		if (f.getline(tmp, 256))
		{
			string s(tmp);
			if (s.substr(0, 4) == "crop") { n++; } // old version
			if (s.substr(0, 5) == "piece") { n++; } // new version
		}
	}
	return n;
}

void RecursePreviewFolder(string dirname)
{
	DIR* dir = opendir(dirname.c_str());
	if (!dir) return;
	int i = 0;
	PreviewFolder* folder = 0;
	struct dirent* dirent;
	vector<string> subdirs;
	while ((dirent = readdir(dir)))
	{
		string n(dirent->d_name);
		if (n!="." && n!=".." && n!="CVS")
		{
			string::size_type pos = n.rfind(".");
			if (pos != string::npos && n.substr(pos)==".pzl")
			{
				if ((i%12)==0) {
					folder = new PreviewFolder(dirname,i);
					previewfolders.push_back(folder);
				}
				i++;
			}
			else
			{
				subdirs.push_back(n);
			}
		}
	}
	closedir(dir);
	sort(subdirs.begin(), subdirs.end());
	vector<string>::const_iterator it;
	for (it = subdirs.begin(); it != subdirs.end(); it++)
	{
		RecursePreviewFolder(dirname + (*it) + "/");
	}	
}


void ReadPreviewFolders(void)
{
	RecursePreviewFolder("puzzles/");
};

void ReadPreviews(void)
{
	currentPreviewFolder = nextPreviewFolder;
	ReadPreviewFolders();
	
	texture = new Texture(global_gl_texture);

	previewfolders[currentPreviewFolder]->Read();

}
void PreviewFolder::Read(void)
{
	RGBA rgba(1024,1024);
	JpgLoader jpgLoader;

	DIR* dir = opendir(directory.c_str());
	struct dirent* dirent;
	while ((dirent = readdir(dir)))
	{
		string n(dirent->d_name);
		string::size_type pos = n.rfind(".");
		if (pos != string::npos && n.substr(pos)==".pzl")
		{
			PuzzleFile puzzlefile(directory+n);
			PreviewSprite *preview;
			preview = new PreviewSprite(directory+n);
			preview->npieces = CountPieces(puzzlefile.Map());
			previews.push_back(preview);
		}
	}
	closedir(dir);

	previews.sort(sort_previews());

	int x = 0, y = 0, i = 0;
	std::list < PreviewSprite * >::iterator it;
	it = previews.begin();
	
	while (it != previews.end() && i < offset) {
		PreviewSprite* preview = *it;
		delete preview;
		it++;
		i++;
	}
	previews.erase(previews.begin(),it);
	
	i = 0;
	
	while (it != previews.end() && i<12) {
		PreviewSprite* preview = *it;
		PuzzleFile puzzlefile(preview->name);
		jpgLoader.Load(puzzlefile.ThumbJpg(),rgba,x,y);
		preview->CreateTextureMap(texture, Rect(x,y,jpgLoader.width,jpgLoader.height));
		preview->pos = Point(
								x+(256-preview->GetRect().w)/2,
								y+(240-preview->GetRect().h)/2);
		x += 256;
		if (x==1024)
		{
			x = 0;
			y += 240;
		}
		it++;
		i++;
	}
	
	std::list < PreviewSprite * >::iterator itB = it;
	while (it != previews.end())
	{
		PreviewSprite* preview = *it;
		delete preview;
		it++;
	}

	previews.erase(itB,previews.end());

	if (previewfolders.size()>1)
		LoadImages(rgba,previewfolders.size());
	else
		LoadImages(rgba,0);
	
	Assign(*texture,rgba,INTERPOLATION);
}

void ReadChars(void)
{
	for (int i = 0; i < 128; i++) {
		chars[i] = 0;
	}

	string mapDataFilename = "font/charmap.map";

	texture = new Texture(global_gl_texture);

	RGBA rgba(1024,1024);

	PngLoader pngLoader;
	pngLoader.Load("font/charmap.png",rgba);

	JpgLoader jpgLoader;
	jpgLoader.Merge("font/charmap.jpg",rgba);

	Assign(*texture,rgba,GL_LINEAR);

	std::fstream f(mapDataFilename.c_str(), std::ios::in);
	if (!f.is_open()) {
		fprintf(stderr, "Failed to open %s\n", mapDataFilename.c_str());
		exit(-1);
	}
	char tmp[256];
	ncolumns = 0;
	nrows = 0;
	while (!f.eof()) {
		if (f.getline(tmp, 256) && tmp[0] != '#') {
			string s(tmp);
			stringstream ss(s);
			Sprite *p = 0;
			Rect rect;
			Point handle;
			string name;
			ss >> name;
			ss >> rect.x;
			ss >> rect.y;
			ss >> rect.w;
			ss >> rect.h;
			ss >> handle.x;
			ss >> handle.y;
			
			int idInNameOffset = -1;
			if (name.substr(0, 4) == "crop") // old version
				idInNameOffset = 4;
			if (name.substr(0, 5) == "piece") // new version
				idInNameOffset = 5;

			if (idInNameOffset!=-1)
			{
				PuzzlePieceSprite *crop = 0;
				stringstream sn(name.substr(idInNameOffset));
				int id;
				sn >> id;
				crop = new PuzzlePieceSprite(id);

				puzzlePieces.push_back(crop);
				drawOrder.push_back(crop);
				ss >> crop->cx;
				ss >> crop->cy;
				ss >> crop->tx;
				ss >> crop->ty;

				p = crop;
			} else {
				char c = strtol(name.c_str(), 0, 16);
				int i = c;
				chars[i] = new Sprite;
				p = chars[i];
			}
			if (p)
			{
				p->handle = handle;
				p->CreateTextureMap(texture, rect);
			}
		}
	}
	std::list < PuzzlePieceSprite * >::iterator it;
	for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
		PuzzlePieceSprite *pp = *it;
		pp->Move(Point(240 + pp->tx * 2, 220 + pp->ty * 2));
	}

	LoadImages(rgba,0);
	Assign(*texture,rgba,INTERPOLATION);
}

void ReadPuzzle(const string & pzl)
{
	PuzzleFile puzzlefile(pzl);

	texture = new Texture(global_gl_texture);

	RGBA rgba;

	PngLoader pngLoader;
	pngLoader.Load(puzzlefile.Png(),rgba);

	JpgLoader jpgLoader;
	jpgLoader.Merge(puzzlefile.Jpg(),rgba);

	Assign(*texture,rgba,GL_LINEAR);

	std::fstream f(puzzlefile.Map().c_str(), std::ios::in);
	char tmp[256];
	ncolumns = 0;
	nrows = 0;
	while (!f.eof()) {
		if (f.getline(tmp, 256) && tmp[0] != '#') {
			string s(tmp);
			stringstream ss(s);
			Sprite *p = 0;
			Rect rect;
			Point handle;
			string name;
			ss >> name;
			ss >> rect.x;
			ss >> rect.y;
			ss >> rect.w;
			ss >> rect.h;
			ss >> handle.x;
			ss >> handle.y;

			int idInNameOffset = -1;
			if (name.substr(0, 4) == "crop") // old version
				idInNameOffset = 4;
			if (name.substr(0, 5) == "piece") // new version
				idInNameOffset = 5;

			if (name == "image") {
				image = new Sprite;
				p = image;
			} else if (idInNameOffset!=-1)
			{
				PuzzlePieceSprite *crop = 0;
				stringstream sn(name.substr(idInNameOffset));
				int id;
				sn >> id;
				crop = new PuzzlePieceSprite(id);

				puzzlePieces.push_back(crop);
				drawOrder.push_back(crop);
				ss >> crop->cx;
				ss >> crop->cy;
				ss >> crop->tx;
				ss >> crop->ty;
				crop->group = NextGroup();
				lastSingleGroup = crop->group;
				if (crop->cx >= ncolumns)
					ncolumns = crop->cx + 1;
				if (crop->cy >= nrows)
					nrows = crop->cy + 1;

				p = crop;
			}
			if (p)
			{
				p->handle = handle;
				p->CreateTextureMap(texture, rect);
			}
		}
	}

	{
		std::list < PuzzlePieceSprite * >::iterator it;
		grid = new PuzzlePieceSprite *[ncolumns * nrows];
		for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
			PuzzlePieceSprite *piece = *it;
			grid[piece->cx + piece->cy * ncolumns] = piece;
		}
	}
	aw = grid[0]->GetRect().w;
	ah = grid[0]->GetRect().h;

	LoadImages(rgba,0);
	Assign(*texture,rgba,INTERPOLATION);
}

Rect DrawString(float x, float y, char *str, float scale)
{
	char *ptr = str;
	float tw = 0;
	while (*ptr) {
		Sprite *s = chars[int (*ptr++)];
		if (s) {
			Rect r = s->GetRect();
			r.w = (r.w * scale);
			tw += r.w;
		}
	}
	x -= tw / 2;
	float minx = x;
	float miny = y,maxy = y;
	ptr = str;
	while (*ptr) {
		Sprite *s = chars[int (*ptr++)];
		if (s) {
			Rect r = s->GetRect();
			r.x = x;
			r.y = y - (s->handle.y * scale);
			r.w = (r.w * scale);
			r.h = (r.h * scale);
			s->Draw(r);
			x += r.w;
			if (r.y < miny) miny = r.y;
			if (r.y + r.h > maxy) maxy = r.y + r.h;
		}
	}
	return Rect(minx,miny,tw,maxy-miny);
}

void Randomize(void)
{
	std::list < PuzzlePieceSprite * >::iterator it;
	int n = nrows * ncolumns;
	std::vector < int >v(n);
	nextGroup = 0;
	for (int i = 0; i < n; i++)
		v[i] = i;
	for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
		int r = rand() % n;
		int i = v[r];
		v[r] = v[--n];
		(*it)->pos = Point(4 + grid[i]->tx + grid[i]->tx / 5,
								 4 + grid[i]->ty + grid[i]->ty / 5);
		(*it)->group = NextGroup();
	}

	drawFoto = 0;
}

void DrawFade(void)
{
	if (fadestart)
	{
		if (fadein)
		{
			fade = 1.-float(SDL_GetTicks() - fadestart) / 200.;
			if (fade < 0.) {
				fadestart = 0;
				fade=0.;
			}
		}else{
			fade= float(SDL_GetTicks() - fadestart) / 200.;
			if (fade>1.) {
				fadestart = 0;
				fade=1.;
				mode = next_mode;
				next_mode = next_next_mode;
			}
		}
	}
	glDisable(GL_TEXTURE_2D);
	glColor4f(BG_R,BG_G,BG_B,fade);
	glRectf(0,0,1024,768);
}

void Display2(void)
{
	/*     if (mode==intro) {
				static float fr = 0, fg = 0, fb = 0;
				static int bgT = 0;
				if (bgT == 0)
					bgT = SDL_GetTicks();
				fr += float (SDL_GetTicks() - bgT) / 2000.;
				fg += float (SDL_GetTicks() - bgT) / 2100.;
				fb += float (SDL_GetTicks() - bgT) / 2200.;
				bgT = SDL_GetTicks();
				if (fr > 2. * M_PI)
					fr -= 2. * M_PI;
				if (fg > 2. * M_PI)
					fg -= 2. * M_PI;
				if (fb > 2. * M_PI)
					fb -= 2. * M_PI;
				
				BG_R = fabs(sin(fr));
				BG_G = fabs(sin(fg));
				BG_B = fabs(sin(fb));
		  }
	*/
	glClearColor(BG_R,BG_G,BG_B,0.0);
	glClear(GL_COLOR_BUFFER_BIT);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
			
	static int f[6] = {0,8000,20000,30000,4000,50000};
	static int d[6] = {6, 8, 9, 7, 11, 10};

	if (mode == start_intro)
	{
		ReadChars();
		prevticks = SDL_GetTicks();
		mode = intro;
		FadeIn();
		return;
	}
	if (mode == end_intro)
	{
		FreeChars();
		mode = next_mode;
		return;
	}

	if (mode == browse) {

		glColor3f(1,1 , 1);
		{
			std::list < PreviewSprite * >::iterator it;
			for (it = previews.begin(); it != previews.end(); it++) {
				(*it)->Draw();
			}
		}
		{
			std::vector < FolderSprite * >::iterator it;
	 	   for (it = folders.begin(); it != folders.end(); it++) {
				if ((*it)->id == currentPreviewFolder)
					glColor3f(0.7,0.3 , 0.3);
				else
					glColor3f(1,1 , 1);
				(*it)->Draw();
			}
		}
		glColor3f(1,1 , 1);
		leave->Draw();
		arrow->Draw();
		return;
	}

	if (mode == intro) {
		{
			std::list < PuzzlePieceSprite * >::iterator it;
			int i = 0;
			Uint32 ticks = SDL_GetTicks();
			Uint32 dticks = ticks-prevticks;
			prevticks = ticks;
			for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
				f[i] += dticks*d[i];
				float ff = M_PI*float(f[i]&65535)/65536.;
				ff = sin(ff);
				ff = ff*ff;
				glColor4f(1,1,1,ff);
				PuzzlePieceSprite *pp = *it;
				pp->Move(Point(1024 / 2 + pp->tx - INTROIMGW/3,
									80 + pp->ty 
				));
				(*it)->Draw();
				i++;
			}
		}

		static Uint32 mark = 0;
		static int frames = 0;
		static bool strinited = 0;
		static char str[256];

		if (!strinited) {
			strinited =1 ; strcpy(str,"----");
		}
		frames++;
		if (SDL_GetTicks()-mark>200)
		{
			sprintf(str,"%f",float(frames)/(float(SDL_GetTicks()-mark)/1000.));
			mark = SDL_GetTicks();
			frames = 0;
		}

		glColor3f(0.9, 1, 1);
		DrawString(1024 / 2, 360, "Version 0.6.1", 80.f/256.f);
		glColor3f(1, 0.8, 0.2);
		DrawString(1024 / 2, 398, "http://www.resorama.com/jigzo/", 80.f/256.f);
		glColor3f(0.9, 1, 1);
		DrawString(1024 / 2, 450, "Options:", 92.f/256.f);
		char tmp[256];
		glColor3f(0.5, 0.5, 1);
		sprintf(tmp,"Rotating pieces: %s",rotateEnabled ? " ON" : "OFF");
		rotateOptionRect = DrawString(1024 / 2, 489, tmp, 80.f/256.f);
		glColor3f(0.7, 0.7, 0.8);
		DrawString(1024 / 2, 528, "(Hold Ctrl to rotate)", 80.f/256.f);
		glColor3f(0.5, 0.5, 1);
#ifdef ENABLE_SOUND
		if (soundFailed)
		{
			sprintf(tmp,"Sound support failed");
		}else{
			sprintf(tmp,"Sound: %s",soundEnabled ? " ON" : "OFF");
		}
#else
		sprintf(tmp,"Sound support not available");
#endif
		soundOptionRect = DrawString(1024 / 2, 570, tmp, 80.f/256.f);
		sprintf(tmp,"Connection tolerance: %s",
				toleranceString[tolerance]);
		toleranceOptionRect = DrawString(1024 / 2, 613, tmp, 80.f/256.f);
		glColor3f(1, 0.8, 0.2);
		DrawString(1024 / 2, 680, "Press 'Esc' to quit", 92.f/256.f);
		glColor3f(0.9, 1, 1);
		DrawString(1024 / 2, 720, "SPACE brings bottom-piece to front",80.f/256.f);
		glColor3f(1, 0.8, 0.2);
		DrawString(1024 / 2, 770, "Click or press key to continue", 92.f/256.f);
		glColor3f(1, 1, 1);
		leave->Draw();
		arrow->Draw();
		return;
	}
	if (mode == start_browse)
	{
		ReadPreviews();
		mode = browse;
		FadeIn();
	}
	if (mode == end_browse) {
		FreePreviews();
		mode = next_mode;
		return;
	}
	if (mode == start_play)
	{
		ReadPuzzle(puzzlename);
		Randomize();
		mode = play;
		FadeIn();
		return;
	}
	if (mode == end_play)
	{
		FreePuzzle();
		mode = next_mode;
		return;
	}
	if (mode == play)
	{

		std::list < PuzzlePieceSprite * >::iterator it;
		for (it = drawOrder.begin(); it != drawOrder.end(); it++) {
			glColor3f(1,1,1);
			(*it)->RotatedDraw();
		}
		std::list < PuzzlePieceSprite * >tmp;
		for (it = drawOrder.begin(); it != drawOrder.end();) {
			PuzzlePieceSprite* piece = *it;
			it++;
			if (piece->fadestart!=0)
			{
				float f= float(SDL_GetTicks() - piece->fadestart) / 300.;
				if (f>1.) {
					f=1.;
					piece->fadestart = 0;
					drawOrder.remove(piece);
					tmp.push_back(piece);
				}
				glColor4f(1,1,1,f);
				piece->RotatedDraw();
			}
		}
		for (it =tmp.begin(); it != tmp.end(); it++) {
			PuzzlePieceSprite *piece = *it;
			drawOrder.push_back(piece);
		}

		if (drawFoto) {
			image->pos = grid[0][0].pos;
			float f= float(SDL_GetTicks() - fotofade) / 1000.;
			if (f>1.) f=1.;
			glColor4f(1, 1, 1, f);
			image->Draw();
		}
		glColor3f(1, 1, 1);
		leave->Draw();
		arrow->Draw();
	}
}
void Display(void)
{
	Display2();
	DrawFade();
}


void MouseMove(int x, int y, bool modifier)
{
	mouseX = x;
	mouseY = y;
	if (arrow)
		arrow->Move(Point(mouseX, mouseY));
	if (mode == intro) {
		return;
	}
	if (mode == browse)
		return;
	if (grab) {
		bool checkFinished = false;
		bool wasConnected = false;
		if (rotateEnabled && modifier)
		{
			if (drawFoto==0)
			{
				float dx = mouseX - rotateX;
				float dy = mouseY - rotateY;
				float d;
				if (fabs(dx) > fabs(dy)) {
					d = dx;
					rotateY = mouseY;
				} else {
					d = dy;
					rotateX = mouseX;
				}
				d = float(int(d/3))/72.f * 2.f*M_PI;
				grab->angle = grabangle + d;
				while (grab->angle > M_PI) grab->angle -= 2.f*M_PI;
				while (grab->angle < -M_PI) grab->angle += 2.f*M_PI;
				if (fabs(grab->angle) < 0.00001)
				{
						grab->angle = 0;
						checkFinished = true;
				}
			}
		}
		else
		{
			grab->Move(Point(mouseX+grabdx, mouseY+grabdy));
		}
		std::list < PuzzlePieceSprite * >::iterator it;
		for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++)
		{
			PuzzlePieceSprite *piece = *it;
			Polygon grabPolygon(grab->GetRect());
			Polygon piecePolygon(piece->GetRect());

			grabPolygon.Rotate(grab->angle);
			piecePolygon.Rotate(piece->angle);

			Point d(grabPolygon[0].x - piecePolygon[0].x,grabPolygon[0].y - piecePolygon[0].y);

			Point solution_d((piece->handle.x - piece->tx) - (grab->handle.x - grab->tx), (piece->handle.y - piece->ty) - (grab->handle.y - grab->ty));

			if (rotateEnabled) d.Rotate(-grab->angle);

			if (piece->group != grab->group)
			{
				Point dif(d.x - solution_d.x,d.y - solution_d.y);

				float dangle = piece->angle - grab->angle;

				while (dangle > M_PI)
					dangle -= 2.f*M_PI;
				while (dangle < -M_PI)
					dangle += 2.f*M_PI;

				if (dif.Length()<toleranceLength[tolerance] && fabs(dangle) < toleranceAngle[tolerance])
				{
					if (GroupNeighbours(grab->group, piece->group))
					{
						JoinGroups(grab->group, piece->group);
						checkFinished = true;
						wasConnected = true;
						GrabToFront();
					}
				}
			}
			if (piece != grab && piece->group == grab->group)
			{
				piece->angle = grab->angle;
				if (fabs(grab->angle) < 0.00001)
				{
					d = solution_d;
					piece->pos.x = grab->pos.x - d.x;
					piece->pos.y = grab->pos.y - d.y;
				} else {
					grabPolygon = Polygon(grab->GetRect());
					grabPolygon.Rotate(grab->angle);
					d = solution_d;
					d.Rotate(grab->angle);
					piecePolygon = Polygon(
							Rect(
								grabPolygon[0].x - d.x,
								grabPolygon[0].y - d.y,
								piece->GetRect().w,
								piece->GetRect().h
							));
					piecePolygon.Rotate(piecePolygon[0],grab->angle);
					piecePolygon.Rotate(-grab->angle);
					piece->pos = piecePolygon[0];
				}
			}
		}
		if (checkFinished)
		{
			if (Finished())
			{
				fotofade = SDL_GetTicks();
				drawFoto = 1;
#ifdef ENABLE_SOUND
				if (mixChunkFinish)
					Mix_PlayChannel(-1, mixChunkFinish, 0);
#endif                     
			}else{
#ifdef ENABLE_SOUND
				if (wasConnected)
					if (mixChunkConnect)
						Mix_PlayChannel(-1, mixChunkConnect, 0);
#endif                     
			}
		}
	}
}

bool clickLeave = false;

void MouseClick(void)
{
	if (mode == browse) {
		{
			std::list < PreviewSprite * >::iterator it;
			for (it = previews.begin(); it != previews.end(); it++) {
				PreviewSprite *s = *it;
				if (arrow->pos.x - arrow->handle.x >= s->pos.x
						&& arrow->pos.y - arrow->handle.y >= s->pos.y
						&& arrow->pos.x - arrow->handle.x <=
						s->pos.x + s->GetRect().w
						&& arrow->pos.y - arrow->handle.y <=
						s->pos.y + s->GetRect().h) {
					puzzlename = s->name;
					FadeOutTo(end_browse,start_play);
				}
			}
		}
		{
			std::vector < FolderSprite* >::iterator it;
			for (it = folders.begin(); it != folders.end(); it++) {
				FolderSprite*s = *it;
				if (arrow->pos.x - arrow->handle.x >= s->pos.x
						&& arrow->pos.y - arrow->handle.y >= s->pos.y
						&& arrow->pos.x - arrow->handle.x <=
						s->pos.x + s->GetRect().w
						&& arrow->pos.y - arrow->handle.y <=
						s->pos.y + s->GetRect().h) {
					nextPreviewFolder = s->id;
					FadeOutTo(end_browse,start_browse);
				}
			}
		}
	}
	if (mode == play) {
		std::list < PuzzlePieceSprite * >::iterator it;
		float min_d = 1000.;
		for (it = puzzlePieces.begin(); it != puzzlePieces.end(); it++) {
			PuzzlePieceSprite *piece = *it;
			float dx =
				piece->pos.x + piece->handle.x -
				arrow->pos.x - arrow->handle.x;
			float dy =
				piece->pos.y + piece->handle.y -
				arrow->pos.y - arrow->handle.y;
			float d = sqrt(dx*dx+dy*dy);
			if (dx > -aw / 2 && dx < aw / 2 && dy > -ah / 2 && dy < ah / 2) {
				if (d<min_d)
				{
					grab = piece;
					grabdx = int(dx + 0.5f);
					grabdy = int(dy + 0.5f);
					rotateX = arrow->pos.x - arrow->handle.x;
					rotateY = arrow->pos.y - arrow->handle.y;
					grabangle = piece->angle;
					min_d = d;
				}
			}
		}
		GrabToFront();
		if (grab) prevticks = SDL_GetTicks();
	}
	{
		Sprite* s = leave;
		if (arrow->pos.x - arrow->handle.x >= s->pos.x
				&& arrow->pos.y - arrow->handle.y >= s->pos.y
				&& arrow->pos.x - arrow->handle.x <=
				s->pos.x + s->GetRect().w
				&& arrow->pos.y - arrow->handle.y <=
				s->pos.y + s->GetRect().h) {
			clickLeave=true;
		}
	}
	if (mode == intro) {
		Point pt = arrow->pos - arrow->handle;
		if (rotateOptionRect.Contains(pt))
		{
			rotateEnabled = !rotateEnabled;
		}
		else
		if (soundOptionRect.Contains(pt))
		{
			SetSound(!soundEnabled);
		}
		else
		if (toleranceOptionRect.Contains(pt))
		{
			tolerance = (tolerance+1)%3;
		}
		else
		if (!clickLeave)
		{
			FadeOutTo(end_intro,start_browse);
		}
	}
}

void usage(void)
{
	printf("jigzo, an OpenGL jigsaw game\n\n");
	printf("version 0.4\n\n");
	printf("http://www.resorama.com/jigzo/\n\n");
	printf
	("Copyright (C) 2005-2008   Maarten de Boer <maarten@resorama.com>\n\n");
	printf("Usage: jigzo [-h | -w]\n\n");
	printf("  -h   show this usage info\n");
	printf("  -w   window mode (default is fullscreen)\n");
	printf("\n");
	printf
	("jigzo requires a OpenGL hardware accelaration and runs at 1024x768 only\n\n");
#ifndef ENABLE_SOUND
	printf("Sound support disabled at compilation\n\n");
#endif
	
	exit(0);
}

int main(int argc, char **argv)
{
	time_t t;
	time(&t);
	srand(t);
	try {
		bool fullscreen = true;
		mode = start_intro;

		string argv0(argv[0]);

#ifdef WIN32
		unsigned int slashpos = argv0.rfind("\\");
#else

		unsigned int slashpos = argv0.rfind("/");
#endif

		if (slashpos != string::npos) {
			string dir = argv0.substr(0, slashpos);
			chdir(dir.c_str());
		}


		if (argc > 1) {
			if (argc > 2)
				usage();
			if (string(argv[1]) == "-h")
				usage();
			if (string(argv[1]) == "-w")
				fullscreen = false;
		}

		// todo: for non 4/3 resolution, the contents should be
		// repositioned
		int w = 1024;
		int h = 768;
		float ar = float(w)/float(h);
		float glhf = sqrt(1024.f*768.f/ar);
		float glwf = glhf*ar;
		int glw = int(glwf+0.5f);
		int glh = int(glhf+0.5f);
		INTERPOLATION = GL_LINEAR;
		//if (w==glw && h==glh) INTERPOLATION = GL_NEAREST;
		//else INTERPOLATION = GL_LINEAR;

		Screen s(w, h, glw, glh);
		s.DoubleBuffer();
		if (fullscreen)
			s.FullScreen();
		s.Init();

		glGenTextures(1, &global_gl_texture);

		SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

		SetSound(true);

		FadeIn();

		SDL_ShowCursor(0);
		int i;
		Uint8 *keys = SDL_GetKeyState(&i);
		SDL_Event event;
		while (mode!=end) {
			//static Uint32 mark = 0;
			SDL_Delay(10);
			while (SDL_PollEvent(&event)) {
				clickLeave = false;
				if (event.type == SDL_MOUSEBUTTONDOWN) {
					MouseClick();
				}
				if (event.type == SDL_MOUSEBUTTONUP)
					grab = 0;
				if (event.type == SDL_MOUSEMOTION) {
					MouseMove(event.motion.x, event.motion.y,
						keys[SDLK_LCTRL] |
						keys[SDLK_RCTRL] |
						keys[SDLK_LALT] |
						keys[SDLK_RALT]
					);
				}
				if (event.type == SDL_KEYDOWN) {
					if (keys[SDLK_ESCAPE] || keys['q']) {
						clickLeave = true;
					} else {
						if (mode == play )
						{
							if (keys['f'] || keys[' ']) {
								if (drawOrder.front()->fadestart==0)
								{
									drawOrder.front()->fadestart=SDL_GetTicks();
								}
							}
						}
						if (mode == intro) {
							FadeOutTo(end_intro,start_browse);
						}
					}
				}
				if (clickLeave)
				{
					if (mode == intro)
						FadeOutTo(end_intro,end);
					else if (mode == browse) {
						FadeOutTo(end_browse,start_intro);
					} else {
						FadeOutTo(end_play,start_browse);
					}
				}
				if (event.type == SDL_QUIT) {
					mode = end;
					break;
				}
			}

			s.Flip();
			Display();
		}

		s.Exit();

	} catch (LoaderException& e) {
		fprintf(stderr,e.what());
	}

	return 0;
}

