#include "tile/base/buffer/builtin_buffer_block.h"

#include "tile/base/object_pool.h"
#include "tile/init/on_init.h"

#include "gflags/gflags.h"

#include <array>
#include <string>
#include <unordered_map>

DEFINE_string(tile_buffer_block_size, "4K", "Valid choices are 4K, 64K, 1M");

#define BLOCK_SIZE_4K (4096)
#define BLOCK_SIZE_64K (65536)
#define BLOCK_SIZE_1M (1048576)

namespace tile {

namespace {

template<std::size_t kSize>
struct alignas(hardware_destructive_interference_size) FixedNativeBufferBlock : NativeBufferBlock {
public:
    static RefPtr<FixedNativeBufferBlock> New()
    {
        // WARNING: don't use adopt_ptr
        return RefPtr<FixedNativeBufferBlock<kSize>>(
            adopt_ptr, object_pool::Get<FixedNativeBufferBlock<kSize>>().Leak());
    }

    // HACK: add ref ensure `RefCount<Base>`.
    static void Delete(FixedNativeBufferBlock *ptr)
    {
        ptr->Save();
        object_pool::Put<FixedNativeBufferBlock<kSize>>(ptr);
    }

    char *mutable_data() noexcept override { return buffer.data(); }

    const char *data() const noexcept override { return buffer.data(); }

    std::size_t size() const noexcept override { return buffer.size(); }

    void Destroy() noexcept override { Delete(this); }

    static constexpr std::size_t kBufferSize = kSize - sizeof(NativeBufferBlock);
    std::array<char, kBufferSize> buffer;
};

template<std::size_t kSize>
RefPtr<NativeBufferBlock>
MakeNativeBufferBlockOfBytes()
{
    return FixedNativeBufferBlock<kSize>::New();
}

RefPtr<NativeBufferBlock> (*make_native_buffer_block)() = MakeNativeBufferBlockOfBytes<BLOCK_SIZE_4K>;

void
InitializeMakeNativeBufferBlock()
{
    static const std::unordered_map<std::string, RefPtr<NativeBufferBlock> (*)()> kMakers = {
        {"4K",  MakeNativeBufferBlockOfBytes<BLOCK_SIZE_4K> },
        {"64K", MakeNativeBufferBlockOfBytes<BLOCK_SIZE_64K>},
        {"1M",  MakeNativeBufferBlockOfBytes<BLOCK_SIZE_1M> }
    };
    auto iter = kMakers.find(FLAGS_tile_buffer_block_size);
    if (iter != kMakers.end()) {
        make_native_buffer_block = iter->second;
    } else {
        TILE_UNEXPECTED("Unexpected buffer block size [{}]. Only 4K/64K/1M buffer block "
                        "is supported.",
                        FLAGS_tile_buffer_block_size);
    }
}

TILE_ON_INIT(0, InitializeMakeNativeBufferBlock);

}// namespace

RefPtr<NativeBufferBlock>
MakeNativeBufferBlock()
{
    return make_native_buffer_block();
}

template<>
struct PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_4K>> {
    static constexpr auto kType                   = PoolType::MemoryNodeShared;
    static constexpr auto kLowWaterMark           = 256; // 1M per node.
    static constexpr auto kHighWaterMark          = 1024;// 4M
    static constexpr auto kMaxIdle                = std::chrono::seconds(10);
    static constexpr auto kMinimumThreadCacheSize = 4096;// 16M per thread.
    static constexpr auto kTransferBatchSize      = 1024;// Extra 4M.
};

template<>
struct PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_64K>> {
    static constexpr auto kType                   = PoolType::MemoryNodeShared;
    static constexpr auto kLowWaterMark           = 32; // 2M per node.
    static constexpr auto kHighWaterMark          = 128;// 8M
    static constexpr auto kMaxIdle                = std::chrono::seconds(10);
    static constexpr auto kMinimumThreadCacheSize = 256;// 16M per thread.
    static constexpr auto kTransferBatchSize      = 64; // Extra 4M.
};

template<>
struct PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_1M>> {
    static constexpr auto kType                   = PoolType::MemoryNodeShared;
    static constexpr auto kLowWaterMark           = 4; // 4M per node.
    static constexpr auto kHighWaterMark          = 16;// 16M
    static constexpr auto kMaxIdle                = std::chrono::seconds(10);
    static constexpr auto kMinimumThreadCacheSize = 64;// 64M per thread.
    static constexpr auto kTransferBatchSize      = 16;// Extra 16M.
};

constexpr PoolType PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_4K>>::kType;
constexpr PoolType PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_64K>>::kType;
constexpr PoolType PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_1M>>::kType;

constexpr std::chrono::seconds PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_4K>>::kMaxIdle;
constexpr std::chrono::seconds PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_64K>>::kMaxIdle;
constexpr std::chrono::seconds PoolTraits<FixedNativeBufferBlock<BLOCK_SIZE_1M>>::kMaxIdle;

}// namespace tile
