/**
 * 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 ItemCard extends AcediaObject;

//! Utility class designed for storing either class of an object
//! (possibly also a specific instance) along with authorization information:
//! which user groups are allowed to use stored entity and with what level of
//! permissions (defined by the name of a config with permissions).
//!
//! [`ItemCard`] has to be initialized with either [`InitializeWithClass()`] or
//! [`InitializeWithInstance()`] before it can be used.

/// Class of object that this card describes.
var private class<AcediaObject> storedClass;
/// Instance of an object (can also *optionally* be stored in this card)
var private AcediaObject storedInstance;

/// This [`HashTable`] maps authorized groups to their respective config names.
///
/// Each key represents an authorized group, and its corresponding value
/// indicates the associated config name. If a key has a value of `none`,
/// the default config (named "default") should be used for that group.
var private HashTable groupToConfig;

var LoggerAPI.Definition errGroupAlreadyHasConfig;

protected function Finalizer() {
    _.memory.Free2(storedInstance, groupToConfig);
    storedInstance = none;
    storedClass = none;
    groupToConfig = none;
}

/// Initializes the caller [`ItemCard`] object with class to be stored.
///
/// Initialization can only be done once: once method returned `true`,
/// all future calls will fail.
///
/// Returns `false` if caller was already initialized or `none` is provided as
/// an argument. Otherwise succeeds and returns `true`.
public function bool InitializeWithClass(class<AcediaObject> toStore) {
    if (storedClass != none)    return false;
    if (toStore == none)        return false;

    storedClass = toStore;
    groupToConfig = _.collections.EmptyHashTable();
    return true;
}

/// Initializes the caller [`ItemCard`] object with an object to be stored.
///
/// Initialization can only be done once: once method returned `true`,
/// all future calls will fail.
///
/// Returns `false` caller was already initialized or `none` is provided as
/// an argument. Otherwise succeeds and returns `true`.
public function bool InitializeWithInstance(AcediaObject toStore) {
    if (storedClass != none)    return false;
    if (toStore == none)        return false;

    storedClass = toStore.class;
    storedInstance = toStore;
    storedInstance.NewRef();
    groupToConfig = _.collections.EmptyHashTable();
    return true;
}

/// Authorizes a new group to use the this card's item.
///
/// This function allows to specify the config name for a particular user group.
/// If this config name is skipped (specified as `none`), then "default" will be
/// used instead.
///
/// Function will return `true` if group was successfully authorized and
/// `false` otherwise (either group already authorized or caller [`ItemCard`]
/// isn't initialized).
///
/// # Errors
///
/// If specified group was already authorized to use card's item, then it
/// will log an error message about it.
public function bool AuthorizeGroupWithConfig(BaseText groupName, optional BaseText configName) {
    local Text itemKey;
    local Text storedConfigName;

    if (storedClass == none)    return false;
    if (groupToConfig == none)  return false;
    if (groupName == none)      return false;
    if (groupName.IsEmpty())    return false;

    /// Make group name immutable and have its characters have a uniform case to
    /// be usable as case-insensitive keys for [`HashTable`].
    itemKey = groupName.LowerCopy();
    storedConfigName = groupToConfig.GetText(itemKey);
    if (storedConfigName != none) {
        _.logger.Auto(errGroupAlreadyHasConfig)
            .ArgClass(storedClass)
            .Arg(groupName.Copy())
            .Arg(storedConfigName)
            .Arg(configName.Copy());
        _.memory.Free(itemKey);
        return false;
    }
    // We don't actually record "default" value at this point, instead opting
    // to return "default" in getter functions in case stored `configName`
    // is `none`.
    groupToConfig.SetItem(itemKey, configName);
    _.memory.Free(itemKey);
    return true;
}

/// Returns item instance for the caller [`ItemCard`].
///
/// Returns `none` iff this card wasn't initialized with an instance.
public function AcediaObject GetItem() {
    if (storedInstance != none) {
        storedInstance.NewRef();
    }
    return storedInstance;
}

/// Returns item class for the caller [`ItemCard`].
///
/// Returns `none` iff this card wasn't initialized.
public function class<AcediaObject> GetItemClass() {
    return storedClass;
}

/// Returns the name of config that was authorized for the specified group.
///
/// Returns `none` if group wasn't authorized, otherwise guaranteed to
/// return non-`none` and non-empty `Text` value.
public function Text GetConfigNameForGroup(BaseText groupName) {
    local Text groupNameAsKey, result;

    if (storedClass == none)    return none;
    if (groupToConfig == none)  return none;
    if (groupName == none)      return none;

    /// Make group name immutable and have its characters a uniform case to
    /// be usable as case-insensitive keys for [`HashTable`]
    groupNameAsKey = groupName.LowerCopy();
    if (groupToConfig.HasKey(groupNameAsKey)) {
        result = groupToConfig.GetText(groupNameAsKey);
        if (result == none) {
            // If we do have specified group recorded as a key, then we must
            // return non-`none` config name, defaulting to "default" value
            // if none was provided
            result = P("default").Copy();
        }
    }
    _.memory.Free(groupNameAsKey);
    return result;
}

defaultproperties {
    errGroupAlreadyHasConfig = (l=LOG_Error,m="Item `%1` is already added to group '%2' with config '%3'. Attempt to add it with config '%4' is ignored.")
}