-- Copyright (C) 2025  snoutie
-- Authors: snoutie (copyright@achtarmig.org)
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU Affero General Public License as published
-- by the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU Affero General Public License for more details.

-- You should have received a copy of the GNU Affero General Public License
-- along with this program.  If not, see <https://www.gnu.org/licenses/>.

local M = {}

M.dependencies = { 'career_career', 'career_saveSystem', 'career_modules_inventory' }

local extensionName = "career_vehicleSaveSystem"
local vehicleSaves_temp = "/temp/career/vehicle_saves"

local function CreateDirectories(dir)
    local p = path.dirname(dir)
    if not FS:directoryExists(p) then
        CreateDirectories(string.sub(p, 1, #p - 1))
    end
    FS:directoryCreate(dir)
end

local function ClearDirectory(dir)
    local files = FS:findFiles(dir .. "/", '*', 0, false, true)
    for i = 1, tableSize(files) do
        FS:remove(files[i])
    end
end

local function CopySaves(from, to)
    local save_folders = FS:directoryList(from, false, true)
    for i = 1, tableSize(save_folders) do
        local folder_dir, folder_name, _ = path.split(save_folders[i])
        FS:directoryCreate(to .. "/" .. folder_name)
        local save_files = FS:findFiles(folder_dir .. folder_name .. "/", '*.json', 0, false, false)
        for n = 1, tableSize(save_files) do
            local file_dir, file_name, file_ext = path.split(save_files[n])
            FS:remove(to .. "/" .. folder_name .. "/" .. file_name)
            FS:copyFile(file_dir .. file_name, to .. "/" .. folder_name .. "/" .. file_name)
        end
    end
end

local function LoadFiles(from)
    ClearDirectory(vehicleSaves_temp)
    CopySaves(from, vehicleSaves_temp)
end

local function SaveFiles(to)
    CopySaves(vehicleSaves_temp, to)
end

local function GetVehicleSaveFile(root, inventoryId)
    local path = root .. "/" .. inventoryId
    CreateDirectories(path)

    return path .. "/save.json"
end

local function EnqueueVehicleToSave(inventoryId)
    log("I", "saving", "enqueued vehicle " .. inventoryId)
    M.queuedVehicleSaves[inventoryId] = true
end

local function DequeueVehicleToSave(inventoryId)
    log("I", "saving", "dequeued vehicle " .. inventoryId)
    M.queuedVehicleSaves[inventoryId] = nil
end

local function VehiclesSaved()
    if next(M.queuedVehicleSaves) == nil then
        log('I', 'saving', 'all vehicles saved')
        return true
    else
        log('I', 'saving', 'still saving vehicles')
    end
    return false
end

local function CheckSavedAsync(callback)
    extensions.core_jobsystem.create(
        function(job)
            while true do
                if VehiclesSaved() then
                    break
                end
                job.sleep(0.1)
            end
            if callback then
                callback()
            end
        end)
end

local function SaveVehicle(inventoryId)
    local saveFile = GetVehicleSaveFile(vehicleSaves_temp, inventoryId)

    local vehicleId = career_modules_inventory.getVehicleIdFromInventoryId(inventoryId)
    if vehicleId then
        log('I', 'saving', 'saving vehicle ' .. inventoryId .. " to " .. saveFile)
        local object = be:getObjectByID(vehicleId)
        object:queueLuaCommand("beamstate.save(\"" ..
            saveFile ..
            "\"); obj:queueGameEngineLua('career_vehicleSaveSystem.DequeueVehicleToSave(" .. inventoryId .. ")');")
        return true
    else
        log('I', 'saving', 'vehicle ' .. inventoryId .. " not spawned")
        return false
    end
end

local function LoadVehicle(inventoryId)
    local saveFile = GetVehicleSaveFile(vehicleSaves_temp, inventoryId)

    local vehicleId = career_modules_inventory.getVehicleIdFromInventoryId(inventoryId)
    if vehicleId then
        log("I", "loading", "loading vehicle from " .. saveFile)
        local object = be:getObjectByID(vehicleId)
        object:queueLuaCommand("beamstate.load(\"" ..
            saveFile .. "\");" ..
            "for key, value in pairs(v.data.controller) do if value['fileName'] == 'advancedCouplerControl' then local c = controller.getController(value['name']) if c['reset'] then c['reset']() end end end"
        )
    end
end

local function onSaveCurrentSaveSlotAsyncStart()
    career_saveSystem.registerAsyncSaveExtension(extensionName)
end

local function onSaveCurrentSaveSlot(currentSavePath, oldSaveDate, vehiclesThumbnailUpdate)
    local vehicles = career_modules_inventory.getVehicles()

    for id, _ in pairs(vehicles) do
        EnqueueVehicleToSave(id)
    end

    for id, _ in pairs(M.queuedVehicleSaves) do
        if not SaveVehicle(id) then
            DequeueVehicleToSave(id)
        end
    end
    CheckSavedAsync(function()
        SaveFiles(currentSavePath .. "/career/vehicle_saves")
        career_saveSystem.asyncSaveExtensionFinished(extensionName)
    end)
end

local function onCareerActive()
    local _, saveSlot = career_saveSystem.getCurrentSaveSlot()
    local vehicleSaves_saveSlot = saveSlot .. "/career/vehicle_saves"

    CreateDirectories(vehicleSaves_saveSlot)
    CreateDirectories(vehicleSaves_temp)

    LoadFiles(vehicleSaves_saveSlot)
end

M.queuedVehicleSaves = {}

M.SaveVehicle = SaveVehicle
M.LoadVehicle = LoadVehicle

M.CheckSavedAsync = CheckSavedAsync

M.EnqueueVehicleToSave = EnqueueVehicleToSave
M.DequeueVehicleToSave = DequeueVehicleToSave

M.onSaveCurrentSaveSlotAsyncStart = onSaveCurrentSaveSlotAsyncStart
M.onSaveCurrentSaveSlot = onSaveCurrentSaveSlot
M.onCareerActive = onCareerActive

return M