--camera and character controller
local math = assert( math )
local mat = require( "mat4" )
local sfx = require( "sfx" )

local maxPitch = math.pi / 2

local function logistic( x )
  return 1.0 / ( 1.0 + math.exp( x ) )
end

local player = {
  turnRate = 0,
  rate = 0.01, --tick rate
  speed = 2, --movement speed m/s
  x = 0,
  y = 1.5,
  z = 3,
  vx = 0,
  vz = 0,
  yaw = 1.5,
  pitch = 0,
  desx = 0,
  desz = 0,
  view = mat.id(),
  proj = mat.id(),
  footstepTimer = 0,
  footstepStride = 0.5 --seconds before taking a step
}

function player:updateDesiredDirection( forward, left, right, back )
  local x = (right and 1 or 0) -   (left and 1 or 0)
  local z = (forward and 1 or 0) - (back and 1 or 0)
  local c, s = math.cos( self.yaw ), math.sin( self.yaw )
  local n = math.sqrt( x * x + z * z )
  if n < 0.0001 then n = 1.0 end
  x, z = x / n, z / n
  self.desx, self.desz = x * c + z * s, -x * s + z * c
end



function player:setFOV( fov )
  self.proj = mat.projection( 0.05, 100, math.rad( fov or 90 ) )
end

function player:setTurnRate( sensitivity )
  self.turnRate = 0.001 * logistic( sensitivity )
end

function player:turn( dx, dy )
  self.yaw = self.yaw + dx * self.turnRate
  self.pitch = math.min( maxPitch, math.max( -maxPitch,
  self.pitch + dy * self.turnRate ))
end

local function sqDistance( x, y, a, b )
  return (x-a)*(x-a)+(y-b)*(y-b)
end

local function dot( x, y, a, b )
  return x * a + y * b
end

--segment ixy->fxy, point cxy
local function distanceToSegment( ix, iy, fx, fy, cx, cy )
  local d = sqDistance( ix, iy, fx, fy )
  if d < 0.00001 then return sqDistance( ix, iy, cx, cy ) end
  local t = math.min( 1, math.max( 0, dot( cx - ix, cy - iy, fx - ix, fy - iy) / d ))
  local u = 1.0 - t
  return sqDistance( cx, cy, u*ix+t*fx,u*iy+t*fy )
end

--collide in 2D on the xz-plane.
local function collide( ix, iz, fx, fz, circles, lines )
  local x, z = ix, iz
  for _, circle in pairs( circles ) do
    if distanceToSegment( ix, iz, fx, fz, circle.x, circle.z ) <
    circle.r * circle.r then
      
    end
  end
  
  for _, line in pairs( lines ) do
    
  end
  
  return x, z, fx, fz
end

function player:update()
  self.vx = self.desx * self.speed
  self.vz = self.desz * self.speed
  
  self.x = self.x + self.vx * self.rate
  self.z = self.z + self.vz * self.rate
  
  self.view = mat.view( self.x, self.y, self.z, self.yaw, self.pitch )
  
  self:footstep()
end

function player:isMoving()
  return 
    self.vx > 0.01 or self.vz > 0.01 or
    self.vx < 0.01 or self.vz < 0.01 
end

function player:footstep()
  if not self:isMoving() then return end
  self.footstepTimer = self.footstepTimer + self.rate
  if self.footstepTimer > self.footstepStride then
    self.footstepTimer = 0
    return sfx.playFootstep()
  end
end

function player:tostring()
  return ([[
    x:    %3.2f
    y:    %3.2f
    z:    %3.2f
    vx:   %3.2f
    vz:   %3.2f
    yaw:  %3.2f
    pitch:%3.2f
    desx: %3.2f
    desz: %3.2f
  ]]):format(
  player.x,
  player.y,
  player.z,
  player.vx,
  player.vz,
  player.yaw,
  player.pitch,
  player.desx,
  player.desz  
  )
end

return player