-- ===================================================== --
--  ____  _                              _        ____  _
-- |  _ \(_)_ __   ___  __ _ _ __  _ __ | | ___  |  _ \(_)__________ _
-- | |_) | | '_ \ / _ \/ _` | '_ \| '_ \| |/ _ \ | |_) | |_  /_  / _` |
-- |  __/| | | | |  __/ (_| | |_) | |_) | |  __/ |  __/| |/ / / / (_| |
-- |_|   |_|_| |_|\___|\__,_| .__/| .__/|_|\___| |_|   |_/___/___\__,_|
--                          |_|   |_|
--  Made for the the Linux Game Jam March 2017
--  Requires love2d 0.10 (Super Toast)
--
--  A simple game about protecting pizza from yourself
--
--  This is a propaganga piece made to show pineapples
--  ruin pizza. Don't deny it, you know it to be true
--
--  Also, it's in space
--
--  The game quits when the pineapple touches a pizza
--  as what's the point anymore if pizza has been ruined
--
--  Controls : WASD and Space, P to pause and ESC to exit
--
-- ===================================================== --
--
--  Images cliparts.co
--  BG     spacetelescope.org/images/potw1006a
--  Music  toneden.io/mi77ermusic/post/stardust
--  Sound  freesound.org/people/greenvwbeetle/sounds/244654
--
-- ===================================================== --

-- ===================================================== --
-- Settings

-- Whole scene properties
kppm = 64 -- Pixels per metre
kwidth = 0
kheight = 0

-- player properties
kplayerThrust = 30000
kplayerMaxV = 350
kplayerFriction = 5

-- Bullet info
knumBullets = 3
kbullRadius = 10
kbulletReturnForce = 300
kbulletMaxV = 400

-- Enemy Info
knumEnemies = 50
kenemySpawnStartRate = 2

-- Music volume
kmusicVolume = 0.05

-- Settings
kdifficultyFactor = 1.02

-- ===================================================== --
--  Objects

-- The physics world
world = {}

-- Arena Stuff
bounds = {}
bounds.ground = {}
bounds.lwall = {}
bounds.rwall = {}
bounds.ceil = {}

-- The player
ktypePlayer = {}
player = {}
player.type = ktypePlayer

-- Bullets
ktypeBullet = {}
bullets = {}
for i=1,knumBullets do
	table.insert(bullets, {})
end
for key,bull in pairs(bullets) do
	bull.projectile = false
	bull.returning = false
	bull.type = ktypeBullet
end

-- Enemies
ktypeEnemy = {}
enemySpawnRate = kenemySpawnStartRate
enemies = {}
for i=1,knumEnemies do
	table.insert(enemies, {})
end
for key,enemy in pairs(enemies) do
	enemy.alive =false 
	enemy.type = ktypeEnemy
end

-- Score
score = 0
paused = true

-- ===================================================== --
--  Load
function love.load()

	-- Store some variables
	kwidth, kheight = love.graphics.getDimensions()

	-- Create images
	imageBG     = love.graphics.newImage("space.png")
	imagePlayer = love.graphics.newImage("pineapple.png")
	imageBullet = love.graphics.newImage("fireball.png")
	imageEnemy  = love.graphics.newImage("pizza.png")

	-- Load sound
	music = love.audio.newSource("Stardust.ogg")
	music:setLooping(true)
	music:setVolume(kmusicVolume)
	music:play()
	
	soundPop = love.audio.newSource("pop.ogg","static")

	-- Create a physics world
	love.physics.setMeter(kppm)
	world = love.physics.newWorld( 0 , 0, true )
	world:setCallbacks(beginContact, endConact, preSolve, postSolve)

	-- Create the ground (and walls)
	bounds.ground.body    = love.physics.newBody(world, kwidth/2, kheight)
	bounds.ground.shape   = love.physics.newRectangleShape( kwidth, 1 )
	bounds.lwall.body     = love.physics.newBody(world, 0, kheight/2)
	bounds.lwall.shape    = love.physics.newRectangleShape( 1, kheight )
	bounds.rwall.body     = love.physics.newBody(world, kwidth, kheight/2)
	bounds.rwall.shape    = love.physics.newRectangleShape( 1, kheight )
	bounds.ceil.body      = love.physics.newBody(world, kwidth/2, 0)
	bounds.ceil.shape     = love.physics.newRectangleShape( kwidth, 1 )

	for key,bound in pairs(bounds) do
		bound.fixture = love.physics.newFixture( bound.body, bound.shape )
		bound.fixture:setCategory(1)
	end

	-- Create the player
	player.body    = love.physics.newBody(world, kwidth/2, kheight-40, "dynamic")
	player.shape   = love.physics.newRectangleShape(0, 0, 20, 40)
	player.fixture = love.physics.newFixture(player.body, player.shape )
	player.body:setFixedRotation(true)
	player.body:setLinearDamping(kplayerFriction)
	player.fixture:setCategory(2)
	player.fixture:setUserData(player)

	-- Create the bullets
	for key,bull in pairs(bullets) do
		x, y = player.body:getPosition()
		x, y = x + math.random(-30,30), y + math.random(-30,30)
		bull.body    = love.physics.newBody(world, x, y, "dynamic")
		bull.shape   = love.physics.newCircleShape(kbullRadius)
		bull.fixture = love.physics.newFixture(bull.body, bull.shape)
		bull.fixture:setCategory(3)
		bull.fixture:setMask(1,2,3) -- bullets don't hit wall, the player, or eachother
		bull.fixture:setUserData(bull)
		bull.image = imageBullet
	end

	-- Create the enemies
	enemyTimer = love.timer.getTime()
	enemyScale = 1
	for key,enemy in pairs(enemies) do
		enemy.body    = love.physics.newBody(world, kwidth/2, -100, "dynamic")
		enemy.shape   = love.physics.newCircleShape(10 * enemyScale)
		enemy.scale   = enemyScale
		enemy.fixture = love.physics.newFixture(enemy.body, enemy.shape)
		enemy.fixture:setCategory(4)
		enemy.fixture:setMask(1,4) -- enemies don't hit wall or eachother
		enemy.fixture:setUserData(enemy)
		enemyScale = enemyScale + 0.1
	end

	-- Load in save file
	local savedatachunk = love.filesystem.load( "savedata" )
	if savedatachunk then
		savedata = savedatachunk()
	else
		savedata = {}
		savedata.highscore = 0
		savedata.fails     = 0
	end
end

-- ===================================================== --
--  Update
function love.update(dt)
	if paused then
		music:pause()
		return
	else
		music:resume()
	end

	-- Keyboard controls for player
	local dx, dy = 0, 0
	if love.keyboard.isDown("left") then
		dx = dx - kplayerThrust
	end
	if love.keyboard.isDown("right") then
		dx = dx + kplayerThrust
	end
	if love.keyboard.isDown("up") then
		dy = dy - kplayerThrust
	end
	if love.keyboard.isDown("down") then
		dy = dy + kplayerThrust
	end
	player.body:applyForce( dx*dt, dy*dt )

	-- Limit player max velocity
	local vx, vy = player.body:getLinearVelocity()
	local vel = math.sqrt(vx*vx + vy*vy)
	if vel > kplayerMaxV then
		vx = vx/vel * kplayerMaxV
		vy = vy/vel * kplayerMaxV
		player.body:setLinearVelocity(vx,vy)
	end

	-- handle the enemies
	local spawnEnemy = love.timer.getTime() - enemyTimer > enemySpawnRate
	for key,enemy in pairs(enemies) do
		-- alive enemies should slowly drop
		if enemy.alive then
			local x, y = enemy.body:getPosition()
			if y > kheight then
				enemy.alive = false
			end
		else
			enemy.body:setPosition(-100,-100) -- innactive enemies off screen
			if spawnEnemy then
				spawnEnemy = false
				enemyTimer = love.timer.getTime() -- reset the timer
				enemy.body:setPosition( math.random(0,kwidth), 0 )
				enemy.body:setLinearVelocity(math.random(-20,20),math.random(50,200))
				enemy.alive = true
			end
		end
	end

	-- Sort out the bullets
	local px, py = player.body:getPosition()
	for key,bull in pairs(bullets) do
		local bx, by = bull.body:getPosition()
		-- non projectiles orbit the player
		if not bull.projectile then
			-- Get the delta to the player
			local dx, dy = px-bx, py-by
			bull.body:applyForce( dx * kbulletReturnForce * dt, dy * kbulletReturnForce * dt )

			-- Account for returned bullets
			local dmag = math.sqrt(dx*dx + dy*dy)
			if dmag < bull.shape:getRadius()*3 then
				bull.returning = false
			end

			-- limit vel
			local vx, vy = bull.body:getLinearVelocity()
			local vel = math.sqrt(vx*vx + vy*vy)
			if vel > kbulletMaxV then
				vx = vx/vel * kbulletMaxV
				vy = vy/vel * kbulletMaxV
				bull.body:setLinearVelocity(vx,vy)
			end
		-- otherwise they fly and come back
		else
			if by < 0 then
				bull.projectile = false
				bull.returning  = true
			end
		end
	end	

	-- Update the physics world last
	world:update(dt)
end

-- ===================================================== --
--  Key presses
function love.keypressed( key, sc, isrepeat )

	-- Fire
	if key == "space" then
		-- find a bullet
		for key,bull in pairs(bullets) do
			if not bull.projectile and not bull.returning then
				bull.projectile = true
				bull.body:setPosition(player.body:getPosition())
				bull.body:setLinearVelocity( 0, -1000 )
				break
			end
		end

	-- Pause
	elseif key == "p" then
		paused = not paused

	-- Quit
	elseif key == "escape" then
		love.event.quit()
	end
end

-- ===================================================== --
--  Draw
function love.draw()

	-- Draw the BG
	love.graphics.setColor( 128,128,128,256 )
	love.graphics.draw( imageBG, kwidth/2, kheight/2, love.timer.getTime()/10, 0.8, 0.8, 600, 600)

	-- If we're paused
	if paused then
		love.graphics.setColor( 256,256,256,256 )
		love.graphics.print( "     Paused!\n[P] to unpause", kwidth/2, kheight/2, 0, 1, 1, 40 )
	end

	-- Draw the player
	local x,y = player.body:getPosition()
	love.graphics.setColor( 256,256,256,256 )
	love.graphics.draw( imagePlayer, x, y+5, 0, 0.2, 0.2, 72,170 )

	-- Draw the enemies
	love.graphics.setColor( 256,256,256,256 )
	for key,enemy in pairs(enemies) do
		if enemy.alive then
			love.graphics.draw( imageEnemy,
			                    enemy.body:getX(),
			                    enemy.body:getY(),
			                    enemy.scale-1,
			                    0.2*enemy.scale, 0.2*enemy.scale,
			                    60, 60 )
		end
	end

	-- Draw the bullets
	for key,bull in pairs(bullets) do
		if bull.projectile then
			love.graphics.setColor( 256,256,256,256 )
		elseif bull.returning then
			love.graphics.setColor( 128,128,128,64 )
		else
			love.graphics.setColor( 200,200,200,180 )
		end
		love.graphics.draw( bull.image,
		                    bull.body:getX(), bull.body:getY(),
		                    love.timer.getTime()*4,
		                    0.2, 0.2,
		                    60, 60 )
	end

	-- Draw the UI
	love.graphics.setColor( 256,256,256,256 )
	love.graphics.print( string.format("Pizzas Saved: %i\nHigh Score: %i\nRuined Pizzas: %i",
	                     score,
	                     savedata.highscore,
	                     savedata.fails), 1, 1 )
end


-- ===================================================== --
--  Quit
function love.quit()
	-- Save out the data
	local highest = score
	if highest < savedata.highscore then highest = savedata.highscore end
	love.filesystem.write( "savedata", string.format("return {\nhighscore = %i,\nfails = %i\n}",
	                       highest,
	                       savedata.fails) )
end

-- ===================================================== --
--  Physics callbacks

-- ie if a and be are type x and y
-- if so, returns them in correct order
function abxy( a, b, x, y )
	if not a or not b then return end
	local at, bt = a.type, b.type
	if not at or not bt then return
	elseif ( at == x and bt == y) then
		return a, b
	elseif ( bt == x and at == y) then
		return b, a
	else
		return
	end
end

-- called at start of overlap (always)
function beginContact(first, second, collision)
	local fd, sd = first:getUserData(), second:getUserData()
	
	-- Enemies and bullets
	local bullet, enemy = abxy( fd, sd, ktypeBullet, ktypeEnemy )
	if bullet and enemy then
		if bullet.projectile and enemy.alive then
			enemy.alive = false
			bullet.projectile = false
			bullet.returning = true
			score = score + 1
			soundPop:play()
			enemySpawnRate = enemySpawnRate/kdifficultyFactor -- speed up spawns
		end
	end

	local enemy, player = abxy( fd, sd, ktypeEnemy, ktypePlayer )
	if enemy and player then
		savedata.fails = savedata.fails + 1
		love.event.quit()
	end
end

-- called after overlap (only if overlap occured)
function endContact(first, second, collision)
end

-- called before the collision gets resolved
function preSolve(first, second, collision)
	local fd, sd = first:getUserData(), second:getUserData()

	-- Don't do any physics collisions for bullets and enemies
	local bullet, enemy = abxy( fd, sd, ktypeBullet, ktypeEnemy )
	if bullet and enemy then
		collision:setEnabled(false)
	end
end

-- called after collision resolved, with details
function postSolve(first, second, collision, normalimpulse, tangentimpulse)
	-- Not needed yet
end
