if S.trains == nil then S.trains = {} end
if not S.odsd then S.odsd = {} end
if not S.cpoints then S.cpoints = {} end
S.pd = {}
S.pdt = {}
__approach_callback_mode = 1

F.odj = {}

-- F.dst - Get summer time
function F.dst()
  if os.date().isdst then return "CEST" else return "CET" end
end

function F.pccheck(pos, state)
  if is_passive(pos) then return true end
  return (getstate(pos) == state)
end

-- F.error - Send error with error code
function F.error(errorcode)
  error(F.errlist[errorcode])
end

--[[ F.stnbasic - Basis for all station functions:
(By default, nil is defined as false or "no changes")
stn        = Station Code
side       = Door opening side
optime     = Length of time before train departs
reverse    = Reverse train before departing
acc        = Departure Speed
out        = Change outside text
reventry   = Allow trains to pass from the reverse direction
predepart  = Function to execute before departure
postdepart = Function to execute after departure
next       = Next stop
]]

function F.stnbasic(stn, side, optime, reverse, acc, out, reventry, predepart, postdepart, next,track)
  if not event then return end
  if event.type == "train" then
    if atc_arrow then
      if (get_rc() or ""):match ("FREIGHT") then
        atc_send (reverse and "RSM" or "SM")
        return
      end
      local cmdstr=""
      local t_sched = ((optime or 15) + 1)
      if out then
        atc_set_text_outside (out)
      end
      local intext = "Unknown Station"
      if stn then
        intext = F.stnlist[stn] or stn
      end
      atc_set_text_inside (intext)
      cmdstr = "BBWO" .. (side or "R")
      if type (predepart) == "function" then 
        predepart ()
      end
      S.trains[atc_id] = {
        v = 1,
        l = (get_line() or ""),
        s = stn,
        t = (trk or "N/A"),
      }
      cmdstr = cmdstr .."D" .. math.floor((optime or 15)) .. "OCD1"
      if reverse then
        cmdstr = cmdstr .. "R"
      end
      cmdstr = cmdstr .. "S" .. (acc or "M")
      atc_send (cmdstr)
      interrupt (t_sched,"")
    end
  elseif event.type == "int" then
    if atc_id then
      local intext = ""
      if next then
        intext = "Next stop: " .. (F.stnlist[next] or next)
      end
      if type (postdepart) == "function" then
        postdepart ()
      end
      atc_set_text_inside (intext)
    end
  end
end

-- F.hst - preset function for small stations (Haltestelle)
function F.hst(cur, nxt, side, spd, out, trk)
  F.stnbasic(cur, side, 10, false, spd, out, true, nil, nil, nxt, trk)
end

-- F.bhf - preset function for large stations (Bahnhof)
function F.bhf(cur, nxt, side, spd, out, trk)
  F.stnbasic(cur, side, 15, false, spd, out, true, nil, nil, nxt, trk)
end

-- F.kbhf - preset function for termini
function F.kbhf(cur, nxt, side, spd, out, trk)
  F.stnbasic(cur, side, 15, true, spd, out, true, nil, nil, nxt, trk)
end

-- F.timing - station with timing
function F.timing(d_off, d_int, cur, nxt, side, spd, out, trk, term, pre, post)
		  local timenow = os.time()
	  	local timesincelast = (timenow+d_off) % d_int
  local wait = d_int - timesincelast
  F.stnbasic(cur, side, wait, term, spd, out, true, pre, post, nxt, trk)
end

-- F.brk(direction) - sends ATC B2 command
function F.brk(dir)
  if event.type == "train" then
    if atc_arrow == dir then
      atc_send("B2S2")
    end
  end
end

-- Second version of the station function
function F.stn2gen(stn, trk, door, ret, chout, depspeed)
  __approach_callback_mode = 1
  if not stn then return end
  if not trk then return end
  if not door then return end
  if door~="L" and door~="R" and door~="C" then return end
  if not atc_arrow then return end
  if not F.stndet[stn] then return end
  if not F.stndet[stn][trk] then return end
  local t = F.stndet[stn][trk]
  local stop = false
  if #t==0 then return end
  local l = get_line()
  if (not l) or l==" " then return end
  local rc = get_rc() or ""
  if rc:match("FREIGHT") and atc_id then
    atc_send (ret and "BBWRSM" or "SM")
    return
  end
  if event.type == "approach" and not event.has_entered then
    for i = 1, #t, 1 do
      if t[i][1]==l then
        atc_set_ars_disable(true)
        atc_set_lzb_tsr(2)
        local intext = ("Arriving at: %s (Track: %s)"):format(F.stnlist[stn] or stn, trk)
        atc_set_text_inside(intext)
        S.trains[atc_id] = {
          v = 2,
          l = get_line() or "",
          s = stn,
          t = trk or "N/A",
          ts = os.time(),
          mode = "APP",
        }
        return
      end
    end
    return
  elseif event.type == "train" then
    for i = 1, #t, 1 do
      if t[i][1]==l then
        stop = true
        local timenow = os.time()
        local interval = F.lines[l].interval
        local offset = t[i][7]
        local arroff
        if interval and offset then
          arroff = timenow%(F.lines[l].rtt or interval)
          interval = (offset-timenow)%interval
          interval = math.max(5, math.min(45, interval))
        else
          interval = 15
        end
        --atc_send(string.format("A0B0WO%sD%dOCD1%sA1S%s", door, interval, (ret and "R" or ""), tostring(depspeed or "M")))
        atc_send(("B0WO%s"):format(door))
        local nxt = t[i][2]
        local intext_nxt = ""
        local nxtdisp = ""
        if F.stnlist[nxt] then
          intext_nxt = F.stnlist[nxt] and ("Next station: %s (Track: %s)"):format(F.stnlist[nxt], t[i][3] or "?") or ""
          nxtdisp = intext_nxt
        end
        local intext = ("%s\n%s\nDebug: %d %d"):format(F.stnlist[stn] or stn, intext_nxt, arroff or timenow, interval)
        local outtext = F.lines[l].name or l
        if t[i][4] then outtext = outtext..": "..(F.stnlist[t[i][4]] or t[i][4]) end
        t[i][5] = t[i][5] or (timenow-(F.depint[l] or 300))
        -- note that the "average" time is weighted
        t[i][6] = t[i][6] and (t[i][6]+timenow-t[i][5]+30)/2 or (timenow-t[i][5])
        t[i][5] = timenow
        atc_set_text_inside(intext)
        if chout then
          if type(chout) == "string" then
            atc_set_text_outside(chout)
          else
            atc_set_text_outside(outtext)
          end
        end
        S.trains[atc_id] = {
          v = 2,
          l = (get_line() or ""),
          s = stn,
          t = (trk or "N/A"),
          ts = os.time(),
          mode = "ARR",
        }
        schedule_in(interval, {intext = nxtdisp})
        break
      end
    end
    if (not stop) then
      atc_send((ret and "BBWR" or "").."A1S"..(depspeed or "M"))
    end
  elseif event.type == "schedule" then
    if atc_id then
      local msg = event.msg or {}
      if msg.intext then
        atc_set_text_inside(msg.intext)
      end
      atc_send("OCD1"..((ret and atc_arrow) and "B0WR" or "").."A1S"..(depspeed or "M"))
      S.trains[atc_id] = {
        v = 2,
        l = get_line() or "",
        s = stn,
        t = trk or "N/A",
        ts = os.time(),
        mode = "DEP",
      }
    end
  end
end

function F.tram2gen(stn,trk,ret)
  return F.stn2gen(stn,trk,"C",ret,true,8)
end

function F.disp2gen(stn)
  if not stn then return end
  if not F.stndet[stn] then return end
  local s = F.stndet[stn]
  local sn = F.stnlist[stn] or stn
  local d={{
    {sn.."\nTrack    Line"},
    {(string.sub(sn, 28, 54) or "").." \nDestination"},
    {" \nEstimated Arrival"}
  }}
  local c=2
  local t={}
  for i,_ in pairs(s) do t[#t+1]=i end
  table.sort(t)
  for i = 1,#t,1 do
    local trkname = t[i]
    local trk = s[trkname]
    for j = 1,#trk,1 do
      local det = trk[j]
      local r = (c%4==0) and (c/4+1) or ((c-c%4)/4+1)
      if not d[r] then d[r]={{},{},{}} end
      d[r][1][#d[r][1]+1] = string.format("%-8s %s", tostring(trkname), F.lines[det[1]].name)
      d[r][2][#d[r][2]+1] = (F.lines[det[1]].ring and "Ring Line" or (F.stnlist[det[4]] or det[4] or ""))
      c=c+1
      local earr = (det[5] or os.time())+(det[6] or F.depint[det[1]] or 600)
      d[r][3][#d[r][3]+1] = os.date("%Y-%m-%d %H:%M %Z", earr)
    end
  end
  for i = 1, #d, 1 do
    for j = 1, #d[i], 1 do
      digiline_send("d_"..tostring(i).."_"..tostring(j), table.concat(d[i][j],"\n"))
    end
  end
end

--function F.disp2gen() end

function F.pdisp(stn, trk, dn)
  if not (stn and trk and dn) then return end
  if not F.stndet[stn] or not F.stndet[stn][trk] then return end
  if not S.pd[stn] then S.pd[stn] = { [trk] = {[dn] = 0} } end
  if not S.pd[stn][trk] then S.pd[stn][trk] = {[dn] = 0} end
  if not S.pd[stn][trk][dn] then S.pd[stn][trk][dn] = 0 end
  if not S.pdt[stn] then S.pdt[stn] = {} end
  if not S.pdt[stn][trk] then
    local t = {}
    for i = 1, #F.stndet[stn][trk], 1 do
      local v = F.stndet[stn][trk][i]
      t[#t+1] = ("Track: %s\n%s\nTo: %s\nNext: %s"):format(
        trk, F.lines[v[1]].name, F.stnlist[v[4] or "N/A"] or v[4] or "N/A",
        F.stnlist[v[2] or "N/A"] or v[2] or "N/A")
    end
    S.pdt[stn][trk] = t
  end
  local n = S.pd[stn][trk][dn] + 1
  local t = S.pdt[stn][trk]
  digiline_send("track"..trk,t[n])
  if n >= #t then n = 0 end
  S.pd[stn][trk][dn] = n
end

function F.dirsign(stn, trk, ln, arrow)
  if event.type == "init" or event.type == "punch" then
    if not (stn and trk and ln and arrow) then return end
    local t = F.stndet[stn]
    if not t then return end
    t = t[trk]
    if not t then return end
    for i = 1, #t, 1 do
      if t[i][1] == ln then
        digiline_send("lcd",("%s T. %s | %s | %s"):format(arrow, trk, F.lines[ln].name, F.stnlist[t[i][4]]))
        break
      end
    end
  end
end

function F.eval(expr, rettrue, retfalse)
  if expr then return rettrue else return retfalse end
end

function F.checkpoint(name,arrow,opp)
  if event.train then
    S.trains[atc_id] = "C "..name.." "
    if atc_arrow then
      S.trains[atc_id] = S.trains[atc_id]..arrow
    else
      S.trains[atc_id] = S.trains[atc_id]..opp
    end
  end
end

function F.atc (cmd, intext, outtext)
  if event.type == "train" then
    if atc_arrow then
      if cmd then
        atc_send (cmd)
      end
      if intext then
        atc_set_text_inside (intext)
      end
      if outtext then
        atc_set_text_outside (outtext)
      end
    end
  end
end

F.stnlist = {
  ["N/A"]          = "N/A",
  Bts              = "Berton St.",
  cg               = "Colored Grasses",
  clockwise        = "Clockwise",
  counterclockwise = "Counterclockwise",
  cras             = "ARSE7's Shop",
  crbfost          = "Station East",
  crbfsm           = "Station St. Central",
  crbfso           = "Station St. East",
  crbfsw           = "Station St. West",
  crch             = "Crossroads City Hall",
  crchs            = "City Hall South",
  crmtrail         = "Mountain Railway Terminus",
  crshelter1       = "Shelter I",
  crsfterm         = "South Forest St. Terminal",
  crsmacker        = "Smacker's Station",
  crwm             = "West Mountains",
  elchateau        = "Chateau d'Erstazi",
  elgp             = "Greener Pastures",
  elsf             = "Salt Factory",
  evo              = "EVO",
  grsc             = "Grassy Scarp",
  krasnograd       = "Krasnograd-FTNT",
  mushroom         = "Mushroom Land",
  neverbuild       = "Neverbuild",
  nvbcentral       = "Neverbuild Central",
  nvbold           = "Old Terminus",
  nvboutskirts     = "Neverbuild Outskirts",
  ["NRG-bplatz"]   = "Berliner Platz",
  ["NRG-CW"]       = "Dörfle",
  ["NRG-harbor"]   = "Hafengebiet",
  ["NRG-krstr"]    = "Krasnograder Straße",
  ["NRG-museum"]   = "Museum",
  ["NRG-pek"]      = "Pekinger Straße",
  ["NRG-south"]    = "Südbahnhof",
  ["NRG-townhall"] = "Rathaus",
  ["NRG-townhall2"] = "Hintere Rathausstraße",
  ["NRG-yard"]     = "Betriebshof",
  oc               = "Ocean City",
  occh             = "City Hall",
  occrt            = "CRT Office",
  ocmushroom       = "Mushroom Market",
  ocoutskirts      = "Ocean City Outskirts",
  phsc             = "Southern Crossing",
  phwest           = "Personhood West",
  scc              = "Silver Coast Central",
  scn              = "Silver Coast North",
  scs              = "Silver Coast South",
  thecube          = "The Cube",
}

for k, v in pairs{[1] = "Station",} do
  for _, i in pairs{6,} do
    F.stnlist[string.format("crc_%d_%d", k, i)] = string.format("%d %s St.", i, v)
  end
end

for k, v in pairs{[5] = "Fifth", [6] = "Sixth", [7] = "Seventh", [8] = "Eighth",} do
  for l, w in pairs{[1] = "First", [3] = "Third", [5] = "Fifth",} do
    F.stnlist[string.format("crs_%d_%d", k, l)] = string.format("%s St./%s Alley", v, w)
  end
end

F.lines = {
  ["AB"] = {
    name = "ATL-B Commuter", short = "ATL-B/C",
    ring = false,
    interval = 240,
    [1] = { {"crch","2"}, --[[ {"crwm","3"},]] {"scs","N1"}, {"scc","N1"}, {"scn","N1"}, {"cg","1"}, {"thecube","4"}, --[[{"phsc","N"},]] {"grsc", "4"}, {"phwest","1"} },
    [2] = { {"phwest","1"}, {"grsc", "3"}, --[[{"phsc","S"},]] {"thecube","1"}, {"cg","2"}, {"scn","S1"}, {"scc","S1"}, {"scs","S1"}, --[[ {"crwm","4"}, ]] {"crch","2"} },
  },
  ["ABE"] = {
    name = "ATL-B Express", short = "ATL-B/E",
    ring = false,
    interval = 240,
    [1] = {{"crch","1"}, {"scc","N2"}, {"cg","1"}, {"thecube", "4"}, {"phwest", "2"}},
    [2] = {{"phwest", "2"}, {"thecube", "1"}, {"cg", "2"}, {"scc", "S2"}, {"crch", "1"}},
  },
  ["AZ"] = {
    name = "ATL-Z", short = "ATL-Z",
    ring = false,
    interval = 480,
    [1] = { {"Bts","3"}, {"evo","N/A"}, {"scs","N1"}, {"scc","N1"}, {"scn","N1"}, {"oc","K1"}, --[[{"elgp","N"}, {"elsf","E"}, {"elchateau","S"}]] },
    [2] = { --[[{"elchateau","S"}, {"elsf","W"}, {"elgp","S"},]] {"oc","K1"}, {"scn","S1"}, {"scc", "S1"}, {"scs","S1"}, {"evo","N/A"}, {"Bts","3"}, },
  },
  ["CRT1"] = {
    name = "1",
    ring = false,
    [1] = { {"crsmacker", "R3"}, --[[{"crch", "U1"},]] {"crchs", "W"}, {"cras", "N"} },
    [2] = { {"cras", "N"}, {"crchs", "E"}, --[[{"crch", "U2"},]] {"crsmacker", "R3"} },
  },
  ["CRT4"] = {
    name = "4",
    ring = false,
    rtt = 120,
    interval = 60,
    [1] = { {"crch", "11", 0}, {"crsfterm", "1N", 0}, },
    [2] = { {"crsfterm", "1N", 0}, {"crch", "11", 0}, },
  },
  ["CRT12"] = {
    name = "12",
    ring = false,
    [1] = {{"crch", "M"}, {"crwm", "3"}},
    [2] = {{"crwm", "3"}, {"crch", "M"}},
  },
  ["CRT21"] = {
    name = "21",
    ring = false,
    rtt = 120,
    interval = 120,
    [1] = { {"crch", "12", 15}, {"crshelter1", "T", 75}, },
    [2] = { {"crshelter1", "T", 75}, {"crch", "12", 15}, },
  },
  ["NRG1"] = {
    name = "1",
    ring = "counterclockwise",
    rtt = 120,
    interval = 120,
    [1] = { {"NRG-townhall","E",0}, {"NRG-south","N",40}, {"NRG-museum","S",75} },
  },
  ["NRG2"] = {
    name = "2",
    ring = false,
    rtt = 240,
    interval = 240,
    [1] = { {"NRG-harbor", "S", 35}, {"NRG-townhall2", "W", 70}, {"NRG-museum", "S", 135}, },
    [2] = { {"NRG-museum", "S", 135}, {"NRG-townhall", "E", 195}, {"NRG-bplatz", "S", 235}, {"NRG-harbor", "S", 275}, },
  },
  ["NRG3"] = {
    name = "3",
    ring = false,
    [1] = { {"NRG-yard", "W"}, {"NRG-krstr", "N"}, {"NRG-CW", "N"}, {"NRG-pek", "E"}, {"krasnograd", "W"}, },
    [2] = { {"krasnograd", "W"}, {"NRG-pek", "W"}, {"NRG-CW", "S"}, {"NRG-krstr", "S"}, {"NRG-yard", "W"}, },
  },
}

for k, v in pairs{["B"] = {6, 5, 3, 20},} do
  local t = {{"crc_1_"..v[1], "E", v[4]}}
  for i = 5, 8 do
    t[#t+1] = {string.format("crs_%d_%d", i, v[2]),"S", v[4] + 45 + (i-5)*30}
  end
  for i = 8, 5, -1 do
    t[#t+1] = {string.format("crs_%d_%d", i, v[3]),"N", v[4] + 175 + (8-i)*30}
  end
  F.lines["CRT1"..k] = {
    name = "1"..k,
    ring = "clockwise",
    rtt = 320,
    interval = 80,
    [1] = t
  }
end

for k, v in pairs{["A"] = {5}, ["B"]={6}, ["C"]={7}, ["D"]={8}} do
  local t = {}
  local u = {}
  local phase = ((v[1]-5)%2)*15
  t[1] = {"crsfterm", (v[1]-4).."S", phase}
  for i = 1, 5, 2 do
    t[#t+1] = {string.format("crs_%d_%d",v[1],i), "E", 0}
  end
  for i = 5, 1, -2 do
    u[#u+1] = {string.format("crs_%d_%d",v[1],i), "W", 0}
  end
  u[#u+1] = {"crsfterm", (v[1]-4).."S", phase}
  F.lines["CRT3"..k] = {
    name = "3"..k, ring = false, rtt = 30, interval = 30,
    [1] = t, [2] = u,
  }
end

F.stndet = {}
for i, l in pairs(F.lines) do
  for j = 1, #l, 1 do
    local dir = l[j]
    for k = 1, #dir, 1 do
      local s = dir[k][1]
      local t = dir[k][2]
      local o = dir[k][3]
      if not F.stndet[s] then F.stndet[s] = {} end
      if not F.stndet[s][t] then F.stndet[s][t] = {} end
      local det = F.stndet[s][t]
      if k~=#dir or l.ring then
        local nxt = dir[k==#dir and 1 or k+1]
        det[#det+1] = {i, nxt[1], nxt[2], l.ring or dir[#dir][1], nil, nil, o}
      else
        local addent = true
        for m = 1, #l, 1 do addent = addent and (l[m][1]==dir[k]) end
        if addent then det[#det+1] = {i, nil, nil, s, nil, nil, o} end
      end
    end
  end
end

F.errlist = {
  ["incorrect_setup"]   = "Incorrect LuaATC track setup!",
  ["runaway_train"]     = "Runaway train found!",
  ["train_disappeared"] = "Train has disappeared!",
  ["unexpected_train"]  = "Train is not expected to pass!",
  ["wrong_direction"]   = "Train passed in wrong direction!",
}

F.depint = {
  ["AB"]   =  90,
  ["AG"]   =  120,
  ["CRT1"] =  30,
}


F.dpts = {
  ['CRT1'] = POS(1835,5,1000),
  ["CRT2"] = POS(1741,-1,1020),
  ['AG'] = POS(1399,4,2020),
}

function F.odd(ln)
  if event.type=='ext_int' then
    if atc_id then
      set_rc(event.message.rc)
      atc_send('SM')
    else
      if type(F.odj[ln])~='table' then F.odj[ln] = {} end
      for i = 1,(#F.odj[ln]) do
        if F.odj[ln][i]==event then return end
      end
      table.insert(F.odj[ln],1,event)
    end
  end
  if event.type=='train' then
    if F.odj[ln] and #F.odj[ln]~=0 then
      ev = table.remove(F.odj[ln])
      if ev then
        set_rc(ev.message.rc)
        atc_send('BBWRSM')
        return
      else
        F.odj[ln] = {}
      end
    end
    atc_send('BBWR')
  end
end

function F.ods(rc, side, rev)
  if event.type=='train' then
    if atc_arrow then
      if string.find((get_rc() or ''),(rc or '')) then
        atc_send('BBWO' .. (side or 'R') .. 'D10OCD1' .. ((rev or string.find((get_rc() or ''), 'R' .. rc)) and 'R' or '')..'SM')
      elseif rev then
        atc_send('BBWRSM')
      end
    end
  end
end

function F.odc(ln, rc)
  interrupt_pos(F.dpts[ln],{['rc']=rc})
end

function F.cpoint(name, int)
  __approach_callback_mode = 1
  local last = S.cpoints[name] or 0
  local next = last+int
  local now = os.time()
  if event.type == "approach" then
    if now < next then atc_set_lzb_tsr(2) end
  elseif event.type == "train" and atc_arrow then
    if now < next then
      atc_send(string.format("A0B0WD%dA1SM", next-now))
      schedule_in(next-now)
    else
      S.cpoints[name] = now
      atc_send("A1SM")
    end
  elseif event.type == "schedule" and atc_arrow then
    S.cpoints[name] = now
    atc_send("A1SM")
  end
end