/**
 * Author: dkanus
 * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
 * License: GPL
 * Copyright 2023 Anton Tarasenko
 *------------------------------------------------------------------------------
 * 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 ACommandSideEffects extends Command;

// Maps `UserID` to `ArrayList` with side effects listed for that player last time
var private HashTable displayedLists;

protected function Constructor() {
    super.Constructor();
    displayedLists = _.collections.EmptyHashTable();
}

protected function Finalizer() {
    super.Finalizer();
    _.memory.Free(displayedLists);
    displayedLists = none;
}

protected function BuildData(CommandDataBuilder builder) {
    builder.Group(P("debug"));
    builder.Summary(P("Displays information about current side effects."));
    builder.Describe(P("This command allows to display current side effects, optionally filtering"
        @ "them by specified package names."));
    builder.OptionalParams();
    builder.ParamTextList(P("package_names"));

    builder.SubCommand(P("show"));
    builder.Describe(P("This sub-command is only usable after side effects have been shown"
        @ "at least once. It takes an index from the last displayed list and displays a verbose"
        @ "information about it."));
    builder.ParamInteger(P("side_effect_number"));

    builder.Option(P("verbose"));
    builder.Describe(P("Display verbose information about each side effect."));
}

protected function Executed(
    CallData arguments,
    EPlayer instigator,
    CommandPermissions permissions
) {
    local UserID playerID;
    local array<SideEffect> relevantSideEffects;
    local ArrayList packagesList, storedSideEffectsList;

    playerID = instigator.GetUserID();
    if (arguments.subCommandName.IsEmpty()) {
        relevantSideEffects = _.sideEffects.GetAll();
        packagesList = arguments.parameters.GetArrayList(P("package_names"));
        FilterSideEffects(/*out*/ relevantSideEffects, packagesList);
        _.memory.Free(packagesList);
        DisplaySideEffects(relevantSideEffects, arguments.options.HasKey(P("verbose")));
        // Store new side effect list
        storedSideEffectsList = _.collections.NewArrayList(relevantSideEffects);
        displayedLists.SetItem(playerID, storedSideEffectsList);
        _.memory.FreeMany(relevantSideEffects);
        _.memory.Free(storedSideEffectsList);
    } else {
        ShowInfoFor(playerID, arguments.parameters.GetInt(P("side_effect_number")));
    }
    _.memory.Free(playerID);
}

private function FilterSideEffects(out array<SideEffect> sideEffects, ArrayList allowedPackages) {
    local int i, j;
    local int packagesLength;
    local bool matchedPackage;
    local Text nextSideEffectPackage, nextAllowedPackage;

    if (allowedPackages == none)            return;
    if (allowedPackages.GetLength() <= 0)   return;

    packagesLength = allowedPackages.GetLength();
    while (i < sideEffects.length) {
        nextSideEffectPackage = sideEffects[i].GetPackage();
        matchedPackage = false;
        for (j = 0; j < packagesLength; j += 1) {
            nextAllowedPackage = allowedPackages.GetText(j);
            if (nextAllowedPackage.Compare(nextSideEffectPackage, SCASE_INSENSITIVE)) {
                matchedPackage = true;
                _.memory.Free(nextAllowedPackage);
                break;
            }
            _.memory.Free(nextAllowedPackage);
        }
        if (!matchedPackage) {
            sideEffects.Remove(i, 1);
        } else {
            i += 1;
        }
        _.memory.Free(nextSideEffectPackage);
    }
}

private function DisplaySideEffects(array<SideEffect> toDisplay, bool verbose) {
    local int i;
    local MutableText nextPrefix;

    if (toDisplay.length <= 0) {
        callerConsole.Write(F("List of side effects is {$TextNeutral empty}."));
    }
    for (i = 0; i < toDisplay.length; i += 1) {
        nextPrefix = _.text.FromIntM(i + 1);
        nextPrefix.Append(P("."));
        DisplaySideEffect(toDisplay[i], nextPrefix, verbose);
        _.memory.Free(nextPrefix);
    }
}

private function DisplaySideEffect(SideEffect toDisplay, BaseText prefix, bool verbose) {
    local Text effectName, effectDescription, effectPackage, effectSource, effectStatus;

    if (toDisplay == none) {
        return;
    }
    if (prefix != none) {
        callerConsole.Write(prefix);
        callerConsole.Write(P(" "));
    }
    effectName = toDisplay.GetName();
    effectPackage = toDisplay.GetPackage();
    effectSource = toDisplay.GetSource();
    effectStatus = toDisplay.GetStatus();
    callerConsole.UseColor(_.color.TextEmphasis);
    callerConsole.Write(P("["));
    callerConsole.Write(effectPackage);
    callerConsole.Write(P(" \\ "));
    callerConsole.Write(effectSource);
    callerConsole.Write(P("] "));
    callerConsole.ResetColor();
    callerConsole.Write(effectName);
    callerConsole.Write(P(" {"));
    callerConsole.Write(effectStatus);
    callerConsole.WriteLine(P("}"));
    if (verbose) {
        effectDescription = toDisplay.GetDescription();
        callerConsole.WriteBlock(effectDescription);
    }
    _.memory.Free5(effectName, effectDescription, effectPackage, effectSource, effectStatus);
}

private function ShowInfoFor(UserID playerID, int sideEffectIndex) {
    local SideEffect toDisplay;
    local ArrayList sideEffectList;

    if (playerID == none) {
        return;
    }
    if (sideEffectIndex <= 0) {
        callerConsole.WriteLine(F("Specified side effect index {$TextNegative isn't positive}!"));
        return;
    }
    sideEffectList = displayedLists.GetArrayList(playerID);
    if (sideEffectList == none) {
        callerConsole.WriteLine(F("{$TextNegative Cannot display} side effect by index without"
            @ "first listing them. Call {$TextEmphasis sideeffects} command without"
            @ "{$TextEmphasis show} subcommand first."));
        return;
    }
    if (sideEffectIndex > sideEffectList.GetLength()) {
        callerConsole.WriteLine(F("Specified side effect index is {$TextNegative out of bounds}."));
        _.memory.Free(sideEffectList);
        return;
    }
    // Above we checked that `sideEffectIndex` lies within `[0; sideEffectList.GetLength()]` segment
    // This means that `sideEffectIndex - 1` points at non-`none` value
    toDisplay = SideEffect(sideEffectList.GetItem(sideEffectIndex - 1));
    if (!_.sideEffects.IsRegistered(toDisplay)) {
        callerConsole.UseColorOnce(_.color.TextWarning);
        callerConsole.WriteLine(P("Selected side effect is no longer active!"));
    }
    DisplaySideEffect(toDisplay, none, true);
    _.memory.Free2(toDisplay, sideEffectList);
}

defaultproperties {
    preferredName = "sideeffects"
}