/*  -*- Mode: C; indent-tabs-mode: nil; c-basic-offset: 4; tab-width: 4 -*-  */

/**

    @file       confini.h
    @brief      libconfini header
    @author     Stefano Gioffré
    @copyright  GNU General Public License, version 3 or any later version
    @version    1.14.0
    @date       2016-2020
    @see        https://madmurphy.github.io/libconfini

**/


#ifndef _LIBCONFINI_HEADER_
#define _LIBCONFINI_HEADER_



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



#ifdef __cplusplus
extern "C" {
#endif



/*  PRIVATE (HEADER-SCOPED) MACROS  */


#define __INIFORMAT_TABLE_CB_FIELDS__(NAME, OFFSET, SIZE, DEFVAL) \
    unsigned char NAME:SIZE;
#define __INIFORMAT_TABLE_CB_DEFAULT__(NAME, OFFSET, SIZE, DEFVAL) DEFVAL,
#define __INIFORMAT_TABLE_CB_ZERO__(NAME, OFFSET, SIZE, DEFVAL) 0,
#define _LIBCONFINI_INIFORMAT_TYPE_ \
    struct IniFormat { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_FIELDS__) }
#define _LIBCONFINI_DEFAULT_FORMAT_ \
    { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_DEFAULT__) }
#define _LIBCONFINI_UNIXLIKE_FORMAT_ \
    { INIFORMAT_TABLE_AS(__INIFORMAT_TABLE_CB_ZERO__) }



/*  PUBLIC MACROS  */


/**
    @brief  Calls a user-given macro (that accepts four arguments) for each row
            of the table
**/
/*
    NOTE: The following table and the order of its rows **define** (and link
    together) both the #IniFormat and #IniFormatNum data types declared in this
    header
*/
#define INIFORMAT_TABLE_AS(_____)                 /*  IniFormat table  *\

        NAME                      BIT  SIZE DEFAULT
                                                                      */\
 _____( delimiter_symbol,         0,   7,   INI_EQUALS                ) \
 _____( case_sensitive,           7,   1,   false                     )/*
                                                                      */\
 _____( semicolon_marker,         8,   2,   INI_DISABLED_OR_COMMENT   ) \
 _____( hash_marker,              10,  2,   INI_DISABLED_OR_COMMENT   ) \
 _____( section_paths,            12,  2,   INI_ABSOLUTE_AND_RELATIVE ) \
 _____( multiline_nodes,          14,  2,   INI_MULTILINE_EVERYWHERE  )/*
                                                                      */\
 _____( no_single_quotes,         16,  1,   false                     ) \
 _____( no_double_quotes,         17,  1,   false                     ) \
 _____( no_spaces_in_names,       18,  1,   false                     ) \
 _____( implicit_is_not_empty,    19,  1,   false                     ) \
 _____( do_not_collapse_values,   20,  1,   false                     ) \
 _____( preserve_empty_quotes,    21,  1,   false                     ) \
 _____( disabled_after_space,     22,  1,   false                     ) \
 _____( disabled_can_be_implicit, 23,  1,   false                     )



/**
    @brief  Checks whether a format does **not** support escape sequences
**/
#define INIFORMAT_HAS_NO_ESC(FORMAT) \
    (FORMAT.multiline_nodes == INI_NO_MULTILINE && \
    FORMAT.no_double_quotes && FORMAT.no_single_quotes)



/*  PUBLIC TYPEDEFS  */


/**
    @brief  24-bit bitfield representing the format of an INI file (INI
            dialect)
**/
typedef _LIBCONFINI_INIFORMAT_TYPE_ IniFormat;


/**
    @brief  Global statistics about an INI file
**/
typedef struct IniStatistics {
    const IniFormat format;
    const size_t bytes;
    const size_t members;
} IniStatistics;


/**
    @brief  Dispatch of a single INI node
**/
typedef struct IniDispatch {
    const IniFormat format;
    uint8_t type;
    char * data;
    char * value;
    const char * append_to;
    size_t d_len;
    size_t v_len;
    size_t at_len;
    size_t dispatch_id;
} IniDispatch;


/**
    @brief  The unique ID of an INI format (24-bit maximum)
**/
typedef uint32_t IniFormatNum;


/**
    @brief  Callback function for handling an #IniStatistics structure
**/
typedef int (* IniStatsHandler) (
    IniStatistics * statistics,
    void * user_data
);


/**
    @brief  Callback function for handling an #IniDispatch structure
**/
typedef int (* IniDispHandler) (
    IniDispatch * dispatch,
    void * user_data
);


/**
    @brief  Callback function for handling an INI string belonging to a
            sequence of INI strings
**/
typedef int (* IniStrHandler) (
    char * ini_string,
    size_t string_length,
    size_t string_num,
    IniFormat format,
    void * user_data
);


/**
    @brief  Callback function for handling a selected fragment of an INI string
**/
typedef int (* IniSubstrHandler) (
    const char * ini_string,
    size_t fragm_offset,
    size_t fragm_length,
    size_t fragm_num,
    IniFormat format,
    void * user_data
);



/*  PUBLIC FUNCTIONS  */


extern int strip_ini_cache (
    register char * const ini_source,
    const size_t ini_length,
    const IniFormat format,
    const IniStatsHandler f_init,
    const IniDispHandler f_foreach,
    void * const user_data
);


extern int load_ini_file (
    FILE * const ini_file,
    const IniFormat format,
    const IniStatsHandler f_init,
    const IniDispHandler f_foreach,
    void * const user_data
);


extern int load_ini_path (
    const char * const path,
    const IniFormat format,
    const IniStatsHandler f_init,
    const IniDispHandler f_foreach,
    void * const user_data
);


extern bool ini_string_match_ss (
    const char * const simple_string_a,
    const char * const simple_string_b,
    const IniFormat format
);


extern bool ini_string_match_si (
    const char * const simple_string,
    const char * const ini_string,
    const IniFormat format
);


extern bool ini_string_match_ii (
    const char * const ini_string_a,
    const char * const ini_string_b,
    const IniFormat format
);


extern bool ini_array_match (
    const char * const ini_string_a,
    const char * const ini_string_b,
    const char delimiter,
    const IniFormat format
);


extern size_t ini_unquote (
    char * const ini_string,
    const IniFormat format
);


extern size_t ini_string_parse (
    char * const ini_string,
    const IniFormat format
);


extern size_t ini_array_get_length (
    const char * const ini_string,
    const char delimiter,
    const IniFormat format
);


extern int ini_array_foreach (
    const char * const ini_string,
    const char delimiter,
    const IniFormat format,
    const IniSubstrHandler f_foreach,
    void * const user_data
);


extern size_t ini_array_shift (
    const char ** const ini_strptr,
    const char delimiter,
    const IniFormat format
);


extern size_t ini_array_collapse (
    char * const ini_string,
    const char delimiter,
    const IniFormat format
);


extern char * ini_array_break (
    char * const ini_string,
    const char delimiter,
    const IniFormat format
);


extern char * ini_array_release (
    char ** const ini_strptr,
    const char delimiter,
    const IniFormat format
);


extern int ini_array_split (
    char * const ini_string,
    const char delimiter,
    const IniFormat format,
    const IniStrHandler f_foreach,
    void * const user_data
);


extern void ini_global_set_lowercase_mode (
    const bool lowercase
);


extern void ini_global_set_implicit_value (
    char * const implicit_value,
    const size_t implicit_v_len
);


extern IniFormatNum ini_fton (
    const IniFormat format
);


extern IniFormat ini_ntof (
    const IniFormatNum format_id
);


extern int ini_get_bool (
    const char * const ini_string,
    const int when_fail
);



/*  PUBLIC LINKS  */


extern int (* const ini_get_int) (
    const char * ini_string
);


extern long int (* const ini_get_lint) (
    const char * ini_string
);


extern long long int (* const ini_get_llint) (
    const char * ini_string
);


extern double (* const ini_get_double) (
    const char * ini_string
);


/**
    @brief  Legacy support, soon to be replaced with a `float` data type --
            please **do not use `ini_get_float()`!**
**/
#define ini_get_float \
    _Pragma("GCC warning \"function `ini_get_float()` is deprecated for parsing a `double` data type; use `ini_get_double()` instead\"") \
    ini_get_double



/*  PUBLIC CONSTANTS AND VARIABLES  */


/**
    @brief  Error mask (flags not present in user-generated interruptions)
**/
#define CONFINI_ERROR 252


/**
    @brief  Error codes
**/
enum ConfiniInterruptNo {
    CONFINI_SUCCESS = 0,    /**< There have been no interruptions, everything
                                 went well [value=0] **/
    CONFINI_IINTR = 1,      /**< Interrupted by the user during `f_init()`
                                 [value=1] **/
    CONFINI_FEINTR = 2,     /**< Interrupted by the user during `f_foreach()`
                                 [value=2] **/
    CONFINI_ENOENT = 4,     /**< File inaccessible [value=4] **/
    CONFINI_ENOMEM = 5,     /**< Error allocating virtual memory [value=5] **/
    CONFINI_EIO = 6,        /**< Error reading the file [value=6] **/
    CONFINI_EOOR = 7,       /**< Out-of-range error: callbacks are more than
                                 expected [value=7] **/
    CONFINI_EBADF = 8,      /**< The stream specified is not a seekable stream
                                 [value=8] **/
    CONFINI_EFBIG = 9,      /**< File too large [value=9] **/
    CONFINI_EROADDR = 10    /**< Address is read-only [value=10] **/
};


/**
    @brief  INI node types
**/
enum IniNodeType {
    INI_UNKNOWN = 0,            /**< This is a node impossible to categorize
                                     [value=0] **/
    INI_VALUE = 1,              /**< Not used by **libconfini** (values are
                                     dispatched together with keys) -- but
                                     available for user's implementations
                                     [value=1] **/
    INI_KEY = 2,                /**< This is a key [value=2] **/
    INI_SECTION = 3,            /**< This is a section or a section path
                                     [value=3] **/
    INI_COMMENT = 4,            /**< This is a comment [value=4] **/
    INI_INLINE_COMMENT = 5,     /**< This is an inline comment [value=5] **/
    INI_DISABLED_KEY = 6,       /**< This is a disabled key [value=6] **/
    INI_DISABLED_SECTION = 7    /**< This is a disabled section path
                                     [value=7] **/
};


/**
    @brief  Common array and key-value delimiters (but a delimiter may also be
            any other ASCII character not present in this list)
**/
enum IniDelimiters {
    INI_ANY_SPACE = 0,  /**< In multi-line INIs:
                             `/(?:\\(?:\n\r?|\r\n?)|[\t \v\f])+/`, in
                             non-multi-line INIs: `/[\t \v\f])+/` **/
    INI_EQUALS = '=',   /**< Equals character (`=`) **/
    INI_COLON = ':',    /**< Colon character (`:`) **/
    INI_DOT = '.',      /**< Dot character (`.`) **/
    INI_COMMA = ','     /**< Comma character (`,`) **/
};


/**
    @brief  Possible values of #IniFormat::semicolon_marker and
            #IniFormat::hash_marker (i.e., meaning of `/\s+;/` and `/\s+#/` in
            respect to a format)
**/
enum IniCommentMarker {
    INI_DISABLED_OR_COMMENT = 0,    /**< This marker opens a comment or a
                                         disabled entry **/
    INI_ONLY_COMMENT = 1,           /**< This marker opens a comment **/
    INI_IGNORE = 2,                 /**< This marker opens a comment that has
                                         been marked for deletion and must not
                                         be dispatched or counted **/
    INI_IS_NOT_A_MARKER = 3         /**< This is not a marker at all, but a
                                         normal character instead **/
};


/**
    @brief  Possible values of #IniFormat::section_paths
**/
enum IniSectionPaths {
    INI_ABSOLUTE_AND_RELATIVE = 0,  /**< Section paths starting with a dot
                                         express nesting to the current parent,
                                         to root otherwise **/
    INI_ABSOLUTE_ONLY = 1,          /**< Section paths starting with a dot will
                                         be cleaned of their leading dot and
                                         appended to root **/
    INI_ONE_LEVEL_ONLY = 2,         /**< Format supports sections, but the dot
                                         does not express nesting and is not a
                                         meta-character **/
    INI_NO_SECTIONS = 3             /**< Format does *not* support sections --
                                         `/\[[^\]]*\]/g`, if any, will be
                                         treated as keys! **/
};


/**
    @brief  Possible values of #IniFormat::multiline_nodes
**/
enum IniMultiline {
    INI_MULTILINE_EVERYWHERE = 0,       /**< Comments, section paths and keys
                                             -- disabled or not -- are allowed
                                             to be multi-line **/
    INI_BUT_COMMENTS = 1,               /**< Only section paths and keys --
                                             disabled or not -- are allowed to
                                             be multi-line **/
    INI_BUT_DISABLED_AND_COMMENTS = 2,  /**< Only active section paths and
                                             active keys are allowed to be
                                             multi-line **/
    INI_NO_MULTILINE = 3                /**< Multi-line escape sequences are
                                             disabled **/
};


/**
    @brief  A model format for standard INI files
**/
static const IniFormat INI_DEFAULT_FORMAT = _LIBCONFINI_DEFAULT_FORMAT_;


/**
    @brief  A model format for Unix-like .conf files (where space characters
            are delimiters between keys and values)
**/
/*  All fields are set to `0` here.  */
static const IniFormat INI_UNIXLIKE_FORMAT = _LIBCONFINI_UNIXLIKE_FORMAT_;


/**
    @brief  If set to `true`, key and section names in case-insensitive INI
            formats will be dispatched lowercase, verbatim otherwise (default
            value: `false`)
**/
extern bool INI_GLOBAL_LOWERCASE_MODE;


/**
    @brief  Value to be assigned to implicit keys (default value: `NULL`)
**/
extern char * INI_GLOBAL_IMPLICIT_VALUE;


/**
    @brief  Length of the value assigned to implicit keys (default value: `0`)
**/
extern size_t INI_GLOBAL_IMPLICIT_V_LEN;



/*  CLEAN THE PRIVATE ENVIRONMENT  */


#undef _LIBCONFINI_UNIXLIKE_FORMAT_
#undef _LIBCONFINI_DEFAULT_FORMAT_
#undef _LIBCONFINI_INIFORMAT_TYPE_
#undef __INIFORMAT_TABLE_CB_ZERO__
#undef __INIFORMAT_TABLE_CB_DEFAULT__
#undef __INIFORMAT_TABLE_CB_FIELDS__



/*  END OF `_LIBCONFINI_HEADER_`  */


#ifdef __cplusplus
}
#endif


#endif



/*  EOF  */