--
-- manages other cars on the road
--

function traffic_ctor (self)

   self.road = nil
   self.roadstream = nil

   self.distance_m = 0

   self.trouble = nil

   -- shadows is an array of vehicle "shadows" on a very long strip; each one is a car plus a distance m
   -- plus an average speed; when they get close to the player, they become a bona-fide car and have their
   -- "live" flag set
   self.shadows = {}
   self.add_shadow_counter = 1 -- only can add one shadow every second

end

traffic = class (traffic_ctor)

function traffic:init_road (Road)

   self.distance_m = 0
   self.road = Road
   self.roadstream = Road.roadstream

   -- add N cars
--   local NumCars = self.road:get_want_cars ()
--   local MinDistanceM = self.distance_m - pix2m (400 * 2)
--   local MaxDistanceM = self.distance_m + pix2m (400 * 8)
   self.shadows = {}
--   for i = 1, NumCars, 1 do

      -- create shadow randomly
--      local Shadow = self:get_shadow (MinDistanceM + (MaxDistanceM - MinDistanceM) * RN ())
--      table.insert (self.shadows, Shadow)
--   end
end

function traffic:advance (M)

   self.distance_m = self.distance_m + M
end

function traffic:update (T)

   if (self.add_shadow_counter > 0.0) then self.add_shadow_counter = self.add_shadow_counter - T
   end

   -- handle overlaps
   for ID, Shadow in pairs (self.shadows) do

      if (Shadow.live and not Shadow.car.exit_flag) then

         -- passed ?
         if (Shadow.passable and not Shadow.passed and cur_play.car.distance_m > Shadow.car.distance_m) then

            cur_play:pass ()
            Shadow.passed = true
         end

         local OverlapCar = self:find_overlap_car (ID, ID, Shadow.car.distance_m, Shadow.car.space_m, Shadow.car.lane)
         if (OverlapCar) then

            -- which one is front, which back?
            local FrontCar, BackCar
            if (OverlapCar.distance_m > Shadow.car.distance_m) then

               FrontCar = OverlapCar
               BackCar = Shadow.car
            else

               FrontCar = Shadow.car
               BackCar = OverlapCar
            end

            -- if they are very close, then crash them; otherwise, adjust so they don't overlap
            if ((FrontCar.distance_m - BackCar.distance_m) < (FrontCar.length_m + BackCar.length_m) / 2) then

               -- if the cars are both already spinning out, then don't reset, but if one of them is new then do
               if (not FrontCar.spinout_flag or not BackCar.spinout_flag) then

                  local AvgKPH = (FrontCar.speed_kph + BackCar.speed_kph) / 2
                  FrontCar:set_spinout (AvgKPH)
                  BackCar:set_spinout (AvgKPH)
                  cur_play.deaths = cur_play.deaths + 1
               end
            else

               FrontCar.distance_m = interpolate_towards (FrontCar.distance_m, FrontCar.distance_m + FrontCar.space_m, 1, T)
               BackCar.distance_m = interpolate_towards (BackCar.distance_m, BackCar.distance_m - BackCar.space_m, 1, T)
               FrontCar:set_want_speed_kph (BackCar.speed_kph + 10)
               BackCar:set_want_speed_kph (FrontCar.speed_kph - 25)

               -- more likely to think about a lane change
               Shadow.lanechange_counter = Shadow.lanechange_counter - T * 10.0
            end

         else

            Shadow.car:set_want_speed_kph ((Shadow.want_speed_kph + Shadow.speed_kph) / 2)
         end

         -- think about a lane change
         Shadow.lanechange_counter = Shadow.lanechange_counter - T
         if (Shadow.lanechange_counter < 0.0) then

            self:try_lanechange (ID, Shadow.car)
            Shadow.lanechange_counter = Shadow.lanechange_time * (RN () * 0.5 + 0.75)
         end

      end
   end

   -- remove any dead shadows, add new ones to replace them (keeps traffic more or less constant); and scan for troble
   local MinDistanceM = self.distance_m - pix2m (400 * 2)
   local MaxDistanceM = self.distance_m + pix2m (400 * 8)
   local LiveMinDistanceM = self.distance_m - pix2m (400)
   local LiveMaxDistanceM = self.distance_m + pix2m (400 * 2)
   local PrevTrouble = self.trouble
   self.trouble = nil
   for ID, Shadow in pairs (self.shadows) do

      -- for now, just wrap
      if (Shadow.distance_m > MaxDistanceM or
         Shadow.distance_m < MinDistanceM or
         (Shadow.live and Shadow.car.dead_flag)) then

         -- replace this shadow
         table.remove (self.shadows, ID)--self:add_shadow (ID)

      else

         if (Shadow.live and Shadow.car.distance_m > self.distance_m and Shadow.car.spinout_flag) then self.trouble = true
         end

         -- should it be live?
         local Live = (Shadow.distance_m > LiveMinDistanceM and Shadow.distance_m < LiveMaxDistanceM)
         if (Live and not Shadow.live) then

            -- just went live;
            Shadow.live = true
            Shadow.car:set_live (Shadow.distance_m, Shadow.speed_kph)

            -- segment
            local CurSeg = Shadow.car:get_cur_roadseg ()
            if (CurSeg) then Shadow.car:set_lane (RN_index (CurSeg.lanes + 1))
            else Shadow.live = nil
            end

         elseif (Shadow.live and (not Live or not Shadow.car:is_live ())) then

            -- just went dead
            Shadow.live = nil
         end
      end
   end

   -- trouble after absence of trouble agitates the player
   if (self.trouble and not PrevTrouble) then cur_play:agitate (10)
   end

   -- remove shadows so that we have only the appropriate number of cars; remove only one per frame
   local WantCars = self.roadstream:get_want_cars ()
   if (sizeof (self.shadows) > WantCars) then

      local ID = RN_index (sizeof (self.shadows))
      if (not self.shadows[ID].live) then

         table.remove (self.shadows, ID)
      end

   elseif (sizeof (self.shadows) < WantCars) then

      self:add_shadow ()
   end

   -- update shadows/cars
   for ID, Shadow in pairs (self.shadows) do

      -- if it's live, then it works differently
      if (Shadow.live) then

         -- it's live; extract from the car itself
         Shadow.car:update (T)
         Shadow.distance_m = Shadow.car.distance_m
         Shadow.speed_kph = Shadow.car.speed_kph
      else

         -- not live; just advance in raw KPH
         Shadow.speed_kph = interpolate_towards (Shadow.speed_kph, Shadow.want_speed_kph, Shadow.car.max_accel_kphps, T)
         local MPS = kph2mps (Shadow.speed_kph)
         Shadow.distance_m = Shadow.distance_m + MPS * T
      end
   end

end

function traffic:render (R)

   local RList = {}
   for ID, Shadow in pairs (self.shadows) do

      if (Shadow.live) then table.insert (RList, Shadow.car:render (R))
      end
   end
   return R:union (RList)
end

function traffic:get_shadow (DistanceM)

   local SpeedRN = RN ()

   local Shadow = {}
   Shadow.distance_m = DistanceM
   Shadow.want_speed_kph = self.roadstream:get_want_speed () * (SpeedRN * 0.2 + 1.0)
   Shadow.speed_kph = Shadow.want_speed_kph
   Shadow.live_flag = nil
   Shadow.car = car ()
   Shadow.car:init_road (self.road)
   Shadow.car:set_live (DistanceM, Shadow.speed_kph)
   Shadow.car:set_lane (RN_index (self.roadstream.cur_lanes + 1))
   Shadow.lanechange_time = SpeedRN * 15 + 5 -- average time between lane changes
   Shadow.lanechange_counter = Shadow.lanechange_time * (RN () * 0.5 + 0.75)
   Shadow.passable = DistanceM > cur_play.road.distance_m
   Shadow.passed = nil

   return Shadow
end

function traffic:find_overlap_car (StartID, SkipID, DistanceM, LengthM, Lane, WeakOverlapFlag)

   -- see if a car overlaps the given lane segment
   local MinM = DistanceM - LengthM
   local MaxM = DistanceM + LengthM

   function check_overlap (CarMinM, CarMaxM)

      -- line segment collision check
      return (CarMinM < MinM and CarMaxM > MaxM) -- car contains this overlap completely
         or (MinM < CarMinM and MaxM > CarMaxM) -- overlap contains the car completely
         or (MinM > CarMinM and MinM < CarMaxM) -- first position is contained; shadow is ahead of the car
         or (MaxM > CarMinM and MaxM < CarMaxM) -- last position contained; shadow is behind the car
   end

   -- find all overlapping cars
   local OverlapCars = {}

   for ID, Shadow in pairs (self.shadows) do

      if (ID > StartID and ID ~= SkipID) then

         if (Shadow.car.lane == Lane and not Shadow.car.exit_flag and not Shadow.car.dead_flag and (Shadow.live or WeakOverlapFlag)) then

            local CarMinM = Shadow.car.distance_m - Shadow.car.space_m
            local CarMaxM = Shadow.car.distance_m + Shadow.car.space_m
            if (check_overlap (CarMinM, CarMaxM)) then table.insert (OverlapCars, Shadow.car)
            end
         end
      end
   end

   -- compare to player
   local PlayerCar = cur_play.car
   if (PlayerCar.lane == Lane and not PlayerCar.exit_flag) then

      local CarMinM = PlayerCar.distance_m - PlayerCar.space_m
      local CarMaxM = PlayerCar.distance_m + PlayerCar.space_m
      if (check_overlap (CarMinM, CarMaxM)) then table.insert (OverlapCars, PlayerCar)
      end
   end

   -- if no overlap cars, fine, otherwise find the closest one
   local ClosestCar = nil
   local ClosestM = nil
   for ID, Car in pairs (OverlapCars) do

      local DistanceM = math.abs (Car.distance_m - DistanceM)
      if (not ClosestM or DistanceM < ClosestM) then

         ClosestCar = Car
         ClosestM = DistanceM
      end
   end

   return ClosestCar
end

function traffic:try_lanechange (ID, Car)

   -- randomly change lanes, if possible
   local LaneD = -1
   if (RN () < 0.5) then LaneD = 1
   end
   local NewLane = Car.lane + LaneD

   -- check if it's a valid lane
   local CurSeg = Car:get_cur_roadseg ()
   if (NewLane > CurSeg.lanes or NewLane < 1) then return
   end

   -- see if anything is there
   local OverlapCar = self:find_overlap_car (1, ID, Car.distance_m, Car.space_m + 3, NewLane)
   if (not OverlapCar) then Car:lanechange (LaneD)
   end
end

function traffic:add_shadow (ID)

   if (self.add_shadow_counter > 0.0) then return
   end

   self.add_shadow_counter = RN () + 1.0

   -- choose the ideal spawn distance
   local MinDistanceM = self.distance_m - pix2m (400 * 2)
   local MaxDistanceM = self.distance_m + pix2m (400 * 8)
   local ShadowDistanceM = RN () * (MaxDistanceM - MinDistanceM) + MinDistanceM

   -- is it on the screen?
   local ScreenMinDistanceM = self.distance_m - pix2m (200 * 1.5)
   local ScreenMaxDistanceM = self.distance_m + pix2m (600 * 1.5)
   if (ShadowDistanceM > ScreenMinDistanceM and ShadowDistanceM < ScreenMaxDistanceM) then return
   end

   -- see if anything is there
   local Shadow = self:get_shadow (ShadowDistanceM)
   local OverlapCar = self:find_overlap_car (1, ID, Shadow.car.distance_m, Shadow.car.space_m + 15, Shadow.car.lane, true)
   if (OverlapCar) then return
   end

   -- add it
   if (ID) then self.shadows[ID] = Shadow
   else table.insert (self.shadows, Shadow)
   end
end
