TerrainMap = inherited("TerrainMap", SimEntity)

Network.registerClass(TerrainMap)

TerrainMap.structure = {
	new = {"e", "i", "i", "i"},
	sync = {},
}

TerrainMap.variables = {
	new = {"sim", "width", "height", "seed"},
	sync = {},
}

function TerrainMap:new(sim, width, height, seed)
	local self = instance(self)
	self.width = width
	self.height = height
	self.atmosphereY = height*0.25
	self.atmosphereBase = height*0.5
	self.seed = seed
	self.terrain = {}
	self.sim = sim
	self.ents = {}
	self.fg = {}
	self.bg = {}
	self.asteroids = {}
	self.resources = {}
	self.backgrounds = {}
	self.fx = {}
	self.removeFxQue = {}
	self.teams = {}
	self:generateTerrain()
	self.spawnDelay = 0
	self.hailGraduation = 10000
	return self
end

function TerrainMap:emitSoundAt(id, x, y, vol, mod)
	local cam = states.get("game").camera
	local snd = states.get("sound")

	local relX = math.normalize((x-cam.x), -self.width*0.5, self.width*0.5)
	local relY = x-cam.x
	snd:playRelativeSound(id, relX, relY, vol, mod, 0)
end


function TerrainMap:emitLoopAt(id, x, y, vol, mod, looptable)
	local cam = states.get("game").camera
	local snd = states.get("sound")

	local relX = math.normalize((x-cam.x), -self.width*0.5, self.width*0.5)
	local relY = y-cam.y
	snd:playRelativeLoop(id, relX, relY, vol, mod, looptable)
end


function TerrainMap:initProxy()
	self:generateStars()
end

function TerrainMap:updateFx(time)
	if DEBUG.showDebug and DEBUG.showGraphs then
		debug.addToGraph("fx", #self.fx)
	end
	for index, fx in pairs(self.fx) do
		fx:update(time)
	end

	self:processRemoveFx()
end

function TerrainMap:getTeamOptions()
	if not self.teamOptions then
		self.teamOptions = {}
		for index, team in pairs(self.teams) do
			table.insert(self.teamOptions, {ent = team})
		end
	end
	return self.teamOptions
end
	
function TerrainMap:addFx(fx)
	if not self.sim.primary then
		for index, f in pairs(self.fx) do
			if f == fx then
				print("cannot add fx twice")
				debug.printtrace()
				return f
			end
		end
		table.insertUnique(self.fx, fx)
		return fx
	else
		print("cannot add fx to primary sim")
	end
end

function TerrainMap:removeFx(f)
	table.insert(self.removeFxQue, f)
end

function TerrainMap:processRemoveFx()
	for i=1,#self.removeFxQue do
		local f = self.removeFxQue[#self.removeFxQue]
		local removed = false
		for index, fx in pairs(self.fx) do
			if fx == f then
				table.remove(self.fx, index)
				removed = true
				break
			end
		end
		if not removed then
			print("COULD NOT REMOVE FX", f)
		end
		--print("removed", #self.removeFxQue)
		table.remove(self.removeFxQue, #self.removeFxQue)
	end
end


function TerrainMap:addResource(e)
	for index, ent in pairs(self.resources) do
		if ent == e then
			return
		end
	end
	table.insertUnique(self.resources, e)
end

function TerrainMap:removeResource(e)
	local removed = false
	for index, ent in pairs(self.resources) do
		if ent == e then
			table.remove(self.resources, index)
			removed = true
			break
		end
	end
	if not removed then
		print("COULD NOT REMOVE RESPIRCE", e)
		debug.printtrace()
		printtable(self.resources)
	end
end


function TerrainMap:addAsteroid(e)
	for index, ent in pairs(self.asteroids) do
		if ent == e then
			return
		end
	end
	table.insertUnique(self.asteroids, e)
end

function TerrainMap:removeAsteroid(e)
	local removed = false
	for index, ent in pairs(self.asteroids) do
		if ent == e then
			table.remove(self.asteroids, index)
			removed = true
			break
		end
	end
	if not removed then
		print("COULD NOT REMOVE ASTEROID", e)
		debug.printtrace()
		printtable(self.asteroids)
	end
end


function TerrainMap:addBackground(e)
	for index, ent in pairs(self.backgrounds) do
		if ent == e then
			return
		end
	end
	table.insertUnique(self.backgrounds, e)
	if e:isBackground() then
		table.insertUnique(self.bg, e)
	end
end

function TerrainMap:removeBackground(e)
	local removed = false
	for index, ent in pairs(self.backgrounds) do
		if ent == e then
			table.remove(self.backgrounds, index)
			removed = true
			break
		end
	end
	if not removed then
		print("COULD NOT REMOVE BACKGROUND", e)
		printtable(e)
	end
	if e:isBackground() then
		table.removeUnique(self.bg, e)
	end
	self.sim:remove(e)
end

function TerrainMap:addEnt(e)

	for index, ent in pairs(self.ents) do
		if ent == e then
			--print("trying to add twice", e)
			--debug.printtrace()
			return
		end
	end
	table.insertUnique(self.ents, e)
	if e:isBackground() then
		table.insertUnique(self.bg, e, 1)
	else
		table.insertUnique(self.fg, e)
	end

end

function TerrainMap:removeEnt(e)
	local removed = false
	for index, ent in pairs(self.ents) do
		if ent == e then
			table.remove(self.ents, index)
			removed = true
			break
		end
	end
	if not removed then
		return
	end
	
	if e:isBackground() then
		table.removeUnique(self.bg, e)
	else
		table.removeUnique(self.fg, e)
	end
	self.sim:remove(e)
end

function TerrainMap:addTeam(t)
	table.insert(self.teams, t)
	self.teamOptions = nil
end

function TerrainMap:getStepFrequency()
	return APP.tickTime*100
end

function TerrainMap:removeTeam(e)
	for index, ent in pairs(self.teams) do
		if ent == e then
			table.remove(self.teams, index)
			break
		end
	end
	self.sim:remove(e)
	self.teamOptions = nil
end


function TerrainMap:getHailAmount(x,y)
	return math.abs(math.min(y,0))/self.hailGraduation/5000
end
function TerrainMap:getPlanetFactor(x,y)
	return math.min(1,math.max(0, y-self.atmosphereY)/(self.height-self.atmosphereY-self.atmosphereBase))
end

function TerrainMap:getGravity(x,y)
	return self:getPlanetFactor(x,y)*400
end

function TerrainMap:getAtmosphere(x,y)
	return math.min(1,math.retardingCurve(self:getPlanetFactor(x,y)))
end

function TerrainMap:getFriction(x,y)
	return 0.001
end


function TerrainMap:getWidth()
	return self.width
end

function TerrainMap:getHeight()
	return self.height
end

function TerrainMap:step(time)
	if #self.asteroids < 45 then
		if self.spawnDelay <= 0 then
			if math.random() >0.9 then
				self.sim:create(Asteroid, self, math.random(0, self.width), math.random(0,self.atmosphereY*0.5), math.randomGaussian()*75, math.random()*100 + 300, ASTEROID_TYPES.large, math.random(1,100))
			else
				self.sim:create(Asteroid, self, math.random(0, self.width), math.random(0,self.atmosphereY*0.3), 0, 0, ASTEROID_TYPES.large, math.random(1,100))
			end
			--self.sim:create(Asteroid, self, math.random(0, self.width), math.random(self.atmosphereY,self.height), math.randomGaussian()*5550, 0, ASTEROID_TYPES.large, math.random(1,100))
			self.spawnDelay = 4
		else
			self.spawnDelay = self.spawnDelay- time
		end
	end
end

function TerrainMap:renderTeamsHud(x,y,a,r,g,b, homeTeam)
	local totw = canvas.w
	for index, team in pairs(self.teams) do
		local col = team:def().color
		for index, building in pairs(team.buildings) do
			if building:canLaunch() then
				local highlight = nil
				local relX = math.normalize(building.x-self.width*0.5, -self.width*0.5,self.width*0.5)/self.width
				
				video.renderSpriteState(SPRITES.hud_nukeTriangle, x + relX*totw, y, 0.5, 0, a, col.r, col.g, col.b)
				local left = false
				local right = false
				for index, battery in pairs(team.buildings) do
					if battery.parent == building then
						if battery:isCharged() then
							if battery.x < building.x then
								left = true
							else
								right = true
							end
						end
					end
				end
				if left and right then
					video.renderSpriteState(SPRITES.hud_nukeComplete, x + relX*totw, y, 0.5, 0, a, col.r, col.g, col.b)
					highlight = math.cos(building.launchProgress*100*math.pi) > 0
					if highlight then
						video.renderSpriteState(SPRITES.hud_nukeComplete, x + relX*totw, y, 0.5, 0, 255, col.r, col.g, col.b)
					end
				end
				if left then
					video.renderSpriteState(SPRITES.hud_nukeLeft, x + relX*totw, y, 0.5, 0, a, col.r, col.g, col.b)
				end
				if right then
					video.renderSpriteState(SPRITES.hud_nukeRight, x + relX*totw, y, 0.5, 0, a, col.r, col.g, col.b)
				end
				if highlight then
					video.renderSpriteState(SPRITES.hud_nukeTriangle, x + relX*totw, y, 0.75, 0, 255, col.r, col.g, col.b)
				end
			end
		end
		if team.dead then
			video.renderTextSprites(team.name.. " is Dead!!!", x, canvas.h*0.4 + index*50, 1, "big", 255, 255, 255)
		end
	end
	for index, ent in pairs(self.bg) do
		if ent:hasWarningHud() then
			local relX = math.normalize(ent.x-self.width*0.5, -self.width*0.5, self.width*0.5)/self.width
			local col = ent.owner:def().color
			video.renderSpriteState(SPRITES.hud_nukeComplete, x + relX*totw, y, 0.5, ent.angle+math.pi*0.5, a, col.r, col.g, col.b)
		end
	end
	for index, ent in pairs(self.fg) do
		if ent:hasWarningHud() then
			local relX = math.normalize(ent.x-self.width*0.5, -self.width*0.5, self.width*0.5)/self.width
			local col = ent.owner:def().color
			video.renderSpriteState(SPRITES.hud_nukeComplete, x + relX*totw, y, 0.5, ent.angle+math.pi*0.5, a, col.r, col.g, col.b)
		end
	end
end

function TerrainMap:isInEnt(sEnt,x,y, dx, dy, eRadius)
	local moveAngle = math.angleBetweenPoints(0,0,dx,dy)
	for index, ent in pairs(self.ents) do
		if ent ~= sEnt then
			if ent:getOwner() ~= sEnt:getOwner() then
				local angTo = sEnt:getAngleTo(ent)
				local eRadius = sEnt:getRadius(angTo)
				if ent.getRadius then
					local radius = ent:getRadius(angTo+math.pi)
					local tx, ty = math.normalize(ent.x - x, - self.width*0.5, self.width*0.5), ent.y - y
					local dist = math.distance( 0,0, tx, ty)
					if dist <= eRadius + radius then
						return true, ent, math.angleBetweenPoints(0,0, tx, ty)
					end
				end
				if ent.getHeight then
					local xdiff = ent:getRelativeX(sEnt)
					local ydiff = ent:getRelativeY(sEnt)

					local apx, apy = math.approach(ent.x+xdiff, ent.x, eRadius),  math.approach(ent.y+ydiff, ent.y, eRadius)
					local width, height = ent:getWidth(), ent:getHeight()
					if apx >= ent.x - width*0.5 and apx <= ent.x + width*0.5 and apy >= ent.y - height and apy <= ent.y then
						local collAngle = ent:getAngleTo(sEnt, width, height)--math.angleBetweenPoints(x/width,y/height, ent.x/width, ent.y/height)
						if math.abs(math.angleDifference(collAngle, moveAngle)) < 0.5*math.pi then
							return true, ent, collAngle
						end
					end
				end
			end
		end
	end
end

function TerrainMap:isInTerrain(x,y, dx, dy, radius)
	return self.terrain:isInside(x,y, dx, dy, radius)
end

function TerrainMap:getYAt(x,y)
	return self.terrain:getYAt(x,y)
end


function TerrainMap:findLowestTerrain(x,y, width)
	local range = self.terrain:getRange(width)
	local best = y
	for i=0,range do
		local p = self.terrain:toPoint(x-width*0.5 + width/range*i)
		local he = self.terrain:getY(p)
		best = math.max(best, he)
	end
	return best
end

function TerrainMap:flattenAt(x,y,width)
	local v = self.terrain:getAt(x,y)
	self.terrain:setTo(x,width, v, 1)
end

function TerrainMap:renderCollision(...)
	return self.terrain:renderCollision(...)
end


function TerrainMap:mapToCameraX(x, camera, factor)
	return (x - camera.cx)*(factor or 1)
end

function TerrainMap:mapToCameraY(y, camera, factor)
	return (y - camera.cy)*(factor or 1)
end

function TerrainMap:cameraToMapX(x, camera)
	return x + camera.cx
end

function TerrainMap:cameraToMapY(y, camera)
	return y + camera.cy
end


function TerrainMap:renderAt(camera,x,y,scale, angle, a, r, g, b)
	for index, ent in pairs(self.ents) do
		ent:renderAt(x + ent.x*scale, y + ent.y*scale, scale, angle, a, r, g, b)
	end


	for index, fx in pairs(self.fx) do
		fx:renderAt(x + fx.x*scale, y + fx.y*scale, scale, angle, a, r, g, b)
	end
end

function TerrainMap:renderBackgroundAt(camera,x,y,scale, angle, a, r, g, b)

	local atmosphereL = self:cameraToMapX(camera.left, camera)
	local atmosphereY = self:mapToCameraY(self.atmosphereY, camera)
	local groundY = self:mapToCameraY(self.height, camera)

	if self.stars then
		for index, star in pairs(self.stars) do
			local sx = self:mapToCameraX(star.x, camera, star.para)
			local sy = self:mapToCameraY(star.y, camera, star.para)
			--if sx > camera.left-100 and sx < camera.right + 100 then
			local zf = (1*(1-star.para) + camera.zoom*star.para)
			video.renderSpriteState(SPRITES.star_1, sx*star.para * zf, sy*star.para * zf, zf*(0.25 + 0.75*star.opacity), 0, 255*star.opacity, 255, 255, 255)
			--end
		end
	end
	--print(camera:xToView(left), camera:yToView(atmosphereY), camera:xToView(right), camera:yToView(atmosphereY), camera:xToView(left), camera:yToView(groundY), camera:xToView(right), camera:yToView(groundY))
	video.renderSpriteStateFreeShape(SPRITES.sky, camera:xToView(camera.left), camera:yToView(atmosphereY), camera:xToView(camera.right), camera:yToView(atmosphereY), camera:xToView(camera.left), camera:yToView(groundY), camera:xToView(camera.right), camera:yToView(groundY), 255,255,255,255)
end

function TerrainMap:renderWrappedAt(method, camera, ent, wx, wy, ex, ey, scale, angle, a, r, g, b, interp)
	local x1 = wx + math.normalize(ex,0,self.width)*scale
	local y = wy + ey*scale
	if canvas.containsPoint(x1, y, ent:getRenderRadius(method)*scale) then
		ent[method](ent,x1, y, scale, angle, a, r, g, b, interp)
	end
	local x2 = wx + math.normalize(ex,-self.width,0)*scale
	if canvas.containsPoint(x2, y, ent:getRenderRadius(method)*scale) then
		ent[method](ent,x2, y, scale, angle, a, r, g, b, interp)
	end
	local x3 = wx + math.normalize(ex,self.width,self.width*2)*scale
	if canvas.containsPoint(x3, y, ent:getRenderRadius(method)*scale) then
		ent[method](ent,x3, y, scale, angle, a, r, g, b, interp)
	end
end


function TerrainMap:renderInterpolatedRawAt(camera,rawx,rawy,scale, angle, a, r, g, b)
	local x = rawx -camera.x*camera.zoom
	local y = rawy -camera.y*camera.zoom

	local wx = rawx -math.normalize(camera.x, 0, self.width)*camera.zoom
	local wy = rawy -camera.y*camera.zoom

	local left = self:cameraToMapX(camera.left, camera)
	local right = self:cameraToMapX(camera.right, camera)


	for index, ent in pairs(self.backgrounds) do
		local interp = ent:getInterpolation(self.sim.time)
		if interp then
			self:renderWrappedAt("renderAt",camera, ent, wx, wy, ent:interp("x", interp), ent:interp("y", interp), scale, angle, a, r, g, b, interp)
		end
	end

	for index, ent in pairs(self.bg) do
		local interp = ent:getInterpolation(self.sim.time)
		if interp then
			self:renderWrappedAt("renderAt",camera, ent, wx, wy, ent:interp("x", interp), ent:interp("y", interp), scale, angle, a, r, g, b, interp)
		end
	end

	self.terrain:renderRange(left, right, x + self.terrain.x*scale, y + self.terrain.y*scale, scale, angle, a, r*0.1, g*0.1, b*0.1)
	video.renderRectangle(0,y+self.terrain.y*scale, canvas.w, canvas.h, a, r*0.1, g*0.1, b*0.1)

	for index, ent in pairs(self.fg) do
		local interp = ent:getInterpolation(self.sim.time)
		if interp then
			self:renderWrappedAt("renderAt",camera, ent, wx, wy, ent:interp("x", interp), ent:interp("y", interp), scale, angle, a, r, g, b, interp)
		end
	end

	for index, fx in pairs(self.fx) do
		self:renderWrappedAt("renderAt",camera, fx, wx, wy, fx.x, fx.y, scale, angle, a, r, g, b)
	end

	for index, ent in pairs(self.bg) do
		if ent.renderHudAt then
			local interp = ent:getInterpolation(self.sim.time)
			if interp then
				self:renderWrappedAt("renderHudAt",camera, ent, wx, wy, ent:interp("x", interp), ent:interp("y", interp), scale, angle, a, r, g, b, interp)
			end
		end
	end
	for index, ent in pairs(self.fg) do
		if ent.renderHudAt then
			local interp = ent:getInterpolation(self.sim.time)
			if interp then
				self:renderWrappedAt("renderHudAt",camera, ent, wx, wy, ent:interp("x", interp), ent:interp("y", interp), scale, angle, a, r, g, b, interp)
			end
		end
	end
end

local terrainStyles = {
	"flat", "mountain", "mountains", "valley", "valleys", "bubbly", "pillars", "craters"
}

local bias = 0.3
local invBias = 1/bias

local generators = {}
generators.flat = {
	start = 0.1,
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.25 + math.random()*terrain:getTotalPoints()*0.25)*bias end,
		x = function(terrain) return math.random()*terrain.width end,
		radius = function(terrain) return (328 + 1000*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.025*math.random() end,
	}
}

generators.mountain = {
	start = 0.1,
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.25 + math.random()*terrain:getTotalPoints()*0.25)*bias end,
		x = function(terrain) return terrain.width*0.5 + math.randomGaussian()*terrain.width*0.25 end,
		radius = function(terrain) return (128 + 650*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.1*math.random() end,
	}
}

generators.mountains = {
	start = 0.1,
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.25 + math.random()*terrain:getTotalPoints()*0.5)*bias end,
		x = function(terrain) 
		local side = math.round(math.random())
		return side*terrain.width*0.5 + math.randomGaussian()*terrain.width*0.25 end,
		radius = function(terrain) return (128 + 650*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.1*math.random() end,
	}
}

generators.valley = {
	start = 0.6,
	{
		method = "approach",
		i = function(terrain) return (terrain:getTotalPoints()*0.05+ math.random()*terrain:getTotalPoints()*0.05)*bias end,
		x = function(terrain, i) return terrain.nodeWidth*i*4 + math.randomGaussian()*terrain.nodeWidth*3 end,
		radius = function(terrain) return (256 + 500*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.5 + terrain.height*0.1*math.randomGaussian() end,
	},
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.05 + math.random()*terrain:getTotalPoints()*0.05)*bias end,
		x = function(terrain) return terrain.width*0.3 + math.random()*terrain.width*0.4 end,
		radius = function(terrain) return (228 + 1250*math.random())*invBias end,
		amt = function(terrain) return - terrain.height*0.05 -terrain.height*0.1*math.random() end,
	}
}


generators.valleys = {
	start = 0.6,
	{
		method = "approach",
		i = function(terrain) return (terrain:getTotalPoints()*0.1 + math.random()*terrain:getTotalPoints()*0.1)*bias end,
		x = function(terrain, i) return terrain.nodeWidth*i*4 + math.randomGaussian()*terrain.nodeWidth*3 end,
		radius = function(terrain) return (256 + 500*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.5 + terrain.height*0.1*math.randomGaussian() end,
	},
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.1 + math.random()*terrain:getTotalPoints()*0.1)*bias end,
		x = function(terrain) 
		local side = math.round(math.random())
		return side*terrain.width*0.5-terrain.width*0.2 + math.random()*terrain.width*0.4 end,
		radius = function(terrain) return (228 + 1250*math.random())*invBias end,
		amt = function(terrain) return - terrain.height*0.05 - terrain.height*0.1*math.random() end,
	},
}

generators.bubbly = {
	start = 0.3,
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.05 + math.random()*terrain:getTotalPoints()*0.1)*bias end,
		x = function(terrain) return math.random()*terrain.width end,
		radius = function(terrain) return (60 + 750*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.3*math.randomGaussian() end,
	}
}

generators.pillars = {
	start = 0.1,
	{
		method = "approach",
		i = function(terrain) return (terrain:getTotalPoints()*0.02 + math.random()*terrain:getTotalPoints()*0.03)*bias end,
		x = function(terrain) return math.random()*terrain.width end,
		radius = function(terrain) return (130 + 600*math.random()*math.random())*invBias end,
		amt = function(terrain) return terrain.height*0.2 + terrain.height*0.5*math.random() end,
		expo = function(terrain) return 0.5 + math.random()*0.5 end,
	}
}

generators.craters = {
	start = 0.4,
	{
		method = "adjust",
		i = function(terrain) return (terrain:getTotalPoints()*0.01 + math.random()*terrain:getTotalPoints()*0.03)*bias end,
		x = function(terrain) return math.random()*terrain.width end,
		radius = function(terrain) return (10 + 2700*math.random()*math.random()*math.random())*invBias end,
		amt = function(terrain, radius) return -(terrain.height*0.1 + terrain.height*0.2*math.random())*radius/500 end,
		expo = function(terrain) return math.random()*0.75 end,
	}
}

function TerrainMap:getStartingLocation(i)
	local x = self.width/2*(i-1) + self.width*0.25
	local y = self.terrain:getYAt(x)
	return x,y
end

function TerrainMap:generateStars()
	math.randomseed(self.seed)
	math.random()
	self.stars = {}
	for i=1,400 do
		local para = 0.01 + math.random()*0.09
		table.insert(self.stars, {x = math.random(0,self.width/para), y = math.random(0,self.height/para), para = para, opacity = math.random()})
	end
end

function TerrainMap:generateTerrain()
	math.randomseed(self.seed)
	math.random()
	self.terrain = Terrain:new(0, self.height, self.width, 32, self.height*0.03, self.height*0.15)

	local style = terrainStyles[math.random(1,#terrainStyles)]
	--print("picked", style)
	local generator = generators[style]
	
	if generator.start then
		self.terrain:setAll(self.terrain.minHeight + (self.terrain.maxHeight-self.terrain.minHeight)*generator.start)
	end

	for index, def in ipairs(generator) do
		local maxI = def.i(self.terrain)
		for i=1,math.ceil(maxI) do
			local x = def.x(self.terrain, i)
			local radius = def.radius(self.terrain)
			local amt = def.amt(self.terrain, radius)
			local expo = def.expo and def.expo(self.terrain) or 0
			self.terrain[def.method](self.terrain, x, radius, amt, expo)
		end
	end

	--for i=1,2 do
	--	local x,y = self:getStartingLocation(i)
	--	local g = self.terrain:getAt(x)
	--	local p = self.terrain:toPoint(x)
	--	self.terrain:set(p, g+100)
	--	self.terrain:setTo(x,1200, g+20, 0.9)
	--end
end


Terrain = class("Terrain")

function Terrain:new(x, y, width, nodeWidth, minHeight, maxHeight)
	local t = instance(self)
	t.x = x
	t.y = y
	t.width = width
	t.data = {}
	t.nodeWidth = nodeWidth
	t.minHeight = minHeight
	t.maxHeight = maxHeight
	t.height = maxHeight - minHeight
	for i=1,t:getTotalPoints() do
		t.data[i] = 0
	end
	return t
end

function Terrain:renderRange(fromX, toX, x, y, scale, angle, a, r, g, b)
	local fromPoint = self:toPoint(fromX)-1
	local toPoint = self:toPoint(toX)+1
	for i=fromPoint, toPoint do
		local x1 = self:toRawX(i)
		local x2 = self:toRawX(i+1)
		
		local h1 = self:get(i)
		local h2 = self:get(i+1)

		video.renderSpriteStateFreeShape(SPRITES.square, x + x1*scale, y - h1*scale, x + x2*scale, y - h2*scale, x + x1*scale, y, x + x2*scale, y, a, r, g, b)
	end
end

function Terrain:getTotalPoints()
	return math.ceil(self.width/self.nodeWidth)
end

function Terrain:toRawX(point)
	return point*self.nodeWidth
end

function Terrain:toPoint(x)
	return math.round(x/self.nodeWidth)
end

function Terrain:toX(point)
	return self:normalize(point)*self.nodeWidth
end

function Terrain:normalize(index)
	return math.normalize(index, 1, self:getTotalPoints())
end

function Terrain:getPointAt(x)
	local index = self:toPoint(x)
	return self:normalize(index)
end

function Terrain:getRange(range)
	return math.ceil(range/self.nodeWidth)
end

function Terrain:set(index, v)
	self.data[self:normalize(index)] = math.clamp(v,self.minHeight,self.maxHeight)
end

function Terrain:setAll(v)
	for i=1, self:getTotalPoints() do
		self:set(i, v)
	end
end

function Terrain:getY(index)
	return self.y - (self.data[self:normalize(index)] or 0)
end

function Terrain:getYAt(x)
	return self.y - (self.data[self:normalize(self:toPoint(x))] or 0)
end

function Terrain:getAt(x)
	return self.data[self:normalize(self:toPoint(x))]
end

function Terrain:get(index)
	return self.data[self:normalize(index)] or 0
end

function Terrain:getIntersectAt(i, x, y, dx, dy, radius)
	local aX1 = self:toRawX(i)
	local aY1 = self:getY(i)
	
	local aX2 = self:toRawX(i+1)
	local aY2 = self:getY(i+1)
	
	local avgX = (aX1 + aX2)*0.5
	local avgY = (aY1 + aY2)*0.5

	local ang = math.angleBetweenPoints(aX1, aY1, aX2, aY2)
	local speedAngle = math.angleBetweenPoints(0,0,dx, dy)

	local invnormal = ang + math.pi*0.5
	--local angToCollider = math.angleBetweenPoints(x, y, avgX, avgY)
	--if math.abs(math.angleDifference(invnormal, angToCollider)) > math.pi*0.5 then
	--	invnormal = invnormal + math.pi
	--end

	if math.abs(math.angleDifference(speedAngle,invnormal)) <= math.pi*0.5 then
		--print(invnormal)
		local intersect = math.lineIntersects(aX1, aY1, aX2, aY2, x, y, x + math.cos(invnormal)*radius, y + math.sin(invnormal)*radius)
		return intersect, invnormal + math.pi
	else
		return false, invnormal + math.pi
	end
end

function Terrain:isInside(x,y, dx, dy, radius)
	local cy = self:getYAt(x)
	if cy < y then
		return true, math.pi*0.5
	end
	if radius > 0 then
		local center = self:getPointAt(x)
		local range = self:getRange(radius)
		for index = 0, range*2+1 do
			local i = center - range + index-1
			local intersect, normal = self:getIntersectAt(i, x, y, dx, dy, radius)
			if intersect then
				return intersect, normal
			end
		end
	end
end

function Terrain:renderCollision(x, y, dx, dy, radius, atx, aty, scale, angle, a, r, g, b)
	if radius and radius > 0 then
		local center = self:getPointAt(x)
		local range = self:getRange(radius)
		for index = 0, range*2+1 do
			local i = center - range + index
			local intersect, normal = self:getIntersectAt(i, x, y, dx, dy+10, radius)

			local red = intersect and 255 or 0

			--video.renderSpriteLine(atx +( - x + aX1)*scale,aty +( - y + aY1)*scale,atx +( - x + aX2)*scale,aty +( - y + aY2)*scale, 128, red, 255, 0, nil, intersect and 5 or 2)
					
			video.renderSpriteLine(atx ,aty ,atx +math.cos(normal)*radius*scale,aty +math.sin(normal)*radius*scale, 128, intersect and 255 or 2, intersect and 255 or 2, 0)
			
		end
	end
end

function Terrain:adjust(x, radius, amt, expo)
	local heightRange = self.maxHeight - self.minHeight
	if radius > 0 then
		local center = self:getPointAt(x)
		local range = self:getRange(radius)
		for index = 1, range*2 do
			local i = center - range + index
			local xAt = self:toRawX(i)
			local dist = math.difference(x, xAt)
			local factor = math.max(0,1-dist/radius) * (1-expo) + 1*expo

			local hfactor = math.difference(self:get(i)+amt*factor, self.minHeight + heightRange*0.5) / heightRange*0.5
			local maxFactor = 1 - math.pow(hfactor, 2)
			self:set(i,self:get(i)+amt*factor*maxFactor)
		end
	else
		print("radius must be non zero")
	end	
end

function Terrain:setTo(x, radius, amt, expo)
	if radius > 0 then
		local center = self:getPointAt(x)
		local range = self:getRange(radius)
		for index = 1, range*2 do
			local i = center - range + index
			local xAt = self:toRawX(i)
			local dist = math.difference(x, xAt)
			local factor = math.max(0,1-dist/radius) * (1-expo) + 1*expo
			local diff = math.difference(self:get(i), amt)
			self:set(i,math.approach(self:get(i), amt, diff*factor))
		end
	else
		print("radius must be non zero")
	end	
end


function Terrain:approach(x, radius, amt, expo)
	if radius > 0 then
		local center = self:getPointAt(x)
		local range = self:getRange(radius)
		for index = 1, range*2 do
			local i = center - range + index
			local xAt = self:toRawX(i)
			local dist = math.difference(x, xAt)
			local factor = math.max(0,1-dist/radius) * (1-expo) + 1*expo
			local diff = math.difference(self:get(i), amt)
			self:set(i,math.approach(self:get(i), self.minHeight + amt, diff*factor))
		end
	else
		print("radius must be non zero")
	end	
end

