// This file is part of CAF, the C++ Actor Framework. See the file LICENSE in
// the main distribution directory for license terms and copyright or visit
// https://github.com/actor-framework/actor-framework/blob/master/LICENSE.

#pragma once

#include <cstddef>
#include <cstring>
#include <limits>
#include <memory>

#include "caf/allowed_unsafe_message_type.hpp"
#include "caf/detail/io_export.hpp"

namespace caf::io::network {

/// A container that does not call constructors and destructors for its values.
class CAF_IO_EXPORT receive_buffer {
public:
  using value_type = char;
  using size_type = size_t;
  using difference_type = std::ptrdiff_t;
  using reference = value_type&;
  using const_reference = const value_type&;
  using pointer = value_type*;
  using const_pointer = std::pointer_traits<pointer>::rebind<const value_type>;
  using iterator = pointer;
  using const_iterator = const_pointer;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
  using buffer_ptr
    = std::unique_ptr<value_type[], std::default_delete<value_type[]>>;

  /// Create an empty container.
  receive_buffer() noexcept;

  /// Create an empty container of size `count`. Data in the storage is not
  /// initialized.
  receive_buffer(size_t count);

  /// Move constructor.
  receive_buffer(receive_buffer&& other) noexcept;

  /// Copy constructor.
  receive_buffer(const receive_buffer& other);

  /// Move assignment operator.
  receive_buffer& operator=(receive_buffer&& other) noexcept;

  /// Copy assignment operator.
  receive_buffer& operator=(const receive_buffer& other);

  /// Returns a pointer to the underlying buffer.
  pointer data() noexcept {
    return buffer_.get();
  }

  /// Returns a const pointer to the data.
  const_pointer data() const noexcept {
    return buffer_.get();
  }

  /// Returns the number of stored elements.
  size_type size() const noexcept {
    return size_;
  }

  /// Returns the number of elements that the container has allocated space for.
  size_type capacity() const noexcept {
    return capacity_;
  }

  /// Returns the maximum possible number of elements the container
  /// could theoretically hold.
  size_type max_size() const noexcept {
    return std::numeric_limits<size_t>::max();
  }

  /// Resize the container to `new_size`. While this may increase its storage,
  /// no storage will be released.
  void resize(size_type new_size);

  /// Set the size of the storage to `new_size`. If `new_size` is smaller than
  /// the current capacity nothing happens. If `new_size` is larger than the
  /// current capacity all iterators are invalidated.
  void reserve(size_type new_size);

  /// Shrink the container to its current size.
  void shrink_to_fit();

  /// Check if the container is empty.
  bool empty() const noexcept {
    return size_ == 0;
  }

  /// Clears the content of the container and releases the allocated storage.
  void clear();

  /// Swap contents with `other` receive buffer.
  void swap(receive_buffer& other) noexcept;

  /// Returns an iterator to the beginning.
  iterator begin() noexcept {
    return buffer_.get();
  }

  /// Returns an iterator to the end.
  iterator end() noexcept {
    return buffer_.get() + size_;
  }

  /// Returns an iterator to the beginning.
  const_iterator begin() const noexcept {
    return buffer_.get();
  }

  /// Returns an iterator to the end.
  const_iterator end() const noexcept {
    return buffer_.get() + size_;
  }

  /// Returns an iterator to the beginning.
  const_iterator cbegin() const noexcept {
    return buffer_.get();
  }

  /// Returns an iterator to the end.
  const_iterator cend() const noexcept {
    return buffer_.get() + size_;
  }

  /// Returns jan iterator to the reverse beginning.
  reverse_iterator rbegin() noexcept {
    return reverse_iterator{buffer_.get() + size_};
  }

  /// Returns an iterator to the reverse end of the data.
  reverse_iterator rend() noexcept {
    return reverse_iterator{buffer_.get()};
  }

  /// Returns an iterator to the reverse beginning.
  const_reverse_iterator rbegin() const noexcept {
    return const_reverse_iterator{buffer_.get() + size_};
  }

  /// Returns an iterator to the reverse end of the data.
  const_reverse_iterator rend() const noexcept {
    return const_reverse_iterator{buffer_.get()};
  }

  /// Returns an iterator to the reverse beginning.
  const_reverse_iterator crbegin() const noexcept {
    return const_reverse_iterator{buffer_.get() + size_};
  }

  /// Returns an iterator to the reverse end of the data.
  const_reverse_iterator crend() const noexcept {
    return const_reverse_iterator{buffer_.get()};
  }

  /// Insert `value` before `pos`.
  iterator insert(iterator pos, value_type value);

  /// Insert `value` before `pos`.
  template <class InputIterator>
  iterator insert(iterator pos, InputIterator first, InputIterator last) {
    auto n = std::distance(first, last);
    if (n == 0)
      return pos;
    auto offset = static_cast<size_t>(std::distance(begin(), pos));
    auto old_size = size_;
    resize(old_size + static_cast<size_t>(n));
    pos = begin() + offset;
    if (offset != old_size) {
      memmove(pos + n, pos, old_size - offset);
    }
    return std::copy(first, last, pos);
  }

  /// Append `value`.
  void push_back(value_type value);

private:
  // Increse the buffer capacity, maintaining its data. May invalidate
  // iterators.
  void increase_by(size_t bytes);

  // Reduce the buffer capacity, maintaining its data. May invalidate iterators.
  void shrink_by(size_t bytes);

  buffer_ptr buffer_;
  size_type capacity_;
  size_type size_;
};

} // namespace caf::io::network
