/*
 * Copyright (C) 2021 Liquidaty and the zsv/lib contributors
 * All rights reserved
 *
 * This file is part of zsv/lib, distributed under the license defined at
 * https://opensource.org/licenses/MIT
 */

#ifndef ZSV_EXT_H
#define ZSV_EXT_H

#define ZSV_EXTENSION_ID_MIN_LEN 2
#define ZSV_EXTENSION_ID_MAX_LEN 8

#include <stdio.h>
#include <stdint.h>
#include "common.h"
#include "ext/sheet.h"
#include "utils/sql.h"
#include "utils/prop.h"
#include "utils/writer.h"

/**
 * @file ext.h
 * @brief ZSV extension api, common to zsvlib and any zsv extension
 * @defgroup zsv_extension api
 * @ingroup ZSV
 * @{
 */
ZSV_BEGIN_DECL

/**
 * Status and error codes for zsv extensions. If a command's `main()` program
 * returns a status other than zsv_ext_status_ok, `zsv_ext_errcode()` will be
 * called and its result used for the final exit code. If it returns
 * zsv_ext_status_error, then furthermore, `zsv_ext_errstr()` will be called
 * and any non-null result displayed as an error message
 */
enum zsv_ext_status {
  zsv_ext_status_ok = 0,
  zsv_ext_status_memory,
  zsv_ext_status_unrecognized_cmd,

  /**
   * if a zsv_ext function returns zsv_ext_status_error, an error message
   * is generated by calling the module's `zsv_ext_errcode()` and `zsv_ext_errstr()`
   * functions. Otherwise an error string is generated from the zsv_ext_status code
   */
  zsv_ext_status_error,

  /**
   * operation not permitted, such as an extension attempting
   * to modify a buffer that is owned by a different extension
   */
  zsv_ext_status_not_permitted,

  /**
   * zsv_ext_status_other is treated as a silent error by ZSV, e.g. if you have
   * already handled your error and do not want any further error message displayed
   */
  zsv_ext_status_other
};

/**
 * The context in which any call to your extension is made, used with:
 *   `set_ext_context()`
 *   `get_ext_context()`
 */
typedef void *zsv_execution_context;

/**
 * Signature of the function called for each implemented sub-command
 */
typedef enum zsv_ext_status (*zsv_ext_main)(zsv_execution_context ctx, int argc, const char *argv[],
                                            struct zsv_opts *opts);

/**
 * ZSV callbacks structure
 *
 * Contains the addresses of the ZSV functions that can be used by custom
 * extensions, and which are passed from ZSV to the extension via the
 * custom `zsv_ext_init()` implementation. See zsv_ext.h for additional
 * extension specifications, or examples/extension.c for an example
 * implementation
 *
 * For a description of each callback, see the corresponding zsv_-prefixed
 * function in zsv.h
 */
struct zsvsheet_buffer_data {
  unsigned char has_row_num : 1;
  unsigned char _ : 7;
};

typedef uint32_t zsvsheet_cell_attr_t;
enum zsvsheet_cell_profile_t {
  zsvsheet_cell_attr_profile_none = 0,
  zsvsheet_cell_attr_profile_link,
};

struct zsv_ext_callbacks {
  void (*set_row_handler)(zsv_parser handle, void (*row)(void *ctx));
  void (*set_context)(zsv_parser handle, void *ctx);
  enum zsv_status (*parse_more)(struct zsv_scanner *scanner);
  void (*abort)(struct zsv_scanner *scanner);
  size_t (*cell_count)(zsv_parser parser);
  struct zsv_cell (*get_cell)(zsv_parser parser, size_t ix);
  enum zsv_status (*finish)(zsv_parser);
  enum zsv_status (*delete)(zsv_parser);

  /**
   * Set or get custom context (e.g. to persist private data between
   * ZSV-initiated calls to your extension)
   */
  void (*ext_set_context)(zsv_execution_context ctx, void *private_context);
  void *(*ext_get_context)(zsv_execution_context ctx);

  zsv_parser (*ext_get_parser)(zsv_execution_context ctx);

  /**
   * To add an extension command, invoke `ext_add_command`, passing it your command's
   * handler function as a callback with a `zsv_ext_main` signature
   */
  enum zsv_ext_status (*ext_add_command)(zsv_execution_context ctx, const char *id, const char *help,
                                         zsv_ext_main main);
  void (*ext_set_help)(zsv_execution_context ctx, const char *help);
  void (*ext_set_license)(zsv_execution_context ctx, const char *license);
  void (*ext_set_thirdparty)(zsv_execution_context ctx, const char *thirdparty[]);

  /**
   * fetch options from execution context. used to fetch just the ctx-related parser
   * opts, including any of the below if specified by the user when zsv was invoked:
   *     -B,--buff-size <N>
   *     -c,--max-column-count <N>
   *     -r,--max-row-size <N>
   *     -t,--tab-delim
   *     -O,--other-delim <C>
   *     -q,--no-quote
   *     -v,--verbose
   *
   * @param ctx execution context
   * @return option values
   */
  struct zsv_opts (*ext_parser_opts)(zsv_execution_context ctx);

  /**
   * convenience function that calls ext_args_to_opts, allocates parser,
   * sets custom ctx, runs parser, and de-allocates parser
   */
  enum zsv_ext_status (*ext_parse_all)(zsv_execution_context ctx, void *user_context, void (*row_handler)(void *ctx),
                                       struct zsv_opts *const custom);
  /**
   * As an alternative to `ext_parse_all()`, for more granular control:
   * ```
   * struct zsv_opts opts = custom ? *custom : zsv_get_default_opts();
   * zsv_parser parser = zsv_new(&opts);
   * if(!parser)
   *   stat = zsv_ext_status_memory;
   * else {
   *   opts.row = YOUR_COMMAND_rowhandler;
   *   // ... set other options ...
   *   zsv_parser p = new_with_context(ctx, &opts);
   *   while((stat = zsv_parse_more(parser)) == zsv_status_ok) ;
   *   if(stat == zsv_status_no_more_input)
   *     stat = zsv_finish(p);
   *   zsv_delete(p);
   * }
   * ```
   */

  /****************************************
   * Registering a custom `sheet` command *
   ****************************************/
  zsvsheet_proc_id_t (*ext_sheet_register_proc)(const char *name, const char *description,
                                                zsvsheet_status (*handler)(zsvsheet_proc_context_t ctx));

  /**
   * Bind a command to a key binding
   * TO DO: allow binding of key that already exists; in which case
   * allow handler to act as middleware that can cancel or allow other handlers to be executed
   *
   * @return 0 on success, else error
   */
  int (*ext_sheet_register_proc_key_binding)(char ch, zsvsheet_proc_id_t proc_id);

  /*** Custom command prompt ***/
  /**
   * Set the prompt for entering a subcommand
   * @param  s text to set the subcommand prompt to. must be < 256 bytes in length
   * returns zsvsheet_status_ok on success
   */
  zsvsheet_status (*ext_sheet_prompt)(zsvsheet_proc_context_t ctx, char *buffer, size_t bufsz, const char *fmt, ...);

  /*** Custom command handling ***/
  /**
   * Set a status message
   */
  zsvsheet_status (*ext_sheet_set_status)(zsvsheet_proc_context_t ctx, const char *fmt, ...);

  /**
   * Get the key press that triggered this subcommand handler
   */
  int (*ext_sheet_keypress)(zsvsheet_proc_context_t ctx);

  /****** Managing buffers ******/
  /**
   * Get the current buffer
   */
  zsvsheet_buffer_t (*ext_sheet_buffer_current)(zsvsheet_proc_context_t ctx);

  /**
   * Get the prior buffer
   */
  zsvsheet_buffer_t (*ext_sheet_buffer_prior)(zsvsheet_buffer_t b);

  /**
   * Get info about a buffer
   */
  struct zsvsheet_buffer_data (*ext_sheet_buffer_info)(zsvsheet_buffer_t);

  /**
   * Get the filename associated with a buffer
   */
  const char *(*ext_sheet_buffer_filename)(zsvsheet_buffer_t);

  /**
   * Get the data file associated with a buffer. This might not be the same as the filename,
   * such as when the data has been filtered
   */
  const char *(*ext_sheet_buffer_data_filename)(zsvsheet_buffer_t);

  /**
   * Get the currently-selected cell
   */
  zsvsheet_status (*ext_sheet_buffer_get_selected_cell)(zsvsheet_buffer_t, struct zsvsheet_rowcol *);

  /**
   * Open a tabular file as a new buffer
   */
  zsvsheet_status (*ext_sheet_open_file)(zsvsheet_proc_context_t, const char *filepath, struct zsv_opts *zopts);

  /**
   * Set custom context
   * @param on_close optional callback to invoke when the buffer is closed
   *
   * @return zsv_ext_status_ok on success, else zsv_ext_status error code
   */
  enum zsv_ext_status (*ext_sheet_buffer_set_ctx)(zsvsheet_buffer_t h, void *ctx, void (*on_close)(void *));

  /**
   * Get custom context previously set via zsvsheet_buffer_set_ctx()
   * @param ctx_out result will be written to this address
   *
   * @return zsv_ext_status_ok on success, else zsv_ext_status error code
   */
  enum zsv_ext_status (*ext_sheet_buffer_get_ctx)(zsvsheet_buffer_t h, void **ctx_out);

  /**
   *
   */
  zsvsheet_cell_attr_t (*ext_sheet_cell_profile_attrs)(enum zsvsheet_cell_profile_t);

  /**
   * Set custom cell attributes
   */
  void (*ext_sheet_buffer_set_cell_attrs)(zsvsheet_buffer_t h,
                                          enum zsv_ext_status (*get_cell_attrs)(void *pdh, zsvsheet_cell_attr_t *attrs,
                                                                                size_t start_row, size_t row_count,
                                                                                size_t cols));

  /**
   * Set custom handler on Enter key press
   *
   * @return zsv_ext_status_ok on success, else zsv_ext_status error code
   */
  enum zsv_ext_status (*ext_sheet_buffer_on_newline)(zsvsheet_buffer_t h,
                                                     zsvsheet_status (*on_newline)(zsvsheet_proc_context_t));

  /**
   * Get zsv_opts used to open the buffer's data file
   */
  struct zsv_opts (*ext_sheet_buffer_get_zsv_opts)(zsvsheet_buffer_t h);

  /**
   * SQLITE3 helpers
   */
  int (*ext_sqlite3_add_csv)(struct zsv_sqlite3_db *zdb, const char *csv_filename, struct zsv_opts *opts,
                             struct zsv_prop_handler *custom_prop_handler);
  void (*ext_sqlite3_db_delete)(zsv_sqlite3_db_t);
  zsv_sqlite3_db_t (*ext_sqlite3_db_new)(struct zsv_sqlite3_dbopts *dbopts);

  /**
   * Create a new buffer from the current one using a transformation
   * and make the new buffer the current one
   *
   * Note that the transformation is performed in a seperate thread so the user_context
   * must not be a stack variable
   *
   * See struct zsvsheet_buffer_transformation_opts in zsv/ext/sheet.h
   */
  zsvsheet_status (*ext_sheet_push_transformation)(zsvsheet_proc_context_t ctx,
                                                   struct zsvsheet_buffer_transformation_opts opts);

  /**
   * Get the writer associated with a transformation.
   *
   * The transformation itself is passed as the context variable to the row handler
   */
  zsv_csv_writer (*ext_sheet_transformation_writer)(zsvsheet_transformation trn);

  /**
   * Get the user provided context from the context provided to a transformation row handler
   */
  void *(*ext_sheet_transformation_user_context)(zsvsheet_transformation trn);

  /**
   * Get the parser from the context provided to a transformation row handler
   */
  zsv_parser (*ext_sheet_transformation_parser)(zsvsheet_transformation trn);

  /**
   * Get the filename that the transformation writer outputs to from the context provided to a transformation row
   * handler.
   */
  const char *(*ext_sheet_transformation_filename)(zsvsheet_transformation trn);
};

/** @} */
ZSV_END_DECL
#endif
