--
-- a car; cars are fairly autopilot
--

function car_ctor (self)

   self.want_speed_kph = 0
   self.speed_kph = 0
   self.speed_mps = 0
   self.distance_m = 0
   self.road = nil -- what road are we on
   self.lane = 1 -- what lane are we in
   self.texture = texture_load ("cars/hatchback")

   -- these are more or less "car stats"
   self.max_speed_kph = 150
   self.max_accel_kphps = 6.5
   self.max_brake_kphps = 15.0
   self.braking_flag = nil -- updated each frame, set if we're braking
   self.max_fuel_l = 55
   self.length_m = 1.5 -- how long is this car (/2)
   self.space_m = 5 -- how much space does this car require?
   self.mpg = 30 -- fuel efficiency in miles per gallon

   -- warning flag
   self.too_fast = nil

   -- shakiness; driver craziness
   self.shakiness = 0
   self.shakiness_counter = 1
   self.shakiness_dx = 0
   self.shakiness_x = 0

   -- lane changing; specify a lane change "want" offset as well as "cur"; this is -1 or 1 and represents how far
   -- we are off the current lane; we drift towards the want offset, and change lanes appropriately
   self.lane_offset = 0
   self.lane_offset_want = 0

   -- are we dead?
   self.dead_flag = nil
   self.nofuel_counter = 3

   -- are we spinning out/dying
   self.spinout_flag = nil
   self.spinout_theta = 0
   self.spinout_theta_rate = 0
   self.spinout_x = 0 -- in pixels
   self.spinout_dx = 0

   -- are we exiting?
   self.exit_flag = nil
   self.exit_radius = 0
   self.exit_theta_start = 0
   self.exit_theta = 0
   self.exit_want_theta = 0
end

car = class (car_ctor)

function car:set_lane (Lane)

   self.lane = Lane
end

function car:set_shakiness (Shakiness)

   self.shakiness = Shakiness
end

function car:set_live (DistanceM, SpeedKPH)

   -- used by traffic drone to instance live cars
   self.want_speed_kph = SpeedKPH
   self.speed_kph = SpeedKPH
   self.distance_m = DistanceM
end

function car:init_road (Road)

   self.road = Road
   self.lane = 1
   self.lane_offset = 0
   self.lane_offset_want = 0
   self.spinout_flag = nil
   self.exit_flag = nil
   self.distance_m = 0
end

function car:set_dead ()

   self.dead_flag = true
end

function car:set_spinout (KPH)

   -- if we are already spinnng out, then reset our theta/etc.
   self.spinout_theta_rate = (0.15 + (RN () * .5 + 2.0) * (self.speed_kph / 100)) * math.pi * 2.0
   if (RN () < 0.5) then self.spinout_theta_rate = self.spinout_theta_rate * -1.0
   end

   -- these things only get set when we first spin out
   if (not self.spinout_flag) then

      if (KPH) then self:set_speed_kph (KPH)
      end

      self.spinout_flag = true
      self.spinout_theta = 0.0
      self.spinout_x = 0.0
      self.spinout_dx = RN_balance () * 500.0 * (self.speed_kph / 100)
      self.spinout_counter = 2.5 + RN () * 0.5
  end
end

function car:set_exit (Radius, SpinoutKPH, Node)

   if (self.speed_kph > SpinoutKPH * 1.2) then

      -- took the exit too fast!
      self:set_spinout ()
      return

   elseif (self.speed_kph > SpinoutKPH) then

      self:set_want_speed_kph (SpinoutKPH)
   end

   self.exit_flag = true
   self.exit_radius = Radius
   self.exit_node = Node
   self.exit_distance_m = self.distance_m -- what distance we were when we exited
   self.exit_theta_start = math.pi * 1.0
   self.exit_theta = math.pi * 1.0
   self.exit_want_theta = math.pi * 1.5
end

function car:get_exit_state ()

   if (not self.exit_flag) then return nil
   elseif (self.exit_want_theta == self.exit_theta) then return 2 -- finished!
   else return 1 -- exiting
   end
end

function car:set_speed_kph (KPH)

   self.want_speed_kph = KPH
   self.speed_kph = KPH
end

function car:set_want_speed_kph (KPH)

   -- this is used by other cars
   self.want_speed_kph = clamp (KPH, 0, self.max_speed_kph)

   -- need more space as we travel faster
   self.space_m = 5 -- how much space does this car require?
   if (KPH > 50) then self.space_m = self.space_m + (KPH - 50) / 10
   end
end

function car:get_cur_roadseg ()

   return self.road:get_cur_roadseg (self.distance_m)
end

function car:is_live ()

   local CurSeg = self:get_cur_roadseg ()
   if (CurSeg) then return true
   end

   return false
end

function car:update (T)

   if (self.dead_flag) then

      return
   end

   -- check for spinout
   if (self.spinout_flag) then

      self.speed_kph = interpolate_towards (self.speed_kph, 0, 50, T)
      self.speed_mps = kph2mps (self.speed_kph)
      self.spinout_theta = self.spinout_theta + self.spinout_theta_rate * T
      self.spinout_theta_rate = interpolate_towards (self.spinout_theta_rate, 0, math.pi * 2.0 * 1.5, T)
      self.spinout_x = self.spinout_x + self.spinout_dx * T
      self.spinout_counter = self.spinout_counter - T
      self.distance_m = self.distance_m + self.speed_mps * T

      local CurSeg = self:get_cur_roadseg ()
      if (not CurSeg or self.spinout_counter <= 0) then

         self.dead_flag = true

      elseif (not self.exit_flag) then

         -- spinout initiated a lane change?
         if (self.spinout_x < -35) then

            if (self.lane > 1) then

               self.lane = self.lane - 1
               self.spinout_x = self.spinout_x + 50
            else

               -- don't cross the median if there is one
               if (CurSeg.median) then

                  self.spinout_x = -35
                  self.spinout_dx = self.spinout_dx * -0.8
               end
            end

         elseif (self.spinout_x > 35) then

            self.lane = self.lane + 1
            self.spinout_x = self.spinout_x - 50

            if (self.lane < CurSeg.lanes) then

            else

               -- on the shoulder
               self.spinout_dx = self.spinout_dx * 0.8
               self.speed_mps = self.speed_mps * 0.8
            end
         end
      end

      return
   end

   -- shakiness controls lane changes, etc.
   self.shakiness_counter = self.shakiness_counter - T * (1.0 + self.shakiness)
   if (self.shakiness_counter < 0.0) then

      self.shakiness_counter = 0.25
      if (RN () < (self.shakiness - 0.85) and self.speed_kph > 30) then

         self:lanechange (table_choose { -1, 1 })
      end
   end
   self.shakiness_x = clamp (self.shakiness_x + self.shakiness_dx * T * 50, -20, 20)
   self.shakiness_dx = (self.shakiness_dx * 2 + RN_balance ()) / 3 * self.shakiness * (self.speed_kph / 100)

   -- try and get our current segment
   local CurSeg = self.road:get_cur_roadseg (self.distance_m)
   if (not CurSeg) then return
   end

   -- check for exit
   if (not self.exit_flag and CurSeg.exit_flag) then

      if (self.lane == CurSeg.lanes) then self:set_exit (CurSeg.exit_radius, CurSeg.exit_spinout_kph, CurSeg.exit_node)
      elseif (self.lane > CurSeg.lanes) then self:set_spinout () -- on the shoulder
      end
   end

   if (self.exit_flag) then

      -- compute theta rate based on our speed kph
      local ThetaRate = self.speed_mps * math.pi * 2.0 / self.exit_radius

      -- process exit stats; just involves updating theta *** NOTE: we should calculate theta based on
      -- actual speed, and adjust our speed mps accordingly, etc.
      self.exit_theta = interpolate_towards (self.exit_theta, self.exit_want_theta, ThetaRate, T)
      return
   end

   -- adjust speed towards the want speed; if they are wanting to slow down quickly, then brake!
   self.braking_flag = (self.want_speed_kph - self.speed_kph < -30)
   local AccelKPHPS = self.max_accel_kphps
   if (self.braking_flag) then AccelKPHPS = self.max_brake_kphps
   end
   self.speed_kph = interpolate_towards (self.speed_kph, self.want_speed_kph, AccelKPHPS, T)

   -- compute mps speed
   self.speed_mps = kph2mps (self.speed_kph)

   -- travel forward
   self.distance_m = self.distance_m + self.speed_mps * T

   -- update lane offset
   local LaneChangeRate = 1.5
   if (self.speed_kph < 15) then LaneChangeRate = LaneChangeRate * self.speed_kph / 15
   end
   self.lane_offset = interpolate_towards (self.lane_offset, self.lane_offset_want, LaneChangeRate, T)

   -- if our lane offset is > 0.5, then advance our lane and flip the offset relative to the new lane; similar
   -- to if our lane offset is < -0.5
   local MaxLane = self:get_max_lane ()
   if (self.lane + self.lane_offset_want > MaxLane) then

      -- we ran out of lanes!
      self.lane_offset_want = self.lane_offset_want - 1.0

   elseif (self.lane_offset > 0.5) then

      self.lane = self.lane + 1
      self.lane_offset = self.lane_offset - 1.0
      self.lane_offset_want = 0.0

   elseif (self.lane_offset < -0.5) then

      self.lane = self.lane - 1
      self.lane_offset = 1.0 + self.lane_offset
      self.lane_offset_want = 0.0

   end

   -- are we going too fast?
   local LaneSpeed = CurSeg.lane_speeds[self.lane]
   self.too_fast = (LaneSpeed and self.speed_kph > LaneSpeed)
end

function car:render (R)

   if (not self:is_live ()) then return R:none ()
   end

   -- are we spining out
   if (self.spinout_flag) then

      local X = self.road:get_lane_x (self.distance_m, self.lane + self.lane_offset)
      local Y = self.road:get_y (self.distance_m)
      if (X and Y) then

         X = X + self.spinout_x
         return R:translate (X, Y, R:rotate (self.spinout_theta, R:picture (self.texture)))
      end

      return R:none ()
   end

   -- are we exiting?
   if (self.exit_flag) then

      local CX = self.road:get_lane_x (self.exit_distance_m, self.lane)
      local CY = self.road:get_y (self.exit_distance_m)
      if (CX and CY) then

         CX = CX + self.exit_radius + 25
         local X = math.cos (self.exit_theta) * (self.exit_radius + 25) + CX
         local Y = math.sin (self.exit_theta) * (self.exit_radius + 25) + CY
         return R:translate (X, Y, R:rotate (self.exit_theta - self.exit_theta_start, R:picture (self.texture)))
      end

      return R:none ()
   end

   local X = self.road:get_lane_x (self.distance_m, self.lane + self.lane_offset) + self.shakiness_x
   local Y = self.road:get_y (self.distance_m)
   return R:translate (X, Y, R:picture (self.texture))
end

function car:lanechange (LaneD)

   -- can't change lanes if we're not going fast enough
   if (self.speed_kph < 5) then return
   end

   -- based on current want offset; if it's between 0..0.5 and we want -1, then just go to zero (this is a
   -- lane change cancel); if it's between 0..-0.5 and we want 1, then just go to zero as well (another cancel)
   if (LaneD == -1 and self.lane_offset > 0 and self.lane_offset < 0.5) then self.lane_offset_want = 0
   elseif (LaneD == 1 and self.lane_offset < 0 and self.lane_offset > -0.5) then self.lane_offset_want = 0
   else

      local MaxLane = self:get_max_lane ()
      local WantLane = clamp (self.lane + LaneD, 1, MaxLane)
      self.lane_offset_want = (WantLane - self.lane)
   end
end

function car:get_max_lane ()

   local MaxLane = self.road:get_max_lane (self.distance_m)
   if (self.speed_kph < 50) then MaxLane = MaxLane + 1
   end
   return MaxLane
end
