#include "..\script_component.hpp"

/*
 * Author: IDSolutions
 * Manages attack-only dynamic mission generation.
 *
 * Arguments:
 * None
 *
 * Return Value:
 * None
 *
 * Example:
 * [] call forge_server_task_fnc_missionManager
 *
 * Public: No
 */

#pragma hemtt ignore_variables ["_self"]
GVAR(MissionManagerBaseClass) = compileFinal createHashMapFromArray [
    ["#type", "MissionManagerBaseClass"],
    ["#create", compileFinal {
        private _missionConfig = missionConfigFile >> "CfgMissions";
        _self set ["missionConfig", _missionConfig];
        _self set ["locationsConfig", (_missionConfig >> "Locations")];
        _self set ["aiGroupsConfig", (_missionConfig >> "AIGroups")];
        _self set ["attackConfig", (_missionConfig >> "MissionTypes" >> "Attack")];
        _self set ["maxConcurrentMissions", getNumber (_missionConfig >> "maxConcurrentMissions")];
        _self set ["missionInterval", getNumber (_missionConfig >> "missionInterval")];
        _self set ["recentLocationRegistry", createHashMap];
        _self set ["activeMissionRegistry", createHashMap];
    }],
    ["getMissionInterval", compileFinal {
        private _interval = _self getOrDefault ["missionInterval", 300];
        if (_interval <= 0) then { _interval = 300; };
        _interval
    }],
    ["getMaxConcurrentMissions", compileFinal {
        private _maxConcurrent = _self getOrDefault ["maxConcurrentMissions", 1];
        if (_maxConcurrent <= 0) then { _maxConcurrent = 1; };
        private _attackLocationCount = _self call ["getAttackLocationCount", []];
        if (_attackLocationCount > 0) then {
            _maxConcurrent = _maxConcurrent min _attackLocationCount;
        };
        _maxConcurrent
    }],
    ["getAttackLocationCount", compileFinal {
        private _locationsConfig = _self getOrDefault ["locationsConfig", configNull];
        if (isNull _locationsConfig) exitWith { 0 };

        private _count = 0;
        {
            if ("attack" in getArray (_x >> "suitable")) then {
                _count = _count + 1;
            };
        } forEach ("true" configClasses _locationsConfig);

        _count
    }],
    ["getLocationReuseCooldown", compileFinal {
        private _missionConfig = _self getOrDefault ["missionConfig", configNull];
        private _cooldown = getNumber (_missionConfig >> "locationReuseCooldown");
        if (_cooldown <= 0) then { _cooldown = 900; };
        _cooldown
    }],
    ["getActiveMissionIds", compileFinal {
        private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
        keys _activeMissionRegistry
    }],
    ["getActiveLocationKeys", compileFinal {
        private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
        private _locationKeys = [];
        {
            private _locationKey = _y getOrDefault ["locationKey", ""];
            if (_locationKey isNotEqualTo "") then {
                _locationKeys pushBackUnique _locationKey;
            };
        } forEach _activeMissionRegistry;
        _locationKeys
    }],
    ["buildAttackSpawnPosition", compileFinal {
        params [["_locationConfig", configNull, [configNull]]];

        if (isNull _locationConfig) exitWith { [0, 0, 0] };

        private _center = getArray (_locationConfig >> "position");
        private _radius = getNumber (_locationConfig >> "radius");
        if (_radius <= 0) exitWith { _center };

        private _spawnPosition = +_center;
        private _attempts = 0;
        while { _attempts < 8 } do {
            private _angle = random 360;
            private _distance = (_radius * 0.2) + random (_radius * 0.65);
            private _candidate = [
                (_center # 0) + ((sin _angle) * _distance),
                (_center # 1) + ((cos _angle) * _distance),
                _center param [2, 0]
            ];

            if !(surfaceIsWater _candidate) exitWith {
                _spawnPosition = _candidate;
            };

            _attempts = _attempts + 1;
        };

        _spawnPosition
    }],
    ["selectAttackLocation", compileFinal {
        private _locationsConfig = _self getOrDefault ["locationsConfig", configNull];
        private _locations = [];
        private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap];
        private _activeLocationKeys = _self call ["getActiveLocationKeys", []];
        private _reuseCooldown = _self call ["getLocationReuseCooldown", []];
        private _now = serverTime;

        {
            private _locationKey = configName _x;
            private _lastUsed = _recentLocationRegistry getOrDefault [_locationKey, -1];
            private _isCoolingDown = (_lastUsed >= 0) && { (_now - _lastUsed) < _reuseCooldown };

            if (
                "attack" in getArray (_x >> "suitable")
                && { !(_locationKey in _activeLocationKeys) }
                && { !_isCoolingDown }
            ) then {
                _locations pushBack _x;
            };
        } forEach ("true" configClasses _locationsConfig);

        if (_locations isEqualTo []) then {
            {
                if ("attack" in getArray (_x >> "suitable") && { !(configName _x in _activeLocationKeys) }) then {
                    _locations pushBack _x;
                };
            } forEach ("true" configClasses _locationsConfig);
        };

        if (_locations isEqualTo []) exitWith { createHashMap };

        private _location = selectRandom _locations;
        createHashMapFromArray [
            ["config", _location],
            ["key", configName _location],
            ["position", _self call ["buildAttackSpawnPosition", [_location]]]
        ]
    }],
    ["spawnAttackGroup", compileFinal {
        params [["_position", [0, 0, 0], [[]]]];

        private _aiGroupsConfig = _self getOrDefault ["aiGroupsConfig", configNull];
        private _attackConfig = _self getOrDefault ["attackConfig", configNull];
        private _groups = [];
        {
            if ("attack" in getArray (_x >> "suitable")) then {
                _groups pushBack _x;
            };
        } forEach ("true" configClasses _aiGroupsConfig);

        if (_groups isEqualTo []) exitWith { grpNull };

        private _groupConfig = selectRandom _groups;
        private _side = getText (_groupConfig >> "side");
        private _group = createGroup (call compile _side);
        private _minUnits = getNumber (_attackConfig >> "minUnits");
        private _maxUnits = getNumber (_attackConfig >> "maxUnits");
        if (_minUnits <= 0) then { _minUnits = 4; };
        if (_maxUnits < _minUnits) then { _maxUnits = _minUnits; };
        private _targetUnitCount = floor random [ _minUnits, ceil ((_minUnits + _maxUnits) / 2), _maxUnits + 1 ];

        private _unitPool = [];
        {
            if ((getText (_x >> "side")) isNotEqualTo _side) then { continue; };

            {
                _unitPool pushBack createHashMapFromArray [
                    ["vehicle", getText (_x >> "vehicle")],
                    ["rank", getText (_x >> "rank")],
                    ["position", getArray (_x >> "position")]
                ];
            } forEach ("true" configClasses (_x >> "Units"));
        } forEach _groups;

        if (_unitPool isEqualTo []) exitWith {
            deleteGroup _group;
            grpNull
        };

        private _leaderPool = _unitPool select {
            toUpperANSI (_x getOrDefault ["rank", "PRIVATE"]) in ["SERGEANT", "LIEUTENANT", "CAPTAIN", "MAJOR", "COLONEL"]
        };
        if (_leaderPool isEqualTo []) then { _leaderPool = +_unitPool; };

        private _spawnDefs = [selectRandom _leaderPool];
        for "_i" from 1 to (_targetUnitCount - 1) do {
            _spawnDefs pushBack (selectRandom _unitPool);
        };

        {
            private _unitClass = _x getOrDefault ["vehicle", ""];
            if (_unitClass isEqualTo "") then { continue; };

            private _unitOffset = +(_x getOrDefault ["position", [0, 0, 0]]);
            if (count _unitOffset < 3) then { _unitOffset resize 3; };
            _unitOffset set [0, (_unitOffset # 0) + (random 6 - 3)];
            _unitOffset set [1, (_unitOffset # 1) + (random 6 - 3)];

            private _unit = _group createUnit [_unitClass, _position vectorAdd _unitOffset, [], 0, "NONE"];
            _unit setRank (_x getOrDefault ["rank", "PRIVATE"]);
        } forEach _spawnDefs;

        _group
    }],
    ["rollRewards", compileFinal {
        private _attackConfig = _self getOrDefault ["attackConfig", configNull];
        private _equipmentRewards = [];
        private _supplyRewards = [];
        private _weaponRewards = [];
        private _vehicleRewards = [];
        private _specialRewards = [];

        {
            private _category = _x;
            {
                _x params ["_item", "_chance"];
                if (random 1 < _chance) then {
                    switch (_category) do {
                        case "equipment": { _equipmentRewards pushBack _item; };
                        case "supplies": { _supplyRewards pushBack _item; };
                        case "weapons": { _weaponRewards pushBack _item; };
                        case "vehicles": { _vehicleRewards pushBack _item; };
                        case "special": { _specialRewards pushBack _item; };
                    };
                };
            } forEach (getArray (_attackConfig >> "Rewards" >> _category));
        } forEach ["equipment", "supplies", "weapons", "vehicles", "special"];

        createHashMapFromArray [
            ["equipment", _equipmentRewards],
            ["supplies", _supplyRewards],
            ["weapons", _weaponRewards],
            ["vehicles", _vehicleRewards],
            ["special", _specialRewards]
        ]
    }],
    ["startAttackMission", compileFinal {
        private _attackConfig = _self getOrDefault ["attackConfig", configNull];
        private _locationData = _self call ["selectAttackLocation"];
        if (_locationData isEqualTo createHashMap) exitWith { "" };

        private _locationKey = _locationData getOrDefault ["key", ""];
        private _position = _locationData getOrDefault ["position", [0, 0, 0]];
        private _group = _self call ["spawnAttackGroup", [_position]];
        if (isNull _group) exitWith { "" };

        private _units = units _group;
        if (_units isEqualTo []) exitWith {
            deleteGroup _group;
            ""
        };

        private _taskID = format ["task_attack_%1", round (diag_tickTime * 1000)];
        private _rewardRange = getArray (_attackConfig >> "Rewards" >> "money");
        private _reputationRange = getArray (_attackConfig >> "Rewards" >> "reputation");
        private _penaltyRange = getArray (_attackConfig >> "penalty");
        private _timeRange = getArray (_attackConfig >> "timeLimit");
        private _rewards = _self call ["rollRewards"];

        private _success = [
            "attack",
            _taskID,
            _position,
            format ["Attack: %1", _locationKey],
            format ["Eliminate hostile forces operating near %1.", _locationKey],
            createHashMapFromArray [["targets", _units]],
            createHashMapFromArray [
                ["limitFail", 0],
                ["limitSuccess", count _units],
                ["funds", _rewardRange call BFUNC(randomNum)],
                ["ratingFail", _penaltyRange call BFUNC(randomNum)],
                ["ratingSuccess", _reputationRange call BFUNC(randomNum)],
                ["endSuccess", false],
                ["endFail", false],
                ["timeLimit", _timeRange call BFUNC(randomNum)],
                ["equipment", _rewards get "equipment"],
                ["supplies", _rewards get "supplies"],
                ["weapons", _rewards get "weapons"],
                ["vehicles", _rewards get "vehicles"],
                ["special", _rewards get "special"]
            ],
            0,
            "",
            "mission_manager"
        ] call FUNC(startTask);

        if !(_success) exitWith { "" };

        private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
        _activeMissionRegistry set [_taskID, createHashMapFromArray [
            ["locationKey", _locationKey],
            ["startedAt", serverTime]
        ]];
        _self set ["activeMissionRegistry", _activeMissionRegistry];

        _taskID
    }],
    ["completeMission", compileFinal {
        params [["_taskID", "", [""]]];

        if (_taskID isEqualTo "") exitWith { false };

        private _activeMissionRegistry = _self getOrDefault ["activeMissionRegistry", createHashMap];
        private _missionRecord = _activeMissionRegistry getOrDefault [_taskID, createHashMap];
        private _locationKey = _missionRecord getOrDefault ["locationKey", ""];

        _activeMissionRegistry deleteAt _taskID;
        _self set ["activeMissionRegistry", _activeMissionRegistry];

        if (_locationKey isNotEqualTo "") then {
            private _recentLocationRegistry = _self getOrDefault ["recentLocationRegistry", createHashMap];
            _recentLocationRegistry set [_locationKey, serverTime];
            _self set ["recentLocationRegistry", _recentLocationRegistry];
        };

        true
    }]
];

GVAR(MissionManager) = createHashMapObject [GVAR(MissionManagerBaseClass)];

[{
    {
        private _status = GVAR(TaskStore) call ["getTaskStatus", [_x]];
        private _hasCatalogEntry = GVAR(TaskStore) call ["hasTaskCatalogEntry", [_x]];
        if (_status in ["succeeded", "failed"] || { _status isEqualTo "" && { !_hasCatalogEntry } }) then {
            GVAR(MissionManager) call ["completeMission", [_x]];
            GVAR(TaskStore) call ["clearTaskStatus", [_x]];
        };
    } forEach (GVAR(MissionManager) call ["getActiveMissionIds", []]);

    if (count (GVAR(MissionManager) call ["getActiveMissionIds", []]) >= (GVAR(MissionManager) call ["getMaxConcurrentMissions", []])) exitWith {};

    private _taskID = GVAR(MissionManager) call ["startAttackMission", []];
    if (_taskID isEqualTo "") exitWith {
        ["WARNING", "Mission manager failed to start an attack mission."] call EFUNC(common,log);
    };

    ["INFO", format ["Mission manager started attack mission %1.", _taskID]] call EFUNC(common,log);
}, GVAR(MissionManager) call ["getMissionInterval", []], []] call CFUNC(addPerFrameHandler);
