/**
 *  This actor's role is to perform Acedia's server startup.
 *      Copyright   2019-2023 Anton Tarasenko
 *                  2023 Shtoyan
 *------------------------------------------------------------------------------
 * This file is part of Acedia.
 *
 * Acedia is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3 of the License, or
 * (at your option) any later version.
 *
 * Acedia 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Acedia.  If not, see <https://www.gnu.org/licenses/>.
 */
class StartUp extends Actor
    dependson(Packages);

//  Acedia's reference to a `Global` object.
var private Global          _;
var private ServerGlobal    _server;
//  Responsible for setting up Acedia's game modes in current voting system
var private VotingHandlerAdapter votingAdapter;

var private LoggerAPI.Definition infoFeatureEnabled;
var private LoggerAPI.Definition errorCannotRunTests;

function PreBeginPlay()
{
	super.PreBeginPlay();
    InitializeServer();
    if (level != none && level.game != none) {
        level.game.AddMutator(string(class'AcediaLauncherMut'));
    }
}

public function VotingHandlerAdapter GetVotingHandlerAdapter()
{
    if (votingAdapter != none) {
        votingAdapter.NewRef();
    }
    return votingAdapter;
}

private function InitializeServer()
{
    local int       i;
    local GameMode  currentGameMode;
    local array<Packages.FeatureConfigPair> availableFeatures;

    if (class'Packages'.default.clientside) {
        AddToPackageMap("AcediaLauncher");
    }
    CheckForGarbage();
    //  Launch and setup core Acedia
    _       = class'Global'.static.GetInstance();
    _server = class'ServerGlobal'.static.GetInstance();
    class'ServerLevelCore'.static.CreateLevelCore(self);
    class'MapList'.static.Initialize();
    for (i = 0; i < class'Packages'.default.package.length; i += 1) {
        _.environment.RegisterPackage_S(class'Packages'.default.package[i]);
    }
    if (class'TestingService'.default.runTestsOnStartUp) {
        RunStartUpTests();
    }
    //  Determine required features and launch them
    availableFeatures = GetAutoConfigurationInfo();
    if (class'Packages'.default.useGameModes)
    {
        class'GameMode'.static.Initialize();
        votingAdapter = VotingHandlerAdapter(
            _.memory.Allocate(class'VotingHandlerAdapter'));
        currentGameMode = votingAdapter.SetupGameModeAfterTravel();
        if (currentGameMode != none) {
            currentGameMode.UpdateFeatureArray(availableFeatures);
        }
    }
    EnableFeatures(availableFeatures);
}

//  Checks whether Acedia has left garbage after the previous map.
//  This can lead to serious problems, so such diagnostic check is warranted.
private function CheckForGarbage()
{
    local int           leftoverObjectAmount;
    local int           leftoverActorAmount;
    local int           leftoverDBRAmount;
    local AcediaObject  nextObject;
    local AcediaActor   nextActor;
    local DBRecord      nextRecord;

    foreach AllObjects(class'AcediaObject', nextObject) {
        leftoverObjectAmount += 1;
    }
    foreach AllActors(class'AcediaActor', nextActor) {
        leftoverActorAmount += 1;
    }
    foreach AllObjects(class'DBRecord', nextRecord) {
        leftoverDBRAmount += 1;
    }
    if (    leftoverObjectAmount == 0 && leftoverActorAmount == 0
        &&  leftoverDBRAmount == 0)
    {
        Log("Acedia garbage check: nothing was found.");
    }
    else
    {
        Log("Acedia garbage check: garbage was found." @
            "This can cause problems, report it.");
        Log("Leftover object:" @ leftoverObjectAmount);
        Log("Leftover actors:" @ leftoverActorAmount);
        Log("Leftover database records:" @ leftoverDBRAmount);
    }
}

public function array<Packages.FeatureConfigPair> GetAutoConfigurationInfo()
{
    local int                               i;
    local Text                              autoConfig;
    local array< class<Feature> >           availableFeatures;
    local Packages.FeatureConfigPair        nextPair;
    local array<Packages.FeatureConfigPair> result;

    availableFeatures = _.environment.GetAvailableFeatures();
    for (i = 0; i < availableFeatures.length; i += 1)
    {
        autoConfig = availableFeatures[i].static.GetAutoEnabledConfig();
        if (autoConfig != none)
        {
            nextPair.featureClass   = availableFeatures[i];
            nextPair.configName     = autoConfig;
            result[result.length] = nextPair;
        }
    }
    return result;
}

private function EnableFeatures(array<Packages.FeatureConfigPair> features)
{
    local int   i;
    local Text  defaultConfigName;
    local Text  nextConfigName;

    defaultConfigName = _.text.FromString("default");
    for (i = 0; i < features.length; i += 1)
    {
        if (features[i].featureClass == none) {
            continue;
        }
        //  `configName` being `none` here means that config name was not
        //  specified through config and we should fallback to "default"
        if (features[i].configName == none) {
            nextConfigName = defaultConfigName.Copy();
        }
        else {
            nextConfigName = features[i].configName.Copy();
        }
        features[i].featureClass.static.EnableMe(nextConfigName);
        _.logger.Auto(infoFeatureEnabled)
            .Arg(_.text.FromString(string(features[i].featureClass)))
            .Arg(nextConfigName);  //  consumes `nextConfigName`
    }
    defaultConfigName.FreeSelf();
}

private final function RunStartUpTests()
{
    local TestingService testService;

    testService = TestingService(class'TestingService'.static.Require());
    testService.PrepareTests();
    if (testService.filterTestsByName) {
        testService.FilterByName(testService.requiredName);
    }
    if (testService.filterTestsByGroup) {
        testService.FilterByGroup(testService.requiredGroup);
    }
    if (!testService.Run()) {
        _.logger.Auto(errorCannotRunTests);
    }
}

defaultproperties
{
    //  This is a server-only actor
    remoteRole = ROLE_None
    infoFeatureEnabled      = (l=LOG_Info,m="Feature `%1` enabled with config \"%2\".")
    errorCannotRunTests     = (l=LOG_Error,m="Could not perform Acedia's tests.")
}