<commit_before>require "defines"
require "util"

MOD_NAME = "TheFatController"

local function init_global()
  global = global or {}
  global.guiSettings = global.guiSettings or {}
  global.trainsByForce = global.trainsByForce or {}
  global.character = global.charactor or {}
  global.unlocked = global.unlocked or false
  global.unlockedByForce = global.unlockedByForce or {}
  global.version = "0.3.1"
end

defaultGuiSettings = {  alarm={active=false,noPath=true,timeAtSignal=true,timeToStation=true},
  displayCount=9,
  fatControllerButtons = {},
  fatControllerGui = {},
  page = 2,
  pageCount = 5,
  stationFilterList = {}
}

local function init_player(player)
  global.guiSettings[player.index] = global.guiSettings[player.index] or util.table.deepcopy(defaultGuiSettings)
  if global.unlockedByForce[player.force.name] then
    init_gui(player)
  end
end

local function init_players()
  for i,player in pairs(game.players) do
    init_player(player)
  end
end

local function init_force(force)
  global.trainsByForce[force.name] = global.trainsByForce[force.name] or {}
  if force.technologies["rail-signals"].researched then
    global.unlockedByForce[force.name] = true
    global.unlocked = true
    register_events()
    for i,p in pairs(force.players) do
      init_player(p)
    end
  end
end

local function init_forces()
  for i, force in pairs(game.forces) do
    init_force(force)
  end
end

local function on_init()
  init_global()
  init_forces()
end

local function on_load()
  if global.unlocked then
    register_events()
  end
end

-- run once
local function on_configuration_changed(data)
  if not data or not data.mod_changes then
    return
  end
  if data.mod_changes[MOD_NAME] then
    local newVersion = data.mod_changes[MOD_NAME].new_version
    local oldVersion = data.mod_changes[MOD_NAME].old_version
    if oldVersion then
      if oldVersion < "0.3.14" then
        -- Kill all old versions of TFC
        if global.fatControllerGui ~= nil or global.fatControllerButtons ~= nil then
          destroyGui(global.fatControllerGui)
          destroyGui(global.fatControllerButtons)
        end
        for i,p in pairs(game.players) do
          destroyGui(p.gui.top.fatControllerButtons)
          destroyGui(p.gui.left.fatController)
        end
        global = nil
      end
    end
    init_global()
    init_forces()
    init_players()
    if not oldVersion or oldVersion < "0.3.14" then
      findTrains()
    end
    global.version = newVersion
  end
  --check for other mods
end

local function on_player_created(event)
  init_player(game.players[event.player_index])
end

local function on_force_created(event)
  init_force(event.force)
end
local function on_forces_merging(event)

end

script.on_init(on_init)
script.on_load(on_load)
script.on_configuration_changed(on_configuration_changed)
script.on_event(defines.events.on_player_created, on_player_created)
script.on_event(defines.events.on_force_created, on_force_created)
script.on_event(defines.events.on_forces_merging, on_forces_merging)

function register_events()
  script.on_event(defines.events.on_train_changed_state, on_train_changed_state)
  script.on_event(defines.events.on_tick, onTickAfterUnlocked)
  script.on_event(defines.events.on_gui_click, on_gui_click)
end

--Called if tech is unlocked
function init_gui(player)
  debugLog("Init: " .. player.name .. " - " .. player.force.name)
  if player.gui.top.fatControllerButtons ~= nil then
    return
  end

  local guiSettings = global.guiSettings[player.index]
  local forceName = player.force.name
  guiSettings.stationFilterList = buildStationFilterList(global.trainsByForce[player.force.name])

  debugLog("create guis")
  if player.gui.left.fatController == nil then
    guiSettings.fatControllerGui = player.gui.left.add({ type="flow", name="fatController", direction="vertical"})--, style="fatcontroller_thin_flow"}) --caption="Fat Controller",
  else
    guiSettings.fatControllerGui = player.gui.left.fatController
  end
  if player.gui.top.fatControllerButtons == nil then
    guiSettings.fatControllerButtons = player.gui.top.add({ type="flow", name="fatControllerButtons", direction="horizontal", style="fatcontroller_thin_flow"})
  else
    guiSettings.fatControllerButtons = player.gui.top.fatControllerButtons
  end
  if guiSettings.fatControllerButtons.toggleTrainInfo == nil then
    guiSettings.fatControllerButtons.add({type="button", name="toggleTrainInfo", caption = {"text-trains-collapsed"}, style="fatcontroller_button_style"})
  end

  if guiSettings.fatControllerGui.trainInfo ~= nil then
    newTrainInfoWindow(guiSettings)
    updateTrains(global.trainsByForce[forceName])
  end

  guiSettings.pageCount = getPageCount(global.trainsByForce[forceName], guiSettings)

  filterTrainInfoList(global.trainsByForce[forceName], guiSettings.activeFilterList)

  updateTrains(global.trainsByForce[forceName])
  refreshTrainInfoGui(global.trainsByForce[forceName], guiSettings, player.character)

  return guiSettings
end

script.on_event(defines.events.on_research_finished, function(event)
  local status, err = pcall(on_research_finished, event)
  if err then debugDump(err,true) end
end)

function on_research_finished(event)
  if event.research.name == "rail-signals" then
    global.unlockedByForce[event.research.force.name] = true
    global.unlocked = true
    register_events() ;
    for _, p in pairs(event.research.force.players) do
      init_gui(p)
    end
  end
end

onTickAfterUnlocked = function(event)
  local status, err = pcall(function()
    if event.tick%60==13 then
      local updateGui = false
      for i,guiSettings in pairs(global.guiSettings) do
        if guiSettings.fatControllerGui ~= nil and guiSettings.fatControllerGui.trainInfo ~= nil then
          updateGui = true
        end
      end
      if updateGui then
        debugLog("updateGUI")
        for _,trains in pairs(global.trainsByForce) do
          updateTrains(trains)
        end
        refreshAllTrainInfoGuis(global.trainsByForce, global.guiSettings, game.players, false)
        for i,player in pairs(game.players) do
          refreshTrainInfoGui(global.trainsByForce[player.force.name], global.guiSettings[i], player.character)
          global.guiSettings[i].pageCount = getPageCount(global.trainsByForce[player.force.name], global.guiSettings[i])
        end
      end
    end

    --handle remote camera
    for i,guiSettings in pairs(global.guiSettings) do
      if guiSettings.followEntity ~= nil then -- Are we in remote camera mode?
        if not guiSettings.followEntity.valid  or game.players[i].vehicle == nil then
          swapPlayer(game.players[i], global.character[i])
          if guiSettings.fatControllerButtons ~= nil and guiSettings.fatControllerButtons.returnToPlayer ~= nil then
            guiSettings.fatControllerButtons.returnToPlayer.destroy()
          end
          if not guiSettings.followEntity.valid then
            removeTrainInfoFromEntity(global.trainsByForce[game.players[i].force.name], guiSettings.followEntity)
            newTrainInfoWindow(guiSettings)
            refreshTrainInfoGui(global.trainsByForce[game.players[i].force.name], guiSettings, game.players[i].character)
          end
          global.character[i] = nil
          guiSettings.followEntity = nil
      elseif global.character[i] and global.character[i].valid and guiSettings.fatControllerButtons ~= nil and guiSettings.fatControllerButtons.returnToPlayer == nil then
        guiSettings.fatControllerButtons.add({ type="button", name="returnToPlayer", caption={"text-player"}, style = "fatcontroller_selected_button"})
        --game.players[1].teleport(global.followEntity.position)
      elseif global.character[i] ~= nil and not global.character[i].valid  then
        game.set_game_state({gamefinished=true, playerwon=false})
      end
      end
    end

    -- on_the_path = 0,
    -- -- had path and lost it - must stop
    -- path_lost = 1,
    -- -- doesn't have anywhere to go
    -- no_schedule = 2,
    -- has no path and is stopped
    -- no_path = 3,
    -- -- braking before the railSignal
    -- arrive_signal = 4,
    -- wait_signal = 5,
    -- -- braking before the station
    -- arrive_station = 6,
    -- wait_station = 7,
    -- -- switched to the manual control and has to stop
    -- manual_control_stop = 8,
    -- -- can move if user explicitly sits in and rides the train
    -- manual_control = 9,
    -- -- train was switched to auto control but it is moving and needs to be stopped
    -- stop_for_auto_control = 10

    if event.tick%120 == 37 then
      local alarmState = {}
      alarmState.timeToStation = false
      alarmState.timeAtSignal = false
      alarmState.noPath = false
      local newAlarm = false
      for forceName,trains in pairs(global.trainsByForce) do
        for i,trainInfo in pairs(trains) do
          local alarmSet = false
          if trainInfo.lastState == 1 or trainInfo.lastState == 3 then
            --game.players[1].print("No Path " .. i .. " " .. game.tick)
            if not trainInfo.alarm then
              alarmState.noPath = true
              newAlarm = true
              trainInfo.updated = true
            end
            alarmSet = true
            trainInfo.alarm = true
          end
          -- 36000, 10 minutes
          if trainInfo.lastState ~= 7 and trainInfo.lastStateStation ~= nil and (trainInfo.lastStateStation + 36000 < game.tick and (trainInfo.lastState ~= 2 or trainInfo.lastState ~= 8 or trainInfo.lastState ~= 9)) then
            if not trainInfo.alarm then
              alarmState.timeToStation = true
              newAlarm = true
              trainInfo.updated = true
            end
            alarmSet = true
            trainInfo.alarm = true
          end
          -- 72002 minutes lol, wtf?
          if trainInfo.lastState == 5 and (trainInfo.lastStateTick ~= nil and trainInfo.lastStateTick + 7200 < game.tick ) then
            if not trainInfo.alarm then
              alarmState.timeAtSignal = true
              newAlarm = true
              trainInfo.updated = true
            end
            alarmSet = true
            trainInfo.alarm = true
          end
          if not alarmSet then
            if trainInfo.alarm then
              trainInfo.updated = true
            end
            trainInfo.alarm = false
          end
        end
      end

      for i,guiSettings in pairs(global.guiSettings) do
        if guiSettings.alarm == nil or guiSettings.alarm.noPath == nil then
          guiSettings.alarm = {}
          guiSettings.alarm.timeToStation = true
          guiSettings.alarm.timeAtSignal = true
          guiSettings.alarm.noPath = true
        end
        if (guiSettings.alarm.timeToStation or guiSettings.alarm.timeAtSignal or guiSettings.alarm.noPath) and newAlarm then
          if guiSettings.alarm.timeToStation and alarmState.timeToStation then
            guiSettings.alarm.active = true
            alertPlayer(game.players[i], guiSettings, game.tick, ({"msg-alarm-tolongtostation"}))
          end
          if guiSettings.alarm.timeAtSignal and alarmState.timeAtSignal then
            guiSettings.alarm.active = true
            alertPlayer(game.players[i], guiSettings, game.tick, ({"msg-alarm-tolongatsignal"}))
          end
          if guiSettings.alarm.noPath and alarmState.noPath then
            guiSettings.alarm.active = true
            alertPlayer(game.players[i], guiSettings, game.tick, ({"msg-alarm-nopath"}))
          end
        else
          guiSettings.alarm.active = false
        end
      end
    end
  end)
  if err then debugDump(err,true) end
  --if event.tick%30==4 and global.fatControllerGui.trainInfo ~= nil then
  --refreshTrainInfoGui(global.trains, global.fatControllerGui.trainInfo, global.guiSettings, game.players[1].character)
  --end
end

function alertPlayer(player,guiSettings,tick,message)
  if player ~= nil and guiSettings ~= nil and guiSettings.alarm ~= nil and guiSettings.alarm.active and (guiSettings.alarm.lastMessage == nil or guiSettings.alarm.lastMessage + 120 < tick) then
    guiSettings.lastMessage = tick
    player.print(message)
  end
end

function destroyGui(guiA)
  if guiA ~= nil and guiA.valid then
    guiA.destroy()
  end
end

function buildStationFilterList(trains)
  local newList = {}
  if trains ~= nil then
    for i, trainInfo in pairs(trains) do
      -- if trainInfo.group ~= nil then
      -- newList[trainInfo.group] = true
      -- end
      if trainInfo.stations ~= nil then
        for station, value in pairs(trainInfo.stations) do
          --debugLog(station)
          newList[station] = true
        end
      end
    end
  end
  return newList
end

function getLocomotives(train)
  if train ~= nil and train.valid then
    local locos = {}
    for i,carriage in pairs(train.carriages) do
      if carriage ~= nil and carriage.valid and isTrainType(carriage.type) then
        table.insert(locos, carriage)
      end
    end
    return locos
  end
end

function getNewTrainInfo(train)
  if train ~= nil then
    local carriages = train.carriages
    if carriages ~= nil and carriages[1] ~= nil and carriages[1].valid then
      local newTrainInfo = {}
      newTrainInfo.train = train
      --newTrainInfo.firstCarriage = getFirstCarriage(train)
      newTrainInfo.locomotives = getLocomotives(train)

      --newTrainInfo.display = true
      return newTrainInfo
    end
  end
end

entityBuilt = function(event)
  local entity = event.created_entity
  if entity.type == "locomotive" and global.unlocked then --or entity.type == "cargo-wagon"
    getTrainInfoOrNewFromEntity(global.trainsByForce[entity.force.name], entity)
  end
end

script.on_event(defines.events.on_built_entity, entityBuilt)
script.on_event(defines.events.on_robot_built_entity, entityBuilt)

-- -- normal state - following the path
-- on_the_path = 0,
-- -- had path and lost it - must stop
-- path_lost = 1,
-- -- doesn't have anywhere to go
-- no_schedule = 2,
-- -- has no path and is stopped
-- no_path = 3,
-- -- braking before the railSignal
-- arrive_signal = 4,
-- wait_signal = 5,
-- -- braking before the station
-- arrive_station = 6,
-- wait_station = 7,
-- -- switched to the manual control and has to stop
-- manual_control_stop = 8,
-- -- can move if user explicitly sits in and rides the train
-- manual_control = 9,
-- -- train was switched to auto control but it is moving and needs to be stopped
-- stop_for_auto_control = 10

function on_train_changed_state(event)
  local status, err = pcall(function()
    --debugLog("State Change - " .. game.tick)

    local train = event.train
    local entity = train.carriages[1]

    local trains = global.trainsByForce[entity.force.name]
    local trainInfo = getTrainInfoOrNewFromEntity(trains, entity)
    if trainInfo ~= nil then
      local newtrain = false
      if trainInfo.updated == nil then
        newtrain = true
      else
      end
      updateTrainInfo(trainInfo,game.tick)
      if newtrain then
        for i,player in pairs(game.players) do
          global.guiSettings[i].pageCount = getPageCount(trains, global.guiSettings[i])
        end
      end
      refreshAllTrainInfoGuis(global.trainsByForce, global.guiSettings, game.players, newtrain)
    end
  end)
  if err then debugDump(err,true) end
end

function getTrainInfoOrNewFromTrain(trains, train)
  if trains ~= nil then
    for i, trainInfo in pairs(trains) do
      if trainInfo ~= nil and trainInfo.train and trainInfo.train.valid and train == trainInfo.train then
        return trainInfo
      end
    end
    local newTrainInfo = getNewTrainInfo(train)
    table.insert(trains, newTrainInfo)
    return newTrainInfo
  end
end

function getTrainInfoOrNewFromEntity(trains, entity)
  local trainInfo = getTrainInfoFromEntity(trains, entity)
  if trainInfo == nil then
    local newTrainInfo = getNewTrainInfo(entity.train)
    table.insert(trains, newTrainInfo)
    return newTrainInfo
  else
    return trainInfo
  end
end

function on_gui_click(event)
  local status, err = pcall(function()
    local refreshGui = false
    local newInfoWindow = false
    local rematchStationList = false
    local guiSettings = global.guiSettings[event.element.player_index]
    local player = game.players[event.element.player_index]
    local trains = global.trainsByForce[player.force.name]
    debugLog("CLICK! " .. event.element.name .. game.tick)

    if guiSettings.alarm == nil then
      guiSettings.alarm = {}
    end


    if event.element.name == "toggleTrainInfo" then
      if guiSettings.fatControllerGui.trainInfo == nil then
        newInfoWindow = true
        refreshGui = true
        event.element.caption = {"text-trains"}
      else
        guiSettings.fatControllerGui.trainInfo.destroy()
        event.element.caption = {"text-trains-collapsed"}
      end
    elseif event.element.name == "returnToPlayer" then
      if global.character[event.element.player_index] ~= nil then
        if player.vehicle ~= nil then
          player.vehicle.passenger = nil
        end
        swapPlayer(player, global.character[event.element.player_index])
        global.character[event.element.player_index] = nil
        event.element.destroy()
        guiSettings.followEntity = nil
      end
    elseif endsWith(event.element.name,"_toggleManualMode") then
      local trainInfo = getTrainInfoFromElementName(trains, event.element.name)
      if trainInfo ~= nil and trainInfo.train ~= nil and trainInfo.train.valid then
        trainInfo.train.manual_mode = not trainInfo.train.manual_mode
        swapCaption(event.element, "ll", ">")
      end
    elseif endsWith(event.element.name,"_toggleFollowMode") then
      local trainInfo = getTrainInfoFromElementName(trains, event.element.name)
      if trainInfo ~= nil and trainInfo.train ~= nil and trainInfo.train.valid then
        if global.character[event.element.player_index] == nil then --Move to train
          if trainInfo.train.carriages[1].passenger ~= nil then
            player.print({"msg-intrain"})
        else
          global.character[event.element.player_index] = player.character
          guiSettings.followEntity = trainInfo.train.carriages[1] -- HERE

          --fatControllerEntity =
          swapPlayer(player,newFatControllerEntity(player))
          --event.element.style = "fatcontroller_selected_button"
          event.element.caption = "X"
          trainInfo.train.carriages[1].passenger = player.character
        end
        elseif guiSettings.followEntity ~= nil and trainInfo.train ~= nil and trainInfo.train.valid then
          if player.vehicle ~= nil then
            player.vehicle.passenger = nil
          end
          if guiSettings.followEntity == trainInfo.train.carriages[1] or trainInfo.train.carriages[1].passenger ~= nil then --Go back to player
            swapPlayer(player, global.character[event.element.player_index])
            --event.element.style = "fatcontroller_button_style"
            event.element.caption = "c"
            if guiSettings.fatControllerButtons ~= nil and guiSettings.fatControllerButtons.returnToPlayer ~= nil then
              guiSettings.fatControllerButtons.returnToPlayer.destroy()
            end
            global.character[event.element.player_index] = nil
            guiSettings.followEntity = nil
          else -- Go to different train

            guiSettings.followEntity = trainInfo.train.carriages[1] -- AND HERE
            --event.element.style = "fatcontroller_selected_button"
            event.element.caption = "X"

            trainInfo.train.carriages[1].passenger = player.character
            --game.players[1].vehicle = trainInfo.train.carriages[1]
          end
        end

      end
    elseif event.element.name == "page_back" then
      if guiSettings.page > 1 then
        guiSettings.page = guiSettings.page - 1
        --global.fatControllerGui.trainInfo.trainInfoControls.page_number.caption = global.guiSettings.page
        --newTrainInfoWindow(global.fatControllerGui, global.guiSettings)
        --refreshTrainInfoGui(global.trains, global.fatControllerGui.trainInfo, global.guiSettings)
        newInfoWindow = true
        refreshGui = true
      end
    elseif event.element.name == "page_forward" then
      if guiSettings.page < getPageCount(trains, guiSettings) then
        guiSettings.page = guiSettings.page + 1
        --debugLog(global.guiSettings.page)
        --newTrainInfoWindow(global.fatControllerGui, global.guiSettings)
        --refreshTrainInfoGui(global.trains, global.fatControllerGui.trainInfo, global.guiSettings)
        newInfoWindow = true
        refreshGui = true
      end
    elseif event.element.name == "page_number" then
      togglePageSelectWindow(player.gui.center, guiSettings)
    elseif event.element.name == "pageSelectOK" then
      local gui = player.gui.center.pageSelect
      if gui ~= nil then

        local newInt = tonumber(gui.pageSelectValue.text)

        if newInt then
          if newInt < 1 then
            newInt = 1
          elseif newInt > 50 then
            newInt = 50
          end
          guiSettings.displayCount = newInt
          guiSettings.pageCount = getPageCount(trains, guiSettings)
          guiSettings.page = 1
          refreshGui = true
          newInfoWindow = true
        else
          player.print({"msg-notanumber"})
        end
        gui.destroy()
      end
    elseif event.element.name == "toggleStationFilter" then
      guiSettings.stationFilterList = buildStationFilterList(trains)
      toggleStationFilterWindow(player.gui.center, guiSettings)
    elseif event.element.name == "clearStationFilter" or event.element.name == "stationFilterClear" then
      if guiSettings.activeFilterList ~= nil then
        guiSettings.activeFilterList = nil

        rematchStationList = true
        newInfoWindow = true
        refreshGui = true
      end
      if player.gui.center.stationFilterWindow ~= nil then
        player.gui.center.stationFilterWindow.destroy()
      end
    elseif event.element.name == "stationFilterOK" then
      local gui = player.gui.center.stationFilterWindow
      if gui ~= nil and gui.checkboxGroup ~= nil then
        local newFilter = {}
        local listEmpty = true
        for station,value in pairs(guiSettings.stationFilterList) do
          local checkboxA = gui.checkboxGroup[station .. "_stationFilter"]
          if checkboxA ~= nil and checkboxA.state then
            listEmpty = false
            --debugLog(station)
            newFilter[station] = true
          end
        end
        if not listEmpty then
          guiSettings.activeFilterList = newFilter

          --glo.filteredTrains = buildFilteredTrainInfoList(global.trains, global.guiSettings.activeFilterList)
        else
          guiSettings.activeFilterList = nil
        end

        gui.destroy()
        guiSettings.page = 1
        rematchStationList = true
        newInfoWindow = true
        refreshGui = true
      end
    elseif endsWith(event.element.name,"_stationFilter") then
      local stationName = string.gsub(event.element.name, "_stationFilter", "")
      if event.element.state then
        if guiSettings.activeFilterList == nil then
          guiSettings.activeFilterList = {}
        end

        guiSettings.activeFilterList[stationName] = true
      elseif guiSettings.activeFilterList ~= nil then
        guiSettings.activeFilterList[stationName] = nil
        if tableIsEmpty(guiSettings.activeFilterList) then
          guiSettings.activeFilterList = nil
        end
      end
      --debugLog(event.element.name)
      guiSettings.page = 1
      rematchStationList = true
      newInfoWindow = true
      refreshGui = true
      --alarmOK alarmTimeToStation alarmTimeAtSignal alarmNoPath alarmButton
    elseif event.element.name == "alarmButton" or event.element.name == "alarmOK" then
      toggleAlarmWindow(player.gui.center, guiSettings)
    elseif event.element.name == "alarmTimeToStation" then
      guiSettings.alarm.timeToStation = event.element.state
    elseif event.element.name == "alarmTimeAtSignal" then
      guiSettings.alarm.timeAtSignal = event.element.state
    elseif event.element.name == "alarmNoPath" then
      guiSettings.alarm.noPath = event.element.state
    end

    if rematchStationList then
      filterTrainInfoList(trains, guiSettings.activeFilterList)
      guiSettings.pageCount = getPageCount(trains, guiSettings)
    end

    if newInfoWindow then
      newTrainInfoWindow(guiSettings)
    end

    if refreshGui then
      refreshTrainInfoGui(trains, guiSettings, player.character)
    end
  end)
  if err then debugDump(err,true) end
end

-- function swapStyle(guiElement, styleA, styleB)
-- if guiElement ~= nil and styleA ~= nil and styleB ~= nil then
-- if guiElement.style == styleA then
-- guiElement.style = styleB
-- elseif guiElement.style == styleB then
-- guiElement.style = styleA
-- end
-- end
-- end

function swapCaption(guiElement, captionA, captionB)
  if guiElement ~= nil and captionA ~= nil and captionB ~= nil then
    if guiElement.caption == captionA then
      guiElement.caption = captionB
    elseif guiElement.caption == captionB then
      guiElement.caption = captionA
    end
  end
end

function tableIsEmpty(tableA)
  if tableA ~= nil then
    for i,v in pairs(tableA) do
      return false
    end
  end
  return true
end

function toggleStationFilterWindow(gui, guiSettings)
  if gui ~= nil then
    if gui.stationFilterWindow == nil then
      --local sortedList = table.sort(a)
      local window = gui.add({type="frame", name="stationFilterWindow", caption={"msg-stationFilter"}, direction="vertical" }) --style="fatcontroller_thin_frame"})
      window.add({type="table", name="checkboxGroup", colspan=3})
      for name, value in pairsByKeys(guiSettings.stationFilterList) do
        if guiSettings.activeFilterList ~= nil and guiSettings.activeFilterList[name] then
          window.checkboxGroup.add({type="checkbox", name=name .. "_stationFilter", caption=name, state=true}) --style="filter_group_button_style"})
        else
          window.checkboxGroup.add({type="checkbox", name=name .. "_stationFilter", caption=name, state=false}) --style="filter_group_button_style"})
        end
      end
      window.add({type="flow", name="buttonFlow"})
      window.buttonFlow.add({type="button", name="stationFilterClear", caption={"msg-Clear"}})
      window.buttonFlow.add({type="button", name="stationFilterOK", caption={"msg-OK"}})
    else
      gui.stationFilterWindow.destroy()
    end
  end
end

function togglePageSelectWindow(gui, guiSettings)
  if gui ~= nil then
    if gui.pageSelect == nil then
      local window = gui.add({type="frame", name="pageSelect", caption={"msg-displayCount"}, direction="vertical" }) --style="fatcontroller_thin_frame"})
      window.add({type="textfield", name="pageSelectValue", text=guiSettings.displayCount .. ""})
      window.pageSelectValue.text = guiSettings.displayCount .. ""
      window.add({type="button", name="pageSelectOK", caption={"msg-OK"}})
    else
      gui.pageSelect.destroy()
    end
  end
end

function toggleAlarmWindow(gui, guiSettings)
  if gui ~= nil then
    if gui.alarmWindow == nil then
      local window = gui.add({type="frame",name="alarmWindow", caption={"text-alarmwindow"}, direction="vertical" })
      local stateTimeToStation = true
      if guiSettings.alarm ~= nil and not guiSettings.alarm.timeToStation then
        stateTimeToStation = false
      end
      window.add({type="checkbox", name="alarmTimeToStation", caption={"text-alarmtimetostation"}, state=stateTimeToStation}) --style="filter_group_button_style"})
      local stateTimeAtSignal = true
      if guiSettings.alarm ~= nil and not guiSettings.alarm.timeAtSignal then
        stateTimeAtSignal = false
      end
      window.add({type="checkbox", name="alarmTimeAtSignal", caption={"text-alarmtimeatsignal"}, state=stateTimeAtSignal}) --style="filter_group_button_style"})
      local stateNoPath = true
      if guiSettings.alarm ~= nil and not guiSettings.alarm.noPath then
        stateNoPath = false
      end
      window.add({type="checkbox", name="alarmNoPath", caption={"text-alarmtimenopath"}, state=stateNoPath}) --style="filter_group_button_style"})
      window.add({type="button", name="alarmOK", caption={"msg-OK"}})
    else
      gui.alarmWindow.destroy()
    end
  end
end

function getPageCount(trains, guiSettings)
  local trainCount = 0
  for i,trainInfo in pairs(trains) do
    if guiSettings.activeFilterList == nil or trainInfo.matchesStationFilter then
      trainCount = trainCount + 1
    end
  end
  return math.floor((trainCount - 1) / guiSettings.displayCount) + 1
end

local onEntityDied = function (event)
  if global.unlocked and global.guiSettings ~= nil then
    for forceName,trains in pairs(global.trainsByForce) do
      updateTrains(trains)
    end

    for i, player in pairs(game.players) do
      local guiSettings = global.guiSettings[i]
      if guiSettings.followEntity ~= nil and guiSettings.followEntity == event.entity then --Go back to player
        if game.players[i].vehicle ~= nil then
          game.players[i].vehicle.passenger = nil
      end
      swapPlayer(game.players[i], global.character[i])
      if guiSettings.fatControllerButtons.returnToPlayer ~= nil then
        guiSettings.fatControllerButtons.returnToPlayer.destroy()
      end
      global.character[i] = nil
      guiSettings.followEntity = nil
      end
    end
    refreshAllTrainInfoGuis(global.trainsByForce, global.guiSettings, game.players, true)
  end
end
<commit_msg>swap players back when updating from versions < 0.3.14 filter on_entity_died to only run for locomotives/cargo-wagons<commit_after>require "defines"
require "util"

MOD_NAME = "TheFatController"

local function init_global()
  global = global or {}
  global.guiSettings = global.guiSettings or {}
  global.trainsByForce = global.trainsByForce or {}
  global.character = global.charactor or {}
  global.unlocked = global.unlocked or false
  global.unlockedByForce = global.unlockedByForce or {}
  global.version = "0.3.1"
end

defaultGuiSettings = {  alarm={active=false,noPath=true,timeAtSignal=true,timeToStation=true},
  displayCount=9,
  fatControllerButtons = {},
  fatControllerGui = {},
  page = 2,
  pageCount = 5,
  stationFilterList = {}
}

local function init_player(player)
  global.guiSettings[player.index] = global.guiSettings[player.index] or util.table.deepcopy(defaultGuiSettings)
  if global.unlockedByForce[player.force.name] then
    init_gui(player)
  end
end

local function init_players()
  for i,player in pairs(game.players) do
    init_player(player)
  end
end

local function init_force(force)
  global.trainsByForce[force.name] = global.trainsByForce[force.name] or {}
  if force.technologies["rail-signals"].researched then
    global.unlockedByForce[force.name] = true
    global.unlocked = true
    register_events()
    for i,p in pairs(force.players) do
      init_player(p)
    end
  end
end

local function init_forces()
  for i, force in pairs(game.forces) do
    init_force(force)
  end
end

local function on_init()
  init_global()
  init_forces()
end

local function on_load()
  if global.unlocked then
    register_events()
  end
end

-- run once
local function on_configuration_changed(data)
  if not data or not data.mod_changes then
    return
  end
  if data.mod_changes[MOD_NAME] then
    local newVersion = data.mod_changes[MOD_NAME].new_version
    local oldVersion = data.mod_changes[MOD_NAME].old_version
    if oldVersion then
      if oldVersion < "0.3.14" then
        -- Kill all old versions of TFC
        if global.fatControllerGui ~= nil or global.fatControllerButtons ~= nil then
          destroyGui(global.fatControllerGui)
          destroyGui(global.fatControllerButtons)
        end
        for i,p in pairs(game.players) do
          destroyGui(p.gui.top.fatControllerButtons)
          destroyGui(p.gui.left.fatController)
        end
        if type(global.character) == "table" then
          for i, c in pairs(global.character) do
            swapPlayer(game.players[i], c)
          end
        end
        global = nil
      end
    end
    init_global()
    init_forces()
    init_players()
    if not oldVersion or oldVersion < "0.3.14" then
      findTrains()
    end
    global.version = newVersion
  end
  --check for other mods
end

local function on_player_created(event)
  init_player(game.players[event.player_index])
end

local function on_force_created(event)
  init_force(event.force)
end
local function on_forces_merging(event)

end

script.on_init(on_init)
script.on_load(on_load)
script.on_configuration_changed(on_configuration_changed)
script.on_event(defines.events.on_player_created, on_player_created)
script.on_event(defines.events.on_force_created, on_force_created)
script.on_event(defines.events.on_forces_merging, on_forces_merging)

function register_events()
  script.on_event(defines.events.on_train_changed_state, on_train_changed_state)
  script.on_event(defines.events.on_tick, onTickAfterUnlocked)
  script.on_event(defines.events.on_gui_click, on_gui_click)
end

--Called if tech is unlocked
function init_gui(player)
  debugLog("Init: " .. player.name .. " - " .. player.force.name)
  if player.gui.top.fatControllerButtons ~= nil then
    return
  end

  local guiSettings = global.guiSettings[player.index]
  local forceName = player.force.name
  guiSettings.stationFilterList = buildStationFilterList(global.trainsByForce[player.force.name])

  debugLog("create guis")
  if player.gui.left.fatController == nil then
    guiSettings.fatControllerGui = player.gui.left.add({ type="flow", name="fatController", direction="vertical"})--, style="fatcontroller_thin_flow"}) --caption="Fat Controller",
  else
    guiSettings.fatControllerGui = player.gui.left.fatController
  end
  if player.gui.top.fatControllerButtons == nil then
    guiSettings.fatControllerButtons = player.gui.top.add({ type="flow", name="fatControllerButtons", direction="horizontal", style="fatcontroller_thin_flow"})
  else
    guiSettings.fatControllerButtons = player.gui.top.fatControllerButtons
  end
  if guiSettings.fatControllerButtons.toggleTrainInfo == nil then
    guiSettings.fatControllerButtons.add({type="button", name="toggleTrainInfo", caption = {"text-trains-collapsed"}, style="fatcontroller_button_style"})
  end

  if guiSettings.fatControllerGui.trainInfo ~= nil then
    newTrainInfoWindow(guiSettings)
    updateTrains(global.trainsByForce[forceName])
  end

  guiSettings.pageCount = getPageCount(global.trainsByForce[forceName], guiSettings)

  filterTrainInfoList(global.trainsByForce[forceName], guiSettings.activeFilterList)

  updateTrains(global.trainsByForce[forceName])
  refreshTrainInfoGui(global.trainsByForce[forceName], guiSettings, player.character)

  return guiSettings
end

script.on_event(defines.events.on_research_finished, function(event)
  local status, err = pcall(on_research_finished, event)
  if err then debugDump(err,true) end
end)

function on_research_finished(event)
  if event.research.name == "rail-signals" then
    global.unlockedByForce[event.research.force.name] = true
    global.unlocked = true
    register_events() ;
    for _, p in pairs(event.research.force.players) do
      init_gui(p)
    end
  end
end

onTickAfterUnlocked = function(event)
  local status, err = pcall(function()
    if event.tick%60==13 then
      local updateGui = false
      for i,guiSettings in pairs(global.guiSettings) do
        if guiSettings.fatControllerGui ~= nil and guiSettings.fatControllerGui.trainInfo ~= nil then
          updateGui = true
        end
      end
      if updateGui then
        debugLog("updateGUI")
        for _,trains in pairs(global.trainsByForce) do
          updateTrains(trains)
        end
        refreshAllTrainInfoGuis(global.trainsByForce, global.guiSettings, game.players, false)
        for i,player in pairs(game.players) do
          refreshTrainInfoGui(global.trainsByForce[player.force.name], global.guiSettings[i], player.character)
          global.guiSettings[i].pageCount = getPageCount(global.trainsByForce[player.force.name], global.guiSettings[i])
        end
      end
    end

    --handle remote camera
    for i,guiSettings in pairs(global.guiSettings) do
      if guiSettings.followEntity ~= nil then -- Are we in remote camera mode?
        if not guiSettings.followEntity.valid  or game.players[i].vehicle == nil then
          swapPlayer(game.players[i], global.character[i])
          if guiSettings.fatControllerButtons ~= nil and guiSettings.fatControllerButtons.returnToPlayer ~= nil then
            guiSettings.fatControllerButtons.returnToPlayer.destroy()
          end
          if not guiSettings.followEntity.valid then
            removeTrainInfoFromEntity(global.trainsByForce[game.players[i].force.name], guiSettings.followEntity)
            newTrainInfoWindow(guiSettings)
            refreshTrainInfoGui(global.trainsByForce[game.players[i].force.name], guiSettings, game.players[i].character)
          end
          global.character[i] = nil
          guiSettings.followEntity = nil
      elseif global.character[i] and global.character[i].valid and guiSettings.fatControllerButtons ~= nil and guiSettings.fatControllerButtons.returnToPlayer == nil then
        guiSettings.fatControllerButtons.add({ type="button", name="returnToPlayer", caption={"text-player"}, style = "fatcontroller_selected_button"})
        --game.players[1].teleport(global.followEntity.position)
      elseif global.character[i] ~= nil and not global.character[i].valid  then
        game.set_game_state({gamefinished=true, playerwon=false})
      end
      end
    end

    -- on_the_path = 0,
    -- -- had path and lost it - must stop
    -- path_lost = 1,
    -- -- doesn't have anywhere to go
    -- no_schedule = 2,
    -- has no path and is stopped
    -- no_path = 3,
    -- -- braking before the railSignal
    -- arrive_signal = 4,
    -- wait_signal = 5,
    -- -- braking before the station
    -- arrive_station = 6,
    -- wait_station = 7,
    -- -- switched to the manual control and has to stop
    -- manual_control_stop = 8,
    -- -- can move if user explicitly sits in and rides the train
    -- manual_control = 9,
    -- -- train was switched to auto control but it is moving and needs to be stopped
    -- stop_for_auto_control = 10

    if event.tick%120 == 37 then
      local alarmState = {}
      alarmState.timeToStation = false
      alarmState.timeAtSignal = false
      alarmState.noPath = false
      local newAlarm = false
      for forceName,trains in pairs(global.trainsByForce) do
        for i,trainInfo in pairs(trains) do
          local alarmSet = false
          if trainInfo.lastState == 1 or trainInfo.lastState == 3 then
            --game.players[1].print("No Path " .. i .. " " .. game.tick)
            if not trainInfo.alarm then
              alarmState.noPath = true
              newAlarm = true
              trainInfo.updated = true
            end
            alarmSet = true
            trainInfo.alarm = true
          end
          -- 36000, 10 minutes
          if trainInfo.lastState ~= 7 and trainInfo.lastStateStation ~= nil and (trainInfo.lastStateStation + 36000 < game.tick and (trainInfo.lastState ~= 2 or trainInfo.lastState ~= 8 or trainInfo.lastState ~= 9)) then
            if not trainInfo.alarm then
              alarmState.timeToStation = true
              newAlarm = true
              trainInfo.updated = true
            end
            alarmSet = true
            trainInfo.alarm = true
          end
          -- 72002 minutes lol, wtf?
          if trainInfo.lastState == 5 and (trainInfo.lastStateTick ~= nil and trainInfo.lastStateTick + 7200 < game.tick ) then
            if not trainInfo.alarm then
              alarmState.timeAtSignal = true
              newAlarm = true
              trainInfo.updated = true
            end
            alarmSet = true
            trainInfo.alarm = true
          end
          if not alarmSet then
            if trainInfo.alarm then
              trainInfo.updated = true
            end
            trainInfo.alarm = false
          end
        end
      end

      for i,guiSettings in pairs(global.guiSettings) do
        if guiSettings.alarm == nil or guiSettings.alarm.noPath == nil then
          guiSettings.alarm = {}
          guiSettings.alarm.timeToStation = true
          guiSettings.alarm.timeAtSignal = true
          guiSettings.alarm.noPath = true
        end
        if (guiSettings.alarm.timeToStation or guiSettings.alarm.timeAtSignal or guiSettings.alarm.noPath) and newAlarm then
          if guiSettings.alarm.timeToStation and alarmState.timeToStation then
            guiSettings.alarm.active = true
            alertPlayer(game.players[i], guiSettings, game.tick, ({"msg-alarm-tolongtostation"}))
          end
          if guiSettings.alarm.timeAtSignal and alarmState.timeAtSignal then
            guiSettings.alarm.active = true
            alertPlayer(game.players[i], guiSettings, game.tick, ({"msg-alarm-tolongatsignal"}))
          end
          if guiSettings.alarm.noPath and alarmState.noPath then
            guiSettings.alarm.active = true
            alertPlayer(game.players[i], guiSettings, game.tick, ({"msg-alarm-nopath"}))
          end
        else
          guiSettings.alarm.active = false
        end
      end
    end
  end)
  if err then debugDump(err,true) end
  --if event.tick%30==4 and global.fatControllerGui.trainInfo ~= nil then
  --refreshTrainInfoGui(global.trains, global.fatControllerGui.trainInfo, global.guiSettings, game.players[1].character)
  --end
end

function alertPlayer(player,guiSettings,tick,message)
  if player ~= nil and guiSettings ~= nil and guiSettings.alarm ~= nil and guiSettings.alarm.active and (guiSettings.alarm.lastMessage == nil or guiSettings.alarm.lastMessage + 120 < tick) then
    guiSettings.lastMessage = tick
    player.print(message)
  end
end

function destroyGui(guiA)
  if guiA ~= nil and guiA.valid then
    guiA.destroy()
  end
end

function buildStationFilterList(trains)
  local newList = {}
  if trains ~= nil then
    for i, trainInfo in pairs(trains) do
      -- if trainInfo.group ~= nil then
      -- newList[trainInfo.group] = true
      -- end
      if trainInfo.stations ~= nil then
        for station, value in pairs(trainInfo.stations) do
          --debugLog(station)
          newList[station] = true
        end
      end
    end
  end
  return newList
end

function getLocomotives(train)
  if train ~= nil and train.valid then
    local locos = {}
    for i,carriage in pairs(train.carriages) do
      if carriage ~= nil and carriage.valid and isTrainType(carriage.type) then
        table.insert(locos, carriage)
      end
    end
    return locos
  end
end

function getNewTrainInfo(train)
  if train ~= nil then
    local carriages = train.carriages
    if carriages ~= nil and carriages[1] ~= nil and carriages[1].valid then
      local newTrainInfo = {}
      newTrainInfo.train = train
      --newTrainInfo.firstCarriage = getFirstCarriage(train)
      newTrainInfo.locomotives = getLocomotives(train)

      --newTrainInfo.display = true
      return newTrainInfo
    end
  end
end

entityBuilt = function(event)
  local entity = event.created_entity
  if entity.type == "locomotive" and global.unlocked then --or entity.type == "cargo-wagon"
    getTrainInfoOrNewFromEntity(global.trainsByForce[entity.force.name], entity)
  end
end

script.on_event(defines.events.on_built_entity, entityBuilt)
script.on_event(defines.events.on_robot_built_entity, entityBuilt)

-- -- normal state - following the path
-- on_the_path = 0,
-- -- had path and lost it - must stop
-- path_lost = 1,
-- -- doesn't have anywhere to go
-- no_schedule = 2,
-- -- has no path and is stopped
-- no_path = 3,
-- -- braking before the railSignal
-- arrive_signal = 4,
-- wait_signal = 5,
-- -- braking before the station
-- arrive_station = 6,
-- wait_station = 7,
-- -- switched to the manual control and has to stop
-- manual_control_stop = 8,
-- -- can move if user explicitly sits in and rides the train
-- manual_control = 9,
-- -- train was switched to auto control but it is moving and needs to be stopped
-- stop_for_auto_control = 10

function on_train_changed_state(event)
  local status, err = pcall(function()
    --debugLog("State Change - " .. game.tick)

    local train = event.train
    local entity = train.carriages[1]

    local trains = global.trainsByForce[entity.force.name]
    local trainInfo = getTrainInfoOrNewFromEntity(trains, entity)
    if trainInfo ~= nil then
      local newtrain = false
      if trainInfo.updated == nil then
        newtrain = true
      else
      end
      updateTrainInfo(trainInfo,game.tick)
      if newtrain then
        for i,player in pairs(game.players) do
          global.guiSettings[i].pageCount = getPageCount(trains, global.guiSettings[i])
        end
      end
      refreshAllTrainInfoGuis(global.trainsByForce, global.guiSettings, game.players, newtrain)
    end
  end)
  if err then debugDump(err,true) end
end

function getTrainInfoOrNewFromTrain(trains, train)
  if trains ~= nil then
    for i, trainInfo in pairs(trains) do
      if trainInfo ~= nil and trainInfo.train and trainInfo.train.valid and train == trainInfo.train then
        return trainInfo
      end
    end
    local newTrainInfo = getNewTrainInfo(train)
    table.insert(trains, newTrainInfo)
    return newTrainInfo
  end
end

function getTrainInfoOrNewFromEntity(trains, entity)
  local trainInfo = getTrainInfoFromEntity(trains, entity)
  if trainInfo == nil then
    local newTrainInfo = getNewTrainInfo(entity.train)
    table.insert(trains, newTrainInfo)
    return newTrainInfo
  else
    return trainInfo
  end
end

function on_gui_click(event)
  local status, err = pcall(function()
    local refreshGui = false
    local newInfoWindow = false
    local rematchStationList = false
    local guiSettings = global.guiSettings[event.element.player_index]
    local player = game.players[event.element.player_index]
    local trains = global.trainsByForce[player.force.name]
    debugLog("CLICK! " .. event.element.name .. game.tick)

    if guiSettings.alarm == nil then
      guiSettings.alarm = {}
    end


    if event.element.name == "toggleTrainInfo" then
      if guiSettings.fatControllerGui.trainInfo == nil then
        newInfoWindow = true
        refreshGui = true
        event.element.caption = {"text-trains"}
      else
        guiSettings.fatControllerGui.trainInfo.destroy()
        event.element.caption = {"text-trains-collapsed"}
      end
    elseif event.element.name == "returnToPlayer" then
      if global.character[event.element.player_index] ~= nil then
        if player.vehicle ~= nil then
          player.vehicle.passenger = nil
        end
        swapPlayer(player, global.character[event.element.player_index])
        global.character[event.element.player_index] = nil
        event.element.destroy()
        guiSettings.followEntity = nil
      end
    elseif endsWith(event.element.name,"_toggleManualMode") then
      local trainInfo = getTrainInfoFromElementName(trains, event.element.name)
      if trainInfo ~= nil and trainInfo.train ~= nil and trainInfo.train.valid then
        trainInfo.train.manual_mode = not trainInfo.train.manual_mode
        swapCaption(event.element, "ll", ">")
      end
    elseif endsWith(event.element.name,"_toggleFollowMode") then
      local trainInfo = getTrainInfoFromElementName(trains, event.element.name)
      if trainInfo ~= nil and trainInfo.train ~= nil and trainInfo.train.valid then
        if global.character[event.element.player_index] == nil then --Move to train
          if trainInfo.train.carriages[1].passenger ~= nil then
            player.print({"msg-intrain"})
        else
          global.character[event.element.player_index] = player.character
          guiSettings.followEntity = trainInfo.train.carriages[1] -- HERE

          --fatControllerEntity =
          swapPlayer(player,newFatControllerEntity(player))
          --event.element.style = "fatcontroller_selected_button"
          event.element.caption = "X"
          trainInfo.train.carriages[1].passenger = player.character
        end
        elseif guiSettings.followEntity ~= nil and trainInfo.train ~= nil and trainInfo.train.valid then
          if player.vehicle ~= nil then
            player.vehicle.passenger = nil
          end
          if guiSettings.followEntity == trainInfo.train.carriages[1] or trainInfo.train.carriages[1].passenger ~= nil then --Go back to player
            swapPlayer(player, global.character[event.element.player_index])
            --event.element.style = "fatcontroller_button_style"
            event.element.caption = "c"
            if guiSettings.fatControllerButtons ~= nil and guiSettings.fatControllerButtons.returnToPlayer ~= nil then
              guiSettings.fatControllerButtons.returnToPlayer.destroy()
            end
            global.character[event.element.player_index] = nil
            guiSettings.followEntity = nil
          else -- Go to different train

            guiSettings.followEntity = trainInfo.train.carriages[1] -- AND HERE
            --event.element.style = "fatcontroller_selected_button"
            event.element.caption = "X"

            trainInfo.train.carriages[1].passenger = player.character
            --game.players[1].vehicle = trainInfo.train.carriages[1]
          end
        end

      end
    elseif event.element.name == "page_back" then
      if guiSettings.page > 1 then
        guiSettings.page = guiSettings.page - 1
        --global.fatControllerGui.trainInfo.trainInfoControls.page_number.caption = global.guiSettings.page
        --newTrainInfoWindow(global.fatControllerGui, global.guiSettings)
        --refreshTrainInfoGui(global.trains, global.fatControllerGui.trainInfo, global.guiSettings)
        newInfoWindow = true
        refreshGui = true
      end
    elseif event.element.name == "page_forward" then
      if guiSettings.page < getPageCount(trains, guiSettings) then
        guiSettings.page = guiSettings.page + 1
        --debugLog(global.guiSettings.page)
        --newTrainInfoWindow(global.fatControllerGui, global.guiSettings)
        --refreshTrainInfoGui(global.trains, global.fatControllerGui.trainInfo, global.guiSettings)
        newInfoWindow = true
        refreshGui = true
      end
    elseif event.element.name == "page_number" then
      togglePageSelectWindow(player.gui.center, guiSettings)
    elseif event.element.name == "pageSelectOK" then
      local gui = player.gui.center.pageSelect
      if gui ~= nil then

        local newInt = tonumber(gui.pageSelectValue.text)

        if newInt then
          if newInt < 1 then
            newInt = 1
          elseif newInt > 50 then
            newInt = 50
          end
          guiSettings.displayCount = newInt
          guiSettings.pageCount = getPageCount(trains, guiSettings)
          guiSettings.page = 1
          refreshGui = true
          newInfoWindow = true
        else
          player.print({"msg-notanumber"})
        end
        gui.destroy()
      end
    elseif event.element.name == "toggleStationFilter" then
      guiSettings.stationFilterList = buildStationFilterList(trains)
      toggleStationFilterWindow(player.gui.center, guiSettings)
    elseif event.element.name == "clearStationFilter" or event.element.name == "stationFilterClear" then
      if guiSettings.activeFilterList ~= nil then
        guiSettings.activeFilterList = nil

        rematchStationList = true
        newInfoWindow = true
        refreshGui = true
      end
      if player.gui.center.stationFilterWindow ~= nil then
        player.gui.center.stationFilterWindow.destroy()
      end
    elseif event.element.name == "stationFilterOK" then
      local gui = player.gui.center.stationFilterWindow
      if gui ~= nil and gui.checkboxGroup ~= nil then
        local newFilter = {}
        local listEmpty = true
        for station,value in pairs(guiSettings.stationFilterList) do
          local checkboxA = gui.checkboxGroup[station .. "_stationFilter"]
          if checkboxA ~= nil and checkboxA.state then
            listEmpty = false
            --debugLog(station)
            newFilter[station] = true
          end
        end
        if not listEmpty then
          guiSettings.activeFilterList = newFilter

          --glo.filteredTrains = buildFilteredTrainInfoList(global.trains, global.guiSettings.activeFilterList)
        else
          guiSettings.activeFilterList = nil
        end

        gui.destroy()
        guiSettings.page = 1
        rematchStationList = true
        newInfoWindow = true
        refreshGui = true
      end
    elseif endsWith(event.element.name,"_stationFilter") then
      local stationName = string.gsub(event.element.name, "_stationFilter", "")
      if event.element.state then
        if guiSettings.activeFilterList == nil then
          guiSettings.activeFilterList = {}
        end

        guiSettings.activeFilterList[stationName] = true
      elseif guiSettings.activeFilterList ~= nil then
        guiSettings.activeFilterList[stationName] = nil
        if tableIsEmpty(guiSettings.activeFilterList) then
          guiSettings.activeFilterList = nil
        end
      end
      --debugLog(event.element.name)
      guiSettings.page = 1
      rematchStationList = true
      newInfoWindow = true
      refreshGui = true
      --alarmOK alarmTimeToStation alarmTimeAtSignal alarmNoPath alarmButton
    elseif event.element.name == "alarmButton" or event.element.name == "alarmOK" then
      toggleAlarmWindow(player.gui.center, guiSettings)
    elseif event.element.name == "alarmTimeToStation" then
      guiSettings.alarm.timeToStation = event.element.state
    elseif event.element.name == "alarmTimeAtSignal" then
      guiSettings.alarm.timeAtSignal = event.element.state
    elseif event.element.name == "alarmNoPath" then
      guiSettings.alarm.noPath = event.element.state
    end

    if rematchStationList then
      filterTrainInfoList(trains, guiSettings.activeFilterList)
      guiSettings.pageCount = getPageCount(trains, guiSettings)
    end

    if newInfoWindow then
      newTrainInfoWindow(guiSettings)
    end

    if refreshGui then
      refreshTrainInfoGui(trains, guiSettings, player.character)
    end
  end)
  if err then debugDump(err,true) end
end

-- function swapStyle(guiElement, styleA, styleB)
-- if guiElement ~= nil and styleA ~= nil and styleB ~= nil then
-- if guiElement.style == styleA then
-- guiElement.style = styleB
-- elseif guiElement.style == styleB then
-- guiElement.style = styleA
-- end
-- end
-- end

function swapCaption(guiElement, captionA, captionB)
  if guiElement ~= nil and captionA ~= nil and captionB ~= nil then
    if guiElement.caption == captionA then
      guiElement.caption = captionB
    elseif guiElement.caption == captionB then
      guiElement.caption = captionA
    end
  end
end

function tableIsEmpty(tableA)
  if tableA ~= nil then
    for i,v in pairs(tableA) do
      return false
    end
  end
  return true
end

function toggleStationFilterWindow(gui, guiSettings)
  if gui ~= nil then
    if gui.stationFilterWindow == nil then
      --local sortedList = table.sort(a)
      local window = gui.add({type="frame", name="stationFilterWindow", caption={"msg-stationFilter"}, direction="vertical" }) --style="fatcontroller_thin_frame"})
      window.add({type="table", name="checkboxGroup", colspan=3})
      for name, value in pairsByKeys(guiSettings.stationFilterList) do
        if guiSettings.activeFilterList ~= nil and guiSettings.activeFilterList[name] then
          window.checkboxGroup.add({type="checkbox", name=name .. "_stationFilter", caption=name, state=true}) --style="filter_group_button_style"})
        else
          window.checkboxGroup.add({type="checkbox", name=name .. "_stationFilter", caption=name, state=false}) --style="filter_group_button_style"})
        end
      end
      window.add({type="flow", name="buttonFlow"})
      window.buttonFlow.add({type="button", name="stationFilterClear", caption={"msg-Clear"}})
      window.buttonFlow.add({type="button", name="stationFilterOK", caption={"msg-OK"}})
    else
      gui.stationFilterWindow.destroy()
    end
  end
end

function togglePageSelectWindow(gui, guiSettings)
  if gui ~= nil then
    if gui.pageSelect == nil then
      local window = gui.add({type="frame", name="pageSelect", caption={"msg-displayCount"}, direction="vertical" }) --style="fatcontroller_thin_frame"})
      window.add({type="textfield", name="pageSelectValue", text=guiSettings.displayCount .. ""})
      window.pageSelectValue.text = guiSettings.displayCount .. ""
      window.add({type="button", name="pageSelectOK", caption={"msg-OK"}})
    else
      gui.pageSelect.destroy()
    end
  end
end

function toggleAlarmWindow(gui, guiSettings)
  if gui ~= nil then
    if gui.alarmWindow == nil then
      local window = gui.add({type="frame",name="alarmWindow", caption={"text-alarmwindow"}, direction="vertical" })
      local stateTimeToStation = true
      if guiSettings.alarm ~= nil and not guiSettings.alarm.timeToStation then
        stateTimeToStation = false
      end
      window.add({type="checkbox", name="alarmTimeToStation", caption={"text-alarmtimetostation"}, state=stateTimeToStation}) --style="filter_group_button_style"})
      local stateTimeAtSignal = true
      if guiSettings.alarm ~= nil and not guiSettings.alarm.timeAtSignal then
        stateTimeAtSignal = false
      end
      window.add({type="checkbox", name="alarmTimeAtSignal", caption={"text-alarmtimeatsignal"}, state=stateTimeAtSignal}) --style="filter_group_button_style"})
      local stateNoPath = true
      if guiSettings.alarm ~= nil and not guiSettings.alarm.noPath then
        stateNoPath = false
      end
      window.add({type="checkbox", name="alarmNoPath", caption={"text-alarmtimenopath"}, state=stateNoPath}) --style="filter_group_button_style"})
      window.add({type="button", name="alarmOK", caption={"msg-OK"}})
    else
      gui.alarmWindow.destroy()
    end
  end
end

function getPageCount(trains, guiSettings)
  local trainCount = 0
  for i,trainInfo in pairs(trains) do
    if guiSettings.activeFilterList == nil or trainInfo.matchesStationFilter then
      trainCount = trainCount + 1
    end
  end
  return math.floor((trainCount - 1) / guiSettings.displayCount) + 1
end

local onEntityDied = function (event)
  if global.unlocked and global.guiSettings ~= nil then
    local entities = {locomotive=true, ["cargo-wagon"]=true, player=true}
    if not entities[event.entity.type] then
      return
    end
    for forceName,trains in pairs(global.trainsByForce) do
      updateTrains(trains)
    end

    for i, player in pairs(game.players) do
      local guiSettings = global.guiSettings[i]
      if guiSettings.followEntity ~= nil and guiSettings.followEntity == event.entity then --Go back to player
        if game.players[i].vehicle ~= nil then
          game.players[i].vehicle.passenger = nil
      end
      swapPlayer(game.players[i], global.character[i])
      if guiSettings.fatControllerButtons.returnToPlayer ~= nil then
        guiSettings.fatControllerButtons.returnToPlayer.destroy()
      end
      global.character[i] = nil
      guiSettings.followEntity = nil
      end
    end
    refreshAllTrainInfoGuis(global.trainsByForce, global.guiSettings, game.players, true)
  end
end

{'fraction_non_alphanumeric': 0.09295264296857962, 'fraction_numerical': 0.003858348291422724, 'mean_word_length': 3.0005368767196834, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5057366', 'n_tokens_mistral': 17795, 'n_tokens_neox': 17354, 'n_words': 4063}