local bta = require('btafunctions')
local datasaver = require('datasaver')
local md = require('debugFunctions')

local config = (function()
  local function parseTabledItem(source, label)
    return function()
      local name = parse.getString()
      local result = source[name]
      if not (result and result:isValid()) then
        parse.displayMessage('Unknown ' .. label .. '"' .. name .. '"', true)
      end
      return result
    end
  end

  local parseShip = parseTabledItem(tb.ShipClasses, 'ship class')
  local parseWeapon = parseTabledItem(tb.WeaponClasses, 'weapon class')
  local parseIntel = parseTabledItem(tb.IntelEntries, 'intel entry')
  
  local function parseCutscene()
    local result = {}
    local name = parse.getString()
    local scene = ui.TechRoom.Cutscenes[name]
    if not (scene and scene:isValid()) then
      parse.displayMessage('Unknown cutscene entry "' .. name .. '"', true)
    end
    result.scene = scene
    if parse.optionalString('++BtA Choice:') then
      result.key = parse.getString()
      parse.requiredString('++BtA Value:')
      result.value = parse.getString()
      result.default = false
      if parse.optionalString('++Default:') then
        result.default = parse.getBoolean()
      end
    end
    return result
  end

  local function parseInventory(callback)
    local count = parse.getInt()
    local class = callback()
    return { class = class, count = count }
  end

  local function parseRequisition(callback)
    local result = parseInventory(callback)
    parse.requiredString('++Faction:')
    result.faction = parse.getString()
    parse.requiredString('++Cost:')
    result.cost = parse.getInt()
    return result
  end

  local function parseTrigger()
    local result = parse.getString()
    if bta:getGameProgress(result) <= 0 then
      parse.displayMessage('Unknown mission or campaign "' .. result .. '"', true)
    end
    return result
  end

  local tokens = {}
  local function parseToken(default)
    local token = parse.optionalString('+Token:') and parse.getString() or default
    if tokens[token] then
      parse.displayMessage('Multiple triggers share the token "' .. token .. '"', true)
    else
      tokens[token] = true
    end
    return token
  end

  local function parseEntry()
    local entry = {
      intel = {},
      staleIntel = {},
      equipment = {},
      headlines = {},
      editorials = {},
      inventory = {},
      requisitions = {},
      cutscenes = {},
      updates = {}
    }
    local trigger = parseTrigger()
    entry.trigger = trigger
    entry.token = parseToken(trigger)
    while true do
      if parse.optionalString('+Show Ship:') then
        table.insert(entry.intel, parseShip())
      elseif parse.optionalString('+Show Weapon:') then
        table.insert(entry.intel, parseWeapon())
      elseif parse.optionalString('+Show Intel:') then
        table.insert(entry.intel, parseIntel())
      elseif parse.optionalString('+Hide Ship:') then
        table.insert(entry.staleIntel, parseShip())
      elseif parse.optionalString('+Hide Weapon:') then
        table.insert(entry.staleIntel, parseWeapon())
      elseif parse.optionalString('+Hide Intel:') then
        table.insert(entry.staleIntel, parseIntel())
      elseif parse.optionalString('+Show Cutscene:') then
        table.insert(entry.cutscenes, parseCutscene())
      elseif parse.optionalString('+Allow Ship:') then
        table.insert(entry.equipment, parseShip())
      elseif parse.optionalString('+Allow Weapon:') then
        table.insert(entry.equipment, parseWeapon())
      elseif parse.optionalString('+Show Headline:') then
        table.insert(entry.headlines, parse.getString())
      elseif parse.optionalString('+Show Editorial:') then
        table.insert(entry.editorials, parse.getString())
      elseif parse.optionalString('+Add Ship Inventory:') then
        table.insert(entry.inventory, parseInventory(parseShip))
      elseif parse.optionalString('+Add Weapon Inventory:') then
        table.insert(entry.inventory, parseInventory(parseWeapon))
      elseif parse.optionalString('+Add Ship Requisition:') then
        table.insert(entry.requisitions, parseRequisition(parseShip))
      elseif parse.optionalString('+Add Weapon Requisition:') then
        table.insert(entry.requisitions, parseRequisition(parseWeapon))
      elseif parse.optionalString('+Send Update:') then
        table.insert(entry.updates, parse.getString())
      else
        return entry
      end
    end
  end

  local result = {}
  parse.readFileText('progress.tbl', 'data/tables')
  parse.requiredString('#Progress')
  while parse.optionalString('$Trigger:') do
    table.insert(result, parseEntry())
  end
  parse.requiredString('#End')
  parse.stop()
  return result
end)()

local loadState, saveState = (function()
  local function getSource()
    return 'progress' .. ba.getCurrentPlayer():getCampaignFilename()
  end

  local function loadState()
    return datasaver:loadDataFromFile(getSource(), true) or {}
  end

  local function saveState(state)
    datasaver:saveDataToFile(getSource(), state or {}, true)
  end
  
  return loadState, saveState
end)()

local run, updated, recordUpdate = (function()
  local updated = {}

  local function recordUpdate(message)
    for _, v in ipairs(updated) do
      if v == message then return end
    end
    table.insert(updated, message)
  end

  local function processIntel(class, toggle)
    local previous = class.InTechDatabase
    class.InTechDatabase = toggle
    md.print('PROGRESS: ' .. class.Name .. '.InTechDatabase is now ' .. tostring(toggle))
    if toggle and not previous then
      recordUpdate(ba.XSTR('New data is available in the tech room!', 11350))
    end
  end

  local function processStaleIntel(class, toggle)
    class.InTechDatabase = not toggle
    md.print('PROGRESS: ' .. class.Name .. '.InTechDatabase is now ' .. tostring(not toggle))
  end

  local function processCutscene(class, toggle)
    local val = toggle
    if class.key then
      local datasaver = require("datasaver")
      local config = datasaver:loadDataFromFile("playerChoices", true)
    
      if config == nil then
        config = {}
      end
      if config[class.key] ~= nil then
        if config[class.key] == class.value then
          val = true
        else
          val = false
        end
      else
        val = class.default
      end
    end
    --class.scene.isVisible = val
    --md.print('PROGRESS: ' .. class.scene.Name .. '.isVisible is now ' .. tostring(val))
  end

  local function processEquipment(class, toggle)
    class.AllowedInCampaign = toggle
    md.print('PROGRESS: ' .. class.Name .. '.AllowedInCampaign is now ' .. tostring(toggle))
  end

  local function processJournal(section)
    return function(article, toggle)
      if JournalUI:DoesConfigExist() then
        if toggle then
          if JournalUI:UnlockEntry(section, article) then
            recordUpdate(ba.XSTR('New articles are available in the news room!', 11351))
          end
        else
          JournalUI:LockEntry(section, article)
        end
        md.print('PROGRESS: ' .. article .. ' is now ' .. (toggle and 'unlocked' or 'locked'))
      end
    end
  end

  local processHeadline = processJournal('Headlines')
  local processEditorial = processJournal('Editorials')

  local function processInventory(inventory)
    local count = inventory.count
    local class = inventory.class
    md.print('PROGRESS: Granting ' .. count .. 'x ' .. class.Name)
    local current = backstock:GetAmount(class)
    local total = current + count
    backstock:SetAmount(class, total)
    backstock.updated = true -- to make sure loadouts know that Inventory has changed
  end

  local function processRequisition(requisition)
    local count = requisition.count
    local class = requisition.class
    local cost = requisition.cost
    local faction = requisition.faction
    md.print('PROGRESS: Adding requisition for ' .. count .. 'x ' .. class.Name .. ' at ' .. cost .. ' CP each')
    if tb.ShipClasses[class.Name]:isValid() then
      if count >= 0 then
        backstock:AddShipRequisition(class, count, faction, cost)
      else
        backstock:RemoveShipRequisition(class, count, faction)
      end
    else
      if count >= 0 then
        backstock:AddWeaponRequisition(class, count, faction, cost)
      else
        backstock:RemoveWeaponRequisition(class, count, faction)
      end
    end
    if count > 0 then
      recordUpdate(ba.XSTR('New assets are available for requisition!', 11352))
    end
  end

  local function forEach(list, callback, toggle)
    for _, item in ipairs(list) do
      callback(item, toggle)
    end
    return #list > 0
  end

  local function isInCurrentCampaign(trigger)
    local campaign = ba.getCurrentPlayer():getCampaignFilename()
    local pattern = '^' .. campaign:gsub('%..+$', '')
    return trigger:find(pattern)
  end

  local function processEntry(entry, progress, state)
    -- Since the configuration might change in development, this function tries
    -- to process as much as it can each time, so long as doing so won't lead to
    -- inaccurate results during normal play.
    local trigger = entry.trigger
    local token = entry.token
    local toggle = progress >= bta:getGameProgress(entry.trigger)
    -- These can always be run safely (they're "idempotent").
    forEach(entry.intel, processIntel, toggle)
    forEach(entry.staleIntel, processStaleIntel)
    forEach(entry.cutscenes, processCutscene, toggle)
    forEach(entry.equipment, processEquipment, toggle)
    forEach(entry.headlines, processHeadline, toggle)
    forEach(entry.editorials, processEditorial, toggle)
    -- The remaining items touch campaign assets. They can only
    -- run once and only if we're in the appropriate campaign.
    if state[token] then
      md.print('PROGRESS: Already processed token ' .. token)
    elseif toggle and isInCurrentCampaign(trigger) then
      local i = forEach(entry.inventory, processInventory)
      local r = forEach(entry.requisitions, processRequisition)
      if i or r then
        backstock:SaveChanges()
        state[token] = true
        md.print('PROGRESS: Ran one-time unlocks for ' .. token)
      else
        md.print('PROGRESS: ' .. token .. ' has no one-time unlocks')
      end
      forEach(entry.updates, recordUpdate)
    end
  end

  local function run(reset)
    if reset then saveState({}) end
    local progress = bta:getGameProgress()
    local state = loadState()
    for _, entry in ipairs(config) do
      processEntry(entry, progress, state)
    end
    saveState(state)
    ui.OptionsMenu.savePlayerData()
  end

  return run, updated, recordUpdate
end)()

local sendUpdate, processUpdates = (function()
  local queue = {}

  local function sendUpdate(message)
    table.insert(queue, message)
  end

  local function processUpdates(send)
    if send then
      for _, message in ipairs(queue) do
        recordUpdate(message)
      end
    end
    queue = {}
  end

  return sendUpdate, processUpdates
end)()

return {
  run = run,
  updated = updated,
  sendUpdate = sendUpdate,
  processUpdates = processUpdates
}

