/**
 * Console - VCOM command engine
 *
 * Created on 2020/02/28 by Ondrej Hruska
 *
 * Parts are based on the console component from esp-idf
 * licensed under the Apache 2 license.
 */

#ifndef LIBCONSOLE_H
#define LIBCONSOLE_H

#include <stdio.h>

#include <console/config.h>
#include <stdint.h>
#include <stdbool.h>

typedef enum {
  /* Colors */
  COLOR_RESET     = 0xF0,
  COLOR_BLACK     = 0x01,
  COLOR_RED       = 0x02,
  COLOR_GREEN     = 0x03,
  COLOR_YELLOW    = 0x04,
  COLOR_BLUE      = 0x05,
  COLOR_MAGENTA   = 0x06,
  COLOR_CYAN      = 0x07,
  COLOR_WHITE     = 0x08,
  /* Modifiers */
  COLOR_NORMAL    = 0x0F,
  COLOR_BOLD      = 0x10,
  COLOR_UNDERLINE = 0x20,
  COLOR_BLINK     = 0x30,
  COLOR_HIDE      = 0x40,
} console_color_t;

#if CONSOLE_USE_FREERTOS
    #include "freertos/FreeRTOS.h"
    #include "freertos/task.h"
    #include "freertos/semphr.h"

    typedef SemaphoreHandle_t console_mutex_t;
#endif

#if CONSOLE_USE_PTHREADS
  #include <pthread.h>
    typedef pthread_mutex_t console_mutex_t;
#endif

#if CONSOLE_USE_TERMIOS
#include <termios.h>
#endif

/**
 * Console config struct
 */
struct console_config {
    /**
     * Timeout waiting for execution lock when handling a command.
     * This should be longer than the slowest command in the system.
     */
    uint32_t execution_lock_timeout_ms;
};

/**
 * Macro to init the console config struct
 */
#define CONSOLE_CONFIG_DEFAULTS() { \
    .execution_lock_timeout_ms = 10000, \
}

typedef struct console_config console_config_t;

struct console_ctx; // early declaration

/**
 * Console context
 */
typedef struct console_ctx console_ctx_t;

/**
 * Console errors - return codes
 */
enum console_err {
    CONSOLE_OK = 0,
    /** unspecified error */
    CONSOLE_ERROR = 1,
    /** Allocation failed */
    CONSOLE_ERR_NO_MEM,
    /** Function call not allowed (e.g. console not inited) */
    CONSOLE_ERR_BAD_CALL,
    /** Argument validation failed */
    CONSOLE_ERR_INVALID_ARG,
    /** Command not recognized */
    CONSOLE_ERR_UNKNOWN_CMD,
    /** Timeout */
    CONSOLE_ERR_TIMEOUT,
    /** IO error (file open fail, etc.) */
    CONSOLE_ERR_IO,
    /** Operation denied (not allowed / insufficient rights) */
    CONSOLE_ERR_NOT_POSSIBLE,
    /** End marker */
    _CONSOLE_ERR_MAX,
};

/**
 * Print string describing console error.
 * In case of unknown error, the number is shown.
 *
 * The argument should be `enum console_err`, but any number is valid.
 */
void console_err_print_ctx(struct console_ctx *ctx, int e);

// TODO error-to-string function

typedef enum console_err console_err_t;

// early decl's
struct cmd_signature;
typedef struct cmd_signature cmd_signature_t;

/**
 * Command signature, passed as the last argument to the command handler
 * to perform registration.
 *
 * \note Fill only fields that differ from default (zeros/NULLs)
 */
struct cmd_signature {
    const char* command;  //!< Command name, used in invocations (filled internally, do not set)
    const char* help;     //!< Command help text, shown when called with -h
    const char* hint;     //!< Hint text, generated from argtable if hint==NULL & argtable!=NULL
    bool no_history;      //!< Command skips history
    bool custom_args;     //!< Disable argtable parsing in the handler function, will be parsed manually from argv/argc

    /**
     * Argtable, struct or array that must end with arg_end().
     * Used by the register function and for disambiguation.
     */
    void* argtable;
};

/**
 * Active console context pointer, valid only when handling a console command (otherwise NULL).
 *
 * Used by the console printf & other IO methods.
 */
extern struct console_ctx * console_active_ctx;

/**
 * Function handling a callback from the console loop.
 */
typedef void(*console_callback_t)(console_ctx_t *ctx);

/**
 * Console context struct
 */
struct console_ctx {
#if CONSOLE_USE_FILE_IO_STREAMS
    // Streams
    FILE* in;  //!< stdin fd, can be -1 if not available (running commands in non-interactive mode)
    FILE* out; //!< stdout fd

#if CONSOLE_USE_TERMIOS
    // original termios is stored here before entering raw mode
    struct termios orig_termios;
#endif

#else
    void *ioctx;
#endif //CONSOLE_USE_FILE_IO_STREAMS

#if CONSOLE_FILE_SUPPORT
    char *history_file;
#endif //CONSOLE_FILE_SUPPORT

    bool __internal_heap_allocated;
    bool exit_allowed;

    char prompt[CONSOLE_PROMPT_MAX_LEN]; //!< Prompt, can be modified by a command or `before_readline_fn`
    char line_buffer[CONSOLE_LINE_BUF_LEN];

    /**
     * Callback fired in the command evaluation loop, each time before the prompt is shown and new line read.
     * This command can print to the output streams, change prompt, shutdown console, etc.
     */
    console_callback_t loop_handler;

    /**
     * Callback fired before the console task shuts down
     */
    console_callback_t shutdown_handler;

    /**
     * Shutdown requested. Console will exit as soon as possible.
     */
    bool exit_requested;

    /**
     * Interactive mode. Enables additional outputs for user convenience.
     */
    bool interactive;

    /**
     * Enable ANSI colors
     */
    bool use_colors;

    /* These fields are valid only during command execution */
    const char **argv; //!< The current argv
    size_t argc;       //!< The current argc
    const cmd_signature_t *cmd; //!< Pointer to the currently executed command signature

    /** Used for argument validation */
    uint32_t __internal_magic;
};

#define CONSOLE_CTX_MAGIC 0x6f587468

/**
 * Command handler type.
 *
 * @param ctx - console context, including input/output files
 * @param reg - signature struct; if not NULL, init the static argtable, fill this struct, and return OK (0).
 * @return status code, 0 = OK (use cons_err_t constants if possible)
 */
typedef int (*console_command_t)(console_ctx_t *ctx, struct cmd_signature *reg);

/**
 * Intialize the console
 *
 * @param config - config pointer, NULL to use defaults
 * @return status code
 */
console_err_t console_init(const console_config_t *config);

/**
 * @brief Register console command
 *
 * If the command function is already registered, this creates an alias.
 * A multi-word command automatically create a command group. The group can
 * be described using a description string by calling `console_group_add()`
 * - at convenience before or after the commands are registered.
 *
 * @param name - command name (may contain spaces for "multi-part commands")
 * @param handler pointer to the command handler.
 * @return status code
 */
console_err_t console_cmd_register(console_command_t handler, const char *name);

/**
 * @brief Register a command group.
 *
 * Command groups are created automatically when used.
 * This method can create a group with description, or attach a custom description
 * to an existing group.
 *
 * @param name - group name (first word of multi-part commands)
 * @param descr - description to attach, can be NULL
 * @return staus code
 */
console_err_t console_group_add(const char *name, const char *descr);

/**
 * Add alias to an existing command by name.
 *
 * @param original - original command name
 * @param alias - command's alias
 * @return status code
 */
console_err_t console_cmd_add_alias(const char *original, const char *alias);

/**
 * Add alias by handler function
 *
 * @param handler - command handler
 * @param alias - new name
 * @return status code
 */
console_err_t console_cmd_add_alias_fn(console_command_t handler, const char *alias);

/**
 * Internal error print function. Has WEAK linkage, can be overridden.
 *
 * This function is used to report detected bugs and should not be called
 * in well-written "production code".
 *
 * @param msg - error message
 */
void console_internal_error_print(const char *msg);

/**
 * This function is guarded by a mutex and will wait for the execution lock as
 * configured in console_config.
 *
 * @brief Run command line
 * @param[in] outf
 * @param[in]  inf
 * @param cmdline command line (command name followed by a number of arguments)
 * @param[out] pRetval return code from the command (set if command was run)
 * @param[out] pCommandSig - is set to a pointer to the matched command signature, or NULL on error
 * @return status code
 */
console_err_t console_handle_cmd(
    console_ctx_t *ctx,
    const char *cmdline,
    int *pRetval,
    const struct cmd_signature **pCommandSig
);

/**
 * Count all registered commands
 *
 * @return
 */
size_t console_count_commands(void);

/**
 * Create a console IO context and init it to defaults.
 *
 * takes stdin and stdout file descriptors, or IO context (based on config flags)
 *
 * In the FD variant, pass NULL as STDIN if not available.
 *
 * @param ctx - context, if using static alloc, NULL to allocate internally.
 * @return the context, NULL if alloc or init fails
 */
console_ctx_t *console_ctx_init(
    console_ctx_t *ctx,
#if CONSOLE_USE_FILE_IO_STREAMS
    FILE* inf, FILE* outf
#else
    void * ioctx
#endif
);

/**
 * Destroy a console IO context.
 *
 * Make sure to release any user fields (ioctx, for example) beforehand.
 *
 * @attention ONLY CALL THIS IF THE CONTEXT WAS DYNAMICALLY ALLOCATED!
 *
 * @param[in,out] ctx - pointer to context, will be set to NULL.
 */
void console_ctx_destroy(console_ctx_t *ctx);

/**
 * Console task
 *
 * @param[in] param - must be a valid console context (see `console_ctx_init()`)
 */
void console_task(void *param);

/**
 * Variant of 'console_task' for pthreads (returns NULL)
 */
void* console_task_posix(void *param);

#include "console_io.h"

#endif //LIBCONSOLE_H