#pragma once
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"

#include <balancer/serval/contrib/cone/mun.h>

#if __cplusplus
extern "C" {
#endif

enum CNO_ERRNO {
    CNO_OK                    = 0,
    CNO_ERRNO_ASSERTION       = EINVAL,
    CNO_ERRNO_NO_MEMORY       = ENOMEM,
    CNO_ERRNO_NOT_IMPLEMENTED = ENOSYS,
    CNO_ERRNO_PROTOCOL        = EBADMSG, // (irrecoverable) protocol error
    CNO_ERRNO_INVALID_STREAM  = EPIPE, // cno_write_* with wrong arguments
    CNO_ERRNO_WOULD_BLOCK     = EWOULDBLOCK, // cno_write_message would go above the limit on concurrent messages - wait for a request to complete
    CNO_ERRNO_DISCONNECT      = EPIPE, // connection has already been closed
};

struct cno_buffer_t {
    // depending on where this thing is used, it may hold either binary octets
    // or human-readable data (http headers). casting the buffer to uint8_t
    // where necessary is easy, converting all string literals to uint8_t is not.
    const char *data;
    size_t size;
};

struct cno_buffer_dyn_t mun_vec(char);

#define CNO_ERROR(n, ...)    mun_error_at(CNO_ERRNO_ ## n, "cno/" #n, MUN_CURRENT_FRAME, __VA_ARGS__)
#define CNO_ERROR_UP()       mun_error_up(MUN_CURRENT_FRAME)

// cffi does not compile inline functions
#if !CFFI_CDEF_MODE

static inline struct cno_buffer_t CNO_BUFFER_STRING(const char *s) {
    return (struct cno_buffer_t) { s, strlen(s) };
}

static inline struct cno_buffer_t CNO_BUFFER_VIEW(const struct cno_buffer_dyn_t b) {
    return (struct cno_buffer_t) { b.data, b.size };
}

static inline int cno_buffer_eq(const struct cno_buffer_t a, const struct cno_buffer_t b) {
    return a.size == b.size && 0 == memcmp(a.data, b.data, b.size);
}

static inline int cno_buffer_startswith(const struct cno_buffer_t a, const struct cno_buffer_t b) {
    return a.size >= b.size && 0 == memcmp(a.data, b.data, b.size);
}

static inline int cno_buffer_endswith(const struct cno_buffer_t a, const struct cno_buffer_t b) {
    return a.size >= b.size && 0 == memcmp(a.data + a.size - b.size, b.data, b.size);
}

static inline struct cno_buffer_t cno_buffer_shift(const struct cno_buffer_t x, size_t offset) {
    return (struct cno_buffer_t) {x.data + offset, x.size - offset};
}

static inline struct cno_buffer_t cno_buffer_cut(struct cno_buffer_t *x, size_t offset) {
    struct cno_buffer_t ret = {x->data, offset};
    *x = cno_buffer_shift(*x, offset);
    return ret;
}

#define cno_buffer_dyn_clear mun_vec_fini
#define cno_buffer_dyn_shift(x, n) mun_vec_erase(x, 0, n)
#define cno_buffer_dyn_reserve mun_vec_reserve

static inline int cno_buffer_dyn_concat(struct cno_buffer_dyn_t *a, const struct cno_buffer_t b) {
    return mun_vec_extend(a, b.data, b.size);
}

#endif

#if __cplusplus
}
#endif
