--	GameState.lua - Main script run by LuaGameState.cpp.
--  ----------------------------------------------------------------------------
--	This file is part of 'EXCOSC Base' a simple OSC-aware game skeleton.
--	Copyright (C) 2013  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/>.
--	----------------------------------------------------------------------------

--	The background colour.
backColour = ThreeFloats()
--	The background tiles.
backTiles = {"BackTile0",
			 "BackTile1",
			 "BackTile2",
			 "BackTile3"}
--	The current background tile.
backIndex = 1

--	The player's position.
playerPos = TwoFloats()
--	The player's colour.
playerCol = ThreeFloats()

--	The second player's position (offscreen until we receive an OSC message).
player2Pos = TwoFloats(-256.0)
--	The second player's colour.
player2Col = ThreeFloats()

--	The 8 blocks/entities on the screen.
blocks = {}
--	Temp colour value used to convert block colours to HSL.
blockCol = ThreeFloats()

--	TextLayout object for the title text (caches layout data; v.fast to render).
titleLayout = TextLayout()
--	Fades the title text out.
titleFade = 1.0

--------------------------------------------------------------------------------
--	Called when the state is first initialised.
function initialise(app)
	--Turn on the granulator and delay audio fx.
	app:toggleGranulator(true)
	app:toggleDelay(true)

	--Set the audio fx parameters.
	app:setGranulatorParams(1.0, 0.5, 4.0, 0.0, 0.5, 2.0)
	app:setDelayParams(0.125, 0.9, 0.5)

	--Setup the blocks. We arrange them randomly within a grid centred on the
	--middle of the screen.
	colour = ThreeFloats(0.0, 0.6, 0.7) --HSL colour; converted to RGB in Block:draw()
	gridOffset = TwoFloats()
	gridWidth = math.floor(app:getDrawingAreaSize().x/128.0)
	gridHeight = math.floor(app:getDrawingAreaSize().y/128.0)
	gridOffset.x = (app:getDrawingAreaSize().x - (gridWidth*128.0))/2.0
	gridOffset.y = (app:getDrawingAreaSize().y - (gridHeight*128.0))/2.0
	for i=1,8 do
		blocks[i] = Block(TwoFloats(Random.getGlobal():getRandomInt(gridWidth) * 128.0,
									Random.getGlobal():getRandomInt(gridHeight) * 128.0),
						  colour)
		blocks[i].position = blocks[i].position + gridOffset + 64.0

		colour.x = colour.x + (1.0/8.0)
	end

	--Position the player randomly on the grid.
	playerPos.x = Random.getGlobal():getRandomInt(gridWidth) * 128.0
	playerPos.y = Random.getGlobal():getRandomInt(gridHeight) * 128.0
	playerPos = playerPos + gridOffset + 64.0

	--Set player 1(local) & player 2(remote) to be red.
	colour.x = 0.0
	playerCol = hslToRgb(colour)
	player2Col = hslToRgb(colour)

	--Choose a random-hued background colour.
	backColour.x = Random.getGlobal():getRandomFloat()
	backColour.y = 0.6
	backColour.z = 0.85
end

--------------------------------------------------------------------------------
--	Used to update game logic etc.
function update(app)
	--backColour is an HSL colour value. We cycle through the hue component so
	--the background is not static.
	backColour.x = backColour.x + 0.002
	if (backColour.x > 1.0) then
		backColour.x = backColour.x - 1.0
	end

	--Used to fade out the example title text.
	if (titleFade < 1.0) and (titleFade > 0.0) then
		titleFade = titleFade - 0.01
	end
end

--------------------------------------------------------------------------------
--	Draws the game.
function draw(d)
	----Draw background----
	--Fill the screen with the background colour.
	d:fillRect(TwoFloats(0.0),			--rect top-left position.
			   d:getDrawingAreaSize(),	--rect size.
			   hslToRgb(backColour),	--rect colour.
			   1.0)						--rect alpha.

	--Draw a tiled image so the background isn't a flat colour.
	d:drawTiledImage(backTiles[backIndex],			--Tile image.
					 TwoFloats(0.0),				--top-left position to start drawing at.
					 d:getDrawingAreaSize(),		--Size of area to draw.
					 TwoFloats(0.0),				--Position within the tile image to start drawing from.
					 d:getDrawingAreaSize()/256.0,	--Number of times to repeat the tile image (2D).
					 hslToRgb(backColour) * 0.8,	--Colour to tint the tile image.
					 1.0)							--Alpha value.

	----Draw blocks----
	for i=1,8 do
		blocks[i]:draw(d)
	end

	----Draw players----
	d:drawImage("Player",		--Image to draw.
				playerPos-64.0,	--Position to draw image at (-64.0 centres image on playerPos).
				TwoFloats(1.0),	--Image scale.
				playerCol,		--Colour to tint image.
				1.0)			--Alpha value.
	d:drawImage("Player",
				player2Pos-64.0,
				TwoFloats(1.0),
				player2Col,
				0.66)

	----Draw example text----
	d:drawTextCached("EXCOSC Example",		--Text to draw.
				     "BigFont",				--Font to use.
				     titleLayout,			--TextLayout object. Caches layout data, making rendering v.fast.
				     TwoFloats(0.0, 160.0),	--Position to start drawing at.
				     1280.0,				--Horizontal size of box to render text in. If text extends beyond this value, a line break will be generated.
				     1,						--Text justification. 0 = left, 1 = centre.
				     1.0,					--Text scale.
				     ThreeFloats(0.0),		--Colour to tint text.
				     titleFade)				--Alpha value.
end

--------------------------------------------------------------------------------
--	Called when the user depresses a key or a joystick button.
function keyDown(app, action, keyPressed, keyModifier)
	--We fade the title text out when the player presses a key.
	if (titleFade > 0.0) then
		titleFade = titleFade - 0.01
	end
	
	--When the player presses one of the cursor keys, move the player object.
	if (keyPressed == "CursorLeft") then
		playerPos.x = playerPos.x - 128.0
		app:sendOscMessage("/player/position/x",
						   playerPos.x/app:getDrawingAreaSize().x)

		--moveBlock() checks whether the player object collided with a block,
		--and moves that block accordingly and plays a sound.
		moveBlock(app, TwoFloats(-128.0, 0.0), "Sound0")
	elseif (keyPressed == "CursorRight") then
		playerPos.x = playerPos.x + 128.0
		app:sendOscMessage("/player/position/x",
						   playerPos.x/app:getDrawingAreaSize().x)

		moveBlock(app, TwoFloats(128.0, 0.0), "Sound1")
	elseif (keyPressed == "CursorUp") then
		playerPos.y = playerPos.y - 128.0
		app:sendOscMessage("/player/position/y",
						   playerPos.y/app:getDrawingAreaSize().y)

		moveBlock(app, TwoFloats(0.0, -128.0), "Sound2")
	elseif (keyPressed == "CursorDown") then
		playerPos.y = playerPos.y + 128.0
		app:sendOscMessage("/player/position/y",
						   playerPos.y/app:getDrawingAreaSize().y)

		moveBlock(app, TwoFloats(0.0, 128.0), "Sound3")
	--We change the background when the player hits the space bar.
	elseif (keyPressed == "Space") then
		backIndex = backIndex + 1
		if (backIndex > 4) then
			backIndex = 1
		end

		--Send a "/player/trigger" message.
		app:sendOscMessage("/player/trigger", 1.0)
		--And play the appropriate sound.
		app:triggerSound("Sound4", 1.0, 0.5)
	end
end

--------------------------------------------------------------------------------
--	Called when the user releases a key or a joystick button.
function keyUp(app, action, keyPressed, keyModifier)

end

--------------------------------------------------------------------------------
--	Called when the user depresses a mouse button.
function mouseDown(app, button, pos)
	
end

--------------------------------------------------------------------------------
--	Called when the user releases a mouse button.
function mouseUp(app, button, pos)

end

--------------------------------------------------------------------------------
--	Called when the user moves the mouse.
function mouseMove(app, pos)
	
end

--------------------------------------------------------------------------------
--	Called when the user moves the mousewheel.
function mouseWheel(app, dir)

end

--------------------------------------------------------------------------------
--	Called when the user moves a joystick.
function joyMove(app, action, value, joystick, axis)

end

--------------------------------------------------------------------------------
--	Called when we receive an OSC message.
function oscMessageReceived(app, address, value)
	--Obtain the 1st 7 characters of the OSC address to check which object it's
	--meant for.
	rootAddr = address:sub(1, 7)

	if (rootAddr == "/player") then
		--In the player's case, the characters starting from index 8 represent
		--parameter this message is meant for.
		paramAddr = address:sub(8)

		if (paramAddr == "/position/x") then
			--Our OSC messages are normalised to 0->1, so multiply by the size
			--of the drawing area to obtain the actual value.
			player2Pos.x = value * app:getDrawingAreaSize().x
		elseif (paramAddr == "/position/y") then
			player2Pos.y = value * app:getDrawingAreaSize().y
		elseif (paramAddr == "/colour/r") then
			player2Col.x = value
		elseif (paramAddr == "/colour/g") then
			player2Col.y = value
		elseif (paramAddr == "/colour/b") then
			player2Col.z = value
		elseif (paramAddr == "/trigger") then
			backIndex = backIndex + 1
			if (backIndex > 4) then
				backIndex = 1
			end

			app:triggerSound("Sound4", 1.0, 0.5)
		end
	elseif (rootAddr == "/entity") then
		--Entities are numbered, so we need to extract the entity index from its
		--address.
		index = tonumber(address:sub(8, 8))
		paramAddr = address:sub(9)

		if (paramAddr == "/position/x") then
			blocks[index+1].position.x = value * app:getDrawingAreaSize().x
		elseif (paramAddr == "/position/y") then
			blocks[index+1].position.y = value * app:getDrawingAreaSize().y
		elseif (paramAddr == "/colour/r") then
			blockCol.x = value
		elseif (paramAddr == "/colour/g") then
			blockCol.y = value
		elseif (paramAddr == "/colour/b") then
			blockCol.z = value

			--We store block colours as HSL rather than RGB in this game, so we
			--need to convert any received values back to HSL. This assumes that
			--the 3 colour values will be sent together, which may not be the
			--case.
			blocks[index+1].colour = rgbToHsl(blockCol)
		end
	end
end

--------------------------------------------------------------------------------
--	Helper function. Moves a block, sends out appropriate OSC messages.
function moveBlock(app, direction, sound)
	for i=1,8 do
		--Check if the player has collided with a block.
		if (blocks[i].position:getDistance(playerPos) < 128.0) then
			--If so, move the block accordingly.
			blocks[i].position = blocks[i].position + direction
			--And send out the relevant OSC messages, remembering to normalise
			--the values to 0->1.
			app:sendOscMessage("/entity" .. i-1 .. "/position/x",
							   blocks[i].position.x/app:getDrawingAreaSize().x)
			app:sendOscMessage("/entity" .. i-1 .. "/position/y",
							   blocks[i].position.y/app:getDrawingAreaSize().y)

			--Update the block's colour.
			blocks[i].colour.x = blocks[i].colour.x + (1.0/8.0)
			if (blocks[i].colour.x > 1.0) then
				blocks[i].colour.x = blocks[i].colour.x - 1.0
			end
			app:sendOscMessage("/entity" .. i-1 .. "/colour/r",
							   hslToRgb(blocks[i].colour).x)
			app:sendOscMessage("/entity" .. i-1 .. "/colour/g",
							   hslToRgb(blocks[i].colour).y)
			app:sendOscMessage("/entity" .. i-1 .. "/colour/b",
							   hslToRgb(blocks[i].colour).z)

			--Player takes the block's new colour.
			playerCol = hslToRgb(blocks[i].colour)
			app:sendOscMessage("/player/colour/r",
							   playerCol.x)
			app:sendOscMessage("/player/colour/g",
							   playerCol.y)
			app:sendOscMessage("/player/colour/b",
							   playerCol.z)

			--Trigger the relevant sound.
			app:triggerSound(sound,	--Name of the sound to play.
							 1.0,	--Amplitude of the sound.
							 0.5)	--Panning (0 = left, 1 = right).
			break
		end
	end
end
