//
// Copyright 2002 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

// HandleAllocator.cpp: Implements the gl::HandleAllocator class, which is used
// to allocate GL handles.

#include "libANGLE/HandleAllocator.h"

#include <algorithm>
#include <limits>

#include "common/debug.h"
#include "common/mathutil.h"

namespace gl
{

struct HandleAllocator::HandleRangeComparator
{
    bool operator()(const HandleRange &range, GLuint handle) const { return (range.end < handle); }
};

HandleAllocator::HandleAllocator(GLuint maximumHandleValue, GLuint minimumReleasedToKeep)
    : mMaxValue(maximumHandleValue),
      mMinimumReleasedToKeep(minimumReleasedToKeep),
      mLoggingEnabled(false)
{
    mUnallocatedList.push_back(HandleRange(1, mMaxValue));
}

HandleAllocator::~HandleAllocator() = default;

bool HandleAllocator::allocate(GLuint *outId)
{
    // Allocate from released list, constant time for FIFO pop_front.
    if (mReleasedList.size() > mMinimumReleasedToKeep)
    {
        GLuint reusedHandle = mReleasedList.front();
        mReleasedList.pop_front();

        if (mLoggingEnabled)
        {
            WARN() << "HandleAllocator::allocate reusing " << reusedHandle << std::endl;
        }

        if (outId)
        {
            *outId = reusedHandle;
        }
        return true;
    }

    if (!mUnallocatedList.empty())
    {
        // Allocate from unallocated list, constant time.
        auto listIt = mUnallocatedList.begin();

        GLuint freeListHandle = listIt->begin;
        ASSERT(freeListHandle > 0);

        if (listIt->begin == listIt->end)
        {
            mUnallocatedList.erase(listIt);
        }
        else
        {
            angle::CheckedNumeric<GLuint> checkedBegin = listIt->begin;
            checkedBegin++;
            listIt->begin = checkedBegin.ValueOrDie();
        }

        if (mLoggingEnabled)
        {
            WARN() << "HandleAllocator::allocate allocating " << freeListHandle << std::endl;
        }

        if (outId)
        {
            *outId = freeListHandle;
        }
        return true;
    }

    if (!mReleasedList.empty())
    {
        GLuint reusedHandle = mReleasedList.front();
        mReleasedList.pop_front();

        if (mLoggingEnabled)
        {
            WARN() << "HandleAllocator::allocate reusing " << reusedHandle << std::endl;
        }

        if (outId)
        {
            *outId = reusedHandle;
        }
        return true;
    }

    return false;
}

void HandleAllocator::release(GLuint handle)
{
    if (mLoggingEnabled)
    {
        WARN() << "HandleAllocator::release releasing " << handle << std::endl;
    }

    if (handle >= mMaxValue)
    {
        // Handle is outside the range of allocated handles, do not reclaim it.
        return;
    }

    // Try consolidating the ranges first.
    for (HandleRange &handleRange : mUnallocatedList)
    {
        angle::CheckedNumeric<GLuint> checkedBegin = handleRange.begin;
        angle::CheckedNumeric<GLuint> checkedEnd   = handleRange.end;

        if ((checkedBegin - 1).ValueOrDie() == handle)
        {
            handleRange.begin = (checkedBegin - 1).ValueOrDie();
            return;
        }

        if (checkedEnd.ValueOrDie() == (handle - 1))
        {
            handleRange.end = (checkedEnd + 1).ValueOrDie();
            return;
        }
    }

    // Add to released list, constant time for FIFO push_back.
    mReleasedList.push_back(handle);
}

void HandleAllocator::reserve(GLuint handle)
{
    if (mLoggingEnabled)
    {
        WARN() << "HandleAllocator::reserve reserving " << handle << std::endl;
    }

    if (handle >= mMaxValue)
    {
        // Handle being reserved is outside the range of allocated handles. Allow this and don't
        // update the tracking.
        return;
    }

    // Clear from released list -- might be a slow operation.
    if (!mReleasedList.empty())
    {
        auto releasedIt = std::find(mReleasedList.begin(), mReleasedList.end(), handle);
        if (releasedIt != mReleasedList.end())
        {
            mReleasedList.erase(releasedIt);
            return;
        }
    }

    // Not in released list, reserve in the unallocated list.
    auto boundIt = std::lower_bound(mUnallocatedList.begin(), mUnallocatedList.end(), handle,
                                    HandleRangeComparator());

    ASSERT(boundIt != mUnallocatedList.end());

    GLuint begin = boundIt->begin;
    GLuint end   = boundIt->end;

    if (handle == begin || handle == end)
    {
        if (begin == end)
        {
            mUnallocatedList.erase(boundIt);
        }
        else if (handle == begin)
        {
            boundIt->begin++;
        }
        else
        {
            ASSERT(handle == end);
            boundIt->end--;
        }
        return;
    }

    ASSERT(begin < handle && handle < end);

    // need to split the range
    auto placementIt = mUnallocatedList.erase(boundIt);
    placementIt      = mUnallocatedList.insert(placementIt, HandleRange(handle + 1, end));
    mUnallocatedList.insert(placementIt, HandleRange(begin, handle - 1));
}

void HandleAllocator::reset()
{
    mUnallocatedList.clear();
    mUnallocatedList.push_back(HandleRange(1, mMaxValue));
    mReleasedList.clear();
}

bool HandleAllocator::anyHandleAvailableForAllocation() const
{
    return !mUnallocatedList.empty() || !mReleasedList.empty();
}

void HandleAllocator::enableLogging(bool enabled)
{
    mLoggingEnabled = enabled;
}

}  // namespace gl
