/**
 * Utilities for console commands
 * 
 * Created on 2020/03/11.
 */

#ifndef LIBCONSOLE_UTILS_H
#define LIBCONSOLE_UTILS_H

#include <stdio.h>
#include <console/console.h>

#ifndef STR
#define STR_HELPER(x) #x
#define STR(x) STR_HELPER(x)
#endif

#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif

#ifndef MAX
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif

#ifndef OBC_FIRMWARE
#define EXPENDABLE_STRING(x) x
#define EXPENDABLE_CODE(x) x
#else
#define EXPENDABLE_STRING(x) ""
#define EXPENDABLE_CODE(x) do {} while(0);
#endif

/**
 * Read an argument, or return default if it is empty.
 *
 * This works for commands arg_int0
 *
 * Usage:
 *
 * \code
 * static struct {
 *     struct arg_int *foo;
 * } args;
 *
 * args.foo = arg_int0(...);
 *
 * int foo = GET_ARG_INT0(args.foo, 1234);
 * \endcode
 */
#define GET_ARG_INT0(_arg, _def) ((_arg)->count ? (_arg)->ival[0] : (_def))

/**
 * Get CSP node ID from an argument table, using own address as default.
 *
 * Usage:
 *
 * \code
 * static struct {
 *     struct arg_int *node;
 * } args;
 *
 * args.node = arg_int0(...);
 *
 * int node = GET_ARG_CSPADDR0(args.node);
 * \endcode
 */
#define GET_ARG_CSPADDR0(_arg) GET_ARG_INT0((_arg), csp_get_address())

/**
 * Shortcut to get a timeout argument's value, using CSP_DEF_TIMEOUT_MS as default.
 */
#define GET_ARG_TIMEOUT0(_arg) GET_ARG_INT0((_arg), CONSOLE_CSP_DEF_TIMEOUT_MS)

/**
 * Define an optional CSP node argument
 */
#define arg_cspaddr0() arg_int0(NULL, NULL, "<node>", EXPENDABLE_STRING("node ID"))

/**
 * Define a mandatory CSP node argument
 */
#define arg_cspaddr1() arg_int1(NULL, NULL, "<node>", EXPENDABLE_STRING("node ID"))

/**
 * Define an optional timeout argument, with `CSP_DEF_TIMEOUT_MS`
 * shown as default. Use `GET_ARG_TIMEOUT0()` to retrieve its value.
 */
#define arg_timeout0() arg_int0("t", "timeout", "<ms>", EXPENDABLE_STRING("timeout in ms (default "STR(CONSOLE_CSP_DEF_TIMEOUT_MS)")"))

/**
 * Define a timeout argument with a custom value shown as default.
 * Use `GET_ARG_INT0()` with the matching default to retrieve its value.
 */
#define arg_timeout0_def(_def) arg_int0("t", "timeout", "<ms>", EXPENDABLE_STRING("timeout in ms (default "STR(_def)")"))


#define EMPTY_CMD_SETUP(_helptext)                \
    (void)ctx;                                    \
    static struct {                               \
        struct arg_end *end;                      \
    } args;                                       \
                                                  \
    if (reg) {                                    \
        args.end = arg_end(1);                    \
                                                  \
        reg->argtable = &args;                    \
        reg->help = EXPENDABLE_STRING(_helptext); \
        return 0;                                 \
    }

/**
 * Hexdump a buffer
 *
 * @param outf - output file
 * @param data - data to dump
 * @param len - data size
 */
void console_hexdump(const void *data, size_t len);

/**
 * Decode hexa string to binary
 *
 * @param hex - hexa string, upper or lower case, must have even length
 * @param dest - destination buffer
 * @param capacity - buffer size
 * @return destination length, or: -1 (bad args), -2 (bad format), -3 (too long)
 */
int console_base16_decode(const char *hex, void *dest, size_t capacity);

#if CONSOLE_USE_MEMSTREAM

/**
 * Data struct for the filecap utilities
 */
struct console_filecap {
    char *buf;
    size_t buf_size;
    FILE *file;
};

typedef struct console_filecap console_filecap_t;

/**
 * Open a temporary in-memory file that can be used to capture the output
 * of functions taking a FILE * argument.
 *
 * @param cap - pointer to a filecap struct (can be on stack)
 * @return success
 */
console_err_t console_filecap_init(console_filecap_t *cap);

/**
 * Clean up the capture struct.
 *
 * If the buffer is to be used elsewhere, place NULL in the struct to avoid freeing it.
 *
 * If the struct itself was allocated on heap, it is the caller's responsibility to free it manually.
 *
 * @param cap - pointer to a filecap struct
 */
void console_filecap_end(console_filecap_t *cap);

/**
 * Print the captured output to console output stream and clean up the struct (calls `console_filecap_end()`)
 *
 * @param cap - pointer to a filecap struct
 */
void console_filecap_print_end(console_filecap_t *cap);

#endif // CONSOLE_USE_MEMSTREAM

/**
 * Cross-platform malloc that can be used from console commands.
 * If CSP is available and the platform is not POSIX, the implementation from there is used (i.e. FreeRTOS alloc)
 */
void * __attribute__((malloc)) console_malloc(size_t size);

/**
 * Cross-platform realloc that can be used from console commands.
 *
 * It is not possible to determine the size of an allocated memory region in a portable way,
 * that's why this function takes the old size as an argument. On POSIX, the argument is simply ignored.
 *
 * If CSP is available and the platform is not POSIX, the implementation from there is used (i.e. FreeRTOS alloc).
 * NOTE: CSP does not provide realloc, therefore this function allocates a new buffer, copies data, and frees the old buffer.
 *
 * Returns the original buffer if the new size is <= old size.
 */
void * console_realloc(void *ptr, size_t oldsize, size_t newsize);

/**
 * Cross-platform calloc that can be used from console commands.
 * If CSP is available and the platform is not POSIX, the implementation from there is used (i.e. FreeRTOS alloc)
 */
void * __attribute__((malloc,alloc_size(1,2))) console_calloc(size_t nmemb, size_t size);

/**
 * `free()` for memory allocated by `console_malloc()` or `console_calloc()`
 */
void console_free(void *ptr);

/**
 * `strdup()` using `console_malloc()`. Free with `console_free()`
 */
char * console_strdup(const char *ptr);

/**
 * `strndup()` using `console_malloc()`. Free with `console_free()`
 */
char * console_strndup(const char *ptr, size_t maxlen);

#endif //LIBCONSOLE_UTILS_H