-- GCI v.4.3® 2025© for LOCK-ON GREECE by =GR= Astr0
local GCI = {
    clients = {},
    addedMenus = {},
    activeCoalitions = {
        [coalition.side.BLUE] = true,  -- Enable/disable for BLUE
        [coalition.side.RED] = true     -- Enable/disable for RED
    },
    radarSpecs = { -- Radar nm, feet, degrees °
				-- Ground units
				["SON_9"] = { range = 55, altitude = 40000, cover = 34 },
				["FuMG-401"] = { range = 160, altitude = 40000, cover = 34 },
				["FuSe-65"] = { range = 60, altitude = 30000, cover = 113 },
				["Patriot str"] = { range = 160, altitude = 79200, cover = 103 },
				["KDO_Mod40"] = { range = 30, altitude = 30000, cover = 360 },
				["Allies_Director"] = { range = 30, altitude = 84480, cover = 360 },
				["EWR 1L119 Nebo-SVU"] = { range = 400, altitude = 57024, cover = 360 },
				["1L13 EWR"] = { range = 300, altitude = 79200, cover = 360 },
				["55G6 EWR"] = { range = 400, altitude = 114000, cover = 360 },
				["EWR 55G6U NEBO-U"] = { range = 500, altitude = 120000, cover = 360 },
				["FPS-117"] = { range = 463, altitude = 120000, cover = 360 },
				["FPS-117 Dome"] = { range = 400, altitude = 100000, cover = 360 },
				["EWR Generic radar tower"] = { range = 350, altitude = 80000, cover = 360 },
				["EWR P-37 BAR LOCK"] = { range = 350, altitude = 80000, cover = 360 },
				["HQ-7_LN_P"] = { range = 20, altitude = 18000, cover = 360 },
				["HQ-7_STR_SP"] = { range = 30, altitude = 25000, cover = 360 },
				["HQ-7_LN_SP"] = { range = 20, altitude = 18000, cover = 360 },
				["HEMTT_C-RAM_Phalanx"] = { range = 10, altitude = 9000, cover = 360 },
				["Dog Ear radar"] = { range = 35, altitude = 34000, cover = 360 },
				["M48 Chaparral"] = { range = 9, altitude = 17000, cover = 360 },
				["Hawk cwar"] = { range = 70, altitude = 60000, cover = 360 },
				["Hawk sr"] = { range = 90, altitude = 60000, cover = 360 },
				["M6 Linebacker"] = { range = 8, altitude = 15000, cover = 360 },
				["Hawk tr"] = { range = 90, altitude = 60000, cover = 360 },
				["NASAMS_Radar_MPQ64F1"] = { range = 120, altitude = 39000, cover = 360 },
				["rapier_fsa_blindfire_radar"] = { range = 30, altitude = 9800, cover = 360 },
				["rapier_fsa_launcher"] = { range = 8, altitude = 9800, cover = 360 },
				["rapier_fsa_optical_tracker_unit"] = { range = 20, altitude = 9800, cover = 360 },
				["Roland ADS"] = { range = 12, altitude = 18000, cover = 360 },
				["Roland Radar"] = { range = 35, altitude = 18000, cover = 360 },
				["S-300PS 64H6E sr"] = { range = 160, altitude = 88500, cover = 360 },
				["S-300PS 40B6MD sr"] = { range = 60, altitude = 82000, cover = 360 },
				["S-300PS 40B6M tr"] = { range = 160, altitude = 82000, cover = 360 },
				["S-300PS 5H63C 30H6_tr"] = { range = 160, altitude = 88500, cover = 360 },
				["S-300PS 40B6MD sr_19J6"] = { range = 150, altitude = 88500, cover = 360 },
				["SA-11 Buk SR 9S18M1"] = { range = 140, altitude = 82000, cover = 360 },
				["Strela-10M3"] = { range = 8, altitude = 11500, cover = 360 },
				["Tor 9A331"] = { range = 25, altitude = 20000, cover = 360 },
				["2S6 Tunguska"] = { range = 18, altitude = 11500, cover = 360 },
				["SNR_75V"] = { range = 100, altitude = 82000, cover = 360 },
				["RD_75"] = { range = 100, altitude = 98000, cover = 360 },
				["p-19 s-125 sr"] = { range = 160, altitude = 46000, cover = 360 },
				["snr s-125 tr"] = { range = 100, altitude = 59000, cover = 360 },
				["RPC_5N62V"] = { range = 400, altitude = 114000, cover = 360 },
				["RLS_19J6"] = { range = 150, altitude = 80000, cover = 360 },
				["Kub 1S91 str"] = { range = 70, altitude = 46000, cover = 360 },
				["Osa 9A33 ln"] = { range = 30, altitude = 16500, cover = 360 },
				["Gepard"] = { range = 15, altitude = 10000, cover = 360 },
				-- Naval units
				["BDK-775"] = { range = 25, altitude = 20000, cover = 360 },
				["PIOTR"] = { range = 250, altitude = 98500, cover = 360 },
				["ara_vdm"] = { range = 18, altitude = 27000, cover = 360 },
				["KUZNECOW"] = { range = 300, altitude = 98500, cover = 360 },
				["CV_1143_5"] = { range = 300, altitude = 98500, cover = 360 },
				["Forrestal"] = { range = 460, altitude = 100000, cover = 360 },
				["CVN_71"] = { range = 370, altitude = 100000, cover = 360 },
				["CVN_72"] = { range = 370, altitude = 100000, cover = 360 },
				["CVN_73"] = { range = 370, altitude = 100000, cover = 360 },
				["CVN_74"] = { range = 370, altitude = 100000, cover = 360 },
				["CVN_75"] = { range = 370, altitude = 100000, cover = 360 },
				["Essex"] = { range = 19, altitude = 1000, cover = 360 },
				["CastleClass_01"] = { range = 25, altitude = 20000, cover = 360 },
				["ALBATROS"] = { range = 278, altitude = 60000, cover = 360 },
				["MOLNIYA"] = { range = 127, altitude = 55000, cover = 360 },
				["TICONDEROG"] = { range = 324, altitude = 80000, cover = 360 },
				["MOSCOW"] = { range = 500, altitude = 82000, cover = 360 },
				["USS_Arleigh_Burke_IIa"] = { range = 370, altitude = 110000, cover = 360 },
				["Type_052B"] = { range = 250, altitude = 33000, cover = 360 },
				["Type_052C"] = { range = 350, altitude = 93000, cover = 360 },
				["La_Combattante_II"] = { range = 19, altitude = 40000, cover = 360 },
				["leander-gun-condell"] = { range = 148, altitude = 60000, cover = 360 },
				["leander-gun-lynch"] = { range = 180, altitude = 75000, cover = 360 },
				["PERRY"] = { range = 460, altitude = 80000, cover = 360 },
				["REZKY"] = { range = 200, altitude = 60000, cover = 360 },
				["NEUSTRASH"] = { range = 300, altitude = 70000, cover = 360 },
				["leander-gun-achilles"] = { range = 96, altitude = 35000, cover = 360 },
				["leander-gun-andromeda"] = { range = 370, altitude = 45000, cover = 360 },
				["leander-gun-ariadne"] = { range = 204, altitude = 65000, cover = 360 },
				["Type_054A"] = { range = 250, altitude = 70000, cover = 360 },
		},
    targetTypes = {
        -- Aerial units
        ["A-10A"] = true,
        ["A-10C_2"] = true,
        ["A-10C"] = true,
				["A-20G"] = true,
				["A-4E-C"] = true,
				["A-50"] = true,
				["AJS37"] = true,
				["AV8BNA"] = true,
				["An-26B"] = true,
				["An-30M"] = true,
				["B-17G"] = true,
				["B-1B"] = true,
				["B-52H"] = true,
				["Bf-109K-4"] = true,
				["C-101CC"] = true,
				["C-101EB"] = true,
				["C-130"] = true,
				["C-17A"] = true,
				["C-47"] = true,
				["Christen Eagle II"] = true,
				["E-2C"] = true,
				["E-3A"] = true,
				["F-117A"] = true,
				["F-14A-135-GR"] = true,
				["F-14B"] = true,
				["F-15C"] = true,
				["F-15E"] = true,
				["F-15ESE"] = true,
        ["F-16A"] = true,
				["F-16A MLU"] = true,
				["F-16C bl.50"] = true,
        ["F-16C bl.52d"] = true,
        ["F-16C_50"] = true,
				["F-4E-45MC"] = true,
        ["F-5E"] = true,
				["F-5E-3"] = true,
				["F-5E-3_FC"] = true,
				["F-86F Sabre"] = true,
				["F-86F_FC"] = true,
				["F/A-18A"] = true,
				["F/A-18C"] = true,
				["F4U-1D"] = true,
				["F4U-1D_CW"] = true,
				["FW-190A8"] = true,
				["FW-190D9"] = true,
				["Falcon_Gyrocopter"] = true,
				["H-6J"] = true,
				["Hawk"] = true,
				["Hercules"] = true,
				["I-16"] = true,
				["IL-76MD"] = true,
				["IL-78M"] = true,
				["J-11A"] = true,
				["JF-17"] = true,
				["Ju-88A4"] = true,
				["KC-135"] = true,
				["KC135MPRS"] = true,
				["KC130"] = true,
				["KC130J"] = true,
				["KJ-2000"] = true,
				["L-39C"] = true,
				["L-39ZA"] = true,
				["M-2000C"] = true,
				["MB-339A"] = true,
				["MQ-9 Reaper"] = true,
				["MiG-15bis"] = true,
				["MiG-15bis_FC"] = true,
				["MiG-19P"] = true,
				["MiG-21Bis"] = true,
				["MiG-23MLD"] = true,
				["MiG-25PD"] = true,
				["MiG-25RBT"] = true,
				["MiG-27K"] = true,
				["MiG-29A"] = true,
				["MiG-29G"] = true,
				["MiG-29S"] = true,
				["MiG-31"] = true,
				["Mirage 2000-5"] = true,
				["Mirage-F1AD"] = true,
				["Mirage-F1AZ"] = true,
				["Mirage-F1B"] = true,
				["Mirage-F1BD"] = true,
				["Mirage-F1BE"] = true,
				["Mirage-F1BQ"] = true,
				["Mirage-F1C"] = true,
				["Mirage-F1C-200"] = true,
				["Mirage-F1CE"] = true,
				["Mirage-F1CG"] = true,
				["Mirage-F1CH"] = true,
				["Mirage-F1CJ"] = true,
				["Mirage-F1CK"] = true,
				["Mirage-F1CR"] = true,
				["Mirage-F1CT"] = true,
				["Mirage-F1CZ"] = true,
				["Mirage-F1DDA"] = true,
				["Mirage-F1ED"] = true,
				["Mirage-F1EDA"] = true,
				["Mirage-F1EE"] = true,
				["Mirage-F1EH"] = true,
				["Mirage-F1EQ"] = true,
				["Mirage-F1JA"] = true,
				["Mirage-F1M-CE"] = true,
				["Mirage-F1M-EE"] = true,
				["MosquitoFBMkVI"] = true,
				["P-47D-30"] = true,
				["P-47D-30bl1"] = true,
				["P-47D-40"] = true,
				["P-51D"] = true,
				["P-51D-30-NA"] = true,
				["RQ-1A Predator"] = true,
				["S-3B"] = true,
				["S-3B Tanker"] = true,
				["SpitfireLFMkIX"] = true,
				["SpitfireLFMkIXCW"] = true,
				["Su-17M4"] = true,
				["Su-24M"] = true,
				["Su-24MR"] = true,
				["Su-25"] = true,
				["Su-25T"] = true,
				["Su-25TM"] = true,
				["Su-27"] = true,
				["Su-30"] = true,
				["Su-33"] = true,
				["Su-34"] = true,
				["TF-51D"] = true,
				["Tornado GR4"] = true,
				["Tornado IDS"] = true,
				["Tu-142"] = true,
				["Tu-160"] = true,
				["Tu-22M3"] = true,
				["Tu-95MS"] = true,
				["WingLoong-I"] = true,
				["Yak-40"] = true,
				["Yak-52"] = true,
        -- Rotary units
				["AH-1W"] = true,
				["AH-64A"] = true,
				["AH-64D"] = true,
        ["AH-64D_BLK_II"] = true,
        ["CH-47D"] = true,
				["CH-47Fbl1"] = true,
				["CH-53E"] = true,
				["Ka-27"] = true,
        ["Ka-50"] = true,
        ["Ka-50_3"] = true,
        ["Ka-52"] = true,				
        ["Mi-24P"] = true,
        ["Mi-24V"] = true,
				["Mi-26"] = true,
				["Mi-28N"] = true,
        ["Mi-8MT"] = true,
				["OH-58D"] = true,
				["OH58D"] = true,
        ["SA342L"] = true,
        ["SA342M"] = true,
				["SA342Minigun"] = true,
        ["SA342Mistral"] = true,
				["SH-60B"] = true,
				["UH-1H"] = true,
				["UH-60A"] = true,
				["UH-60L"] = true,
    }
}

-- Checks if a unit is a radar
local function isRadarUnit(unit)
    return GCI.radarSpecs[unit:getTypeName()] ~= nil
end

-- Checks if a radar is active
local function isRadarActive(unit)
    if not unit.getRadar then return false end
    local status, radar = pcall(function() return unit:getRadar() end)
    if not status or not radar then return false end
    if type(radar) == "boolean" then return radar end
    if radar.isOn then return radar:isOn() end
    return true
end

-- Gets active radars for a side
local function getActiveRadars(side)
    local radars = {}
    for _, group in pairs(coalition.getGroups(side)) do
        for _, unit in pairs(group:getUnits()) do
            if unit:isExist() and unit:getLife() > 0 and isRadarUnit(unit) and isRadarActive(unit) then
                table.insert(radars, unit)
            end
        end
    end
    return radars
end

-- Checks if a target is within a radar's coverage
local function isTargetDetectedByRadar(radar, target)
    if not target:isExist() or target:getLife() <= 0 then return false end
    local tgtPos = target:getPoint()
    local tgtAlt = tgtPos.y / 0.3048
    local spec = GCI.radarSpecs[radar:getTypeName()]
    local dist = mist.utils.get2DDist(radar:getPoint(), tgtPos) / 1852
    return dist <= spec.range and tgtAlt <= spec.altitude
end

-- Checks if a unit is a valid target type we want to track
local function isValidTarget(unit)
    if not unit:isExist() or unit:getLife() <= 0 then return false end
    local typeName = unit:getTypeName()
    return GCI.targetTypes[typeName] ~= nil
end

-- Calculates BRAA information (Bearing, Range, Altitude, Aspect)
local function calculateBRAA(clientUnit, targetUnit)
    local clientPos = clientUnit:getPoint()
    local targetPos = targetUnit:getPoint()
    local dx = targetPos.x - clientPos.x
    local dz = targetPos.z - clientPos.z
    local bearing = math.floor((math.deg(math.atan2(dz, dx)) + 360) % 360 + 0.5)
    local range = math.floor(mist.utils.get2DDist(clientPos, targetPos) / 1852 + 0.5)
    local altitude = math.floor(targetPos.y / 0.3048 + 0.5)
    local tgtVel = targetUnit:getVelocity()
    local tgtHeading = math.deg(math.atan2(tgtVel.z, tgtVel.x)) % 360
    local relAngle = math.abs(tgtHeading - bearing)
    if relAngle > 180 then relAngle = 360 - relAngle end
    local aspect = "MA"
    if relAngle < 45 then aspect = "HA"
    elseif relAngle > 135 then aspect = "LA" end
    return bearing, range, altitude, aspect
end

-- Formats BRAA information in requested format
local function formatBRAA(index, bearing, range, altitude, aspect)
    local angels = math.floor(altitude / 1000 + 0.5)
    return string.format("Bandits at %03d° in %2d Nm for Angel %02d | %s", 
        bearing, range, angels, aspect)
end

-- Gets player settings
local function getPlayerSettings(playerName, groupName)
    local key = playerName .. "_" .. groupName
    local prev = GCI.clients[key] or {}
    GCI.clients[key] = {
        enabled = prev.enabled ~= false,
        maxTargets = prev.maxTargets or 2,
        maxRangeNm = prev.maxRangeNm or 80,
        refreshInterval = prev.refreshInterval or 30,
        lastSent = prev.lastSent or 0
    }
    return GCI.clients[key]
end

-- Sends BRAA information to players
local function sendBRAA()
    for currentCoalition, isActive in pairs(GCI.activeCoalitions) do
        if isActive then
            local radars = getActiveRadars(currentCoalition)
            local enemyCoalition = 3 - currentCoalition  -- 1 becomes 2, 2 becomes 1
            
            for _, group in pairs(coalition.getGroups(currentCoalition)) do
                local groupName = group:getName()
                for _, client in pairs(group:getUnits()) do
                    if client:isExist() and client:getPlayerName() then
                        local name = client:getPlayerName()
                        local cs = getPlayerSettings(name, groupName)
                        local now = timer.getTime()
                        
                        if cs.enabled and now - cs.lastSent >= cs.refreshInterval then
                            cs.lastSent = now
                            local message = string.format(
                                "*** GCI v.3.9® | Your Coalition: %s ***\n"..
                                "*** Max Contacts: %d | Max Range: %dNm | Interval: %ds ***\n\n",
                                currentCoalition == coalition.side.BLUE and "BLUE" or "RED",
                                cs.maxTargets, cs.maxRangeNm, cs.refreshInterval
                            )
                            
                            if #radars == 0 then
                                message = message .. "No active friendly radar available.\n"
                            else
                                local contacts = {}
                                
                                for _, tgtGroup in pairs(coalition.getGroups(enemyCoalition)) do
                                    local groupUnits = tgtGroup:getUnits()
                                    if #groupUnits > 0 and groupUnits[1]:isActive() then
                                        for _, tgt in pairs(groupUnits) do
                                            if isValidTarget(tgt) then
                                                local dist = mist.utils.get2DDist(client:getPoint(), tgt:getPoint()) / 1852
                                                if dist <= cs.maxRangeNm then
                                                    for _, radar in ipairs(radars) do
                                                        if isTargetDetectedByRadar(radar, tgt) then
                                                            table.insert(contacts, tgt)
                                                            break
                                                        end
                                                    end
                                                end
                                            end
                                        end
                                    end
                                end
                                
                                if #contacts == 0 then
                                    message = message .. "No valid hostile contacts detected.\n"
                                else
                                    table.sort(contacts, function(a, b)
                                        return mist.utils.get2DDist(client:getPoint(), a:getPoint()) <
                                               mist.utils.get2DDist(client:getPoint(), b:getPoint())
                                    end)
                                    
                                    for i = 1, math.min(cs.maxTargets, #contacts) do
                                        local brg, rng, alt, asp = calculateBRAA(client, contacts[i])
                                        message = message .. formatBRAA(i, brg, rng, alt, asp) .. "\n"
                                    end
                                end
                            end
                            
                            trigger.action.outTextForUnit(client:getID(), message, 10)
                        end
                    end
                end
            end
        end
    end
    
    return timer.getTime() + 1
end

-- Creates the radio menu for GCI
local function setupRadioMenu()
    for _, side in ipairs({coalition.side.BLUE, coalition.side.RED}) do
        for _, group in pairs(coalition.getGroups(side)) do
            local groupID = group:getID()
            local groupName = group:getName()
            if not GCI.addedMenus[groupID] then
                local units = group:getUnits()
                local hasPlayer = false
                for _, unit in ipairs(units) do
                    if unit:isExist() and unit:getPlayerName() then
                        hasPlayer = true
                        break
                    end
                end
                if hasPlayer then
                    local sm = missionCommands.addSubMenuForGroup(groupID, "GCI Settings")
                    
                    local function addCommand(title, func)
                        missionCommands.addCommandForGroup(groupID, title, sm, function()
                            for _, unit in ipairs(units) do
                                local name = unit:getPlayerName()
                                if name then func(unit, name, groupName) end
                            end
                        end)
                    end
                    
                    addCommand("+1 Target", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        if s.maxTargets < 10 then
                            s.maxTargets = s.maxTargets + 1
                            trigger.action.outTextForUnit(unit:getID(), "GCI max targets set to "..s.maxTargets, 5)
                        end
                    end)
                    
                    addCommand("-1 Target", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        if s.maxTargets > 1 then
                            s.maxTargets = s.maxTargets - 1
                            trigger.action.outTextForUnit(unit:getID(), "GCI max targets set to "..s.maxTargets, 5)
                        end
                    end)
                    
                    addCommand("+10Nm Range", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        if s.maxRangeNm < 200 then
                            s.maxRangeNm = s.maxRangeNm + 10
                            trigger.action.outTextForUnit(unit:getID(), "GCI max range set to "..s.maxRangeNm.."Nm", 5)
                        end
                    end)
                    
                    addCommand("-10Nm Range", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        if s.maxRangeNm > 10 then
                            s.maxRangeNm = s.maxRangeNm - 10
                            trigger.action.outTextForUnit(unit:getID(), "GCI max range set to "..s.maxRangeNm.."Nm", 5)
                        end
                    end)
                    
                    addCommand("+15s Refresh", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        if s.refreshInterval < 120 then
                            s.refreshInterval = s.refreshInterval + 15
                            trigger.action.outTextForUnit(unit:getID(), "GCI refresh interval set to "..s.refreshInterval.."s", 5)
                        end
                    end)
                    
                    addCommand("-15s Refresh", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        if s.refreshInterval > 15 then
                            s.refreshInterval = s.refreshInterval - 15
                            trigger.action.outTextForUnit(unit:getID(), "GCI refresh interval set to "..s.refreshInterval.."s", 5)
                        end
                    end)
                    
                    addCommand("Enable GCI", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        s.enabled = true
                        trigger.action.outTextForUnit(unit:getID(), "GCI enabled", 5)
                    end)
                    
                    addCommand("Disable GCI", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        s.enabled = false
                        trigger.action.outTextForUnit(unit:getID(), "GCI disabled", 5)
                    end)
                    
                    addCommand("Show Settings", function(unit, name, groupName)
                        local s = getPlayerSettings(name, groupName)
                        local msg = string.format(
                            "Current GCI Settings:\n"..
                            "Max Targets: %d\n"..
                            "Max Range: %dNm\n"..
                            "Refresh: %ds\n"..
                            "Status: %s",
                            s.maxTargets, s.maxRangeNm, s.refreshInterval,
                            s.enabled and "ENABLED" or "DISABLED"
                        )
                        trigger.action.outTextForUnit(unit:getID(), msg, 10)
                    end)
                    
                    GCI.addedMenus[groupID] = true
                end
            end
        end
    end
end

-- Calls the radio menu setup at regular intervals
local function loopRadioMenu()
    setupRadioMenu()
    return timer.getTime() + 10
end

-- Initialization
setupRadioMenu()
timer.scheduleFunction(sendBRAA, {}, timer.getTime() + 1)
timer.scheduleFunction(loopRadioMenu, {}, timer.getTime() + 10)