#pragma once

#include <MeshCore.h>
#include <string.h>

namespace mesh {

// Packet::header values
#define PH_ROUTE_MASK     0x03   // 2-bits
#define PH_TYPE_SHIFT        2
#define PH_TYPE_MASK      0x0F   // 4-bits
#define PH_VER_SHIFT         6
#define PH_VER_MASK       0x03   // 2-bits

#define ROUTE_TYPE_TRANSPORT_FLOOD   0x00    // flood mode + transport codes
#define ROUTE_TYPE_FLOOD             0x01    // flood mode, needs 'path' to be built up (max 64 bytes)
#define ROUTE_TYPE_DIRECT            0x02    // direct route, 'path' is supplied
#define ROUTE_TYPE_TRANSPORT_DIRECT  0x03    // direct route + transport codes

#define PAYLOAD_TYPE_REQ         0x00    // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_RESPONSE    0x01    // response to REQ or ANON_REQ (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_TXT_MSG     0x02    // a plain text message (prefixed with dest/src hashes, MAC) (enc data: timestamp, text)
#define PAYLOAD_TYPE_ACK         0x03    // a simple ack
#define PAYLOAD_TYPE_ADVERT      0x04    // a node advertising its Identity
#define PAYLOAD_TYPE_GRP_TXT     0x05    // an (unverified) group text message (prefixed with channel hash, MAC) (enc data: timestamp, "name: msg")
#define PAYLOAD_TYPE_GRP_DATA    0x06    // an (unverified) group datagram (prefixed with channel hash, MAC) (enc data: timestamp, blob)
#define PAYLOAD_TYPE_ANON_REQ    0x07    // generic request (prefixed with dest_hash, ephemeral pub_key, MAC) (enc data: ...)
#define PAYLOAD_TYPE_PATH        0x08    // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra)
#define PAYLOAD_TYPE_TRACE       0x09    // trace a path, collecting SNI for each hop
#define PAYLOAD_TYPE_MULTIPART   0x0A    // packet is one of a set of packets
#define PAYLOAD_TYPE_CONTROL     0x0B    // a control/discovery packet
//...
#define PAYLOAD_TYPE_RAW_CUSTOM   0x0F    // custom packet as raw bytes, for applications with custom encryption, payloads, etc

#define PAYLOAD_VER_1       0x00   // 1-byte src/dest hashes, 2-byte MAC
#define PAYLOAD_VER_2       0x01   // FUTURE (eg. 2-byte hashes, 4-byte MAC ??)
#define PAYLOAD_VER_3       0x02   // FUTURE
#define PAYLOAD_VER_4       0x03   // FUTURE

/**
 * \brief  The fundamental transmission unit.
*/
class Packet {
public:
  Packet();

  uint8_t header;
  uint16_t payload_len, path_len;
  uint16_t transport_codes[2];
  uint8_t path[MAX_PATH_SIZE];
  uint8_t payload[MAX_PACKET_PAYLOAD];
  int8_t _snr;

  /**
   * \brief calculate the hash of payload + type
   * \param  dest_hash   destination to store the hash (must be MAX_HASH_SIZE bytes)
   */
  void calculatePacketHash(uint8_t* dest_hash) const;

  /**
   * \returns  one of ROUTE_ values
   */
  uint8_t getRouteType() const { return header & PH_ROUTE_MASK; }

  bool isRouteFlood() const { return getRouteType() == ROUTE_TYPE_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD; }
  bool isRouteDirect() const { return getRouteType() == ROUTE_TYPE_DIRECT || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }

  bool hasTransportCodes() const { return getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD || getRouteType() == ROUTE_TYPE_TRANSPORT_DIRECT; }

  /**
   * \returns  one of PAYLOAD_TYPE_ values
   */
  uint8_t getPayloadType() const { return (header >> PH_TYPE_SHIFT) & PH_TYPE_MASK; }

  /**
   * \returns  one of PAYLOAD_VER_ values
   */
  uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; }

  void markDoNotRetransmit() { header = 0xFF; }
  bool isMarkedDoNotRetransmit() const { return header == 0xFF; }

  float getSNR() const { return ((float)_snr) / 4.0f; }

  /**
   * \returns  the actual byte length of path data.
   *   path_len encodes: lower 6 bits = hop count, upper 2 bits = bytes-per-hop mode
   *   mode 0 = 1 byte/hop (legacy), mode 1 = 2 bytes/hop, mode 2 = 3 bytes/hop
   */
  uint16_t getPathByteLen() const {
    uint8_t hops = path_len & 63;
    uint8_t bph = (path_len >> 6) + 1;
    return hops * bph;
  }

  /** Static variant for computing byte length from any path_len value */
  static uint16_t getPathByteLenFor(uint8_t path_len) {
    return (path_len & 63) * ((path_len >> 6) + 1);
  }

  /** Validate that encoded path_len won't exceed buffer */
  static bool isValidPathLen(uint8_t path_len) {
    return getPathByteLenFor(path_len) <= MAX_PATH_SIZE;
  }

  /** Copy path bytes using encoded path_len; returns path_len unchanged */
  static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) {
    uint16_t bl = getPathByteLenFor(path_len);
    if (bl > MAX_PATH_SIZE) bl = MAX_PATH_SIZE;
    memcpy(dest, src, bl);
    return path_len;
  }

  /** Write path bytes to buffer; returns number of bytes written */
  static uint8_t writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len) {
    uint16_t bl = getPathByteLenFor(path_len);
    if (bl > MAX_PATH_SIZE) bl = MAX_PATH_SIZE;
    memcpy(dest, src, bl);
    return (uint8_t)bl;
  }

  /**
   * \returns  the encoded/wire format length of this packet
   */
  int getRawLength() const;

  /**
   * \brief  save entire packet as a blob
   * \param dest  (OUT) destination buffer (assumed to be MAX_MTU_SIZE)
   * \returns  the packet length
   */
  uint8_t writeTo(uint8_t dest[]) const;

  /**
   * \brief  restore this packet from a blob (as created using writeTo())
   * \param  src  (IN) buffer containing blob
   * \param  len  the packet length (as returned by writeTo())
   */
  bool readFrom(const uint8_t src[], uint8_t len);
};

}