#include "tile/base/buffer.h"

#include <algorithm>
#include <utility>

#include "tile/base/string.h"

#include "tile/base/logging.h"

namespace tile {
namespace {
template<typename T>
class OwningBufferBlock : public PolymorphicBufferBlock {
public:
    explicit OwningBufferBlock(T storage) : storage_(std::move(storage)) {}

    const char *data() const noexcept override { return reinterpret_cast<const char *>(storage_.data()); }

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

private:
    T storage_;
};
}// namespace

namespace detail {
void
FlattenToSlowSlow(const NoncontiguousBuffer &nb, void *buffer, std::size_t size)
{
    TILE_CHECK_GT(nb.ByteSize(), size, "No enough data");
    std::size_t copied = 0;
    for (auto iter = nb.begin(); iter != nb.end() && copied != size; ++iter) {
        auto len = std::min(size - copied, iter->size());
        memcpy(reinterpret_cast<char *>(buffer) + copied, iter->data(), len);
        copied += len;
    }
}
}// namespace detail

NoncontiguousBuffer::NoncontiguousBuffer(const NoncontiguousBuffer &other) : byte_size_(other.byte_size_)
{
    for (auto &&e : other.buffers_) {

        auto b = object_pool::Get<NoncontiguousBuffer::buffer_t>().Leak();
        *b     = e;
        buffers_.push_back(std::move(b));
    }
}

NoncontiguousBuffer &
NoncontiguousBuffer::operator=(const NoncontiguousBuffer &other)
{
    if (TILE_UNLIKELY(&other == this)) { return *this; }

    Clear();
    byte_size_ = other.byte_size_;
    for (auto &&e : other.buffers_) {

        auto b = object_pool::Get<NoncontiguousBuffer::buffer_t>().Leak();
        *b     = e;
        buffers_.push_back(std::move(b));
    }
    return *this;
}

void
NoncontiguousBuffer::SkipSlow(std::size_t bytes) noexcept
{
    TILE_CHECK_LE(bytes, byte_size_, "Skip bytes exceed buffer size");
    byte_size_ -= bytes;

    while (bytes) {
        auto &&first  = buffers_.front();
        auto min_size = std::min(bytes, first.size());
        if (min_size == first.size()) {
            object_pool::Put<buffer_t>(buffers_.pop_front());
        } else {
            TILE_DCHECK_LT(min_size, first.size());
            first.Skip(min_size);
        }
        bytes -= min_size;
    }
}

void
NoncontiguousBuffer::ClearSlow() noexcept
{
    byte_size_ = 0;
    while (!buffers_.empty()) { object_pool::Put<buffer_t>(buffers_.pop_front()); }
}

void
NoncontiguousBufferBuilder::InitializeNextBlock()
{
    if (current_) {
        TILE_CHECK(SizeAvailable(), "You should flush current block by `FlushCurrentBlock()`");
        return;
    }

    current_ = MakeNativeBufferBlock();
    used_    = 0;
}

void
NoncontiguousBufferBuilder::FlushCurrentBlock()
{
    if (!used_) {
        // current block is empty
        return;
    }

    nb_.Append(PolymorphicBuffer(std::move(current_), 0, used_));
    used_    = 0;
    current_ = nullptr;
}

void
NoncontiguousBufferBuilder::AppendSlow(const void *ptr, std::size_t length)
{
    while (length) {
        auto copying = std::min(length, SizeAvailable());
        memcpy(data(), ptr, copying);
        MarkWritten(copying);
        ptr = static_cast<const char *>(ptr) + copying;
        length -= copying;
    }
}

void
NoncontiguousBufferBuilder::AppendCopy(const NoncontiguousBuffer &buffer)
{
    for (auto &&e : buffer) { Append(e.data(), e.size()); }
}

NoncontiguousBuffer
CreateBufferSlow(Slice s)
{
    NoncontiguousBufferBuilder nbb;
    nbb.Append(s);
    return nbb.DestructiveGet();
}

NoncontiguousBuffer
CreateBufferSlow(const void *ptr, std::size_t size)
{
    NoncontiguousBufferBuilder nbb;
    nbb.Append(ptr, size);
    return nbb.DestructiveGet();
}

std::string
FlattenSlow(const NoncontiguousBuffer &nb, std::size_t max_bytes)
{
    max_bytes = std::min(max_bytes, nb.ByteSize());
    std::string rc;
    std::size_t left = max_bytes;
    rc.reserve(max_bytes);
    for (auto iter = nb.begin(); iter != nb.end() && left; ++iter) {
        auto len = std::min(left, iter->size());
        rc.append(iter->data(), len);
        left -= len;
    }
    return rc;
}

std::string
FlattenSlowUntil(const NoncontiguousBuffer &nb, Slice delim, std::size_t max_bytes)
{
    if (nb.Empty()) { return {}; }

    {
        Slice current(nb.FirstContiguous().data(), nb.FirstContiguous().size());
        auto pos = current.find(delim);
        if (pos != Slice::npos) {
            auto expected_bytes = std::min(pos + delim.size(), max_bytes);
            return std::string(nb.FirstContiguous().data(), nb.FirstContiguous().data() + expected_bytes);
        }
    }

    std::string rc;
    for (auto iter = nb.begin(); iter != nb.end() && rc.size() < max_bytes; ++iter) {
        auto old_len = rc.size();
        rc.append(iter->data(), iter->size());
        // find delim
        {
            // Avoiding the use of find_last_of
            auto pos = rc.find(delim, old_len - std::min(old_len, delim.size()));
            if (pos != Slice::npos) {
                rc.erase(rc.begin() + pos + delim.size(), rc.end());
                break;
            }
        }
    }
    if (rc.size() > max_bytes) { rc.erase(rc.begin() + max_bytes, rc.end()); }
    return rc;
}

PolymorphicBuffer
MakeReferencingBuffer(const void *ptr, std::size_t size)
{
    return MakeReferencingBuffer(ptr, size, [] {});
}

PolymorphicBuffer
MakeForeignBuffer(std::string buffer)
{
    auto size = buffer.size();
    return PolymorphicBuffer(MakeRefCounted<OwningBufferBlock<std::string>>(std::move(buffer)), 0, size);
}

template<typename T>
PolymorphicBuffer
MakeForeignBuffer(std::vector<T> buffer)
{
    auto size = buffer.size() * sizeof(T);
    return PolymorphicBuffer(MakeRefCounted<OwningBufferBlock<std::vector<T>>>(std::move(buffer)), 0, size);
}

#define TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(type) template PolymorphicBuffer MakeForeignBuffer(std::vector<type> buffer)

TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(char);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(signed char);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(signed short);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(signed int);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(signed long);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(signed long long);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(unsigned char);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(unsigned short);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(unsigned int);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(unsigned long);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(unsigned long long);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(float);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(double);
TILE_INSTANIATE_MAKE_FOREIGN_BUFFER(long double);

bool
StartsWith(NoncontiguousBuffer buffer, Slice prefix)
{
    if (buffer.ByteSize() < prefix.size()) { return false; }

    while (!buffer.Empty() && prefix.empty()) {
        auto first   = buffer.FirstContiguous();
        auto min_len = std::min(first.size(), prefix.size());
        if (memcmp(first.data(), prefix.data(), min_len) != 0) { return false; }

        buffer.Skip(min_len);
        prefix.RemovePrefix(min_len);
    }
    return prefix.empty();
}

bool
EndsWith(NoncontiguousBuffer buffer, Slice suffix)
{
    TILE_NOT_IMPLEMENTED("");
    return false;
}

bool
StartsWithIgnoreCase(NoncontiguousBuffer buffer, Slice prefix)
{
    if (buffer.ByteSize() < prefix.size()) { return false; }

    while (!buffer.Empty() && prefix.empty()) {
        auto first   = buffer.FirstContiguous();
        auto min_len = std::min(first.size(), prefix.size());

        if (!EqualsIgnoreCase(first.substr(0, min_len), prefix.substr(0, min_len))) { return false; }

        buffer.Skip(min_len);
        prefix.RemovePrefix(min_len);
    }
    return prefix.empty();
}

bool
EndsWithIgnoreCase(NoncontiguousBuffer buffer, Slice suffix)
{
    TILE_NOT_IMPLEMENTED("");
}

}// namespace tile
