/*
 * Copyright 2024 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#ifndef SkTFixedArray_DEFINED
#define SkTFixedArray_DEFINED

#include "include/private/base/SkAssert.h"
#include "include/private/base/SkSpan_impl.h"

#include <cstdint>
#include <cstring>
#include <initializer_list>
#include <type_traits>  // IWYU pragma: keep for std::is_trivial_v

namespace skia_private {

/**
 * Represents an array of `T` (must be a trivial type) that cannot grow past a fixed size `N`.
 * The fixed-size restriction allows for tighter codegen and a smaller memory footprint.
 * Missing methods from TArray (e.g. `fromBack`) can be added on demand.
 *
 * The trivial-type restriction is only to simplify implementation; if there is a need, we can
 * adopt proper move/copy semantics in this class as well.
 */
template <int N, typename T>
class FixedArray {
public:
    using value_type = T;

    FixedArray() = default;

    FixedArray(std::initializer_list<T> values) {
        SkASSERT(values.size() <= N);
        for (T value : values) {
            fData[fSize++] = value;
        }
    }

    FixedArray(int reserveCount) {
        // This is here to satisfy the TArray interface. Setting a reserve count on a fixed array
        // isn't useful.
        SkASSERT(reserveCount >= 0);
        SkASSERT(reserveCount <= N);
    }

    FixedArray(SkSpan<const T> array) {
        this->reset(array);
    }

    FixedArray(const FixedArray<N, T>& that) {
        this->reset({that.data(), (size_t)that.size()});
    }

    FixedArray<N, T>& operator=(const FixedArray<N, T>& that) {
        if (this != &that) {
            this->reset({that.data(), (size_t)that.size()});
        }
        return *this;
    }

    T& operator[](size_t index) {
        SkASSERT(index < fSize);
        return fData[index];
    }

    const T& operator[](size_t index) const {
        SkASSERT(index < fSize);
        return fData[index];
    }

    bool operator==(const FixedArray<N, T>& that) const {
        return fSize == that.fSize && (0 == memcmp(fData, that.fData, fSize * sizeof(T)));
    }

    bool operator!=(const FixedArray<N, T>& that) const {
        return !this->operator==(that);
    }

    int size() const {
        return fSize;
    }

    bool empty() const {
        return fSize == 0;
    }

    void clear() {
        fSize = 0;
    }

    void reset(SkSpan<const T> array) {
        SkASSERT(array.size() <= N);
        fSize = array.size();
        std::memcpy(fData, array.data(), array.size_bytes());
    }

    void resize(int newSize) {
        SkASSERT(newSize >= 0);
        SkASSERT(newSize <= N);

        if (fSize > newSize) {
            fSize = newSize;
        } else {
            while (fSize < newSize) {
                fData[fSize++] = T();
            }
        }
    }

    T& push_back() {
        SkASSERT(fSize < N);
        T& ref = fData[fSize++];
        ref = T();
        return ref;
    }

    void push_back(T x) {
        SkASSERT(fSize < N);
        fData[fSize++] = x;
    }

    T* push_back_n(int n) {
        SkASSERT(n >= 0);
        SkASSERT(fSize + n <= N);
        T* ptr = fData + fSize;
        for (int index = 0; index < n; ++index) {
            ptr[index] = T();
        }
        fSize += n;
        return ptr;
    }

    T* push_back_n(int n, const T& t) {
        SkASSERT(n >= 0);
        SkASSERT(fSize + n <= N);
        T* ptr = fData + fSize;
        for (int index = 0; index < n; ++index) {
            ptr[index] = t;
        }
        fSize += n;
        return ptr;
    }

    void pop_back() {
        SkASSERT(fSize >= 1);
        --fSize;
    }

    void pop_back_n(int n) {
        SkASSERT(fSize >= n);
        fSize -= n;
    }

    void removeShuffle(int n) {
        SkASSERT(n < fSize);
        int last = fSize - 1;
        if (n != last) {
            fData[n] = fData[last];
        }
        fSize = last;
    }

    T* data() {
        return fData;
    }

    const T* data() const {
        return fData;
    }

    T* begin() {
        return fData;
    }

    const T* begin() const {
        return fData;
    }

    T* end() {
        return fData + fSize;
    }

    const T* end() const {
        return fData + fSize;
    }

    T& front() {
        SkASSERT(fSize > 0);
        return fData[0];
    }

    const T& front() const {
        SkASSERT(fSize > 0);
        return fData[0];
    }

    T& back() {
        SkASSERT(fSize > 0);
        return fData[fSize - 1];
    }

    const T& back() const {
        SkASSERT(fSize > 0);
        return fData[fSize - 1];
    }

    void reserve(int size) {
        // This is here to satisfy the TArray interface.
        SkASSERT(size >= 0);
        SkASSERT(size <= N);
    }

    constexpr int capacity() const {
        return N;
    }

private:
    static_assert(std::is_trivial_v<T>);
    static_assert(N > 0);
    static_assert(N < 256);  // limited by `uint8_t fSize`

    T fData[N];
    uint8_t fSize = 0;
};

}  // namespace skia_private

#endif
