/**
 * Author: dkanus
 * Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore
 * License: GPL
 * Copyright 2020-2024 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 AcediaObjectPool extends Object
    config(AcediaSystem);

//! Acedia's implementation for object pool.
//!
//! Unlike generic built in [`Engine::ObjectPool`], that can only store objects
//! of one specific class, it specializes in a single class to allow for both
//! faster allocation and faster deallocation (we don't need to look for
//! an object of particular class to return an unused instance).
//!
//! Allows to set a maximum capacity in a config.

/// Represents config entry about pool capacity.
///
/// This struct and it's associated array [`poolSizeOverwrite`] allows server
/// admins to rewrite
/// the pool capacity for each class.
struct PoolSizeSetting {
    var class<AcediaObject> objectClass;
    var int maxPoolSize;
};

var private config const array<PoolSizeSetting> poolSizeOverwrite;
// Class of objects that this `AcediaObjectPool` stores.
// if `== none`, - object pool is considered uninitialized.
var private class<AcediaObject> storedClass;

// Capacity for object pool that we are using.
// Obtained from [`poolSizeOverwrite`] during initialization and cannot be changed later.
var private int usedMaxPoolSize;

// Actual storage, functions on LIFO principle.
var private array<AcediaObject> objectPool;

// Determines default object pool size for the initialization.
private final function int GetMaxPoolSizeForClass(class<AcediaObject> classToCheck) {
    local int i;
    local int result;

    if (classToCheck != none) {
        result = classToCheck.default.defaultMaxPoolSize;
    }
    else {
        result = -1;
    }
    //  Try to replace it with server's settings
    for (i = 0; i < poolSizeOverwrite.length; i += 1) {
        if (poolSizeOverwrite[i].objectClass == classToCheck) {
            result = poolSizeOverwrite[i].maxPoolSize;
            break;
        }
    }
    return result;
}

/// Initializes caller object pool to store objects of the given class.
///
/// Returns `true` if initialization completed, `false` otherwise (including if
/// it was already completed with passed [`classToStore`]).
///
/// If successful, this action is irreversible: same pool cannot be
/// re-initialized.
///
/// [`forcedPoolSize`] defines max pool size for the caller
/// [`AcediaObjectPool`].
/// Leaving it at default `0` value will cause method to auto-determine
/// the size: gives priority to the [`poolSizeOverwrite`] config array;
/// if not specified, uses [`AcediaObject`]'s
/// [`AcediaObject::defaultMaxPoolSize`]
/// (ignoring [`AcediaObject::usesObjectPool`] setting).
public final function bool Initialize(
    class<AcediaObject> classToStore,
    optional int forcedPoolSize
) {
    if (storedClass != none)    return false;
    if (classToStore == none)   return false;

    //  If does not matter that we've set those variables until
    //  we set `storedClass`.
    if (forcedPoolSize == 0) {
        usedMaxPoolSize = GetMaxPoolSizeForClass(classToStore);
    }
    else {
        usedMaxPoolSize = forcedPoolSize;
    }
    if (usedMaxPoolSize == 0) {
        return false;
    }
    storedClass = classToStore;
    return true;
}

/// Returns class of objects stored inside the caller [`AcediaObjectPool`].
///
/// `none` means object pool was not initialized.
public final function class<AcediaObject> GetClassOfStoredObjects() {
    return storedClass;
}

/// Clear the storage of all its contents.
public final function Clear() {
    objectPool.length = 0;
}

/// Adds object to the caller storage (that needs to be initialized to store
/// [`newObject.class`] classes).
///
/// Returns `true` on success and `false` on failure (can happen if passed
/// [`newObject`] reference was invalid, caller storage is not initialized yet
/// or reached it's capacity).
///
/// For performance purposes does not do duplicates checks, this should be
/// verified from outside [`AcediaObjectPool`].
///
/// Performs type checks and only allows objects of the class that caller
/// [`AcediaObjectPool`] was initialized for.
public final function bool Store(AcediaObject newObject) {
    if (newObject == none)              return true;
    if (newObject.class != storedClass) return false;

    if (usedMaxPoolSize >= 0 && objectPool.length >= usedMaxPoolSize) {
        return false;
    }
    objectPool[objectPool.length] = newObject;
    return true;
}

/// Returns last stored object from the pool, removing it from that pool in
/// the process.
///
/// Only returns `none` if caller `AcediaObjectPool` is either empty or not
/// initialized.
public final function AcediaObject Fetch() {
    local AcediaObject result;

    if (storedClass == none)    return none;
    if (objectPool.length <= 0) return none;

    result = objectPool[objectPool.length - 1];
    objectPool.length = objectPool.length - 1;
    return result;
}

defaultproperties {
}