if not SERVER then return end
zrmine = zrmine or {}
zrmine.f = zrmine.f or {}

local PipeLineEnts = {"zrms_crusher", "zrms_conveyorbelt_n", "zrms_conveyorbelt_s", "zrms_conveyorbelt_c_left", "zrms_conveyorbelt_c_right", "zrms_refiner_iron", "zrms_refiner_bronze", "zrms_refiner_coal", "zrms_refiner_silver", "zrms_refiner_gold"}

-- Used do find the connected Entity for most modules
local function FindConnectedEntByID(id, table)
    local foundEnt = nil

    for k, v in pairs(table) do
        if (v.PipeLine_ID == id) then
            foundEnt = v
            break
        end
    end

    return foundEnt
end

local function SaveMultiConnect(ply,Class)
    local dataTable = {}
    for k, v in pairs(ents.FindByClass(Class)) do
        if (IsValid(v) and (v.IsPipeLineEntity or zrmine.f.IsOwner(ply, v))) then
            local connectedEnt01 = v.connected_module01
            local connectedEnt02 = v.connected_module02
            local connectedID01 = -1
            local connectedID02 = -1

            if (IsValid(connectedEnt01)) then
                connectedID01 = connectedEnt01:EntIndex()
            end

            if (IsValid(connectedEnt02)) then
                connectedID02 = connectedEnt02:EntIndex()
            end

            v.IsPipeLineEntity = true

            dataTable = {
                class = v:GetClass(),
                pos = v:GetPos(),
                ang = v:GetAngles(),
                id = v:EntIndex(),
                coID01 = connectedID01,
                coID02 = connectedID02
            }
        end
    end
    return dataTable
end

-- Here we build our PipeLine Table
local function zrms_PipeLine_Build(ply)
    local data = {}

    for k, v in pairs(ents.GetAll()) do
        if (IsValid(v) and table.HasValue(PipeLineEnts, v:GetClass()) and (v.IsPipeLineEntity or zrmine.f.IsOwner(ply, v))) then

            local connectedEnt = v.connected_module
            local connectedID = -1

            if (IsValid(v.connected_module)) then
                connectedID = connectedEnt:EntIndex()
            end

            v.IsPipeLineEntity = true

            table.insert(data, {
                class = v:GetClass(),
                pos = v:GetPos(),
                ang = v:GetAngles(),
                id = v:EntIndex(),
                coID = connectedID
            })
        end
    end

    -- This Saves all insertes
    for k, v in pairs(ents.FindByClass("zrms_inserter")) do
        if (IsValid(v) and (v.IsPipeLineEntity or zrmine.f.IsOwner(ply, v))) then
            local connectedEnt = v.Inserter_EndPart
            local connectedID = -1

            if (IsValid(v.Inserter_EndPart)) then
                connectedID = connectedEnt:EntIndex()
            end

            v.IsPipeLineEntity = true

            table.insert(data, {
                class = v:GetClass(),
                pos = v:GetPos(),
                ang = v:GetAngles(),
                id = v:EntIndex(),
                coID = connectedID
            })
        end
    end

    -- This Saves all splitter
    table.insert(data,SaveMultiConnect(ply,"zrms_splitter"))

    -- This Saves all sorter
    local aTable = {}

    aTable = SaveMultiConnect(ply,"zrms_sorter_coal")
    if (table.Count(aTable) > 0)then
        table.insert(data,aTable)
    end

    aTable = SaveMultiConnect(ply,"zrms_sorter_iron")
    if (table.Count(aTable) > 0)then
        table.insert(data,aTable)
    end

    aTable = SaveMultiConnect(ply,"zrms_sorter_bronze")
    if (table.Count(aTable) > 0)then
        table.insert(data,aTable)
    end

    aTable = SaveMultiConnect(ply,"zrms_sorter_silver")
    if (table.Count(aTable) > 0)then
        table.insert(data,aTable)
    end

    aTable = SaveMultiConnect(ply,"zrms_sorter_gold")
    if (table.Count(aTable) > 0)then
        table.insert(data,aTable)
    end

    return data
end

-- Here we rebuild our Pipeline
local function zrms_PipeLine_Rebuild(data)
    local createdEnts = {}

    for k, v in pairs(data) do
        if(v.class)then
            local ent = ents.Create(v.class)
            ent:SetPos(v.pos)
            ent:SetAngles(v.ang)
            ent:Spawn()
            local phys = ent:GetPhysicsObject()

            if (phys:IsValid()) then
                phys:Wake()
                phys:EnableMotion(false)
            end

            ent.PipeLine_ID = v.id
            ent.IsPipeLineEntity = true

            if (v.class == "zrms_splitter" or string.sub(v.class, 1, 11) == "zrms_sorter") then
                ent.PipeLine_CoID01 = v.coID01
                ent.PipeLine_CoID02 = v.coID02
            else
                ent.PipeLine_CoID = v.coID
            end

            if (string.sub(v.class, 1, 12) == "zrms_refiner") then
                timer.Simple(0.1, function()
                    if (IsValid(ent)) then
                        ent.PublicEntity = true
                        ent:SpawnBasket()
                    end
                end)
            end

            table.insert(createdEnts, ent)
        end
    end

    local connectdID
    local foundEnt

    -- Connect Normal PipeLine Ents
    for k, v in pairs(createdEnts) do
        if (v:GetClass() ~= "zrms_inserter" or v:GetClass() ~= "zrms_splitter" or string.sub(v:GetClass(), 1, 11) ~= "zrms_sorter") then
            connectdID = v.PipeLine_CoID

            if (connectdID ~= -1) then
                foundEnt = FindConnectedEntByID(connectdID, createdEnts)

                if (IsValid(foundEnt)) then
                    foundEnt:SetBuildMode(false)
                    v.connected_module = foundEnt
                    v.connected_module.connected_Parent = v
                    v:SetHasChild(true)
                    v.PhysgunDisabled = true
                    v.connected_module.PhysgunDisabled = true
                    v:SetBuildMode(false)
                end
            end
        end
    end

    -- Connect Inserter PipeLine Ents
    for k, v in pairs(createdEnts) do
        if (v:GetClass() == "zrms_inserter") then
            connectdID = v.PipeLine_CoID

            if (connectdID ~= -1) then
                foundEnt = FindConnectedEntByID(connectdID, createdEnts)

                if (IsValid(foundEnt)) then
                    v.Inserter_EndPart = foundEnt
                    v:SetInserterEndPart(foundEnt:EntIndex())
                    v.PhysgunDisabled = true
                    v:SetBuildMode(false)
                    --print("Inserter got connected too "..foundEnt:GetClass())
                end
            end
        end
    end

    -- Connect Splitter PipeLine Ents
    for k, v in pairs(createdEnts) do
        if (v:GetClass() == "zrms_splitter") then
            local connectdID01 = v.PipeLine_CoID01
            local connectdID02 = v.PipeLine_CoID02

            if (connectdID01 ~= -1) then
                foundEnt = FindConnectedEntByID(connectdID01, createdEnts)

                if (IsValid(foundEnt)) then
                    v:ConnectModule01(foundEnt)
                    --print("Splitter01 got connected too "..foundEnt:GetClass())
                end
            end

            if (connectdID02 ~= -1) then
                foundEnt = FindConnectedEntByID(connectdID02, createdEnts)

                if (IsValid(foundEnt)) then
                    v:ConnectModule02(foundEnt)
                    --print("Splitter02 got connected too "..foundEnt:GetClass())
                end
            end
        end

        v:SetBuildMode(false)
    end

    -- Connect Sorter ents
    for k, v in pairs(createdEnts) do
        if (string.sub(v:GetClass(), 1, 11) == "zrms_sorter") then
            local connectdID01 = v.PipeLine_CoID01
            local connectdID02 = v.PipeLine_CoID02

            if (connectdID01 ~= -1) then
                foundEnt = FindConnectedEntByID(connectdID01, createdEnts)

                if (IsValid(foundEnt)) then
                    v:ConnectModule01(foundEnt)
                end
            end

            if (connectdID02 ~= -1) then
                foundEnt = FindConnectedEntByID(connectdID02, createdEnts)

                if (IsValid(foundEnt)) then
                    v:ConnectModule02(foundEnt)
                end
            end
        end

        v:SetBuildMode(false)
    end
end

-- Finds all the PipeLine entitys and removes them
local function zrms_PipeLine_Remove()
    for k, v in pairs(ents.GetAll()) do
        if (IsValid(v) and v.IsPipeLineEntity) then
            v:Remove()
        end
    end
end

-- Save Load Delete
function zrmine.f.PipeLine_Save(ply)
    local data = zrms_PipeLine_Build(ply)

    if not file.Exists("zrms", "DATA") then
        file.CreateDir("zrms")
    end

    file.Write("zrms/" .. game.GetMap() .. "_PipeLine" .. ".txt", util.TableToJSON(data))
    zrmine.f.Notify(ply, "The PipeLine has been saved for the map " .. game.GetMap() .. "!", 0)
end

function zrmine.f.PipeLine_Load()
    if file.Exists("zrms/" .. game.GetMap() .. "_PipeLine" .. ".txt", "DATA") then
        local data = file.Read("zrms/" .. game.GetMap() .. "_PipeLine" .. ".txt", "DATA")
        data = util.JSONToTable(data)
        zrms_PipeLine_Rebuild(data)
        print("[Zeros Retro MiningSystem] Finished loading PipeLine entities.")
    else
        print("[Zeros Retro MiningSystem] No map data found for PipeLine entities. Please build a PipeLine first and do !zrms_savepipeline to create the data.")
    end
end

function zrmine.f.PipeLine_Delete()
    if file.Exists("zrms/" .. game.GetMap() .. "_PipeLine" .. ".txt", "DATA") then
        file.Delete("zrms/" .. game.GetMap() .. "_PipeLine" .. ".txt")
        zrms_PipeLine_Remove()
        zrmine.f.Notify(ply, "[Zeros Retro MiningSystem] Removed PipeLine entities.", 1)
    end
end

hook.Add("InitPostEntity", "zrmine_PipeLine_Rebuild_OnMapLoad", zrmine.f.PipeLine_Load)
hook.Add("PostCleanupMap", "zrmine_PipeLine_Rebuild_PostCleanUp", zrmine.f.PipeLine_Load)

hook.Add("PlayerSay", "zrms_PipeLine_Build_PlayerSay", function(aply, text)
    if string.sub(string.lower(text), 1, 18) == "!zrms_savepipeline" then
        if zrmine.f.IsAdmin(aply) then
            zrmine.f.PipeLine_Save(aply)
        else
            zrmine.f.Notify(aply, "You do not have permission to perform this action, please contact an admin.", 1)
        end
    end
end)

concommand.Add("zrmine_pipeline_save", function(aply, cmd, args)
    if zrmine.f.IsAdmin(aply) then
        zrmine.f.PipeLine_Save(aply)
    else
        zrmine.f.Notify(aply, "You do not have permission to perform this action, please contact an admin.", 1)
    end
end)

concommand.Add("zrmine_pipeline_load", function(aply, cmd, args)
    if zrmine.f.IsAdmin(aply) then
        zrms_PipeLine_Remove()
        zrmine.f.PipeLine_Load()
    else
        zrmine.f.Notify(aply, "You do not have permission to perform this action, please contact an admin.", 1)
    end
end)

concommand.Add("zrmine_pipeline_delete", function(aply, cmd, args)
    if zrmine.f.IsAdmin(aply) then
        zrmine.f.PipeLine_Delete()
    else
        zrmine.f.Notify(aply, "You do not have permission to perform this action, please contact an admin.", 1)
    end
end)

function zrmine.f.PublicEnts_Save(aply)
    local data = {}

    for u, j in pairs(ents.FindByClass("zrms_Mineentrance_base")) do
        table.insert(data, {
            class = j:GetClass(),
            pos = j:GetPos(),
            ang = j:GetAngles()
        })
    end

    for u, j in pairs(ents.FindByClass("zrms_melter")) do
        table.insert(data, {
            class = j:GetClass(),
            pos = j:GetPos(),
            ang = j:GetAngles()
        })
    end

    if not file.Exists("zrms", "DATA") then
        file.CreateDir("zrms")
    end

    file.Write("zrms/" .. game.GetMap() .. "_PublicEnts" .. ".txt", util.TableToJSON(data))
    zrmine.f.Notify(aply, "The Public Entitis have been saved for the map " .. game.GetMap() .. "!", 0)
end

function zrmine.f.PublicEnts_Load()
    if file.Exists("zrms/" .. game.GetMap() .. "_PublicEnts" .. ".txt", "DATA") then
        local data = file.Read("zrms/" .. game.GetMap() .. "_PublicEnts" .. ".txt", "DATA")
        data = util.JSONToTable(data)

        for k, v in pairs(data) do
            local ent = ents.Create(v.class)
            ent:SetPos(v.pos)
            ent:SetAngles(v.ang)
            ent:Spawn()

            if (v.class == "zrms_mineentrance_base") then
                timer.Simple(0.1, function()
                    if (IsValid(ent)) then
                        ent.PublicEntity = true
                        ent:OpenMine()
                    end
                end)
            end
        end

        print("[Zeros Retro MiningSystem] Finished loading Public entities.")
    else
        print("[Zeros Retro MiningSystem] No map data found for Public entities.")
    end
end

hook.Add("InitPostEntity", "zrmine_PublicEnts_OnMapLoad", zrmine.f.PublicEnts_Load)
hook.Add("PostCleanupMap", "zrmine_PublicEnts_PostCleanUp", zrmine.f.PublicEnts_Load)

concommand.Add("zrmine_savepublicentities", function(aply, cmd, args)
    if zrmine.f.IsAdmin(aply) then
        zrmine.f.PublicEnts_Save(aply)
    else
        zrmine.f.Notify(aply, "You do not have permission to perform this action, please contact an admin.", 1)
    end
end)
