/**
 * \file
 * \brief
 * CBOR parsing
 */

#ifndef CN_CBOR_H
#define CN_CBOR_H

#ifdef  __cplusplus
extern "C" {
#endif

//typedef int ssize_t;

#ifdef EMACS_INDENTATION_HELPER
} /* Duh. */
#endif

#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>

//typedef ssize_t int;
//#include <unistd.h>

/**
 * All of the different kinds of CBOR values.
 */
typedef enum cn_cbor_type {
  /** false */
  CN_CBOR_FALSE,
  /** true */
  CN_CBOR_TRUE,
  /** null */
  CN_CBOR_NULL,
  /** undefined */
  CN_CBOR_UNDEF,
  /** Positive integers */
  CN_CBOR_UINT,
  /** Negative integers */
  CN_CBOR_INT,
  /** Byte string */
  CN_CBOR_BYTES,
  /** UTF-8 string */
  CN_CBOR_TEXT,
  /** Byte string, in chunks.  Each chunk is a child. */
  CN_CBOR_BYTES_CHUNKED,
  /** UTF-8 string, in chunks.  Each chunk is a child */
  CN_CBOR_TEXT_CHUNKED,
  /** Array of CBOR values.  Each array element is a child, in order */
  CN_CBOR_ARRAY,
  /** Map of key/value pairs.  Each key and value is a child, alternating. */
  CN_CBOR_MAP,
  /** Tag describing the next value.  The next value is the single child. */
  CN_CBOR_TAG,
  /** Simple value, other than the defined ones */
  CN_CBOR_SIMPLE,
  /** Doubles, floats, and half-floats */
  CN_CBOR_DOUBLE,
  /** An error has occurred */
  CN_CBOR_INVALID
} cn_cbor_type;

/**
 * Flags used during parsing.  Not useful for consumers of the
 * `cn_cbor` structure.
 */
typedef enum cn_cbor_flags {
  /** The count field will be used for parsing */
  CN_CBOR_FL_COUNT = 1,
  /** An indefinite number of children */
  CN_CBOR_FL_INDEF = 2,
  /** Not used yet; the structure must free the v.str pointer when the
     structure is freed */
  CN_CBOR_FL_OWNER = 0x80,            /* of str */
} cn_cbor_flags;

// FIXME: These could be used in the future instead of type and flags in struct cn_cbor (will make the sizeof(cn_cbor) 8 bytes less)
//#define CN_CBOR_TYPE(pCbor) (pCbor->enums & 0x0f)
//#define CN_CBOR_FLAGS(pCbor) (pCbor->enums & 0xf0)

/**
 * A CBOR value
 */

typedef struct cn_cbor {
  /** The type of value */
  cn_cbor_type type;
  /** Flags used at parse time */
  cn_cbor_flags flags;
  /** Data associated with the value; different branches of the union are
      used depending on the `type` field. */
  union {
    /** CN_CBOR_BYTES */
    const uint8_t* bytes;
    /** CN_CBOR_TEXT */
    const char* str;
    /** CN_CBOR_INT */
    int64_t sint;
    /** CN_CBOR_UINT */
    uint64_t uint;
    /** CN_CBOR_DOUBLE */
    double dbl;
    /** for use during parsing */
    unsigned long count;
  } v;                          /* TBD: optimize immediate */
  /** Number of children.
    * @note: for maps, this is 2x the number of entries */
  int length;

  // FIXME: This could be used in the future instead of type and flags in struct cn_cbor (will make the sizeof(cn_cbor) 8 bytes less)
  //uint8_t enums;

  /** The first child value */
  struct cn_cbor* first_child;
  /** The last child value */
  struct cn_cbor* last_child;
  /** The sibling after this one, or NULL if this is the last */
  struct cn_cbor* next;
  /** The parent of this value, or NULL if this is the root */
  struct cn_cbor* parent;
} cn_cbor;

/**
 * All of the different kinds of errors
 */
typedef enum cn_cbor_error {
  /** No error has occurred */
  CN_CBOR_NO_ERROR,
  /** More data was expected while parsing */
  CN_CBOR_ERR_OUT_OF_DATA,
  /** Some extra data was left over at the end of parsing */
  CN_CBOR_ERR_NOT_ALL_DATA_CONSUMED,
  /** A map should be alternating keys and values.  A break was found
      when a value was expected */
  CN_CBOR_ERR_ODD_SIZE_INDEF_MAP,
  /** A break was found where it wasn't expected */
  CN_CBOR_ERR_BREAK_OUTSIDE_INDEF,
  /** Indefinite encoding works for bstrs, strings, arrays, and maps.
      A different major type tried to use it. */
  CN_CBOR_ERR_MT_UNDEF_FOR_INDEF,
  /** Additional Information values 28-30 are reserved */
  CN_CBOR_ERR_RESERVED_AI,
  /** A chunked encoding was used for a string or bstr, and one of the elements
      wasn't the expected (string/bstr) type */
  CN_CBOR_ERR_WRONG_NESTING_IN_INDEF_STRING,
  /** An invalid parameter was passed to a function */
  CN_CBOR_ERR_INVALID_PARAMETER,
  /** Allocation failed */
  CN_CBOR_ERR_OUT_OF_MEMORY,
  /** A float was encountered during parse but the library was built without
      support for float types. */
  CN_CBOR_ERR_FLOAT_NOT_SUPPORTED,
  /** Encoder was unable to encode the cn_cbor object that was provided */
  CN_CBOR_ERR_ENCODER
} cn_cbor_error;

/**
 * Strings matching the `cn_cbor_error` conditions.
 *
 * @todo: turn into a function to make the type safety more clear?
 */
extern const char *cn_cbor_error_str[];

/**
 * Errors
 */
typedef struct cn_cbor_errback {
  /** The position in the input where the erorr happened */
  int pos;
  /** The error, or CN_CBOR_NO_ERROR if none */
  cn_cbor_error err;
} cn_cbor_errback;

#ifdef USE_CBOR_CONTEXT


/**
 * Allocate and zero out memory.  `count` elements of `size` are required,
 * as for `calloc(3)`.  The `context` is the `cn_cbor_context` passed in
 * earlier to the CBOR routine.
 *
 * @param[in] count   The number of items to allocate
 * @param[in] size    The size of each item
 * @param[in] context The allocation context
 */
typedef void* (*cn_calloc_func)(size_t count, size_t size, void *context);

/**
 * Free memory previously allocated with a context.  If using a pool allocator,
 * this function will often be a no-op, but it must be supplied in order to
 * prevent the CBOR library from calling `free(3)`.
 *
 * @note: it may be that this is never needed; if so, it will be removed for
 * clarity and speed.
 *
 * @param  context [description]
 * @return         [description]
 */
typedef void (*cn_free_func)(void *ptr, void *context);

/**
 * The allocation context.
 */
typedef struct cn_cbor_context {
    /** The pool `calloc` routine.  Must allocate and zero. */
    cn_calloc_func calloc_func;
    /** The pool `free` routine.  Often a no-op, but required. */
    cn_free_func  free_func;
    /** Typically, the pool object, to be used when calling `calloc_func`
      * and `free_func` */
    void *context;
} cn_cbor_context;

extern cn_cbor_context *cbor_context;

/** When USE_CBOR_CONTEXT is defined, many functions take an extra `context`
  * parameter */
#define CBOR_CONTEXT , cn_cbor_context *cbor_context
/** When USE_CBOR_CONTEXT is defined, some functions take an extra `context`
  * parameter at the beginning */
#define CBOR_CONTEXT_COMMA cn_cbor_context *cbor_context,
/** When USE_CBOR_CONTEXT is defined, some functions take a `context`
  * parameter, as the only argument */
#define CBOR_CONTEXT_NO_COMMA cn_cbor_context *cbor_context

/** The following two defines are used by caller as arguments for the  
  * function that is being called - to use context*/
#define CBOR_CONTEXT_PARAM , cbor_context
#define CBOR_CONTEXT_PARAM_COMMA cbor_context, 

/** The following two defines are used by caller as arguments for the
  * function that is being called - to force use of LIBC allocation instead of pool*/
#define CBOR_CONTEXT_NULL , NULL
#define CBOR_CONTEXT_NULL_COMMA NULL,


#else

#define CBOR_CONTEXT
#define CBOR_CONTEXT_COMMA
#define CBOR_CONTEXT_NO_COMMA void
#define CBOR_CONTEXT_PARAM
#define CBOR_CONTEXT_PARAM_COMMA
#define CBOR_CONTEXT_NULL
#define CBOR_CONTEXT_NULL_COMMA

#endif

/**
 * Decode an array of CBOR bytes into structures.
 *
 * @param[in]  buf          The array of bytes to parse
 * @param[in]  len          The number of bytes in the array
 * @param[in]  CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out] errp         Error, if NULL is returned
 * @return                  The parsed CBOR structure, or NULL on error
 */
cn_cbor* cn_cbor_decode(const uint8_t *buf, size_t len CBOR_CONTEXT, cn_cbor_errback *errp);

/**
 * Get a value from a CBOR map that has the given string as a key.
 *
 * @param[in]  cb           The CBOR map
 * @param[in]  key          The string to look up in the map
 * @return                  The matching value, or NULL if the key is not found
 */
cn_cbor* cn_cbor_mapget_string(const cn_cbor* cb, const char* key);

/**
 * Get a value from a CBOR map that has the given integer as a key.
 *
 * @param[in]  cb           The CBOR map
 * @param[in]  key          The int to look up in the map
 * @return                  The matching value, or NULL if the key is not found
 */
cn_cbor* cn_cbor_mapget_int(const cn_cbor* cb, int key);

/**
 * Get the item with the given index from a CBOR array.
 *
 * @param[in]  cb           The CBOR map
 * @param[in]  idx          The array index
 * @return                  The matching value, or NULL if the index is invalid
 */
cn_cbor* cn_cbor_index(const cn_cbor* cb, unsigned int idx);

/**
 * Free the given CBOR structure.
 * You MUST NOT try to free a cn_cbor structure with a parent (i.e., one
 * that is not a root in the tree).
 *
 * @param[in]  cb           The CBOR value to free.  May be NULL, or a root object.
 * @param[in]  CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 */
void cn_cbor_free(cn_cbor* cb CBOR_CONTEXT);

/**
* Get the size of the buffer that must be provided to cn_cbor_encoder_write().
* This is the size of the CBOR that will be encoded.
*
* @param[in]  cb         Pointer to a cn_cbor structure, which the user wishes to encode.
* @param[out] err	 Error, if -1 is returned.
* @return                -1 on fail, or number size of the allocated buffer containing the encoded data.
*/

int cn_cbor_get_encoded_size(const cn_cbor *cb, cn_cbor_errback *err);

/**
 * Write a CBOR value and all of the child values.
 * Allocates a buffer of the correct size and fills it with the encoded CBOR. 
 * User must free the buffer.
 *
 * @param[in]  cb         Pointer to a cn_cbor structure, which the user wishes to encode.
 * @param[out] bufOut     Pointer a buffer which will be filled with the encoded data.
 * @param[in]  bufSize	  Size of the provided buffer in bytes.
 * @param[out] err	  Error, if -1 is returned.
 * @return                -1 on fail, or number encoded bytes written to the provided buffer.
 */

int cn_cbor_encoder_write(const cn_cbor *cb, uint8_t *bufOut, int bufSize, cn_cbor_errback *err);

/**
* Get the size in bytes of the encoding of only cn_cbor container (map or array) discarding its parents and siblings.
*
* @param[in]  cb_container         Pointer to a cn_cbor structure of either type CN_CBOR_MAP or CN_CBOR_ARRAY. Mey be part of a bigger cn_cbor structure.
* @return                          -1 on fail, or number encoded bytes the container will be .
*/
int cn_cbor_get_encoded_container_size(const cn_cbor *cb_container);

/**
 * Create a CBOR map.
 *
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created map, or NULL on error
 */
cn_cbor* cn_cbor_map_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp);

/**
 * Create a CBOR byte string.  The data in the byte string is *not* owned
 * by the CBOR object, so it is not freed automatically.
 *
 * @param[in]   data         The data
 * @param[in]   len          The number of bytes of data
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_data_create(const uint8_t* data, int len
                             CBOR_CONTEXT,
                             cn_cbor_errback *errp);

/**
 * Create a CBOR UTF-8 string from a buffer. The data is not checked for UTF-8 correctness.
 * The data being stored in the string is *not* owned the CBOR object, so it is
 * not freed automatically.
 *
 * @param[in]   data         UTF-8 string (does not have to be NULL terminated)
 * @param[in]   len          The number of bytes of data
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_text_create(const uint8_t* data, int len
                             CBOR_CONTEXT,
                             cn_cbor_errback *errp);

/**
 * Create a CBOR UTF-8 string.  The data is not checked for UTF-8 correctness.
 * The data being stored in the string is *not* owned the CBOR object, so it is
 * not freed automatically.
 *
 * @note: Do NOT use this function with untrusted data.  It calls strlen, and
 * relies on proper NULL-termination.
 *
 * @param[in]   data         NULL-terminated UTF-8 string
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_string_create(const char* data
                               CBOR_CONTEXT,
                               cn_cbor_errback *errp);

/**
 * Create a CBOR integer (either positive or negative).
 *
 * @param[in]   value    the value of the integer
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_int_create(int64_t value
                            CBOR_CONTEXT,
                            cn_cbor_errback *errp);


/**
* Create a CBOR unsigned integer ( positive ).
*
* @param[in]   value    the value of the  unsigned integer
* @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
* @param[out]  errp         Error, if NULL is returned
* @return                   The created object, or NULL on error
*/
cn_cbor* cn_cbor_uint_create(uint64_t value
    CBOR_CONTEXT,
    cn_cbor_errback *errp);

/**
 * Put a CBOR object into a map with a CBOR object key.  Duplicate checks are NOT
 * currently performed.
 *
 * @param[in]   cb_map       The map to insert into
 * @param[in]   key          The key
 * @param[in]   cb_value     The value
 * @param[out]  errp         Error
 * @return                   True on success
 */
bool cn_cbor_map_put(cn_cbor* cb_map,
                     cn_cbor *cb_key, cn_cbor *cb_value,
                     cn_cbor_errback *errp);

/**
 * Put a CBOR object into a map with an integer key.  Duplicate checks are NOT
 * currently performed.
 *
 * @param[in]   cb_map       The map to insert into
 * @param[in]   key          The integer key
 * @param[in]   cb_value     The value
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error
 * @return                   True on success
 */
bool cn_cbor_mapput_int(cn_cbor* cb_map,
                        int64_t key, cn_cbor* cb_value
                        CBOR_CONTEXT,
                        cn_cbor_errback *errp);

/**
 * Put a CBOR object into a map with a string key.  Duplicate checks are NOT
 * currently performed.
 *
 * @note: do not call this routine with untrusted string data.  It calls
 * strlen, and requires a properly NULL-terminated key.
 *
 * @param[in]   cb_map       The map to insert into
 * @param[in]   key          The string key
 * @param[in]   cb_value     The value
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error
 * @return                   True on success
 */
bool cn_cbor_mapput_string(cn_cbor* cb_map,
                           const char* key, cn_cbor* cb_value
                           CBOR_CONTEXT,
                           cn_cbor_errback *errp);

/**
 * Create a CBOR array
 *
 * @param[in]   CBOR_CONTEXT Allocation context (only if USE_CBOR_CONTEXT is defined)
 * @param[out]  errp         Error, if NULL is returned
 * @return                   The created object, or NULL on error
 */
cn_cbor* cn_cbor_array_create(CBOR_CONTEXT_COMMA cn_cbor_errback *errp);

/**
 * Append an item to the end of a CBOR array.
 *
 * @param[in]   cb_array  The array into which to insert
 * @param[in]   cb_value  The value to insert
 * @param[out]  errp      Error
 * @return                True on success
 */
bool cn_cbor_array_append(cn_cbor* cb_array,
                          cn_cbor* cb_value,
                          cn_cbor_errback *errp);

#ifdef USE_CBOR_CONTEXT

// The maximum number of CBOR objects in an allocated pool.
#define MAX_SIZE_POOL_IN_CBORS 64

/**
* Init a new cn_cbor_context.
* This will dynamically allocate a pool of cn_cbor objects and must be freed by calling cn_cbor_free_context.
* FIXME: The created context is currently stored as a global in cn-cbor.c for easy access and convenience.
*        This means only one CBOR context can be used at a time and is prone to errors if we are not careful.
*        In the future we will probably want to init a context and and pass it on all the way to the cn_cbor functions that need access to it, Instead of using a global.
*        This will also allow us to use several contexts at a time and run multi-threaded.
*
* @param[in]   num_of_cbors_in_pool The number of cn_cbor object in the pool. The size of the dynamically allocated pool will be num_of_cbors_in_pool * sizeof(cn_cbor)
*
* @return      A pointer to the created context, or NULL in case of error
*/
cn_cbor_context *cn_cbor_init_context(size_t num_of_cbors_in_pool);

/**
* Release the resources of a cn_cbor_context.
* Releases the dynamically allocated pool. if either ctx is NULL or the pool is NULL,
* then the function will do nothing. This should never happen, unless user committed an error.
* The function does not return anything to conform with standard freeing practices.
*
* @param[in]    ctx Pointer to the cn_cbor_context object which points to the memory pool we wish to free
*
*/
void cn_cbor_free_context(cn_cbor_context *ctx);

#ifdef USE_CBOR_CONTEXT_STATS

/**
* Print stats of the memory allocations of the CBOR library.
*/
void cn_cbor_context_print_stats(void);

/**
* Resets the stats of the memory allocations of the CBOR library.
*/
void cn_cbor_context_reset_stats(void);

#else
#define cn_cbor_context_print_stats()
#define cn_cbor_context_reset_stats()
#endif // USE_CBOR_CONTEXT_STATS

#endif // USE_CBOR_CONTEXT


#ifdef  __cplusplus
}
#endif

#endif  /* CN_CBOR_H */
