--UI FUNCTIONS

function refreshUI()
	print("<refreshUI>")
	displayLines()
	displayScore()
	displayNext()
	displayLevel()
end

function displayLines()
	print("<displayLines>")
	Media.FloatingText("LINES - " .. lineNum, WPos.New(7 * 1024, 5 * 1024, 1024), 1)
end

function displayScore()
	print("<displayScore>")
	Media.FloatingText("SCORE: " .. score, WPos.New(16 * 1024, 5 * 1024, 1024), 1)
end

function displayNext()
	print("<displayNext>")
	Media.FloatingText("NEXT: ", WPos.New(16 * 1024, 11 * 1024, 1024), 1)
end

function updateNextShape()
	print("<updateNextShape>")
	for x, t in ipairs(nextShapeDisplay) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				v.Destroy()
			end
		end
	end
	print("\t\tCREATING NEXT SHAPE VISUALISATION")
	nextShapeDisplay = createShape(nextShapeIndex, CPos.New(15,11), true)
	print("\t\tNEXT SHAPE VISUALISATION CREATED")
end

function displayLevel()
	print("<displayLevel>")
	Media.FloatingText("LEVEL: " .. levelNum, WPos.New(16 * 1024, 19 * 1024, 1024), 1)
end

--GAME FUNCTIONS

function getNextShape()
	print("<getNextShape>")
	local newIndex = Utils.RandomInteger(1, #shapePrefabs)
	if (possibleIndex == nextShapeIndex) then
		newIndex = Utils.RandomInteger(1, #shapePrefabs)
	end
	nextShapeIndex = newIndex
end

function updateLevel()
	levelNum = math.floor(score/levelIncrement)
	if levelNum < 15 then
		playDelay = 20 - levelNum
	else
		playDelay = 5
	end
end

function initPlayArea()
	print("<initPlayArea>")
	for r = 1, 20 do
		playArea[r] = {}
		for a = 1, 10 do
			playArea[r][a] = false
		end
	end
end

function initControl()
	print("<initControl>")
	controlActor = Actor.Create("control", false, {Owner = player, Location = CPos.New(0,0)})
	Trigger.OnProduction(controlActor, function(actor, move)
				if (shape ~= nil) then
					if (move.Type == "move_left") then
						if (canMoveShapeLeft()) then
							moveShapeLeft()
						end
					elseif (move.Type == "move_right") then
						if (canMoveShapeRight()) then
							moveShapeRight()
						end
					elseif (move.Type == "rotate") then
						if (shape["rotates"] ~= false) then
							rotateShape()
						end
					elseif (move.Type == "move_down") then
						tickCounter = 0
						if (not shapeAtBottom()) then
							if (not blocksBelowShape()) then
								moveShapeDown()
							else
								freezeShape()
								destroyFullRows()
							end
						else
							freezeShape()
							destroyFullRows()
						end
					end
				end
				move.Destroy()
			end)
end

function startPlaying()
	print("<startPlaying>")
	playing = true
	objectiveID = player.AddPrimaryObjective("Don't allow the blocks to overflow!")
end

function play()
	print("<play>")
	if (shape == nil) then
		shape = createShape(nextShapeIndex, blockSpawnArea, false)
		getNextShape()
		updateNextShape()
	elseif (tickCounter % playDelay == 0) then
		tickCounter = 0
		if (shapeAtBottom()) then
			freezeShape()
			destroyFullRows()
		elseif (blocksBelowShape()) then
			freezeShape()
			destroyFullRows()
		else
			moveShapeDown()
		end
	end
end

function blocksBelowShape()
	print("<blocksBelowShape>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local checkPosition = CPos.New(v.Location.X, v.Location.Y + 1)
				if (actorAt(checkPosition)) then
					return true
				end
			end
		end
	end
	return false
end

function shapeAtBottom()
	print("<shapeAtBottom>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if v ~=false then
				local playAreaY = playAreaBottomRight.Y - v.Location.Y + 1
				if (playAreaY == 1) then
					return true
				end
			end
		end
	end
	return false	
end

function actorAt(position)
	print("<actorAt>")
	local playAreaX = position.X - playAreaTopLeft.X + 1
	local playAreaY = playAreaBottomRight.Y - position.Y + 1
	if (playAreaY <= 20) then
		if (playArea[playAreaY][playAreaX] == false) then
			return false
		end
	else
		return false
	end
	return true
end

function freezeShape()
	print("<freezeShapes>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local playAreaX = v.Location.X - playAreaTopLeft.X + 1
				local playAreaY = playAreaBottomRight.Y - v.Location.Y + 1
				if (playAreaY > 20) then
					playing = false
					player.MarkFailedObjective(objectiveID)
					return
				end
				local playAreaPos = playArea[playAreaY][playAreaX]
				if (playAreaPos == false) then
					playArea[playAreaY][playAreaX] = v
					v.Owner = Neutral
				else
					print("BLOCKS TAKING UP SAME SPACE")
				end
			end
		end
	end
	shape = nil
end

function destroyFullRows()
	print("<destroyFullRows>")
	local destroyedRows = 0
	for y, t in ipairs(playArea) do
		if rowFull(t) then
			for i, a in ipairs(t) do
				a.Kill()
				t[i] = false
			end
			lineNum = lineNum + 1
			destroyedRows = destroyedRows + 1
		end
	end
	score = score + ((math.pow(destroyedRows, 2) * 100) * 0.5)
	if (score >= winThreshold) then
		player.MarkCompletedObjective(objectiveID)
		UserInterface.SetMissionText("CONGRATULATIONS!")
		return
	end
	updateLevel()
	if (destroyedRows > 0) then
		moveDownRows()
	end
end

function moveDownRows()
	print("<moveDownRows>")
	for i = 2, 20 do
		for j = i, 2, -1 do
			if (rowEmpty(playArea[j - 1]) and not rowEmpty(playArea[j])) then
				moveDownRow(j)
			end
		end
	end
	destroyFullRows()
end

function moveDownRow(rowIndex)
	print("<moveDownRow>")
	t = playArea[rowIndex]
	r = {}
	for i, v in ipairs(playArea[rowIndex]) do
		if (v ~= false) then
			local newPosition = CPos.New(v.Location.X, v.Location.Y + 1)
			local blockType = v.Type
			v.Destroy()
			playArea[rowIndex][i] = false
			r[i] = Actor.Create(blockType, true, {Owner = Neutral, Location = newPosition})
		else
			r[i] = false
		end
	end
	playArea[rowIndex - 1] = r
end

function rowFull(row)
	print("<rowFull>")
	for i = 1, 10 do
		if row[i] == false then
			return false
		end
	end
	return true
end

function rowEmpty(row)
	print("<rowEmpty>")
	for i = 1, 10 do
		if row[i] ~= false then
			return false
		end
	end
	return true
end

function moveShapeDown()
	print("<moveShapeDown>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local newLocation = CPos.New(v.Location.X, v.Location.Y + 1)
				local inWorld = inPlayArea(newLocation)
				local blockType = v.Type
				v.Destroy()
				t[y] = Actor.Create(blockType, inWorld, {Owner = Neutral, Location = newLocation})
			end
		end
	end
end

function inPlayArea(position)
	print("<inPlayArea>")
	return ((position.X >= playAreaTopLeft.X) and (position.X <= playAreaBottomRight.X) and (position.Y >= playAreaTopLeft.Y) and (position.Y <= playAreaBottomRight.Y))
end

function canMoveShapeLeft()
	print("<canMoveShapeLeft>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local playAreaX = v.Location.X - playAreaTopLeft.X + 1
				local playAreaY = playAreaBottomRight.Y - v.Location.Y + 1
				local newX = v.Location.X - 1
				if (newX < playAreaTopLeft.X) then
					return false
				elseif (newX > playAreaTopLeft.X) then
					if (actorAt(CPos.New(newX, v.Location.Y))) then
						return false
					end
				end
			end
		end
	end
	return true
end

function moveShapeLeft()
	print("<moveShapeLeft>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local newX = v.Location.X - 1
				local newY = v.Location.Y
				local newPosition = CPos.New(newX, newY)
				local inWorld = inPlayArea(newPosition)
				local blockType = v.Type
				v.Destroy()
				t[y] = Actor.Create(blockType, inWorld, {Owner = Neutral, Location = newPosition})
			end
		end
	end
end

function canMoveShapeRight()
	print("<canMoveShapeRight>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local newX = v.Location.X + 1
				if (newX > playAreaBottomRight.X) then
					return false
				elseif (newX < playAreaBottomRight.X) then
					if (actorAt(CPos.New(newX, v.Location.Y))) then
						return false
					end
				end
			end
		end
	end
	return true
end

function moveShapeRight()
	print("<moveShapeRight>")
	for x, t in ipairs(shape) do
		for y, v in ipairs(t) do
			if (v ~= false) then
				local newX = v.Location.X + 1
				local newY = v.Location.Y
				local newPosition = CPos.New(newX, newY)
				local inWorld = inPlayArea(newPosition)
				local blockType = v.Type
				v.Destroy()
				t[y] = Actor.Create(blockType, inWorld, {Owner = Neutral, Location = newPosition})
			end
		end
	end
end

function rotateShape()
	print("<rotateShape>")
	local templateShape = createPositioningTemplate()
	if (validTemplate(templateShape)) then
		for x, t in ipairs(shape) do
			print("KEY(x): " .. x)	
			for y, v in ipairs(t) do
				print("KEY(y): " .. y)
				if (v ~= false) then
					local newPosition = templateShape[x][y]
					local inWorld = inPlayArea(newPosition)
					local blockType = v.Type
					v.Destroy()
					t[y] = Actor.Create(blockType, inWorld, {Owner = Neutral, Location = newPosition})
				end
			end
		end
	end
end

function createPositioningTemplate()
	print("<createPositioningTemplate>")
	local template = {}
	local axisBlock = shape[shape["axisBlock"].X][shape["axisBlock"].Y]
	local centreX
	local centreY
	if (shape["rotationOffset"] ~= false) then
		centreX = axisBlock.Location.X + shape["rotationOffset"].X
		centreY = axisBlock.Location.Y + shape["rotationOffset"].Y
	else
		centreX = axisBlock.Location.X
		centreY = axisBlock.Location.Y
	end
	for x, t in ipairs(shape) do
		template[x] = {}
		for y, v in ipairs(t) do
			if (v ~= false) then
				local relativeX = v.Location.X - centreX
				local relativeY = v.Location.Y - centreY
				local newX = -relativeY + centreX
				local newY = relativeX + centreY
				template[x][y] = CPos.New(newX, newY)
			else
				template[x][y] = false
			end
		end
	end
	return template
end

function validTemplate(template)
	print("<validateTemplate>")
	for x, t in ipairs(template) do
		for y, v in ipairs(t) do
			if (v ~=  false) then
				if not inPlayArea(v) then
					return false
				elseif(actorAt(v)) then
					return false
				end
			end
		end
	end
	return true
end

--SHAPE FUNCTIONS

function createShape(shapeIndex, spawnPosition, addToWorld)
	print("<createShape>")
	local shapePrefab = shapePrefabs[shapeIndex]
	local shape = {}
	shape["rotates"] = shapePrefab["rotates"]
	shape["rotationOffset"] = shapePrefab["rotationOffset"]
	shape["axisBlock"] = shapePrefab["axisBlock"]
	local blockType = shapePrefab["colour"] .. "_block"
	for x, t in ipairs(shapePrefab) do
		shape[x] = {}
		for y, v in ipairs(t) do
			if (v) then
				local position = CPos.New(spawnPosition.X + (x - 1), spawnPosition.Y + (y - 1))
				shape[x][y] = Actor.Create(blockType, addToWorld, {Owner = Neutral, Location = position})
			else
				shape[x][y] = false
			end
		end
	end
	return shape
end

--UTILITY FUNCTIONS

function printCPos(CPosLocation)
	print("<printCPos>")
	print("CPOS:")
	print("\tx: " .. CPosLocation.X)
	print("\ty: " .. CPosLocation.Y)
end

--OPENRA FUNCTIONS

function WorldLoaded()
	print("<WorldLoaded>")
	lineNum = 0
	score = 0
	levelNum = 0
	playing = false
	initPlayArea()
	initControl()
	getNextShape()
	Trigger.AfterDelay(10, startPlaying)
end

function Tick()
	print("<Tick>")
	refreshUI()
	tickCounter = tickCounter + 1
	if (playing) then
		play()
	end
end

--VARIABLES

lineNum = nil
score = nil
levelNum = nil
playing = nil
shape = nil
playArea = {}
controlActor = nil
Neutral = Player.GetPlayer("Neutral")
player = Player.GetPlayer("Multi0")
tickCounter = 0
playDelay = 20
rotationGive = 1
levelIncrement = 200
winThreshold = 10000
nextShapeIndex = nil
nextShapeDisplay = {}

--POSITION VARIABLES

playAreaTopLeft = CPos.New(2, 9)
playAreaBottomRight = CPos.New(11, 28)
blockSpawnArea = CPos.New(5, 7)

--OBJECTIVE VARIABLES

objectiveID = nil

--PREFABS

shapePrefabs = { 
				{colour = "cyan", rotationOffset = {X = 0, Y = 0}, axisBlock = {X = 2, Y = 1}, {true}, {true}, {true}, {true}},
				{colour = "purple", rotationOffset = false, axisBlock = {X = 2, Y = 2}, {false, true}, {true, true}, {false, true}},
				{colour = "yellow", rotates = false, {true, true}, {true, true}},
				{colour = "blue", rotationOffset = false, axisBlock = {X = 2, Y = 2}, {true, true}, {false, true}, {false, true}},
				{colour = "orange", rotationOffset = false, axisBlock = {X = 2, Y = 2}, {false, true}, {false, true}, {true, true}},
				{colour = "lime", rotationOffset = false, axisBlock = {X = 2, Y = 2}, {false, true}, {true, true}, {true, false}},
				{colour = "red", rotationOffset = false, axisBlock = {X = 2, Y = 2}, {true, false}, {true, true}, {false, true}}
				}
