//
// Copyright 2019 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.
//
// serial_utils:
//   Utilities for generating unique IDs for resources in ANGLE.
//

#ifndef LIBANGLE_RENDERER_SERIAL_UTILS_H_
#define LIBANGLE_RENDERER_SERIAL_UTILS_H_

#include <algorithm>
#include <array>
#include <atomic>
#include <limits>

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

namespace rx
{
class ResourceSerial
{
  public:
    constexpr ResourceSerial() : mValue(kDirty) {}
    explicit constexpr ResourceSerial(uintptr_t value) : mValue(value) {}
    constexpr bool operator==(ResourceSerial other) const { return mValue == other.mValue; }
    constexpr bool operator!=(ResourceSerial other) const { return mValue != other.mValue; }

    void dirty() { mValue = kDirty; }
    void clear() { mValue = kEmpty; }

    constexpr bool valid() const { return mValue != kEmpty && mValue != kDirty; }
    constexpr bool empty() const { return mValue == kEmpty; }

  private:
    constexpr static uintptr_t kDirty = std::numeric_limits<uintptr_t>::max();
    constexpr static uintptr_t kEmpty = 0;

    uintptr_t mValue;
};

// Class UniqueSerial defines unique serial number for object identification. It has only
// equal/unequal comparison but no greater/smaller comparison. The default constructor creates an
// invalid value.
class UniqueSerial final
{
  public:
    constexpr UniqueSerial() : mValue(kInvalid) {}
    constexpr UniqueSerial(const UniqueSerial &other)  = default;
    UniqueSerial &operator=(const UniqueSerial &other) = default;

    constexpr bool operator==(const UniqueSerial &other) const
    {
        return mValue != kInvalid && mValue == other.mValue;
    }
    constexpr bool operator!=(const UniqueSerial &other) const
    {
        return mValue == kInvalid || mValue != other.mValue;
    }

    // Useful for serialization.
    constexpr uint64_t getValue() const { return mValue; }
    constexpr bool valid() const { return mValue != kInvalid; }

  private:
    friend class UniqueSerialFactory;
    constexpr explicit UniqueSerial(uint64_t value) : mValue(value) {}
    uint64_t mValue;
    static constexpr uint64_t kInvalid = 0;
};

class UniqueSerialFactory final : angle::NonCopyable
{
  public:
    UniqueSerialFactory() : mSerial(1) {}

    UniqueSerial generate()
    {
        uint64_t current = mSerial++;
        ASSERT(mSerial > current);  // Integer overflow
        return UniqueSerial(current);
    }

  private:
    uint64_t mSerial;
};

// Class Serial defines a monotonically increasing serial number that indicates the timeline of
// execution.
class Serial final
{
  public:
    constexpr Serial() : mValue(0) {}
    constexpr Serial(const Serial &other)  = default;
    Serial &operator=(const Serial &other) = default;

    static constexpr Serial Infinite() { return Serial(std::numeric_limits<uint64_t>::max()); }

    constexpr bool operator==(const Serial &other) const { return mValue == other.mValue; }
    constexpr bool operator!=(const Serial &other) const { return mValue != other.mValue; }
    constexpr bool operator>(const Serial &other) const { return mValue > other.mValue; }
    constexpr bool operator>=(const Serial &other) const { return mValue >= other.mValue; }
    constexpr bool operator<(const Serial &other) const { return mValue < other.mValue; }
    constexpr bool operator<=(const Serial &other) const { return mValue <= other.mValue; }

    // Useful for serialization.
    constexpr uint64_t getValue() const { return mValue; }

  private:
    friend class AtomicSerialFactory;
    friend class RangedSerialFactory;
    friend class AtomicQueueSerial;
    constexpr explicit Serial(uint64_t value) : mValue(value) {}
    uint64_t mValue;
};

// Defines class to track the queue serial that can be load/store from multiple threads atomically.
class alignas(8) AtomicQueueSerial final
{
  public:
    AtomicQueueSerial &operator=(const Serial &other)
    {
        mValue.store(other.mValue, std::memory_order_release);
        return *this;
    }
    Serial getSerial() const { return Serial(mValue.load(std::memory_order_consume)); }

  private:
    static constexpr uint64_t kInvalid = 0;
    std::atomic<uint64_t> mValue       = kInvalid;
};

// Used as default/initial serial
static constexpr Serial kZeroSerial = Serial();

// The factory to generate a serial number within the range [mSerial, mSerial+mCount}
class RangedSerialFactory final : angle::NonCopyable
{
  public:
    RangedSerialFactory() : mSerial(0), mCount(0) {}

    void reset() { mCount = 0; }
    bool empty() const { return mCount == 0; }
    bool generate(Serial *serialOut)
    {
        if (mCount > 0)
        {
            uint64_t current = mSerial++;
            ASSERT(mSerial > current);  // Integer overflow
            *serialOut = Serial(current);
            mCount--;
            return true;
        }
        return false;
    }

  private:
    friend class AtomicSerialFactory;
    void initialize(uint64_t initialSerial, size_t count)
    {
        mSerial = initialSerial;
        mCount  = count;
    }
    uint64_t mSerial;
    size_t mCount;
};

class AtomicSerialFactory final : angle::NonCopyable
{
  public:
    AtomicSerialFactory() : mSerial(1) {}

    Serial generate()
    {
        uint64_t current = mSerial++;
        ASSERT(mSerial > current);  // Integer overflow
        return Serial(current);
    }

    void reserve(RangedSerialFactory *rangeFactory, size_t count)
    {
        uint64_t current = mSerial;
        mSerial += count;
        ASSERT(mSerial > current);  // Integer overflow
        rangeFactory->initialize(current, count);
    }

  private:
    std::atomic<uint64_t> mSerial;
};

// For backend that supports multiple queue serials, QueueSerial includes a Serial and an index.
using SerialIndex                                     = uint32_t;
static constexpr SerialIndex kInvalidQueueSerialIndex = SerialIndex(-1);

class QueueSerial;
// Because we release queue index when context becomes non-current, in order to use up all index
// count, you will need to have 256 threads each has a context current. This is not a reasonable
// usage case.
constexpr size_t kMaxQueueSerialIndexCount = 256;
// Fixed array of queue serials
class AtomicQueueSerialFixedArray final
{
  public:
    AtomicQueueSerialFixedArray()  = default;
    ~AtomicQueueSerialFixedArray() = default;

    void setQueueSerial(SerialIndex index, Serial serial);
    void setQueueSerial(const QueueSerial &queueSerial);
    void fill(Serial serial) { std::fill(mSerials.begin(), mSerials.end(), serial); }
    Serial operator[](SerialIndex index) const { return mSerials[index].getSerial(); }
    size_t size() const { return mSerials.size(); }

  private:
    std::array<AtomicQueueSerial, kMaxQueueSerialIndexCount> mSerials;
};
std::ostream &operator<<(std::ostream &os, const AtomicQueueSerialFixedArray &serials);

class QueueSerial final
{
  public:
    QueueSerial() : mIndex(kInvalidQueueSerialIndex) {}
    QueueSerial(SerialIndex index, Serial serial) : mIndex(index), mSerial(serial)
    {
        ASSERT(index != kInvalidQueueSerialIndex);
    }
    constexpr QueueSerial(const QueueSerial &other)  = default;
    QueueSerial &operator=(const QueueSerial &other) = default;

    constexpr bool operator==(const QueueSerial &other) const
    {
        return mIndex == other.mIndex && mSerial == other.mSerial;
    }
    constexpr bool operator!=(const QueueSerial &other) const
    {
        return mIndex != other.mIndex || mSerial != other.mSerial;
    }
    constexpr bool operator<(const QueueSerial &other) const
    {
        ASSERT(mIndex != kInvalidQueueSerialIndex);
        ASSERT(mIndex == other.mIndex);
        return mSerial < other.mSerial;
    }
    constexpr bool operator<=(const QueueSerial &other) const
    {
        ASSERT(mIndex != kInvalidQueueSerialIndex);
        ASSERT(mIndex == other.mIndex);
        return mSerial <= other.mSerial;
    }
    constexpr bool operator>(const QueueSerial &other) const
    {
        ASSERT(mIndex != kInvalidQueueSerialIndex);
        ASSERT(mIndex == other.mIndex);
        return mSerial > other.mSerial;
    }
    constexpr bool operator>=(const QueueSerial &other) const
    {
        ASSERT(mIndex != kInvalidQueueSerialIndex);
        ASSERT(mIndex == other.mIndex);
        return mSerial >= other.mSerial;
    }

    bool operator>(const AtomicQueueSerialFixedArray &serials) const
    {
        ASSERT(mIndex != kInvalidQueueSerialIndex);
        return mSerial > serials[mIndex];
    }
    bool operator<=(const AtomicQueueSerialFixedArray &serials) const
    {
        ASSERT(mIndex != kInvalidQueueSerialIndex);
        return mSerial <= serials[mIndex];
    }

    constexpr bool valid() const { return mIndex != kInvalidQueueSerialIndex; }

    SerialIndex getIndex() const { return mIndex; }
    Serial getSerial() const { return mSerial; }

  private:
    SerialIndex mIndex;
    Serial mSerial;
};
std::ostream &operator<<(std::ostream &os, const QueueSerial &queueSerial);

ANGLE_INLINE void AtomicQueueSerialFixedArray::setQueueSerial(SerialIndex index, Serial serial)
{
    ASSERT(index != kInvalidQueueSerialIndex);
    ASSERT(index < mSerials.size());
    // Serial can only increase
    ASSERT(serial > mSerials[index].getSerial());
    mSerials[index] = serial;
}

ANGLE_INLINE void AtomicQueueSerialFixedArray::setQueueSerial(const QueueSerial &queueSerial)
{
    setQueueSerial(queueSerial.getIndex(), queueSerial.getSerial());
}

ANGLE_INLINE std::ostream &operator<<(std::ostream &os, const AtomicQueueSerialFixedArray &serials)
{
    // Search for last non-zero index (or 0 if all zeros).
    SerialIndex lastIndex = serials.size() == 0 ? 0 : static_cast<SerialIndex>(serials.size() - 1);
    while (lastIndex > 0 && serials[lastIndex].getValue() == 0)
    {
        lastIndex--;
    }
    os << '{';
    for (SerialIndex i = 0; i < lastIndex; i++)
    {
        os << serials[i].getValue() << ',';
    }
    os << serials[lastIndex].getValue() << '}';
    return os;
}

ANGLE_INLINE std::ostream &operator<<(std::ostream &os, const QueueSerial &queueSerial)
{
    os << '{' << queueSerial.getIndex() << ':' << queueSerial.getSerial().getValue() << '}';
    return os;
}
}  // namespace rx

#endif  // LIBANGLE_RENDERER_SERIAL_UTILS_H_
