-- init code for LW timetable env

-- stop, scheduled departure every
-- d_int: Departure every n seconds (epoch modulo)
-- d_off: Departure time offset
function F.stop_sd(st_name, doors, departcommand, minstoptime, d_int, d_off)
	if event.train then
		local timenow = os.time()
		local timerdy = timenow + minstoptime
		local wait = d_int - ((timerdy-d_off) % d_int)
   local waitcorr = math.floor(wait*0.66)
		digiline_send("monitor", "Departure scheduled for: | "..os.date("%H:%M:%S", timenow+wait))
		atc_send("B0 W O"..doors.." D"..waitcorr.." OCD1"..departcommand)
	else
		local timenow = os.time()
		digiline_send("monitor", "Time: "..os.date("%H:%M:%S", timenow))
	end
end

function F.stop_sd_sched(st_name, doors, departcommand, minstoptime, d_int, d_off)
  depart = false
	if event.train then
   local time_now = rwt.now()
		local next_dep_time = rwt.next_rpt(rwt.add(time_now, minstoptime), d_int, d_off)
		digiline_send("monitor", "Departure scheduled for: | "..rwt.to_string(next_dep_time, true))
   atc_set_text_inside(st_name.."\nDeparture: "..rwt.to_string(next_dep_time, true))
		atc_send("B0 W O"..doors)
    schedule(next_dep_time, "depart")
  elseif event.schedule then
    atc_send("OCD1"..departcommand)
	  digiline_send("monitor", "Last Departure: | "..rwt.to_string(rwt.now(), true))
    atc_set_text_inside("")
    depart = true
	end
end

function F.timedisplay()
digiline_send("time", "Time: | "..rwt.to_string(rwt.now(),true).." | "..os.date("%H:%M:%S"))
schedule(rwt.next_rpt(rwt.now(),5,0), "")
end

-- Stat counter and timetaking utilities
-- Stat from subway
F.stat=function(line, init)
--statistics
-- init
if init then
reftrain = atc_id
a_tbt = 30
a_tbtmax = 30
a_rtt = 500
a_not = 0
c_not = 0
c_tbtmax = 0
time_lt = os.time()
time_rt=os.time()
end
if not a_tbtmax then a_tbtmax = 30 end
if not c_tbtmax then c_tbtmax = 0 end
--real code
if event.train then
local time = os.time()
c_not = c_not + 1
a_tbt = (a_tbt + (time - time_lt)) / 2
c_tbtmax = math.max(c_tbtmax, (time - time_lt))
if atc_id == reftrain then
  a_rtt = (a_rtt*0.2 + (time - time_rt)*0.8)
  a_not = c_not
  c_not = 0
  a_tbtmax = (a_tbtmax + c_tbtmax) / 2
  c_tbtmax = 0
end
  digiline_send("stats", "Stat: "..line..
    " NoT:"..a_not.."("..c_not..")"..
    " TbT:"..math.floor(a_tbt).."("..(time-time_lt)..")"..
    " Tmx:"..math.floor(a_tbtmax).."("..c_tbtmax..")"..
    " R:"..math.floor(a_rtt).."("..(time - time_rt)..")"
    )
time_lt = time
if atc_id == reftrain then
  time_rt = time
end
end
end

S.timetake = {}
function F.timetake_start(ttname)
  if not atc_id then return end
  local nouw = rwt.to_secs(rwt.now())
  if not S.timetake[ttname] then
    S.timetake[ttname] = {}
  end
  S.timetake[ttname][atc_id] = nouw
end

--L100
function F.timetake_end(ttname)
  if not atc_id then return end
  if not S.timetake[ttname] or not S.timetake[ttname][atc_id] then
    digiline_send("timetake", "No start time for "..atc_id)
    return
  end
  local first = S.timetake[ttname][atc_id]
  local nouw = rwt.to_secs(rwt.now())
  local tdiff = nouw - first
  local cavg = S.timetake[ttname].avg
  local cmax = S.timetake[ttname].max
  local cmin = S.timetake[ttname].min
  if cavg and cmax and cmin then
    S.timetake[ttname].avg = tdiff*0.1 + cavg*0.9
    S.timetake[ttname].min = math.min(tdiff, cmin)
    S.timetake[ttname].max = math.max(tdiff, cmax)
  else
    S.timetake[ttname].avg = tdiff
    S.timetake[ttname].min = tdiff
    S.timetake[ttname].max = tdiff    
  end
  digiline_send("timetake", ttname..
    " this:"..tdiff..
    " min:"..math.floor(S.timetake[ttname].min)..
    " avg:"..math.floor(S.timetake[ttname].avg)..
    " max:"..math.floor(S.timetake[ttname].max)
    )

end

--== Timetable prototype (TTP) ===
--[[ table structures:
F.ttp - static timetable data - see below
S.ttp[tt_name] = { - dynamic tt data
  recording_train = <id of the train that is recording travel times, or nil>
  travel_times = {
    <station name> = <time in seconds from initial departure at first station of the line to departure at this station>
  }
  station_order = { <station 1>, <station 2>...}
}
S.ttt[train_id] = { - trains
      timetable = <timetable ID that the train is currently using>,
      initial_dep = <departure at first station of the line>,
      location = <Station where the train was last seen>,
      desired_dep = <Departure time as in timetable>,
      planned_dep = <real departure time calculated as the train reaches station>,
      actual_dep = <actual departure time at last station. is nil while train is stopped>,
      last_delay = <last known delay of the train - calculated every departure>,
    }
}
]]

local STOP_TIME = 10
local DEPCMD="A1OCD1SM"
local RDEPCMD="RA1OCD1SM"

if not S.ttp then S.ttp = {} end
if not S.ttt then S.ttt = {} end
F.ttp={
  CFE_N = {
    outside_text = "[CFE] Warmoneaye\nvia Ehlodex, Personhood West, Crystal Farms",
    inside_line_desc = "CFE to Warmoneaye",
    stn_display = "CFE Warmoneaye  ",
  },
  CFE_S = {
    outside_text = "[CFE] Origin\nvia Crystal Farms, Personhood West, Ehlodex",
    inside_line_desc = "CFE to Origin",
    stn_display = "CFE Origin      ",
  },
  NRG_E = {
    outside_text = "[NRG] Azena Transirejo",
    inside_line_desc = "NRG to Azena Transirejo",
    stn_display = "NRG Azena Trans.",
  },
  NRG_W = {
    outside_text = "[NRG] New Roses Gardens",
    inside_line_desc = "NRG to New Roses Gardens",
    stn_display = "NRG N.Roses Gdns",
  },
  NX_S = {
    outside_text = "[NX] Trisiston\nvia Personhood West, Ehlodex, South Forest, Melinka",
    inside_line_desc = "NX to Trisiston",
    stn_display = "NX  Trisiston   ",
  },
  E1_S = {
    outside_text = "[E1] Melinka\nvia The Cube, Ehlodex, Spawn Main, Mom Junction",
    inside_line_desc = "E1 to Melinka",
    stn_display = "E1  Melinka     ",
  },
  S12_S = {
    outside_text = "[S12] Ehlodex\nvia Gardon St., Schwarzschild St., Anju Crossing, Lesnoi",
    inside_line_desc = "S12 to Spawn Main",
    stn_display = "S12 Spawn Main  ",
  },
}

--[[
Timetable entry point. The train finalizes its last timetable and
registers itself on the given timetable instance. It departs at the next time slot
(given by interval and offset).
F.ttp_begin({
 stn = "Warmoneaye", -- station name
 tt = "CFE_S", -- timetable ID
 depint = "05;00", --departure slot interval
 depoff = "00;00", --departure slot offset
 doorside = "L",
 reverse = true,
 only_lines = nil, --if given a table, only trains where only_lines[get_line()] is true are considered
 force_tt_reset = false, -- force reset of travel times for this timetable
})
]]
-- Make train depart at the next time slot, and save its start time
function F.ttp_begin(p)
  __approach_callback_mode = 1

  if not F.ttp[p.tt] then error("No TT instance "..p.tt) end
  if not atc_id or not atc_arrow then return end
  if p.only_lines and not p.only_lines[get_line()] then return end
  if not S.ttp[p.tt] then S.ttp[p.tt] = {} end
  local tti = S.ttp[p.tt]
--L150
  if event.approach and not event.has_entered then
    -- make the train stop
    atc_set_ars_disable(true)
    atc_set_lzb_tsr(2)
    atc_set_text_inside("Next stop: "..p.stn.."\nTerminal Station.\nThis train continues as "..F.ttp[p.tt].inside_line_desc)
  end
  if event.train then
    -- train arrived, planning departure
		atc_send("B0 W O"..p.doorside)

    local time_now = rwt.now()
    -- Train might have had another TT before, do the cleanup from ttp_end here.
    local trno = S.ttt[atc_id]
    if trno then
      local ttio = S.ttp[trno.timetable]
      if ttio.recording_train == atc_id then
        ttio.travel_times[p.stn] = rwt.diff(trno.initial_dep, time_now)
        ttio.station_order[#ttio.station_order+1] = p.stn
      end
    end
    local next_dep_time = rwt.next_rpt(rwt.add(time_now, 10), p.depint, p.depoff)
    schedule(next_dep_time, "departure")
    S.ttt[atc_id] = {
      timetable = p.tt,
      initial_dep = next_dep_time,
      location = p.stn,
      desired_dep = next_dep_time,
      planned_dep = next_dep_time,
      last_delay = 0,
    }
    -- if no travel times are available yet, set this train as recording
    if not tti.travel_times or p.force_tt_reset then
      tti.travel_times = {}
      tti.station_order = {p.stn}
      tti.recording_train = atc_id
    elseif tti.recording_train == atc_id then
      tti.recording_train = nil
    end
    atc_set_text_outside(F.ttp[p.tt].outside_text)
    atc_set_text_inside(p.stn.."\nAa: "
        ..rwt.to_string(time_now, true).." Da: "
        ..rwt.to_string(next_dep_time, true))
  end
  if event.schedule then
    -- departure. save actual departure time in tt
    if S.ttt[atc_id] then -- failsafe: if entry is deleted externally somehow, train just departs and is not tracked by tt (makes resetting S.ttt possible)
     S.ttt[atc_id].actual_dep = rwt.now()
     local delay = rwt.diff(S.ttt[atc_id].desired_dep, S.ttt[atc_id].actual_dep)
     atc_set_text_inside(F.ttp[p.tt].inside_line_desc
       .."\nDelay:"..rwt.to_string(delay, true))
     S.ttt[atc_id].last_delay = delay
    end
    if p.reverse then
      atc_send(RDEPCMD)
    else
      atc_send(DEPCMD)
    end
  end
end
--[[
Generic stop on timetable. Any train that has a TT instance registered
stops here, waits STOP_TIME and continues. Behavior can be altered by options:
F.ttp_stop({
 stn = "Personhood West", -- station name
 doorside = "L",
 only_lines = nil, --if given a table, only trains where only_lines[get_line()] is true are considered
 end_of_tt = { TT_ID = true },
   -- if present and key is true for a TT identifier, this is the last station on this timetable. Trains will stop recording timetable and be deregistered.
 departure = { TT_ID = RWT relative to initial departure },
   -- If present, override desired departure time. Defaults to travel time + STOP_TIME if not provided
 no_disable_ars = nil,
   -- if true, does not disable ARS on approach (used for example at INTERCAL)
})
]]function F.ttp_stop(p)
  -- set my approach callback mode
  __approach_callback_mode = 1
  if not atc_id or not atc_arrow then return end
  if not S.ttt[atc_id] then return end
  if p.only_lines and not p.only_lines[get_line()] then return end
  local trn = S.ttt[atc_id]
  local tt = trn.timetable
  if not F.ttp[tt] then
    S.ttt[atc_id] = nil
  end
  local tti = S.ttp[tt]
  if event.approach and not event.has_entered then
    -- make the train stop
    if not p.no_disable_ars then
     atc_set_ars_disable(true)
    end
    atc_set_lzb_tsr(2)
    atc_set_text_inside("Next stop: "..p.stn)
  end
  if event.train then
    -- train arrived, planning departure
		atc_send("B0 W O"..p.doorside)
    local time_now = rwt.now()
    -- update our location and determine desired and planned departure
--L200
    local next_dep_time = rwt.add(time_now, STOP_TIME)
    trn.location = p.stn
    trn.desired_dep = nil
    trn.actual_dep = nil

    -- calculate desired departure nouw
    if p.departure and p.departure[tt] then
      trn.desired_dep = rwt.add(trn.initial_dep or 0, 
          p.departure[tt])      
    elseif tti.travel_times[p.stn] then
      trn.desired_dep = rwt.add(trn.initial_dep or 0, 
          tti.travel_times[p.stn] + STOP_TIME)
    end

    if trn.desired_dep then
      -- if we had a source for desired departure, update planned daparture time
      if rwt.to_secs(next_dep_time) < rwt.to_secs(trn.desired_dep) then
        -- don't depart before the planned departure time
        next_dep_time = trn.desired_dep
      end
      atc_set_text_inside(p.stn.."\nAa "
        ..rwt.to_string(time_now, true).." Dd"
        ..rwt.to_string(trn.desired_dep, true).." Da"
        ..rwt.to_string(next_dep_time, true))

      local delay = rwt.diff(trn.desired_dep, next_dep_time)
      trn.last_delay = delay
    else
      atc_set_text_inside(p.stn.."\nAa "
        ..rwt.to_string(time_now, true).." Dd ? Da"
        ..rwt.to_string(next_dep_time, true))
    end

    if tti.recording_train == atc_id then
      -- we are recording. save travel time
      tti.travel_times[p.stn] = rwt.diff(trn.initial_dep or 0, time_now)
      tti.station_order[#tti.station_order+1] = p.stn
      atc_set_text_inside(p.stn.."\nRec TT "
        ..rwt.to_string(tti.travel_times[p.stn], true).." Da"
        ..rwt.to_string(next_dep_time, true))
    end

    trn.planned_dep = next_dep_time
    schedule(next_dep_time, "departure")
  end
  if event.schedule then
    -- departure. save actual departure time in tt
    trn.actual_dep = rwt.now()
    local delay = rwt.diff(trn.desired_dep or trn.actual_dep, trn.actual_dep)
    atc_set_text_inside(F.ttp[tt].inside_line_desc
      .."\nDelay:"..rwt.to_string(delay, true))
    S.ttt[atc_id].last_delay = delay
    atc_send(DEPCMD)
    if p.end_of_tt and p.end_of_tt[tt] then
      -- end of timetable. Deregister train
      if tti.recording_train == atc_id then
        tti.recording_train = nil
      end
      S.ttt[atc_id] = nil
    end
  end
end


function F.ttp_info_times(tt, starttime)
  --L307
  local ttf = F.ttp[tt]
  local tti = S.ttp[tt]
  local p = {}
  if tti.recording_train then
    p[#p+1] = ("recording "..tti.recording_train)
  end
  p[#p+1] = ("Di "..rwt.to_string(starttime, true).." "..tti.station_order[1])
  for i=2,#tti.station_order do
    local ap = rwt.add(starttime, tti.travel_times[tti.station_order[i]])
    p[#p+1] = ("Ap "..rwt.to_string(ap, true)..
      " Dp "..rwt.to_string(rwt.add(ap, STOP_TIME), true)..
      " "..tti.station_order[i])
  end
  return p
end
function F.ttp_info_trains(tt, starttime)
  --L307
  local ttf = F.ttp[tt]
  local tti = S.ttp[tt]
  local p = {}
  for tid,trn in pairs(S.ttt) do
    if trn.timetable==tt then
      if trn.actual_dep then
        p[#p+1] = ("Trn "..tid..
          " after "..trn.location..
          " Dd "..rwt.to_string(trn.desired_dep, true)..
          " Da "..rwt.to_string(trn.actual_dep, true)..
          " Delay "..rwt.to_string(trn.last_delay))
      else
        p[#p+1] = ("Trn "..tid..
          " at "..trn.location..
          " Dd "..rwt.to_string(trn.desired_dep, true)..
          " Delay "..rwt.to_string(trn.last_delay))
      end
    end
  end
  return p
end



--[[F.ttp_station_display({
 lines = {"CFE_S", "NX_S", "E1_S"},
 departure = {},
 station = "The Cube",
 title = "The Cube (Track 2)",
 interval = 30,
 display1 = "display1",
 display2 = "display2",
}]]
function F.ttp_station_display(p)
--L425
-- { dep, text }
local next_trains = {}
local function is_past_station(tstn, stnorder)
  for _,s in ipairs(stnorder) do
    if s==p.station then return true end
    if s==tstn then return false end
  end
  return true
end
local function add_train(deptime, line, train, tid)
  local tent = {dep = deptime, text = 
     rwt.to_string(deptime,true).." "..F.ttp[line].stn_display
     .." +"..train.last_delay}
  for i,ntrn in ipairs(next_trains) do
    if rwt.diff(ntrn.dep, deptime)<0 then
      table.insert(next_trains, i, tent)
      return
    end
  end
  table.insert(next_trains, tent)
end

for _,line in ipairs(p.lines) do
  local fttp = F.ttp[line]
  local sttp = S.ttp[line]
  -- find all trains on this line
  for id, train in pairs(S.ttt) do
   if train.timetable == line then
    if train.location == p.station and not train.actual_dep then
      -- the train is currently standing at this station
      add_train(train.planned_dep, line, train, id)
    elseif not is_past_station(train.location, sttp.station_order) then
      -- train is still approaching, calculate arrival time
      local trav_dep = rwt.add(train.initial_dep, (sttp.travel_times[p.station] or 0) + STOP_TIME)
      local act_dep = rwt.add(trav_dep, train.last_delay)
      if p.departure and p.departure[line] then
        local plan_dep = rwt.add(train.initial_dep, p.departure[line])
        if rwt.to_secs(act_dep) < rwt.to_secs(plan_dep) then
          act_dep = plan_dep
        end
      end
      add_train(act_dep, line, train, id)
    end
   end
  end
end

-- make output
local i
local text1 = p.title .. " * "..rwt.to_string(rwt.now(), true).." * "
for i=1,3 do
  if next_trains[i] then
    text1 = text1 .. "\n".. next_trains[i].text
  end
end
digiline_send(p.display1, text1)
if p.display2 then
 local text2 = ""
 for i=4,7 do
  if next_trains[i] then
    text2 = text2 .. next_trains[i].text .. "\n"
  end
 end
 digiline_send(p.display2, text2)
end
--if not p.notimer then
--  schedule_in(p.interval or 30,"foo")
--end
end