// ACE - Common
// #define ENABLE_PERFORMANCE_COUNTERS
// #define DEBUG_MODE_FULL
#include "script_component.hpp"

//////////////////////////////////////////////////
// Get Map Data
//////////////////////////////////////////////////

//Find MGRS zone and 100km grid for current map
[] call FUNC(getMGRSdata);
//Prepare variables for FUNC(getMapGridFromPos)/FUNC(getMapPosFromGrid)
[] call FUNC(getMapGridData);


//////////////////////////////////////////////////
// Eventhandlers
//////////////////////////////////////////////////

//Status Effect EHs:
["setStatusEffect", {_this call FUNC(statusEffect_set)}] call FUNC(addEventHandler);
["forceWalk", false, ["ACE_SwitchUnits", "ACE_Attach", "ACE_dragging", "ACE_Explosives", "ACE_Ladder", "ACE_Sandbag", "ACE_refuel", "ACE_rearm", "ACE_dragging"]] call FUNC(statusEffect_addType);
["blockSprint", false, []] call FUNC(statusEffect_addType);
["setCaptive", true, [QEGVAR(captives,Handcuffed), QEGVAR(captives,Surrendered), QEGVAR(medical,unconscious)]] call FUNC(statusEffect_addType);
["blockDamage", false, ["fixCollision"]] call FUNC(statusEffect_addType);
["blockEngine", false, ["ACE_Refuel"]] call FUNC(statusEffect_addType);

["forceWalk", {
    params ["_object", "_set"];
    TRACE_2("forceWalk EH",_object,_set);
    _object forceWalk (_set > 0);
}] call FUNC(addEventHandler);
["blockSprint", { //Name reversed from `allowSprint` because we want NOR logic
    params ["_object", "_set"];
    TRACE_2("blockSprint EH",_object,_set);
    _object allowSprint (_set == 0);
}] call FUNC(addEventHandler);
["setCaptive", {
    params ["_object", "_set"];
    TRACE_2("setCaptive EH",_object,_set);
    _object setCaptive (_set > 0);
}] call FUNC(addEventHandler);
["blockDamage", { //Name reversed from `allowDamage` because we want NOR logic
    params ["_object", "_set"];
    if ((_object isKindOf "CAManBase") && {(["ace_medical"] call FUNC(isModLoaded))}) then {
        TRACE_2("blockDamage EH (using medical)",_object,_set);
       _object setvariable [QEGVAR(medical,allowDamage), (_set == 0), true];
    } else {
        TRACE_2("blockDamage EH (using allowDamage)",_object,_set);
       _object allowDamage (_set == 0);
    };
}] call FUNC(addEventHandler);
["blockEngine", {
    params ["_vehicle", "_set"];
    _vehicle setVariable [QGVAR(blockEngine), _set > 0, true];
    _vehicle engineOn false;
}] call FUNC(addEventHandler);

//Add a fix for BIS's zeus remoteControl module not reseting variables on DC when RC a unit
//This variable is used for isPlayer checks
if (isServer) then {
    addMissionEventHandler ["HandleDisconnect", {
        params ["_dcPlayer"];
        private _zeusLogic = getAssignedCuratorLogic _dcPlayer;
        if ((!isNil "_zeusLogic") && {!isNull _zeusLogic}) then {
            {
                if ((_x getvariable ["bis_fnc_moduleRemoteControl_owner", objnull]) isEqualTo _dcPlayer) exitWith {
                    ACE_LOGINFO_3("[%1] DC - Was Zeus [%2] while controlling unit [%3] - manually clearing `bis_fnc_moduleRemoteControl_owner`", [_x] call FUNC(getName), _dcPlayer, _x);
                    _x setVariable ["bis_fnc_moduleRemoteControl_owner", nil, true];
                };
                nil
            } count (curatorEditableObjects  _zeusLogic);
        };
    }];
};

// Listens for global "SettingChanged" events, to update the force status locally
["SettingChanged", {
    params ["_name", "_value", "_force"];

    if (_force) then {
        private _settingData = [_name] call FUNC(getSettingData);

        if (_settingData isEqualTo []) exitWith {};

        _settingData set [6, _force];
    };
}] call FUNC(addEventhandler);


// Event to log Fix Headbug output
["HeadbugFixUsed", {
    params ["_profileName", "_animation"];
    ACE_LOGINFO_2("Headbug Used: Name: %1, Animation: %2",_profileName,_animation);
}] call FUNC(addEventHandler);

["fixCollision", FUNC(fixCollision)] call FUNC(addEventhandler);
["fixFloating", FUNC(fixFloating)] call FUNC(addEventhandler);
["fixPosition", FUNC(fixPosition)] call FUNC(addEventhandler);

["loadPersonEvent", FUNC(loadPersonLocal)] call FUNC(addEventhandler);
["unloadPersonEvent", FUNC(unloadPersonLocal)] call FUNC(addEventhandler);

["lockVehicle", {
    _this setVariable [QGVAR(lockStatus), locked _this];
    _this lock 2;
}] call FUNC(addEventhandler);

["unlockVehicle", {
    _this lock (_this getVariable [QGVAR(lockStatus), locked _this]);
}] call FUNC(addEventhandler);

["setDir", {(_this select 0) setDir (_this select 1)}] call FUNC(addEventhandler);
["setFuel", {(_this select 0) setFuel (_this select 1)}] call FUNC(addEventhandler);
["engineOn", {(_this select 0) engineOn (_this select 1)}] call FUNC(addEventhandler);
["setSpeaker", {(_this select 0) setSpeaker (_this select 1)}] call FUNC(addEventhandler);
["selectLeader", {(_this select 0) selectLeader (_this select 1)}] call FUNC(addEventHandler);
["setVelocity", {(_this select 0) setVelocity (_this select 1)}] call FUNC(addEventHandler);
["playMove", {(_this select 0) playMove (_this select 1)}] call FUNC(addEventHandler);
["playMoveNow", {(_this select 0) playMoveNow (_this select 1)}] call FUNC(addEventHandler);
["switchMove", {(_this select 0) switchMove (_this select 1)}] call FUNC(addEventHandler);
["setVectorDirAndUp", {(_this select 0) setVectorDirAndUp (_this select 1)}] call FUNC(addEventHandler);
["setVanillaHitPointDamage", {(_this select 0) setHitPointDamage (_this select 1)}] call FUNC(addEventHandler);

// Request framework
[QGVAR(requestCallback), FUNC(requestCallback)] call FUNC(addEventHandler);
[QGVAR(receiveRequest), FUNC(receiveRequest)] call FUNC(addEventHandler);

[QGVAR(systemChatGlobal), {systemChat _this}] call FUNC(addEventHandler);

if (isServer) then {
    ["hideObjectGlobal", {(_this select 0) hideObjectGlobal (_this select 1)}] call FUNC(addEventHandler);
    ["enableSimulationGlobal", {(_this select 0) enableSimulationGlobal (_this select 1)}] call FUNC(addEventHandler);
    ["setOwner", {(_this select 0) setOwner (_this select 1)}] call FUNC(addEventHandler);
    [QGVAR(serverLog), FUNC(serverLog)] call FUNC(addEventHandler);
};


//////////////////////////////////////////////////
// Set up remote execution
//////////////////////////////////////////////////

// ACE events
"ACEg" addPublicVariableEventHandler {_this call FUNC(_handleNetEvent)};
"ACEc" addPublicVariableEventHandler {_this call FUNC(_handleNetEvent)};

// Synced ACE events
// Handle JIP scenario
if (!isServer) then {
    ["PlayerJip", {
        ACE_LOGINFO("JIP event synchronization initialized");
        ["SEH_all", [player]] call FUNC(serverEvent);
    }] call FUNC(addEventHandler);
} else {
    ["SEH_all", FUNC(_handleRequestAllSyncedEvents)] call FUNC(addEventHandler);
};

["SEH", FUNC(_handleSyncedEvent)] call FUNC(addEventHandler);
["SEH_s", FUNC(_handleRequestSyncedEvent)] call FUNC(addEventHandler);

if (isServer) then {
    [FUNC(syncedEventPFH), 0.5, []] call CBA_fnc_addPerFrameHandler;
};

// @todo deprecated
QGVAR(remoteFnc) addPublicVariableEventHandler {
    (_this select 1) call FUNC(execRemoteFnc);
};

// @todo figure out what this does.
[missionNamespace] call FUNC(executePersistent);


//////////////////////////////////////////////////
// Check files, previous installed version etc.
//////////////////////////////////////////////////

private _currentVersion = getText (configFile >> "CfgPatches" >> QUOTE(ADDON) >> "version");
private _previousVersion = profileNamespace getVariable ["ACE_VersionNumberString", ""];

// check previous version number from profile
if (_currentVersion != _previousVersion) then {
    // do something

    profileNamespace setVariable ["ACE_VersionNumberString", _currentVersion];
};

call FUNC(checkFiles);


//////////////////////////////////////////////////
// Set up SettingsInitialized eventhandler
//////////////////////////////////////////////////

["SettingsInitialized", {
    [
        GVAR(checkPBOsAction),
        GVAR(checkPBOsCheckAll),
        GVAR(checkPBOsWhitelist)
    ] call FUNC(checkPBOs)
}] call FUNC(addEventHandler);

// Create a pfh to wait until all postinits are ready and settings are initialized
[{
    params ["_args"];

    _args params ["_waitingMsgSent"];

    // If post inits are not ready then wait
    if !(SLX_XEH_MACHINE select 8) exitWith {};

    // If settings are not initialized then wait
    if (isNil QGVAR(settings) || {!isServer && isNil QEGVAR(modules,serverModulesRead)}) exitWith {
        if !(_waitingMsgSent) then {
            _args set [0, true];
            ACE_LOGINFO("Waiting on settings from server...");
        };
    };

    [_this select 1] call CBA_fnc_removePerFrameHandler;

    ACE_LOGINFO("Settings received from server.");

    if (isServer) then { //read settings from paramsArray
        [] call FUNC(readSettingsFromParamsArray);
    };
    // Event so that ACE_Modules have their settings loaded:
    ["InitSettingsFromModules", []] call FUNC(localEvent);

    if (isServer) then {
        // Publish all settings data after all configs and modules are read
        publicVariable QGVAR(settings);
    };

    // Load user settings from profile
    if (hasInterface) then {
        call FUNC(loadSettingsFromProfile);
        call FUNC(loadSettingsLocalizedText);
    };

    ACE_LOGINFO("Settings initialized.");

    //Event that settings are safe to use:
    ["SettingsInitialized", []] call FUNC(localEvent);

    //Set init finished and run all delayed functions:
    GVAR(settingsInitFinished) = true;
    ACE_LOGINFO_1("%1 delayed functions running.",count GVAR(runAtSettingsInitialized));

    {
        (_x select 1) call (_x select 0);
        false
    } count GVAR(runAtSettingsInitialized);

    GVAR(runAtSettingsInitialized) = nil; //cleanup
}, 0, [false]] call CBA_fnc_addPerFrameHandler;


/***************************************************************************/
/***************************************************************************/
/** everything that only player controlled machines need, goes below this **/
/***************************************************************************/
/***************************************************************************/

if (!hasInterface) exitWith {};

//////////////////////////////////////////////////
// Set up mouse wheel eventhandler
//////////////////////////////////////////////////

call FUNC(assignedItemFix);

// @todo remove?
enableCamShake true;


//////////////////////////////////////////////////
// Eventhandler to set player names
//////////////////////////////////////////////////

// Set the name for the current player
["playerChanged", {
    params ["_newPlayer","_oldPlayer"];

    if (alive _newPlayer) then {
        [_newPlayer] call FUNC(setName);
    };

    if (alive _oldPlayer) then {
        [_oldPlayer] call FUNC(setName);
    };
}] call FUNC(addEventhandler);


//////////////////////////////////////////////////
// Set up numerous eventhanders for player controlled units
//////////////////////////////////////////////////

// default variables
GVAR(OldPlayerVehicle) = vehicle objNull;
GVAR(OldPlayerTurret) = [objNull] call FUNC(getTurretIndex);
GVAR(OldPlayerWeapon) = currentWeapon objNull;
GVAR(OldPlayerInventory) = [];
GVAR(OldPlayerInventoryNoAmmo) = [];
GVAR(OldPlayerVisionMode) = currentVisionMode objNull;
GVAR(OldCameraView) = "";
GVAR(OldVisibleMap) = false;
GVAR(OldInventoryDisplayIsOpen) = nil; //@todo check this
GVAR(OldIsCamera) = false;

// PFH to raise varios events
[{
    BEGIN_COUNTER(stateChecker);

    // "playerChanged" event
    private _data = call FUNC(player);
    if !(_data isEqualTo ACE_player) then {
        private _oldPlayer = ACE_player;

        ACE_player = _data;
        uiNamespace setVariable ["ACE_player", _data];

        // Raise ACE event locally
        ["playerChanged", [ACE_player, _oldPlayer]] call FUNC(localEvent);
    };

    // "playerVehicleChanged" event
    _data = vehicle ACE_player;
    if !(_data isEqualTo GVAR(OldPlayerVehicle)) then {
        // Raise ACE event locally
        GVAR(OldPlayerVehicle) = _data;
        ["playerVehicleChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    // "playerTurretChanged" event
    _data = [ACE_player] call FUNC(getTurretIndex);
    if !(_data isEqualTo GVAR(OldPlayerTurret)) then {
        // Raise ACE event locally
        GVAR(OldPlayerTurret) = _data;
        ["playerTurretChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    // "playerWeaponChanged" event
    _data = currentWeapon ACE_player;
    if (_data != GVAR(OldPlayerWeapon)) then {
        // Raise ACE event locally
        GVAR(OldPlayerWeapon) = _data;
        ["playerWeaponChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    // "playerInventoryChanged" event
    _data = getUnitLoadout ACE_player;
    if !(_data isEqualTo GVAR(OldPlayerInventory)) then {
        // Raise ACE event locally
        GVAR(OldPlayerInventory) = _data;

        // we don't want to trigger this just because your ammo counter decreased.
        _data = + GVAR(OldPlayerInventory);

        private _weaponInfo = _data param [0, []];
        if !(_weaponInfo isEqualTo []) then {
            _weaponInfo set [4, primaryWeaponMagazine ACE_player];
            _weaponInfo deleteAt 5;
        };

        _weaponInfo = _data param [1, []];
        if !(_weaponInfo isEqualTo []) then {
            _weaponInfo set [4, secondaryWeaponMagazine ACE_player];
            _weaponInfo deleteAt 5;
        };

        _weaponInfo = _data param [2, []];
        if !(_weaponInfo isEqualTo []) then {
            _weaponInfo set [4, handgunMagazine ACE_player];
            _weaponInfo deleteAt 5;
        };

        if !(_data isEqualTo GVAR(OldPlayerInventoryNoAmmo)) then {
            GVAR(OldPlayerInventoryNoAmmo) = _data;
            ["playerInventoryChanged", [ACE_player, [ACE_player, false] call FUNC(getAllGear)]] call FUNC(localEvent);
        };
    };

    // "playerVisionModeChanged" event
    _data = currentVisionMode ACE_player;
    if !(_data isEqualTo GVAR(OldPlayerVisionMode)) then {
        // Raise ACE event locally
        GVAR(OldPlayerVisionMode) = _data;
        ["playerVisionModeChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    // "cameraViewChanged" event
    _data = cameraView;
    if !(_data isEqualTo GVAR(OldCameraView)) then {
        // Raise ACE event locally
        GVAR(OldCameraView) = _data;
        ["cameraViewChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    // "visibleMapChanged" event
    _data = visibleMap;
    if (!_data isEqualTo GVAR(OldVisibleMap)) then {
        // Raise ACE event locally
        GVAR(OldVisibleMap) = _data;
        ["visibleMapChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    // "activeCameraChanged" event
    _data = call FUNC(isfeatureCameraActive);
    if !(_data isEqualTo GVAR(OldIsCamera)) then {
        // Raise ACE event locally
        GVAR(OldIsCamera) = _data;
        ["activeCameraChanged", [ACE_player, _data]] call FUNC(localEvent);
    };

    END_COUNTER(stateChecker);
}, 0, []] call CBA_fnc_addPerFrameHandler;


//////////////////////////////////////////////////
// Eventhandlers for player controlled machines
//////////////////////////////////////////////////

["displayTextStructured", {_this call FUNC(displayTextStructured)}] call FUNC(addEventhandler);
["displayTextPicture", {_this call FUNC(displayTextPicture)}] call FUNC(addEventhandler);

["medical_onUnconscious", {
    params ["_unit", "_isUnconscious"];

    if (local _unit && {!_isUnconscious}) then {
        [_unit, false, QFUNC(loadPerson), west /* dummy side */] call FUNC(switchToGroupSide);
    };
}] call FUNC(addEventhandler);

["useItem", DFUNC(useItem)] call FUNC(addEventHandler);


//////////////////////////////////////////////////
// Add various canInteractWith conditions
//////////////////////////////////////////////////

["isNotDead", {
    params ["_unit", "_target"];
    alive _unit
}] call FUNC(addCanInteractWithCondition);

["notOnMap", {!visibleMap}] call FUNC(addCanInteractWithCondition);

["isNotInside", {
    params ["_unit", "_target"];

    // Players can always interact with himself if not boarded
    vehicle _unit == _unit ||
    // Players can always interact with his vehicle
    {vehicle _unit == _target} ||
    // Players can always interact with passengers of the same vehicle
    {_unit != _target && {vehicle _unit == vehicle _target}}
}] call FUNC(addCanInteractWithCondition);

["isNotInZeus", {isNull curatorCamera}] call FUNC(addCanInteractWithCondition);

//////////////////////////////////////////////////
// Set up PlayerJIP eventhandler
//////////////////////////////////////////////////

// Lastly, do JIP events
// JIP Detection and event trigger. Run this at the very end, just in case anything uses it
// Note: usage of player is most likely on purpose
if (didJip) then {
    // We are jipping! Get ready and wait, and throw the event
    [{
        if(!isNull player && GVAR(settingsInitFinished)) then {
            ["PlayerJip", [player]] call FUNC(localEvent);
            [_this select 1] call CBA_fnc_removePerFrameHandler;
        };
    }, 0, []] call CBA_fnc_addPerFrameHandler;
};


//////////////////////////////////////////////////
// CBA key input handling
//////////////////////////////////////////////////

//Device Handler:
GVAR(deviceKeyHandlingArray) = [];
GVAR(deviceKeyCurrentIndex) = -1;

// Register localizations for the Keybinding categories
["ACE3 Equipment", localize LSTRING(ACEKeybindCategoryEquipment)] call CBA_fnc_registerKeybindModPrettyName;
["ACE3 Common", localize LSTRING(ACEKeybindCategoryCommon)] call CBA_fnc_registerKeybindModPrettyName;
["ACE3 Weapons", localize LSTRING(ACEKeybindCategoryWeapons)] call CBA_fnc_registerKeybindModPrettyName;
["ACE3 Movement", localize LSTRING(ACEKeybindCategoryMovement)] call CBA_fnc_registerKeybindModPrettyName;
["ACE3 Scope Adjustment", localize LSTRING(ACEKeybindCategoryScopeAdjustment)] call CBA_fnc_registerKeybindModPrettyName;
["ACE3 Vehicles", localize LSTRING(ACEKeybindCategoryVehicles)] call CBA_fnc_registerKeybindModPrettyName;

["ACE3 Equipment", QGVAR(openDevice), (localize "STR_ACE_Common_toggleHandheldDevice"), {
    [] call FUNC(deviceKeyFindValidIndex);
    if (GVAR(deviceKeyCurrentIndex) == -1) exitWith {false};
    [] call ((GVAR(deviceKeyHandlingArray) select GVAR(deviceKeyCurrentIndex)) select 3);
    true
},
{false},
[0xC7, [false, false, false]], false] call CBA_fnc_addKeybind;  //Home Key

["ACE3 Equipment", QGVAR(closeDevice), (localize "STR_ACE_Common_closeHandheldDevice"), {
    [] call FUNC(deviceKeyFindValidIndex);
    if (GVAR(deviceKeyCurrentIndex) == -1) exitWith {false};
    [] call ((GVAR(deviceKeyHandlingArray) select GVAR(deviceKeyCurrentIndex)) select 4);
    true
},
{false},
[0xC7, [false, true, false]], false] call CBA_fnc_addKeybind;  //CTRL + Home Key

["ACE3 Equipment", QGVAR(cycleDevice), (localize "STR_ACE_Common_cycleHandheldDevices"), {
    [1] call FUNC(deviceKeyFindValidIndex);
    if (GVAR(deviceKeyCurrentIndex) == -1) exitWith {false};
    _displayName = ((GVAR(deviceKeyHandlingArray) select GVAR(deviceKeyCurrentIndex)) select 0);
    _iconImage = ((GVAR(deviceKeyHandlingArray) select GVAR(deviceKeyCurrentIndex)) select 1);
    [_displayName, _iconImage] call FUNC(displayTextPicture);
    true
},
{false},
[0xC7, [true, false, false]], false] call CBA_fnc_addKeybind;  //SHIFT + Home Key

GVAR(commonPostInited) = true;