//	LoadingScreen.cpp - Loading screen state.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  Niall Moody
//	
//	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 3 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, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#include "Font.h"
#include "Shader.h"
#include "MiscHelper.h"
#include "SingleImage.h"
#include "LoadingScreen.h"
#include "sound/Sounder.h"
#include "drawing/Drawer.h"
#include "drawing/ImageLoader.h"

#include <cassert>

using std::wstring;

//------------------------------------------------------------------------------
GameStateCreator<LoadingScreen> LoadingScreen::creator;

//------------------------------------------------------------------------------
LoadingScreen::LoadingScreen(Application& app, SounderPtr sound):
GameState(app, sound),
totalResources(0),
resProgress(0),
resType(NumResourceTypes),
resIndex(0),
subIndex(0),
sounder(sound),
spiralLastVal(0.0f),
fadeOut(0.0f)
{
	int x, y;

	/*colours[Background] = ThreeFloats(1.0f);
	colours[PixelBright] = ThreeFloats(1.0f, 0.5f, 0.5f);
	colours[PixelDark] = ThreeFloats(0.0f, 0.0f, 1.0f);
	colours[EndFade] = ThreeFloats(1.0f);*/
	colours[Background] = ThreeFloats(0.0f);
	colours[PixelBright] = ThreeFloats(1.0f, 0.5f, 0.5f);
	colours[PixelDark] = ThreeFloats(0.0f, 0.0f, 1.0f);
	colours[EndFade] = ThreeFloats(0.0f);

	for(y=0;y<GridHeight;++y)
	{
		for(x=0;x<GridWidth;++x)
			grid[y][x] = 0.0f;
	}
}

//------------------------------------------------------------------------------
LoadingScreen::~LoadingScreen()
{

}

//------------------------------------------------------------------------------
void LoadingScreen::draw(DrawerPtr& d, float timestepInterp)
{
	float progress;
	ImageData data;
	TwoFloats barSize;
	TwoFloats barStart;
	ImageLoader imLoad;
	const TwoFloats origin;
	const ThreeFloats black;
	const ThreeFloats white(1.0f);
	const TwoFloats& drawingSize = d->getDrawingAreaSize();
	const ResourceManager& manager = getResourceManager();

	//If progressInc is 0, we need to calculate how many total resources we
	//have to load to work out what progressInc should be.
	//We also figure out what our first resType is.
	//if(progressInc <= 0.0f)
	if(resProgress <= 0)
	{
		int i;

		totalResources = manager.getNumTextureAtlases();
		if(totalResources > 0)
			resType = TextureAtlasRes;

		totalResources += manager.getNumSingleImages();
		if((totalResources > 0) && (resType == NumResourceTypes))
			resType = SingleImageRes;

		for(i=0;i<manager.getNumFolders();++i)
			totalResources += manager.getFolder(i).getNumChildren();
		if((totalResources > 0) && (resType == NumResourceTypes))
			resType = ImageFolderRes;

		totalResources += manager.getNumShaders();
		if((totalResources > 0) && (resType == NumResourceTypes))
			resType = ShaderRes;

		totalResources += manager.getNumFonts();
		if((totalResources > 0) && (resType == NumResourceTypes))
			resType = FontRes;

		totalResources += manager.getNumSounds();
		if((totalResources > 0) && (resType == NumResourceTypes))
			resType = SoundRes;

		for(i=0;i<manager.getNumSoundFolders();++i)
			totalResources += manager.getSoundFolder(i).getNumChildren();
		if((totalResources > 0) && (resType == NumResourceTypes))
			resType = SoundFolderRes;

		if(resType == NumResourceTypes)
		{
			resProgress = 1;
			totalResources = 1;
			fadeOut += 0.02f;
		}
	}

	//Draw progress.
	if(0)
	{
		//Bizarre fix for fillRect() not working on OSX. FIXME!
		d->fillGradient(origin-2, origin-1, white, black);
		//(this is a placeholder).
		//(fill background black)
		d->fillRect(origin, drawingSize, black);

		//(white border)
		barStart.x = 64.0f;
		barStart.y = (drawingSize.y * 0.5f) - 64.0f;
		barSize.x = drawingSize.x - 128.0f;
		barSize.y = 128.0f;
		d->fillRect(barStart, barSize, white);

		//(inside black border)
		barStart += 8.0f;
		barSize -= 16.0f;
		d->fillRect(barStart, barSize, black);

		//(white progress bar)
		progress = (float)resProgress/(float)totalResources;
		barStart += 8.0f;
		barSize -= 16.0f;
		barSize.x *= progress;
		d->fillRect(barStart, barSize, white);
	}
	else
	{
		int x, y;
		TwoFloats tempPos;
		TwoFloats cellDist;
		ThreeFloats tempCol;
		const TwoFloats origin;

		cellDist.x = (d->getDrawingAreaSize().x - 0.0f)/(float)GridWidth;
		cellDist.y = (d->getDrawingAreaSize().y - 0.0f)/(float)GridHeight;

		//Bizarre fix for fillRect() not working on OSX. FIXME!
		d->fillGradient(origin-2, origin-1, white, black);

		//Fill background.
		d->fillRect(origin, d->getDrawingAreaSize(), colours[Background]);

		//Draw 'pixels'.
		tempPos = 0.0f + (cellDist * 0.5f);
		for(y=0;y<GridHeight;++y)
		{
			tempPos.x = 0.0f + (cellDist.x * 0.5f);
			for(x=0;x<GridWidth;++x)
			{
				tempCol = (colours[PixelBright] * grid[y][x]) + (colours[PixelDark] * (1.0f-grid[y][x]));

				d->fillRect(tempPos - (cellDist * grid[y][x] * 0.5f),
							cellDist * grid[y][x],
							tempCol,
							grid[y][x]);

				tempPos.x += cellDist.x;

				if(grid[y][x] > 0.0f)
				{
					grid[y][x] -= 0.02f;
					if(grid[y][x] < 0.0f)
						grid[y][x] = 0.0f;
				}
			}
			tempPos.y += cellDist.y;
		}
	}

	if(fadeOut <= 0.0f)
	{
		//Load current resource.
		switch(resType)
		{
			case TextureAtlasRes:
				{
					TextureAtlasPtr atlas = manager.getTextureAtlas(resIndex);

					data = imLoad.loadImage(atlas->getImageData(), atlas->getImageDataSize());
					d->loadTextureAtlas(data.data, data.width, data.height, *atlas);

					++resIndex;
					if(resIndex >= manager.getNumTextureAtlases())
					{
						if(manager.getNumSingleImages() > 0)
							resType = SingleImageRes;
						else if(manager.getNumFolders() > 0)
							resType = ImageFolderRes;
						else if(manager.getNumShaders() > 0)
							resType = ShaderRes;
						else if(manager.getNumFonts() > 0)
							resType = FontRes;
						else if(manager.getNumSounds() > 0)
							resType = SoundRes;
						else if(manager.getNumSoundFolders() > 0)
							resType = SoundFolderRes;
						resIndex = 0;
					}
				}
				break;
			case SingleImageRes:
				{
					SingleImagePtr image = manager.getSingleImage(resIndex);

					data = imLoad.loadImage(image->getImageData(), image->getImageDataSize());
					d->loadSingleImage(data.data, data.width, data.height, image->getName());

					++resIndex;
					if(resIndex >= manager.getNumSingleImages())
					{
						if(manager.getNumFolders() > 0)
							resType = ImageFolderRes;
						else if(manager.getNumShaders() > 0)
							resType = ShaderRes;
						else if(manager.getNumFonts() > 0)
							resType = FontRes;
						else if(manager.getNumSounds() > 0)
							resType = SoundRes;
						else if(manager.getNumSoundFolders() > 0)
							resType = SoundFolderRes;
						resIndex = 0;
					}
				}
				break;
			case ImageFolderRes:
				{
					const FilePath& folder = manager.getFolder(resIndex);
					FilePath imageFile = folder.getChild(subIndex);

					data = imLoad.loadImage(imageFile);
					d->loadSingleImage(data.data, data.width, data.height, imageFile.getName());

					++subIndex;
					if(subIndex >= folder.getNumChildren())
					{
						++resIndex;
						if(resIndex >= manager.getNumFolders())
						{
							if(manager.getNumShaders() > 0)
								resType = ShaderRes;
							else if(manager.getNumFonts() > 0)
								resType = FontRes;
							else if(manager.getNumSounds() > 0)
								resType = SoundRes;
							else if(manager.getNumSoundFolders() > 0)
								resType = SoundFolderRes;
							resIndex = 0;
						}
						subIndex = 0;
					}
				}
				break;
			case ShaderRes:
				{
					ShaderPtr shader = manager.getShaders(resIndex);

					d->loadShader(shader->getVertexShader(),
								  shader->getFragmentShader(),
								  shader->getName());

					++resIndex;
					if(resIndex >= manager.getNumShaders())
					{
						if(manager.getNumFonts() > 0)
							resType = FontRes;
						else if(manager.getNumSounds() > 0)
							resType = SoundRes;
						else if(manager.getNumSoundFolders() > 0)
							resType = SoundFolderRes;
						resIndex = 0;
					}
				}
				break;
			case FontRes:
				{
					FontPtr font = manager.getFont(resIndex);

					d->loadFont(font->getPath().getName(),
								font->getSize(), 
								font->getId());

					++resIndex;
					if(resIndex >= manager.getNumFonts())
					{
						if(manager.getNumSounds() > 0)
							resType = SoundRes;
						else if(manager.getNumSoundFolders() > 0)
							resType = SoundFolderRes;
						resIndex = 0;
					}
				}
				break;
			case SoundRes:
				{
					sounder->loadSoundFileResource(manager.getSound(resIndex));

					++resIndex;
					if(resIndex >= manager.getNumSounds())
					{
						if(manager.getNumSoundFolders() > 0)
							resType = SoundFolderRes;
						else
							resType = NumResourceTypes;
						resIndex = 0;
					}
				}
				break;
			case SoundFolderRes:
				{
					const FilePath& folder = manager.getSoundFolder(resIndex);
					FilePath soundFile = folder.getChild(subIndex);

					sounder->loadSoundFileResource(manager.getSound(soundFile));

					++subIndex;
					if(subIndex >= folder.getNumChildren())
					{
						++resIndex;
						if(resIndex >= manager.getNumSoundFolders())
						{
							resType = NumResourceTypes;
							resIndex = 0;
						}
						subIndex = 0;
					}
				}
				break;
			default:
				//You shouldn't ever get here. It means you've tried to load a
				//resource type that doesn't exist.
				assert(0);
		}

		//Update progress.
		++resProgress;
		if(resProgress >= totalResources)
			fadeOut += 0.02f;

		//Add to the grid.
		{
			int x, y;
			TwoFloats tempf;
			float spiralIndex;

			spiralIndex = (float)resProgress/(float)totalResources;
			while(spiralLastVal < spiralIndex)
			{
				spiralLastVal += 0.02f;
				if(spiralLastVal > spiralIndex)
					spiralLastVal = spiralIndex;

				tempf.x = ((float)GridWidth * 0.5f);
				tempf.x += sinf(spiralLastVal * 2.0f * NIALL_PI * 4.0f) * spiralLastVal * ((float)GridHeight * 0.49f);
				tempf.y = ((float)GridHeight * 0.5f);
				tempf.y += cosf(spiralLastVal * 2.0f * NIALL_PI * 4.0f) * spiralLastVal * ((float)GridHeight * 0.49f);
				x = (int)tempf.x;
				y = (int)tempf.y;
				assert(x < GridWidth);
				assert(y < GridHeight);
				grid[y][x] = 1.0f;
				if(y >= 1)
				{
					grid[y-1][x] += 0.5f;
					if(grid[y-1][x] > 1.0f)
						grid[y-1][x] = 1.0f;
				}
				if(y <= (GridHeight-2))
				{
					grid[y+1][x] += 0.5f;
					if(grid[y+1][x] > 1.0f)
						grid[y+1][x] = 1.0f;
				}
				if(x >= 1)
				{
					grid[y][x-1] += 0.5f;
					if(grid[y][x-1] > 1.0f)
						grid[y][x-1] = 1.0f;
				}
				if(x <= (GridWidth-2))
				{
					grid[y][x+1] += 0.5f;
					if(grid[y][x+1] > 1.0f)
						grid[y][x+1] = 1.0f;
				}

				for(y=0;y<GridHeight;++y)
				{
					for(x=0;x<GridWidth;++x)
					{
						if(grid[y][x] > 0.0f)
						{
							grid[y][x] -= 0.02f;
							if(grid[y][x] < 0.0f)
								grid[y][x] = 0.0f;
						}
					}
				}
			}
		}
	}

	if(fadeOut > 0.0f)
	{
		d->fillRect(origin, drawingSize, colours[EndFade], fadeOut);

		fadeOut += 0.02f;
	}

	if(fadeOut >= 1.0f)
		nextState(L"-start state-");
}

//------------------------------------------------------------------------------
void LoadingScreen::keyDown(const wstring& action,
							const wstring& keyPressed,
							int keyModifier)
{

}

//------------------------------------------------------------------------------
void LoadingScreen::setColours(const ThreeFloats * const col)
{
	colours[Background] = col[0];
	colours[PixelBright] = col[1];
	colours[PixelDark] = col[2];
	colours[EndFade] = col[3];
}
