#pragma once

#include <algorithm>
#include <vector>

#include "common/serializer/deserializer.h"
#include "common/serializer/serializer.h"
#include "common/types/types.h"
#include "common/uniq_lock.h"
#include "common/utils.h"

namespace lbug {
namespace storage {
class MemoryManager;

template<class T>
class GroupCollection {
public:
    GroupCollection() {}

    common::UniqLock lock() const { return common::UniqLock{mtx}; }

    void deserializeGroups(MemoryManager& memoryManager, common::Deserializer& deSer,
        const std::vector<common::LogicalType>& columnTypes) {
        auto lockGuard = lock();
        deSer.deserializeVectorOfPtrs<T>(groups, [&](common::Deserializer& deser) {
            return T::deserialize(memoryManager, deser, columnTypes);
        });
    }

    void removeTrailingGroups([[maybe_unused]] const common::UniqLock& lock,
        common::idx_t numGroupsToRemove) {
        DASSERT(lock.isLocked());
        DASSERT(numGroupsToRemove <= groups.size());
        groups.erase(groups.end() - numGroupsToRemove, groups.end());
    }

    void serializeGroups(common::Serializer& ser) {
        auto lockGuard = lock();
        ser.serializeVectorOfPtrs<T>(groups);
    }

    void appendGroup(const common::UniqLock& lock, std::unique_ptr<T> group) {
        DASSERT(group);
        DASSERT(lock.isLocked());
        UNUSED(lock);
        groups.push_back(std::move(group));
    }
    T* getGroup(const common::UniqLock& lock, common::idx_t groupIdx) const {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        DASSERT(groupIdx < groups.size());
        return groups[groupIdx].get();
    }
    T* getGroupNoLock(common::idx_t groupIdx) const {
        DASSERT(groupIdx < groups.size());
        return groups[groupIdx].get();
    }
    void replaceGroup(const common::UniqLock& lock, common::idx_t groupIdx,
        std::unique_ptr<T> group) {
        DASSERT(group);
        DASSERT(lock.isLocked());
        UNUSED(lock);
        if (groupIdx >= groups.size()) {
            groups.resize(groupIdx + 1);
        }
        groups[groupIdx] = std::move(group);
    }

    void resize(const common::UniqLock& lock, common::idx_t newSize) {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        if (newSize <= groups.size()) {
            return;
        }
        groups.resize(newSize);
    }

    bool isEmpty(const common::UniqLock& lock) const {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        return groups.empty();
    }
    common::idx_t getNumGroups(const common::UniqLock& lock) const {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        return groups.size();
    }
    common::idx_t getNumGroupsNoLock() const { return groups.size(); }

    const std::vector<std::unique_ptr<T>>& getAllGroups(const common::UniqLock& lock) const {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        return groups;
    }
    T* getFirstGroup(const common::UniqLock& lock) const {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        if (groups.empty()) {
            return nullptr;
        }
        return groups.front().get();
    }
    T* getFirstGroupNoLock() const {
        if (groups.empty()) {
            return nullptr;
        }
        return groups.front().get();
    }
    T* getLastGroup(const common::UniqLock& lock) const {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        if (groups.empty()) {
            return nullptr;
        }
        return groups.back().get();
    }

    void clear(const common::UniqLock& lock) {
        DASSERT(lock.isLocked());
        UNUSED(lock);
        groups.clear();
    }

    common::idx_t getNumEmptyTrailingGroups(const common::UniqLock& lock) {
        const auto& groupsVector = getAllGroups(lock);
        return common::safeIntegerConversion<common::idx_t>(
            std::find_if(groupsVector.rbegin(), groupsVector.rend(),
                [](const auto& group) { return (group->getNumRows() != 0); }) -
            groupsVector.rbegin());
    }

private:
    mutable std::mutex mtx;
    std::vector<std::unique_ptr<T>> groups;
};

} // namespace storage
} // namespace lbug
