local math = math

-- CAMERA CODE --
Camera = class("Camera")

function Camera:new(view)
	local camera = instance(self)
	camera.view = view
	camera.x = 0
	camera.y = 0 
	camera.dx = 0 
	camera.dy = 0 
	camera.cx = 0 
	camera.cy = 0 
	camera.ox = 0 
	camera.oy = 0 
	camera.targetX = 0 
	camera.targetY = 0 
	camera.panSpeed = 0 
	camera.limited = false
	camera.zoomLocked = false
	camera.left = 0
	camera.right = 0
	camera.top = 0
	camera.bottom = 0
	camera.width = 0
	camera.height = 0
	camera.centerX = canvas.w/2
	camera.centerY = canvas.h/2
	camera.currentWidth = 0
	camera.currentHeight = 0
	camera.currentLeft = 0
	camera.currentRight = 0
	camera.currentTop = 0
	camera.currentBottom = 0
	camera.limitedZoom = false
	camera.minZoom = 1
	camera.maxZoom = 1
	camera.zoom = 1
	camera.scale = 1
	camera.zoomTarget = 1 
	camera.zoomTargetSpeed = 0 
	camera.zooming = false 
	camera.panning = false 
	camera.zoomDuration = 0 
	camera.interestPoints = {} 
	camera.focusX = nil 
	camera.shakeTime = 0
	camera.shakeAmplitude = 0
	camera.shakeX = 0
	camera.shakeY = 0
	camera.shakeTX = 0
	camera.shakeTY = 0
	camera.centerFactorX = 0
	camera.centerFactorY = 0
	camera.perspective = 1
	
	return camera
end

function Camera:update(time)
	if self.limitedZoom and self.zoom > self.maxZoom then
		self:setZoomForced(self.maxZoom)
	end
	
	if self.panSpeed > 0 then
		self:updatePan(time)
	end
	
	if self.shakeTime > 0 then
		self:updateShake(time)
	end
	
	self:set_position(self.x + self.dx * time + self.shakeX, self.y + self.dy * time + self.shakeY)
	
	self:updateSize()
end

function Camera:updateShake(time)
	if self.shakeTime > 0 then
		self.shakeTime = self.shakeTime - time
		
		if self.shakeTime <= 0 then
			self.shakeX, self.shakeY = 0, 0
		else
			local halfLife = 100 * time
			if halfLife > 1 then
				halfLife = 1
			end
			self.shakeX = self.shakeX + (self.shakeTX - self.shakeX) * halfLife
			self.shakeY = self.shakeY + (self.shakeTY - self.shakeY) * halfLife
			
			if math.distance2(self.shakeX, self.shakeY, self.shakeTX, self.shakeTY) < 4 then
				local amt = math.min(self.shakeTime*2,1)*math.min(self.shakeTime*2,1)
				local angle = math.random() * math.pi * 2
				self.shakeTX, self.shakeTY = math.cos(angle) * self.shakeAmplitude*amt, math.sin(angle) * self.shakeAmplitude*amt
			end
		end
	end
end

function Camera:updatePan(time)
	if self.panSpeed > 0 and (math.abs(self.x - self.targetX)>1 or math.abs(self.y - self.targetY)>1) then
		self.panning = false
		local lastX,lastY = self.x, self.y
		self:moveSmooth(time, self.targetX, self.targetY, self.panSpeed)
		if lastX == self.x and lastY == self.y then
			self.panSpeed = 0
		else
			self.panning = true
		end
	else
		self.panSpeed = 0
		self.panning = false
	end
end

function Camera:updateSize()
	local view = self.view
	local view_width = view.width
	local view_height = view.height
	local zoom = self.zoom
	local perspective = self.perspective
	self.width = view_width/zoom 
	self.height = view_height/zoom/perspective
	self.left = - view_width/zoom*0.5
	self.right = view_width/zoom*0.5
	self.top = - view_height/zoom*0.5/perspective
	self.bottom = view.height/zoom*0.5/perspective
	self.cx = self.x - self.width*self.centerFactorX
	self.cy = self.y - self.height*self.centerFactorY
end

function Camera:setCenterFactor(x,y)
	self:nudge(-(self.centerFactorX*self.width-x), -(self.centerFactorY*self.height-y))
	self.centerFactorX = x/self.width
	self.centerFactorY = y/self.height
	self:updateSize()
end

function Camera:get_position()
	return self.x, self.y
end

function Camera:getZoom()
	return self.zoom
end

function Camera:setSpeed(dx, dy)
	self.dx = dx or 0
	self.dy = dy or 0
end

function Camera:containsPoint(x,y, buffer)
	return not(x < self.left or x > self.right or y < self.top or y > self.bottom)	
end

function Camera:setLimitedZoom(minFactor, maxfactor)
	self.limitedZoom = true
	if self.limited then
		self.minZoom = math.max(math.max((self.view.width)/self.limitedWidth, (self.view.height)/self.limitedHeight), minFactor)
		self.maxZoom = maxFactor
	else
		self.minZoom = minFactor
		self.maxZoom = maxFactor
	end
end

function Camera:panTo(x,y, speed)
	self.targetX = x
	self.targetY = y
	self.panSpeed = speed
end

function Camera:stopPan()
	self.panSpeed = 0
	self.panning = false
end

function Camera:setZoom(factor)
	if (not self.zooming) then
		if self.limitedZoom then
			self.zoom = math.max(self.minZoom, math.min(self.maxZoom, factor or 1))
		else
			self.zoom = factor or 1
		end
	end
end


function Camera:setPerspective(perspective)
	self.perspective = perspective
end

function Camera:zoomLinear(factor, speed, time)
	local amount = math.min(speed * time, 1)
	self:setZoom(math.approach(self.zoom, factor, amount))
end

function Camera:zoomSmooth(factor, speed, time, linearFactor)
	local amount = math.min(speed * time, 1)
	self:setZoom(math.approach(self.zoom, factor, amount*(linearFactor or 0)*0.001 + math.abs((factor - self.zoom) * (amount*(linearFactor or 0) + amount*self.zoom*(1-(linearFactor or 0))))))
end
	
function Camera:xToView(x)
	return self.view.left + self.view.width*0.5 + x*self.zoom
end

function Camera:yToView(y)
	return self.view.top + self.view.height*0.5 + y*self.zoom
end



function Camera:setZoomForced(zoom)
	self.zoom = zoom
end

function Camera:setLimited(width,height, buffer)
	self.limited = true
	self.limitedWidth = width
	self.limitedHeight = height
	self.limitedBuffer = buffer or 0
	self:set_position(self.x,self.y)
end

function Camera:set_position(x,y, why)
	if not self.panning then
		if self.limited then
			self.x = math.min(self.limitedWidth-self.width/2+self.limitedBuffer,math.max(self.width/2-self.limitedBuffer,x))
			self.y = math.min(self.limitedHeight-self.height/2+self.limitedBuffer,math.max(self.height/2-self.limitedBuffer,y))
		else
			self.x = x
			self.y = y
		end
	end
end

function Camera:nudge(x,y)
	self:set_position(self.x + x,self.y + y)
end

function Camera:move_smooth(time,x,y,speed)		
	local amount = math.min(speed * time, 1)
	x = self.x + (x - self.x) * amount
	y = self.y + (y - self.y) * amount
	self:set_position(x,y)
end

function Camera:push(time, xFactor, yFactor, speed)
	self:set_position(self.x + xFactor * speed * time,self.y + yFactor * speed * time)
end

function Camera:shake(duration, amplitude)
	if self.shakeTime <= 0 then
		self.shakeTime = duration
		self.shakeAmplitude = amplitude
	else
		-- only change values if they are stronger than the previous shake
		if self.shakeTime < duration then
			self.shakeTime = duration
		end
		if self.shakeAmplitude < amplitude then
			self.shakeAmplitude = amplitude
		end
	end
end
