function LevelLoadTiles()
	math.randomseed(3)
	LevelData = dofile("data/levels/"..currLevel)

	--[[
		on level format:

		id = tile identifier
		depth = order in the render
		force = rendering other tile instead of the one in this position
		overlay = render another tile id or, if multiple tiles {id, id, id,}, choose at random
		overlay_depth = foreground/background overlay depth
		type = collision type
	]]
	LevelGetTileData()
	LevelTiles = LevelData.tiles
	LevelUpdateDimensions()
	LevelIndexTiles()
	TileCreateObjects()
end


function LevelExpandCanvas(horizontal,vertical)
	local horizontal = horizontal or 0
	local vertical = vertical or 0
	local h = LevelGetTileWidth()
	local v = LevelGetTileHeight()

	-- get new canvas size
	local newCanvasH = h + math.abs(horizontal)
	local newCanvasV = v + math.abs(vertical)

	-- lets make a new temporal canvas
	local ExpandedLevel = {}
	for i = 1, newCanvasV do
		ExpandedLevel[i] = {}
		for j = 1, newCanvasH do
			ExpandedLevel[i][j] = InstanceTile(0)
		end
	end

	-- lets guess how the new canvas and positions are offset
	local expand_h = 0
	if horizontal < 0 then
		expand_h = -horizontal
	end

	local expand_v = 0
	if vertical < 0 then
		expand_v = -vertical
	end

	-- get data from old canvas to new canvas
	for i = 1, #LevelTiles do
		for j = 1, #LevelTiles[i] do
			ExpandedLevel[i+expand_v][j+expand_h] = InstanceTile(LevelTiles[i][j].id)
		end
	end

	-- use new canvas
	LevelTiles = ExpandedLevel
end


function LevelReduceCanvas(horizontal,vertical)
	local horizontal = horizontal or 0
	local vertical = vertical or 0
	local h = LevelGetTileWidth()
	local v = LevelGetTileHeight()

	-- get new canvas size
	local newCanvasH = h - math.abs(horizontal)
	local newCanvasV = v - math.abs(vertical)

	-- lets make a new temporal canvas
	local ExpandedLevel = {}
	for i = 1, newCanvasV do
		ExpandedLevel[i] = {}
		for j = 1, newCanvasH do
			ExpandedLevel[i][j] = InstanceTile(0)
		end
	end

	-- lets guess how the new canvas and positions are offset
	local expand_h = 0
	if horizontal < 0 then
		expand_h = -horizontal
	end

	local expand_v = 0
	if vertical < 0 then
		expand_v = -vertical
	end

	-- get data from old canvas to new canvas
	for i = 1, #ExpandedLevel do
		for j = 1, #ExpandedLevel[i] do
			ExpandedLevel[i][j] = InstanceTile(LevelTiles[i+expand_v][j+expand_h].id)
		end
	end

	-- use new canvas
	LevelTiles = ExpandedLevel

	LevelExpandCanvas()
end

function LevelGetTileData()
	for k, v in pairs(tileset) do
		if v == LevelData.tileset then
			TileData = dofile("data/tileset/"..k..".lua")
		end
	end
end

function LevelReloadTiles()
	LevelUpdateDimensions()
end

function LevelUpdateDimensions()
	LevelData.Width = LevelGetWidth()
	LevelData.Height = LevelGetHeight()
end

function LevelGetTileHeight()
	return #LevelTiles
end

function LevelGetTileWidth()
	local width = 0
	for i = 1, #LevelTiles do
		if width < #LevelTiles[i] then width = #LevelTiles[i] end
	end
	return width
end

function LevelGetHeight()
	return LevelGetTileHeight() * tileProperties.height
end

function LevelGetWidth()
	return LevelGetTileWidth() * tileProperties.width
end

function LevelIndexTiles()
	TileIndex = {}

	-- index from tileset
	local width = LevelData.tileset:getPixelWidth()/tileProperties.width
	local height = LevelData.tileset:getPixelHeight()/tileProperties.height
	for i = 0, height do
		for j = 0, width do
			TileIndex[i*width+j+1] = love.graphics.newQuad(
														j*tileProperties.width,
														i*tileProperties.height,
														tileProperties.width,
														tileProperties.height,
														LevelData.tileset:getDimensions()
													)
		end
	end
	TileDataInitialize()

-- instance level tiles according to the Properties
	for i = 1, #LevelTiles do
		for j = 1, #LevelTiles[i] do
			SetTile(i,j,LevelTiles[i][j])
		end
	end
end

function TileDataInitialize()
	for _, Properties in pairs(TileData) do
		if Properties.animation ~= nil then
			Properties.tileset = love.graphics.newImage("assets/terrain/"..Properties.animation..".png")
			Properties.imgs = {}
			Properties.current_image = 1
			Properties.current_subimage = 1

			local tileset = Properties.tileset
			local width = tileset:getPixelWidth()/tileProperties.width
			local height = tileset:getPixelHeight()/tileProperties.height
			local image_count = 0

			for i = 0, height-1 do
				for j = 0, width-1 do
					local quad =
						love.graphics.newQuad(
							j*tileProperties.width,
							i*tileProperties.height,
							tileProperties.width,
							tileProperties.height,
							tileset:getDimensions()
						)
					image_count = image_count + 1

					table.insert(Properties.imgs,quad)
				end
			end
			Properties.image_count = image_count
		end
	end
end

function InstanceTile(id)
	local tile = {}

	tile.id = id
	local Properties = TileData[tile.id]

	if Properties ~= nil then
		if type(Properties.overlay) == "table" then
			tile.display_overlay = Properties.overlay[math.random(#Properties.overlay)]
		else
			tile.display_overlay = Properties.overlay
		end

		if type(Properties.force) == "table" then
			tile.display = Properties.force[math.random(#Properties.force)]
		else
			tile.display = Properties.force
		end
	end

	return tile
end

function SetTile(i,j,id)
	LevelTiles[i][j] = InstanceTile(id)
end

function GridDisplay()
	for i = 1, #LevelTiles do
		for j = 1, #LevelTiles[i] do
			love.graphics.rectangle(
				"line",
				tileProperties.scale * (j * tileProperties.width + (levelProperties.offset.x - tileProperties.width)) - Camera.pos.x,
				tileProperties.scale * (i * tileProperties.height + (levelProperties.offset.y - tileProperties.height)) - Camera.pos.y,
				tileProperties.scale * tileProperties.width,
				tileProperties.scale * tileProperties.height
			)
		end
	end
end

function TileOptimizeObjects()
	logPrint("Optimizing Objects...")
	local unoptimized = 0
	local isTileOptimized = {}
	for i = 1, #LevelTiles do
		isTileOptimized[i] = {}
		for j= 1, #LevelTiles[i] do
			isTileOptimized[i][j] = false
		end
	end
	for i = 1, #LevelTiles do
		for j = 1, #LevelTiles[i] do
			if LevelTiles[i][j].id ~= 0 then
				local type = TileData[LevelTiles[i][j].id].type
				if type == "whole" and not isTileOptimized[i][j] then
					isTileOptimized[i][j] = true
					local n = 1
					local check = true
					while check do
					 	check = false
						if LevelTiles[i][j+n] ~= nil
						and TileData[LevelTiles[i][j+n].id] ~= nil
						then
							local type_check = TileData[LevelTiles[i][j+n].id].type
							if type_check == "whole"
							and not isTileOptimized[i][j+n]
							then
								check = true
								isTileOptimized[i][j+n] = true
								n = n + 1
							end
						end
					end
					local m = 1
					local check = true
					while check do
					 	check = false
						local checkline = true
						-- for as long as line, check
						for l = 0, n-1 do
							checkline = false
							if LevelTiles[i+m] ~= nil
							and LevelTiles[i+m][j+l] ~= nil
							and TileData[LevelTiles[i+m][j+l].id] ~= nil
							then
								local type_check = TileData[LevelTiles[i+m][j+l].id].type
								if type_check == "whole"
								and not isTileOptimized[i+m][j+l]
								then
									checkline = true
								else
									break
								end
							else
								break
							end
						end
						if checkline then
							check = true
							for l = 0, n-1 do
								isTileOptimized[i+m][j+l] = true
							end
							m = m + 1
						else
							break
						end
					end

					logPrint("- Group size: "..m.."x"..n)
					unoptimized = unoptimized + m * n
					local base_x = tileProperties.scale * j * tileProperties.width + tileProperties.scale * (levelProperties.offset.x - tileProperties.height)
					local base_y = tileProperties.scale * i * tileProperties.height + tileProperties.scale * (levelProperties.offset.y - tileProperties.height)

					local col = Collision:New(
						base_x,
						base_y,
						base_x + tileProperties.width * tileProperties.scale * n,
						base_y + tileProperties.height * tileProperties.scale * m
					)
					table.insert(LoadedObjects.Collisions,col)
				end
			end
		end
	end

	logPrint("collisions optimized from " .. unoptimized .. " to " .. #LoadedObjects.Collisions)
end

function TileCreateObjects()
	LoadedObjects.Collisions = {}
	LoadedObjects.Platforms = {}
	LoadedObjects.Ladders = {}
	LoadedObjects.Hazards = {}

	TileOptimizeObjects()

	for i = 1, #LevelTiles do
		for j = 1, #LevelTiles[i] do
			if LevelTiles[i][j].id ~= 0 then

				local type = TileData[LevelTiles[i][j].id].type
				local light = TileData[LevelTiles[i][j].id].light
				local base_x = tileProperties.scale * j * tileProperties.width + tileProperties.scale * (levelProperties.offset.x - tileProperties.height)
				local base_y = tileProperties.scale * i * tileProperties.height + tileProperties.scale * (levelProperties.offset.y - tileProperties.height)


				if light ~= 0 and light ~= nil then
					CreateLight(
						base_x + tileProperties.width/2 * tileProperties.scale,
						base_y + tileProperties.height/2 * tileProperties.scale,
						light
					)
				end

				-- wholes are handled in optimization now
				--[[if type == "whole" then
					local col = Collision:New(
						base_x,
						base_y,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)
				else]]if type == "half_bottom" then

					local col = Collision:New(
						base_x,
						base_y + tileProperties.height/2 * tileProperties.scale,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "half_top" then

					local col = Collision:New(
						base_x,
						base_y ,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height/2 * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "half_right" then

					local col = Collision:New(
						base_x + tileProperties.height/2 * tileProperties.scale,
						base_y,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "half_left" then

					local col = Collision:New(
						base_x,
						base_y,
						base_x + tileProperties.height/2 * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "platform" then
					local plat = Collision:New(
						base_x,
						base_y + tileProperties.scale * 2,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height/4 * tileProperties.scale + tileProperties.scale * 2
					)
					table.insert(LoadedObjects.Platforms,plat)

				elseif type == "ramp2_bot_left_whole" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x,
							base_y + k * tileProperties.scale - tileProperties.scale,
							base_x + k * 2 * tileProperties.scale,
							base_y + k * tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end
					-- fill lower half
					local col = Collision:New(
						base_x,
						base_y + tileProperties.height/2 * tileProperties.scale,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "ramp2_bot_left_half" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x,
							base_y + tileProperties.height/2 * tileProperties.scale + k * tileProperties.scale - tileProperties.scale,
							base_x + k * 2 * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale + k * tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end

				elseif type == "ramp2_top_left_whole" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x,
							base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale,
							base_x + tileProperties.width * tileProperties.scale - (k-1) * 2 * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale + tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end
					-- fill higher half
					local col = Collision:New(
						base_x,
						base_y,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height/2 * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "ramp2_top_left_half" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x,
							base_y - tileProperties.scale + k * tileProperties.scale,
							base_x + tileProperties.width * tileProperties.scale - (k-1) * 2 * tileProperties.scale,
							base_y - tileProperties.scale + k * tileProperties.scale + tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end

				elseif type == "ramp2_bot_right_whole" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x + (k-8) * -2 * tileProperties.scale,
							base_y - tileProperties.scale + k * tileProperties.scale,
							base_x + tileProperties.width * tileProperties.scale,
							base_y - tileProperties.scale + k * tileProperties.scale + tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end
					-- fill lower half
					local col = Collision:New(
						base_x,
						base_y + tileProperties.height/2 * tileProperties.scale,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "ramp2_bot_right_half" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x + (k-8) * -2 * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale,
							base_x + tileProperties.width * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale - tileProperties.scale + k * tileProperties.scale + tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end

				elseif type == "ramp2_top_right_half" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x + (k-8) * -2 * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale,
							base_x + tileProperties.width * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale + tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end

				elseif type == "ramp2_top_right_whole" then
					for k = 1, 8 do
					-- do ramp owo
						local slope = Collision:New(
							base_x + (k-8) * -2 * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale,
							base_x + tileProperties.width * tileProperties.scale,
							base_y + tileProperties.height/2 * tileProperties.scale + tileProperties.height/2 * tileProperties.scale - k * tileProperties.scale + tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end
					-- fill higher half
					local col = Collision:New(
						base_x,
						base_y,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height/2 * tileProperties.scale
					)
					table.insert(LoadedObjects.Collisions,col)

				elseif type == "ramp1_bot_left" then

					for k = 1, 16 do
					-- do ramp owo
						local slope = Collision:New(
							base_x,
							base_y + k * tileProperties.scale - tileProperties.scale,
							base_x + k * tileProperties.scale,
							base_y + k * tileProperties.scale
						)
						table.insert(LoadedObjects.Collisions,slope)

					end

				elseif type == "ladder_right" then

					local ladder = Collision:New(
						base_x + (tileProperties.width-4)* tileProperties.scale,
						base_y,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Ladders,ladder)

				elseif type == "ladder_platform_right" then

					local ladder = Collision:New(
						base_x + (tileProperties.width-4)* tileProperties.scale,
						base_y + tileProperties.scale * 2,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Ladders,ladder)

					local plat = Collision:New(
						base_x,
						base_y + tileProperties.scale * 2,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height/4 * tileProperties.scale + tileProperties.scale * 2
					)
					table.insert(LoadedObjects.Platforms,plat)

				elseif type == "ladder_left" then


					local ladder = Collision:New(
						base_x,
						base_y,
						base_x + tileProperties.scale * 4,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Ladders,ladder)

				elseif type == "ladder_platform_left" then


					local ladder = Collision:New(
						base_x,
						base_y + tileProperties.scale * 2,
						base_x + tileProperties.scale * 4,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Ladders,ladder)

					local plat = Collision:New(
						base_x,
						base_y + tileProperties.scale * 2,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height/4 * tileProperties.scale + tileProperties.scale * 2
					)
					table.insert(LoadedObjects.Platforms,plat)

				elseif type == "bottom_hazard" then


					local hazard = Collision:New(
						base_x,
						base_y + tileProperties.height * 12/16 * tileProperties.scale,
						base_x + tileProperties.width * tileProperties.scale,
						base_y + tileProperties.height * tileProperties.scale
					)
					table.insert(LoadedObjects.Hazards,hazard)

				end
			end
		end
	end
	--CreateCollisionTable()
end

function AnimateTiles()
	for _, Properties in pairs(TileData) do
		if Properties ~= nil then
			if Properties.animation ~= nil then
			-- calculate subimage
				Properties.current_subimage = Properties.current_subimage + current_dt
				-- cycle image
				if Properties.current_subimage >= Properties.delay then
					Properties.current_subimage = Properties.current_subimage - Properties.delay
					Properties.current_image = Properties.current_image + 1
				end

				if Properties.current_image > Properties.image_count then
					Properties.current_image = Properties.current_image - Properties.image_count
				end
			end
		end
	end
end

function CreateCollisionTable()
	-- init table
	CollisionTable = {}
	for j=0, 16*LevelGetTileHeight()-1 do
		CollisionTable[j] = {}
		for i=0, 16*LevelGetTileWidth()-1 do
			CollisionTable[j][i] = false
		end
	end

	for _, collision in pairs(LoadedObjects.Collisions) do
		for ci=0, math.floor(collision.width)-1 do
			for cj=0, math.floor(collision.height)-1 do
				print(ci..","..cj)
				CollisionTable[collision.from.y+cj][collision.from.x+ci] = true
			end
		end
	end
end

function DrawColisionTable()
	for j=1, #CollisionTable do
		for i=1, #CollisionTable[j] do
			if CollisionTable[j][i] then
				love.graphics.setColor(0,1,0,1)
			else
				love.graphics.setColor(1,0,0,1)
			end
			love.graphics.points(i,j)
		end
	end
end

function DrawTile(tile,x,y,depth)
	local Properties = TileData[tile.id]

	if Properties ~= nil then
		if Properties.animation ~= nil then
			if Properties.imgs[Properties.current_image] ~= nil
			and Properties.depth == depth
			then love.graphics.draw(
				Properties.tileset,
				Properties.imgs[Properties.current_image],
				x,
				y,
				0,
				tileProperties.scale,
				tileProperties.scale
			)
			end
		elseif Properties.depth == depth then
			if Properties.force ~= nil then
				if Properties.force ~= 0 then
					love.graphics.draw(
					LevelData.tileset,
					TileIndex[tile.display],
					x,
					y,
					0,
					tileProperties.scale,
					tileProperties.scale
				)
				end
			else
				love.graphics.draw(
					LevelData.tileset,
					TileIndex[tile.id],
					x,
					y,
					0,
					tileProperties.scale,
					tileProperties.scale
				)
			end
		end

		if Properties.overlay ~= nil then
			if Properties.overlay_depth == depth or Properties.overlay_depth == nil and Properties.depth == depth then
				if Properties.overlay_animated then
					local overlay_properties = TileData[Properties.overlay]
					love.graphics.draw(
						overlay_properties.tileset,
						overlay_properties.imgs[overlay_properties.current_image],
						x,
						y,
						0,
						tileProperties.scale,
						tileProperties.scale
					)
				else
					love.graphics.draw(
						LevelData.tileset,
						TileIndex[tile.display_overlay],
						x,
						y,
						0,
						tileProperties.scale,
						tileProperties.scale
					)
				end
			end
		end
		--[[
				love.graphics.setColor(0,0,1)
				love.graphics.print(tostring(tile.display),x+16,y)
				love.graphics.setColor(1,1,1)
				]]
	end
end
