#ifndef TILE_BASE_BUFFER_POLYMORPHIC_BUFFER_H
#define TILE_BASE_BUFFER_POLYMORPHIC_BUFFER_H

#pragma once

#include "tile/base/internal/singly_linked_list.h"
#include "tile/base/object_pool.h"
#include "tile/base/ref_ptr.h"

#include <atomic>
#include <chrono>
#include <forward_list>
#include <limits>
#include <memory>
#include <utility>

namespace tile {
namespace detail {
struct PolymorphicBufferBlockDeleter;
}

class PolymorphicBufferBlock : public RefCounted<PolymorphicBufferBlock, detail::PolymorphicBufferBlockDeleter> {
public:
    virtual ~PolymorphicBufferBlock()         = default;
    virtual const char *data() const noexcept = 0;
    virtual std::size_t size() const noexcept = 0;

    virtual void Destroy() noexcept { delete this; }
};

static_assert(!std::is_same<detail::as_ref_counted_t<PolymorphicBufferBlock>, PolymorphicBufferBlock>::value, "");
static_assert(detail::is_default_ref_traits_safe<PolymorphicBufferBlock>::value, "");

class PolymorphicBuffer {
public:
    PolymorphicBuffer()                                     = default;
    PolymorphicBuffer(const PolymorphicBuffer &)            = default;
    PolymorphicBuffer &operator=(const PolymorphicBuffer &) = default;

    PolymorphicBuffer(PolymorphicBuffer &&other) noexcept
        : ptr_(other.ptr_),
          size_(other.size_),
          ref_(std::move(other.ref_))
    {
        other.Clear();
    }

    PolymorphicBuffer &operator=(PolymorphicBuffer &&other) noexcept
    {
        if (this != &other) {
            ptr_  = other.ptr_;
            size_ = other.size_;
            ref_  = other.ref_;
            other.Clear();
        }
        return *this;
    }

    PolymorphicBuffer(RefPtr<PolymorphicBufferBlock> data, std::size_t start, std::size_t size)
        : ptr_(data->data() + start),
          size_(size),
          ref_(std::move(data))
    {}

    const char *data() const noexcept { return ptr_; }

    std::size_t size() const noexcept { return size_; }

    void Skip(std::size_t bytes)
    {
        TILE_CHECK_LT(bytes, size_);
        size_ -= bytes;
        ptr_ += bytes;
    }

    void set_size(std::size_t size)
    {
        TILE_DCHECK_LE(size, size_);
        size_ = size;
    }

    void Reset(RefPtr<PolymorphicBufferBlock> data, std::size_t start, std::size_t size)
    {
        TILE_DCHECK_LE(start, size);
        TILE_DCHECK_LE(size, data->size());

        ref_  = std::move(data);
        ptr_  = data->data() + start;
        size_ = size;
    }

    void Clear()
    {
        ptr_  = nullptr;
        size_ = 0;
        ref_  = nullptr;
    }

private:
    friend class NoncontiguousBuffer;

    internal::SinglyLinkedListEntry chain;
    const char *ptr_{};
    std::size_t size_{};
    RefPtr<PolymorphicBufferBlock> ref_;
};

namespace detail {
struct PolymorphicBufferBlockDeleter {
    void operator()(PolymorphicBufferBlock *p);
};
}// namespace detail

}// namespace tile

namespace tile {
template<>
struct PoolTraits<PolymorphicBuffer> {
    static constexpr auto kType                          = PoolType::MemoryNodeShared;
    static constexpr std::size_t kLowWaterMark           = 32768;
    static constexpr std::size_t kHighWaterMark          = std::numeric_limits<std::size_t>::max();
    static constexpr std::chrono::seconds kMaxIdle       = std::chrono::seconds(10);
    static constexpr std::size_t kMinimumThreadCacheSize = 8192;
    static constexpr std::size_t kTransferBatchSize      = 1024;

    static void OnPut(PolymorphicBuffer *bb);
};
}// namespace tile
#endif// TILE_BASE_BUFFER_POLYMORPHIC_BUFFER_H
