require 'RubySFML'
include SFML

module Tiles
	Grass = 0
	Earth = 1
end

# Collectible item.
class Gem2 < Sprite
	def initialize(image, x, y)
		super(image)
		self.x = x
		self.y = y
	end

	def update
		self.rotation = 25 * Math.sin($clock.to_f*6)
	end
end

class Player < VSprite
	attr_reader :score

	def initialize(image, w, h)
		super(image, w, h)
		@sb = SoundBuffer.new("media/Beep.wav")
		@sound = Sound.new(@sb)
		@score = 0
	end

	def update(window, map)
		time = window.frameTime
		input = window.input

		# If there's no ground beneath my feet, increase speed downward
		if !map.solid?(self.x, self.bottom)
			@vy += 1
		# If there is ground beneath my feet, and the user presses up,
		# boost speed upwards to jump
		elsif input.isKeyDown(Key::Up)
			@vy = -20
		end

		super(1) # Update position based on velocity

		# If we just moved down, see it we landed
		if @vy > 0
			if map.solid?(self.x, self.bottom)
				@vy = 0
				self.top -= self.top % 50
			end
		# If we just moved up, see if we hit our head
		elsif @vy < 0
			if map.solid?(self.x, self.top)
				@vy = 0
				self.top += 50 - self.top % 50
			end
		end

		moved = false
		if input.isKeyDown(Key::Left)
			moved = true
			self.x -= 10
			# If we just moved left, see if we hit a wall
			if map.solid?(self.left, self.y)
				self.left += 50 - self.left % 50
			end
		end

		if input.isKeyDown(Key::Right)
			moved = true
			self.x += 10
			# If we just moved right, see if we hit a wall
			if map.solid?(self.right, self.y)
				self.left -= self.left % 50
			end
		end

		if @vy < 0
			@tx = 3
		elsif moved
			@tx = (@tx + 1) % 3
		else
			@tx = 0
		end

		# Same as in the tutorial game.
		map.gems.reject! do |c|
			if (c.x - self.x).abs < 50 and (c.y - self.y).abs < 50
				@sound.play
				@score += 10
				true
			else
				false
			end
		end
	end
end

# Map class holds and draws tiles and gems.
class Map < Drawable
	attr_reader :width, :height, :gems

	def initialize(filename, player)
		@start = Time.now
		@tileset_image = Image.new("media/Tileset.png")
		@tileset = TSprite.new(@tileset_image, 50, 50)
		@gem_image = Image.new("media/Gem.png")
		@gems = []
		@exit_image = Image.new("media/Star.png")
		@exit = TSprite.new(@exit_image, 25, 25)

		lines = File.readlines(filename).map { |line| line.chop }
		@height = lines.size
		@width = lines[0].size
		@tiles = Array.new(@width) do |x|
			Array.new(@height) do |y|
				case lines[y][x, 1]
				when '"' then Tiles::Grass
				when '#' then Tiles::Earth
				when 'S' then player.x, player.y = x*50, y*50; nil
				when 'E' then @exit.left, @exit.top = x*50+25, y*50+25; nil
				when 'x' then @gems << Gem2.new(@gem_image, x * 50 + 25, y * 50 + 25); nil
				else nil
				end
			end
		end
	end

	def done?(player)
		player.x.to_i/50 == @exit.x.to_i/50 and player.y.to_i/50 == @exit.y.to_i/50
	end

	def render(window)
		# Very primitive drawing function:
		# Draws all the tiles, some off-screen, some on-screen.
		@height.times do |y|
			@width.times do |x|
				tile = @tiles[x][y]
				if tile
					@tileset.tx = tile
					@tileset.left = x * 50
					@tileset.top = y * 50
					window.draw(@tileset) if
						@tileset.right > window.view.left and
						@tileset.left < window.view.right and
						@tileset.bottom > window.view.top and
						@tileset.top < window.view.bottom
				end
			end
		end
		@gems.each { |c|
			c.update
			window.draw(c) if
				c.right > window.view.left and
				c.left < window.view.right and
				c.bottom > window.view.top and
				c.top < window.view.bottom
		}
		window.draw(@exit)
	end

	# Solid at a given pixel position?
	def solid?(x, y)
		x < 0 or y < 0 or x >= @width*50 or y >= @height*50 or @tiles[x / 50][y / 50]
	end
end

$clock = Clock.new
mode = VideoMode.new(640, 480, 32)
win = RenderWindow.new(mode, "RubySFML Test", 0)
view = View.new(FloatRect.new(0, 0, 640, 480))
win.showMouseCursor(false)
win.useVerticalSync(true)
sky_image = Image.new("media/Space.png")
sky = Sprite.new(sky_image)
player_image = Image.new("media/CptnRuby.png")
player = Player.new(player_image, 50, 50)
levels = Dir["media/Level*.txt"].sort
level = 1
level_start = Time.now
map = Map.new(levels.shift, player)

# Simple game loop
done = false
game_over = false
while !done
	if map.done?(player)
		if levels.empty?
			game_over = true
		else
			level += 1
			level_start = Time.now
			map = Map.new(levels.shift, player)
		end
	end

	while e = win.getEvent()
		done = true if e.type == Event::Closed or
			(e.type == Event::KeyReleased and e.code == Key::Escape) 
	end

	player.update(win, map)

	left = player.x - 640/2
	left = 0 if left < 0
	left = map.width*50-640 if left > map.width*50-640
	top = player.y - 480/2
	top = 0 if top < 0
	top = map.height*50-480 if top > map.height*50-480
	view.rect = FloatRect.new(left, top, left+640, top+480)
	win.setView(view)

	sky.left = view.rect.left
	sky.top = view.rect.top
	win.draw(sky)
	win.draw(map)
	win.draw(player)
	score = Text.new("Score: #{player.score}", "", 20)
	score.left, score.top = sky.left, sky.top
	win.draw(score)

	if game_over
		text = Text.new("Game Over!", "", 60)
		text.left, text.top = sky.left+200, sky.top+200
		win.draw(text)
	elsif Time.now - level_start < 3
		text = Text.new("Level #{level}!", "", 60)
		text.left, text.top = sky.left+200, sky.top+200
		win.draw(text)
		text = Text.new("(Collect the gems and find the exit)", "", 25)
		text.left, text.top = sky.left+125, sky.top+260
		win.draw(text)
	end

	win.display()
	sleep(0.01)
end