#ifndef IPC_BUFFER_H
#define IPC_BUFFER_H

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <chrono>
#include <string>
#include <type_traits>
#include <vector>

#include <cassert>
#include <algorithm> //std::sort

#include "ipc_protocol.h"
#include "logger.h"

namespace ipc {

// workaround missing "is_trivially_copyable" in g++ < 5.0
#if __GNUG__ && __GNUC__ < 5
#define IS_TRIVIALLY_COPYABLE(T) __has_trivial_copy(T)
#else
#define IS_TRIVIALLY_COPYABLE(T) std::is_trivially_copyable<T>::value
#endif

template <typename Iterator>
class BufferIterator {
  public:
    //Iterators for ranged loops
    typedef Iterator*                         iterator;
    typedef std::reverse_iterator<iterator>   reverse_iterator;

    BufferIterator(iterator first, iterator last) : f_ (first), l_ (last) {}

    // Iterators are all const, because the data shouldn't be mutable
    iterator begin() const { return iterator(f_); }
    iterator end() const { return iterator(l_); }
    reverse_iterator rbegin() const { return reverse_iterator(end()); }
    reverse_iterator rend() const { return reverse_iterator(begin()); }
    iterator cbegin() const { return iterator(f_); }
    iterator cend() const { return iterator(l_); }
    reverse_iterator crbegin() const { return reverse_iterator(end()); }
    reverse_iterator crend() const { return reverse_iterator(begin()); }

    size_t size() const { return std::distance(f_, l_); }

    Iterator & operator [](size_t i) const { return *(f_ + i); }

  private:
    iterator f_;
    iterator l_;
};

template <typename Iterator>
class ConstBufferIterator {
  public:
    //Iterators for ranged loops
    typedef const Iterator*                         const_iterator;
    typedef std::reverse_iterator<const_iterator>   const_reverse_iterator;

    ConstBufferIterator(const_iterator first, const_iterator last) : f_ (first), l_ (last) {}

    // Iterators are all const, because the data shouldn't be mutable
    const_iterator begin() const { return const_iterator(f_); }
    const_iterator end() const { return const_iterator(l_); }
    const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
    const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
    const_iterator cbegin() const { return const_iterator(f_); }
    const_iterator cend() const { return const_iterator(l_); }
    const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); }
    const_reverse_iterator crend() const { return const_reverse_iterator(begin()); }

    size_t size() const { return std::distance(f_, l_); }

    const Iterator & operator [](size_t i) const { return *(f_ + i); }

  private:
    const_iterator f_;
    const_iterator l_;
};

class Buffer {
  using sys_time_t = std::chrono::time_point<std::chrono::system_clock>;
  using game_time_t = std::chrono::time_point<std::chrono::steady_clock>;

  struct Header {
    MsgType type;
    uint64_t sys_time; // System time which is calculated from a chrono steady clock + system clock timestamp, in milliseconds
    uint32_t gametime; // Gametime timestamp - only used in overloaded functions - otherwise uses chrono
    uint32_t msg_count; // Number of elements for arrays
    uint32_t body_length; // Total size of the rest of the body (sizeof(Msg) * msg_count)
  };

  public:
    Buffer() {
      data_.resize(header_length_);
    }

    //Special for strings
    Buffer(const char *buffer) : Buffer(std::string(buffer), sys_time_t(), game_time_t()) { }

    Buffer(const char *buffer, sys_time_t sys_time) : Buffer(std::string(buffer), sys_time, game_time_t()) { }

    Buffer(const char *buffer, game_time_t gametime) : Buffer(std::string(buffer), sys_time_t(), gametime) { }

    Buffer(const char *buffer, sys_time_t sys_time, game_time_t gametime) : Buffer(std::string(buffer), sys_time, gametime) { }

    Buffer(const std::string &buffer) : Buffer(buffer, sys_time_t(), game_time_t()) { }

    Buffer(const std::string &buffer, sys_time_t sys_time) : Buffer(buffer, sys_time, game_time_t()) { }

    Buffer(const std::string &buffer, game_time_t gametime) : Buffer(buffer, sys_time_t(), gametime) { }

    Buffer(const std::string &buffer, sys_time_t sys_time, game_time_t gametime) {
      static_assert(sizeof(StringMsg) == sizeof(uint8_t), "sizeof(StringMsg) != sizeof(uint8_t)! That is required for memcpy!\n");

      header_.type = MsgType::String;
      header_.body_length = buffer.size() * sizeof(StringMsg);
      header_.msg_count = buffer.size();
      data_.resize(header_length_ + header_.body_length);
      memcpy(data_.data() + header_length_, buffer.data(), header_.body_length);

      setTime(sys_time, gametime);
      encodeHeader();
    }

    //For msg filter arrays - we only need to sort the input - sadly we cannot use delegated constructors to reduce copy-paste
    //NOTE(harijs): It modifies input (sorts it), rather than to make a copy.
    Buffer(std::vector<SubscribeFilterMsg> &msg_array) : Buffer(msg_array, sys_time_t(), game_time_t()) { }
    Buffer(std::vector<SubscribeFilterMsg> &msg_array, sys_time_t sys_time, game_time_t gametime) {
      static_assert(IS_TRIVIALLY_COPYABLE(SubscribeFilterMsg), "IpcBuffer(std::vector<SubscribeFilterMsg> &msg): Message type is not trivially copyable! Cannot memcpy!\n");
      std::sort(msg_array.begin(), msg_array.end(), [](const SubscribeFilterMsg &m1, const SubscribeFilterMsg &m2){ return m1.msg_type < m2.msg_type; });

      data_.resize(header_length_ + sizeof(SubscribeFilterMsg) * msg_array.size());
      header_.type = get_enum_from_type<SubscribeFilterMsg>::type;
      header_.msg_count = msg_array.size();
      header_.body_length = sizeof(SubscribeFilterMsg) * msg_array.size();
      memcpy(data_.data() + header_length_, msg_array.data(), sizeof(SubscribeFilterMsg) * msg_array.size());

      setTime(sys_time, gametime);
      encodeHeader();
    }

    //For arrays of messages
    template <typename Message>
    Buffer(const std::vector<Message> &msg_array) : Buffer(msg_array, sys_time_t(), game_time_t()) { }

    template <typename Message>
    Buffer(const std::vector<Message> &msg_array, game_time_t gametime) : Buffer(msg_array, sys_time_t(), gametime) { }

    template <typename Message>
    Buffer(const std::vector<Message> &msg_array, sys_time_t sys_time) : Buffer(msg_array, sys_time, game_time_t()) { }

    template <typename Message>
    Buffer(const std::vector<Message> &msg_array, sys_time_t sys_time, game_time_t gametime) {
      static_assert(IS_TRIVIALLY_COPYABLE(Message),
                    "IpcBuffer(std::vector<Message> &msg): Message type is not trivially copyable! Cannot memcpy!\n");

      if (msg_array.empty() == true) {
        LOG_ERROR("Buffer(std::vector<Message>) - Cannot send empty array!");
        return;
      }

      data_.resize(header_length_ + sizeof(Message) * msg_array.size());
      header_.type = get_enum_from_type<Message>::type;
      header_.msg_count = msg_array.size();
      header_.body_length = sizeof(Message) * msg_array.size();
      memcpy(data_.data() + header_length_, msg_array.data(), sizeof(Message) * msg_array.size());
      setTime(sys_time, gametime);
      encodeHeader();
    }

    //For messages
    template <typename Message>
    Buffer(const Message &msg) : Buffer(msg, sys_time_t(), game_time_t()) { }

    template <typename Message>
    Buffer(const Message &msg, sys_time_t sys_time) : Buffer(msg, sys_time, game_time_t()) { }

    template <typename Message>
    Buffer(const Message &msg, game_time_t gametime) : Buffer(msg, sys_time_t(), gametime) { }

    template <typename Message>
    Buffer(const Message &msg, sys_time_t sys_time, game_time_t gametime) {
      static_assert(IS_TRIVIALLY_COPYABLE(Message), "IpcBuffer(Message &msg): Message type is not trivially copyable! Cannot memcpy!\n");
      static_assert(get_enum_from_type<Message>::type != MsgType::Error, "IpcBuffer(Message &msg): This message is not an IPC message!\n");

      data_.resize(header_length_ + sizeof(Message));
      header_.type = get_enum_from_type<Message>::type;
      header_.msg_count = 1;
      header_.body_length = sizeof(Message);
      memcpy(data_.data() + header_length_, &msg, sizeof(Message));
      setTime(sys_time, gametime);
      encodeHeader();
    }

    //For querry messages (no data)
    Buffer(const MsgType msg) : Buffer(msg, sys_time_t(), game_time_t()) { }

    Buffer(const MsgType msg, game_time_t gametime) : Buffer(msg, sys_time_t(), gametime) { }

    Buffer(const MsgType msg, sys_time_t sys_time) : Buffer(msg, sys_time, game_time_t()) { }

    Buffer(const MsgType msg, sys_time_t sys_time, game_time_t gametime) {
      data_.resize(header_length_);
      header_.type = msg;
      header_.msg_count = 0;
      header_.body_length = 0;
      setTime(sys_time, gametime);
      encodeHeader();
    }

    //This allows passing <MsgType> and get back <const MsgType*> together with error checking
    template<typename Msg>
    typename std::add_pointer< typename std::add_const<Msg>::type >::type cast() const {
      MsgType type = get_enum_from_type< typename std::remove_const< typename std::remove_pointer<Msg>::type >::type >::type;
      if (type != header_.type) {
        LOG_ERROR("Buffer::msg - Wrong conversion between types! This can be a fatal error! Type requested = %i", static_cast<int>(type));
        assert(header_.type == type);
      }
      if (header_.msg_count == 1) {
        if (header_.body_length != sizeof(Msg)) {
          LOG_ERROR("Buffer::msg - Recieved message is different size than cast target! Protocols don't match! Requested size %i vs recieved size %i", static_cast<int>(sizeof(Msg)), static_cast<int>(header_.body_length));
        }
        return reinterpret_cast< typename std::add_pointer< typename std::add_const<Msg>::type >::type >(body());
      } else if (header_.msg_count == 0) {
        LOG_ERROR("Buffer::msg - Trying to reinterpret cast msg with zero count! This can be a fatal error! Type requested = %i", static_cast<int>(type));
        assert(header_.msg_count != 0);
      } else if (header_.msg_count > 1) {
        LOG_ERROR("Buffer::msg - Trying to reinterpret cast msg with msg_count > 1! If arrays are sent, they need to be parsed as array! Type requested = %i", static_cast<int>(type));
        assert(header_.msg_count <= 1);
      }
      return reinterpret_cast<typename std::add_pointer< typename std::add_const<Msg>::type >::type >(body());
    }

    //This allows passing <MsgType> and get back ConstBufferIterator<MsgType> together with error checking
    template<typename Msg>
    ConstBufferIterator<Msg> cast_array() const {
      MsgType type = get_enum_from_type< typename std::remove_const< typename std::remove_pointer<Msg>::type >::type >::type;
      if (type != header_.type) {
        LOG_ERROR("Buffer::msg - Wrong conversion between types! This can be a fatal error! Type requested = %i", static_cast<int>(type));
        assert(header_.type == type);
      }
      if (header_.body_length != header_.msg_count*sizeof(Msg)) {
        LOG_ERROR("Buffer::msg - Recieved message is different size than cast target! Protocols don't match! Requested size %i vs recieved size %i", static_cast<int>(sizeof(Msg)), static_cast<int>(header_.body_length / header_.msg_count));
      }
      return ConstBufferIterator<Msg> (reinterpret_cast<const Msg*>(data_.data() + header_length_),
                                       reinterpret_cast<const Msg*>(data_.data() + header_length_ + header_.body_length));
    }

    //This allows passing <MsgType> and get back BufferIterator<MsgType> together with error checking
    template<typename Msg>
    BufferIterator<Msg> const_cast_array() const {
      MsgType type = get_enum_from_type< typename std::remove_const< typename std::remove_pointer<Msg>::type >::type >::type;
      if ( type != header_.type ) {
        LOG_ERROR("Buffer::msg - Wrong conversion between types! This can be a fatal error! Type requested = %i", static_cast<int>(type));
        assert(header_.type == type);
      }
      if (header_.body_length != header_.msg_count*sizeof(Msg)) {
        LOG_ERROR("Buffer::msg - Recieved message is different size than cast target! Protocols don't match! Requested size %i vs recieved size %i", static_cast<int>(sizeof(Msg)), static_cast<int>(header_.body_length / header_.msg_count));
      }
      return BufferIterator<Msg> (const_cast<Msg*>(reinterpret_cast<const Msg*>(data_.data() + header_length_)),
                                  const_cast<Msg*>(reinterpret_cast<const Msg*>(data_.data() + header_length_ + header_.body_length)));
    }

    MsgType type() const {
      return header_.type;
    }

    const uint8_t* data() const {
      return data_.data();
    }

    uint8_t* data() {
      return data_.data();
    }

    std::chrono::time_point<std::chrono::system_clock> systime() const {
      return system_time_;
    }

    std::chrono::time_point<std::chrono::steady_clock> gametime() const {
      return gametime_;
    }

    size_t length() const {
      return header_length_ + header_.body_length;
    }

    const uint8_t* body() const {
      return data_.data() + header_length_;
    }

    uint8_t* body() {
      return data_.data() + header_length_;
    }

    size_t bodyLength() const {
      return header_.body_length;
    }

    size_t headerLength() const {
      return header_length_;
    }

    size_t messageCount() const {
      return header_.msg_count;
    }

    bool decodeHeader() {
      if (data_.size() < header_length_) {
        data_.resize(header_length_);
      }
      memcpy(&header_, data_.data(), header_length_);

      if ((data_.size() - header_length_) < header_.body_length) {
        /*if (header_.body_length / header_.msg_count != sizeof(get_type_from_enum<MsgType, header_.type>::type)) {
          LOG_ERROR("One msg size doesn't match sizeof(msg_type)!");
          return false;
        }*/
        data_.resize(header_length_ + header_.body_length);
      }

      std::chrono::milliseconds game_time_dur(header_.gametime);
      gametime_ = game_time_t(game_time_dur);

      std::chrono::milliseconds sys_time_dur(header_.sys_time);
      system_time_ = sys_time_t(sys_time_dur);

      //LOG_DEBUG("Header decoded Timestamp = %s and body length = %s and type = %u", std::to_string(header_.gametime).c_str(), std::to_string(header_.body_length).c_str(), static_cast<unsigned int>(header_.type));
      return true;
    }

    void encodeHeader() {
      memcpy(data_.data(), &header_, header_length_);
      //LOG_DEBUG("Header encoded Timestamp = %s and body length = %s and type = %u and sys_time %s", std::to_string(header_.gametime).c_str(), std::to_string(header_.body_length).c_str(), static_cast<unsigned int>(header_.type), std::to_string(header_.sys_time).c_str());
    }

  void setTime(sys_time_t sys_time, game_time_t gametime) {
    if (sys_time == sys_time_t()) { // We have an empty time - it points to epoch
      // Generate sys_time and send that
      system_time_ = std::chrono::system_clock::now();
    } else {
      system_time_ = sys_time;
    }

    // gametime_ will be zero if gametime is not set and points to epoch
    gametime_ = gametime;

    header_.sys_time = static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(system_time_.time_since_epoch()).count());
    header_.gametime = static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::milliseconds>(gametime_.time_since_epoch()).count());
  }

  private:
    std::vector<uint8_t> data_;
    Header header_ = {MsgType::Error, 0, 0, 0, 0};
    game_time_t gametime_;
    sys_time_t system_time_;
    static const size_t header_length_ = sizeof(Header);
};

}

#endif //IPC_BUFFER_H

