///
/// @file config.h
/// @authors k. headley
/// @date 05 dec 2019

/// Configuration API

/// @sa doxygen-examples.c for more examples of Doxygen markup


/////////////////////////
// Terms of use
/////////////////////////
/*
 Copyright Information
  
 Copyright 2002-2019 MBARI
 Monterey Bay Aquarium Research Institute, all rights reserved.
 
 Terms of Use
 
 This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 3 of the License, or
 (at your option) any later version. You can access the GPLv3 license at
 http://www.gnu.org/licenses/gpl-3.0.html
 
 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details
 (http://www.gnu.org/licenses/gpl-3.0.html)
 
 MBARI provides the documentation and software code "as is", with no warranty,
 express or implied, as to the software, title, non-infringement of third party
 rights, merchantability, or fitness for any particular purpose, the accuracy of
 the code, or the performance or results which you may obtain from its use. You
 assume the entire risk associated with use of the code, and you agree to be
 responsible for the entire cost of repair or servicing of the program with
 which you are using the code.
 
 In no event shall MBARI be liable for any damages, whether general, special,
 incidental or consequential damages, arising out of your use of the software,
 including, but not limited to, the loss or corruption of your data or damages
 of any kind resulting from use of the software, any prohibited use, or your
 inability to use the software. You agree to defend, indemnify and hold harmless
 MBARI and its officers, directors, and employees against any claim, loss,
 liability or expense, including attorneys' fees, resulting from loss of or
 damage to property or the injury to or death of any person arising out of the
 use of the software.
 
 The MBARI software is provided without obligation on the part of the
 Monterey Bay Aquarium Research Institute to assist in its use, correction,
 modification, or enhancement.
 
 MBARI assumes no responsibility or liability for any third party and/or
 commercial software required for the database or applications. Licensee agrees
 to obtain and maintain valid licenses for any additional third party software
 required.
 */

// Always do this
#ifndef CONFIG_H
#define CONFIG_H

/////////////////////////
// Includes 
/////////////////////////
#include "cwgsys.h"
#include "licor8xx.h"

/////////////////////////
// Macros
/////////////////////////

/// @def CFG_KEY_WIDTH_DFL
/// @brief tbd
//#define CFG_KEY_WIDTH_DFL 18

/// @def CFG_LINE_BYTES
/// @brief configuration input/output lines 
// should not be longer than this
#define CFG_LINE_BYTES 80 //48

/// @def CFG_STRING_BYTES
/// @brief string parameter max length 
#define CFG_MEM_STRING_BYTES 64 //24

/// @def CFG_LINE_END
/// @brief configuration file line end
#define CFG_LINE_END "\r\n"
/// @def CFG_KEYVAL_DEL
/// @brief configuration key/value delimiter

#define CFG_KEYVAL_DEL " "

/// @def CFG_TRUE
/// @brief config file true value
#define CFG_TRUE  'Y'
/// @def CFG_FALSE
/// @brief config file false value
#define CFG_FALSE 'N'

// configuration keys
/// @def CK_VERBOSE
/// @brief verbose output mode (bool)
#define CK_VERBOSE          "VERBOSE"
#define CFG_VERBOSE_DFL false

/// @def CK_SERIAL_N
///@brief serial number (0-65535)
#define CK_SERIAL_N         "SERIAL_N"
/// @def CK_PROMPT
/// @brief issue prompt to console (uint16 flags)
#define CK_PROMPT           "PROMPT"
#define CFG_PROMPT_DFL      PROMPT_CONS
/// @def CK_ECHO
/// @brief echo typed characters to console (uint16 flags)
#define CK_ECHO             "ECHO"
#define CFG_ECHO_DFL        ECHO_CONS
/// @def CK_LOG_RAW
/// @brief log raw samples (bool)
#define CK_LOG_RAW          "LOG_RAW"
#define CFG_LOG_RAW_DFL true

/// @def CK_AUTOOUT
/// @brief automatically output samples to host (bool)
#define CK_AUTO_OUT         "AUTO_OUT"
#define CFG_AUTO_OUT_DFL false
/// @def CK_LICOR_MODEL
/// @brief Licor model number (820,840,850)
#define CK_LICOR_MODEL      "LICOR_MODEL"

/// @def CK_LICOR_SPANC (double)
/// @brief Licor span gas concentration
#define CK_LICOR_SPANC          "LI_SPANC"
#define CFG_LICOR_SPANC_DFL     3.14

/// @def CK_LICOR_CFLAGS (u32)
/// @brief Licor configuration flags
#define CK_LICOR_CFLAGS         "LI_CFLAGS"
#define CFG_LICOR_CFLAGS_DFL     LI_CFLAG_DFL
/// @def CK_LICOR_)FLAGS (u32)
/// @brief Licor output flags
#define CK_LICOR_OFLAGS         "LI_OFLAGS"
#define CFG_LICOR_OFLAGS_DFL     LI_OFLAG_DFL

/// @def CK_CYC_PATH_S
/// @brief Current cycle file name
#define CK_CBIN_S             "CBIN"
#define CFG_CBIN_S_DFL ""

/// @def CK_CBUF_S
/// @brief Current cycle buffer file name
#define CK_CBUF_S             "CBUF"
#define CFG_CBUF_DFL          "CBIN.BUF"
#define CFG_CBUF_S_DFL CFG_CBUF_DFL

/// @def CK_OBIN_PATH_S
/// @brief Current orecord log file name
#define CK_OBIN_S             "OBIN"
#define CFG_OBIN_S_DFL ""

/// @def CK_ZBIN_PATH_S
/// @brief Current zrecord log file name
#define CK_ZBIN_S             "ZBIN"
#define CFG_ZBIN_S_DFL ""

/// @def CK_SYS_PATH_S
/// @brief Current syslog file name
#define CK_SYS_S             "SYS"
#define CFG_SYS_S_DFL ""

/// @def CK_S0_S
/// @brief String register
#define CK_S0_S             "S0"
#define CFG_S0_S_DFL ""
/// @def CK_S1_S
/// @brief String register
#define CK_S1_S             "S1"
#define CFG_S1_S_DFL ""
/// @def CK_2_S
/// @brief String register
#define CK_S2_S             "S2"
#define CFG_S2_S_DFL ""
/// @def CK_3_S
/// @brief String register
#define CK_S3_S             "S3"
#define CFG_S3_S_DFL ""

/// @def CK_VALVE_TYPE
/// @brief valve type
#define CK_VALVE_TYPE       "VALVE_TYPE"

/// @def CFG_SERIAL_UNASSIGNED
/// @brief unassigned HW serial number value
#define CFG_SERIAL_UNASSIGNED  0xFFFF

/// @def CK_FOO_S
/// @brief an ersatz string (string)
#define CK_FOO_S             "FOO_S"
#define CFG_FOO_S_DFL "Ersatz String"

#define CK_FOO_U32          "FOO_U32"
#define CFG_FOO_U32_DFL      0xCAFEDEAD
#define CK_FOO_I32          "FOO_I32"
#define CFG_FOO_I32_DFL      1234567
#define CK_FOO_DBL          "FOO_DBL"
#define CFG_FOO_DBL_DFL      12345678.901
#define CK_FOO_BYTE          "FOO_BYTE"
#define CFG_FOO_BYTE_DFL     0x58

/// @def CFG_CX_COUNT
/// @brief number of CX (uint32_t) registers
#define CFG_CX_COUNT 8
/// @def CFG_DX_COUNT
/// @brief number of DX (double) registers
#define CFG_DX_COUNT 8
/// @def CFG_FX_COUNT
/// @brief number of FX (bool/bit) registers
#define CFG_FX_COUNT 32
#define FX_ISVALID(key,pn) ((NULL!=key && NULL!=pn && sscanf(strstr(key,"FX"),"FX%d",pn)==1 && *(pn)>=0 && *(pn)<CFG_FX_COUNT) ? true : false)
#define CX_ISVALID(key,pn) ((NULL!=key && NULL!=pn && sscanf(strstr(key,"CX"),"CX%d",pn)==1 && *(pn)>=0 && *(pn)<CFG_CX_COUNT) ? true : false)
#define DX_ISVALID(key,pn) ((NULL!=key && NULL!=pn && sscanf(strstr(key,"DX"),"DX%d",pn)==1 && *(pn)>=0 && *(pn)<CFG_DX_COUNT) ? true : false)

/////////////////////////
// Type Definitions
/////////////////////////

/// @enum CFG_ECHO_t
/// @brief Configuration ECHO value enum
typedef enum{
    ECHO_NONE=0,
    ECHO_CONS=0x1,
    ECHO_HOST=0x2,
    ECHO_ALL=0x3,
}CFG_ECHO_t;

/// @enum CFG_PROMPT_t
/// @brief Configuration PROMPT value enum
typedef enum{
    PROMPT_NONE=0,
    PROMPT_CONS=0x1,
    PROMPT_HOST=0x2,
    PROMPT_ALL=0x3,
}CFG_PROMPT_t;

/// @typedef enum licor_vtype_id licor_vtype_id
/// @brief valve types
typedef enum{
    CFG_VTYPE_LATCH='L',
    CFG_VTYPE_MONO ='M'
}licor_vtype_id;

/// @typedef enum config_type_id  config_type_id
/// @brief config item data types
/// NOTE: values used to index 
/// cfg_type_strings, cfg_fmt_strings
/// in config.c - they must be adjusted if
/// config_type_id changes
typedef enum{
    CTI_INVALID=-1,
    CTI_BYTE=0,
    CTI_U16,
    CTI_I16,
    CTI_U32,
    CTI_I32,
    CTI_U64,
    CTI_I64,
    CTI_DOUBLE,
    CTI_BOOL,
    CTI_STRING
}config_type_id;

/// @typedef void (*assign_func)(void *lhs, void *rhs)
/// @brief assign lhs to rhs with appropriate casts
typedef void (*assign_func)(void *lhs, void *rhs);
/// @typedef int (*parse_func)(void *dest, char *src)
/// @brief parse src (string) to dest with appropriate conversions
typedef int (*parse_func)(void *dest, char *src);
/// @typedef char *(*str_func)(char *dest, const char *fmt, void *pval)
/// @brief return pval as string using specified formatting
typedef char *(*str_func)(char *dest, const char *fmt, void *pval);
/// @typedef bool(*boolval_func)(void *pval)
/// @brief return pval as bool (false if pval is zero or NULL, true otherwise)
typedef bool(*boolval_func)(void *pval);
/// @typedef double(*doubleval_func)(void *pval)
/// @brief return pval as double (undefined for string)
typedef double(*doubleval_func)(void *pval);
/// @typedef typedef int(*cmp_func)(void *a, void *b)
/// @brief compare a and b, return -1:a<b 0:a==b 1:a>b
typedef int(*cmp_func)(void *a, void *b);

/// @typedef struct config_funcs_s config_funcs_t
/// @brief configuration parsing/conversion function set
typedef struct config_funcs_s
{
    /// @var config_funcs_s::assign
    /// @brief value assignment function
    assign_func assign;
    /// @var config_funcs_s::parse
    /// @brief value parse function (assign from string)
    parse_func parse;
    /// @var config_funcs_s::string
    /// @brief value to string function
    str_func string;
    /// @var config_funcs_s::bool_val
    /// @brief boolean value
    boolval_func bool_val;
    /// @var config_funcs_s::double_val
    /// @brief boolean value
    doubleval_func double_val;
}config_funcs_t;

/// @typedef struct config_item_s config_item_t
/// @brief configuration item
typedef struct config_item_s{
    /// @var config_item_s::key
    /// @brief lookup key
    const char *key;
    /// @var config_item_s::type
    /// @brief data type
    config_type_id type;
    /// @var config_item_s::pval
    /// @brief pointer to value
    byte *pval;
    /// @var config_funcs_s::funcs
    /// @brief parsing/conversion functions
    config_funcs_t *funcs;
    /// @var config_funcs_s::alt_parse
    /// @brief alternative value parse function (assign from string)
    /// e.g. special mnemonic parser
    parse_func alt_parse;
}config_item_t;

/////////////////////////
// Exports
/////////////////////////

#ifdef __cplusplus
extern "C" {
#endif
    /// @fn void config_show(config_item_t *config, bool verbose, int indent)
    /// @brief print config summary to console
    /// @param[in] config configuration (config_item_t array, NULL for system default)
    /// @param[in] verbose verbose output
    /// @param[in] indent indentation level (spaces)
    /// @return returns ERR_SUCCESS if successful, error code otherwise
    void config_show(config_item_t *config, bool verbose, int indent);

    /// @fn return_code_t config_load(char *src)
    /// @brief load config from file
    /// @param[in] src source file name (NULL for SYS_CONFIG_DFL)
    /// @return returns ERR_SUCCESS if successful, error code otherwise
    return_code_t config_load(char *src);
        
    /// @fn return_code_t config_export(char *file_path)
    /// @brief save configuration to file
    /// @param[in] file_path output file (use SYS_CONFIG_DFL if NULL))
    /// @return returns ERR_SUCCESS if successful, error code otherwise
    return_code_t config_export(char *file_path);

    /// @fn return_code_t config_set(char *key, void *pval)
    /// @brief set config item value (do not persist to file)
    /// @param[in] key item name
    /// @param[in] pval pointer to value
    /// @return returns ERR_SUCCESS if successful, error code otherwise
    return_code_t config_set(const char *key, void *pval);

    /// @fn return_code_t config_parse(char *key, char *str)
    /// @brief parse and set config item value (do not persist to file)
    /// @param[in] key item name
    /// @param[in] str pointer to value string
    /// @return returns ERR_SUCCESS if successful, error code otherwise
    return_code_t config_parse(const char *key, char *str);
    
    /// @fn return_code_t config_get(char *key, void *pval)
    /// @brief get configuration item value
    /// @param[in] key item name
    /// @param[in] pval pointer to value 
    /// supported types: byte, u16, i16, u32, i32, double, bool,
    /// u64, i64, string
    /// @return returns ERR_SUCCESS if successful, error code otherwise
    return_code_t config_get(const char *key, void *pval);
    
    /// @fn bool config_contains(const char *key)
    /// @brief return true if config includes specified key
    /// @param[in] key item name
    /// @return returns true if config includes key, false otherwise
    bool config_contains(const char *key);
    
    /// @fn config_type_id config_type(const char *key)
    /// @brief return type identifier (enum) for specified key
    /// @param[in] key item name
    /// @return returns type if config includes key, CTI_INVALID otherwise
    config_type_id config_type(const char *key);

    /// @fn config_type_id config_type(const char *key)
    /// @brief return type identifier name (string) for specified key
    /// @param[in] key item name
    /// @return returns type name if config includes key, NULL otherwise
    const char *config_type_str(const char *key);
 
    /// @fn return_code_t config_bool_val(const char *key, bool *dest))
    /// @brief return config item value as boolean 
    /// @param[in] key item name
    /// @param[out] dest value pointer 
    /// @return ERR_SUCCESS on success, ERR_FAILURE otherwise. Sets dest false if item value is zero, true otherwise
    return_code_t config_bool_val(const char *key, bool *dest);
    
    /// @fn return_code_t config_str_val(const char *key, char **dest, char *alt_fmt)
    /// @brief return config item value as string. 
    /// - dest must be large enough to hold output.
    /// - format must be valid for data type
    /// @param[in] key item name
    /// @param[in] alt_fmt alternative format (printf specifier)
    /// @param[out] dest value pointer 
    /// @return ERR_SUCCESS on success, ERR_FAILURE otherwise. Prints result in dest.
    return_code_t config_str_val(const char *key, char **dest, char *alt_fmt);
    
    /// @fn return_code_t config_double_val(const char *key, double *dest))
    /// @brief return config item value as double 
    /// @param[in] key item name
    /// @param[out] dest value pointer 
    /// @return ERR_SUCCESS on success, ERR_FAILURE otherwise. 
    return_code_t config_double_val(const char *key, double *dest);
    
    /// @fn return_code_t config_int_val(const char *key, int64_t *dest))
    /// @brief return config item value as int64_t 
    /// @param[in] key item name
    /// @param[out] dest value pointer 
    /// @return ERR_SUCCESS on success, ERR_FAILURE otherwise. 
    return_code_t config_int_val(const char *key, int64_t *dest);
    
    /// @fn uint32_t config_cx_val(int idx)
    /// @brief return value of specified CX register
    /// @param[in] int counter register index
    /// @return value of specified counter (uint32_t) or zero if index invalid
    uint32_t config_cx_val(int idx);
    
    /// @fn bool config_fx_val(int idx)
    /// @brief return value of specified FX register
    /// @param[in] int flag register index
    /// @return value of specified flag (boolean) or false if index invalid
    bool config_fx_val(int idx);
    
    /// @fn bool config_dx_val(int idx)
    /// @brief return value of specified DX register
    /// @param[in] int double register index
    /// @return value of specified register (double) or false if index invalid
    double config_dx_val(int idx);

    /// @fn int config_mnem2flag(void *dest, char *src, const char *mnem)
    /// @brief parse mnemonic string  to flag bit (uint32t_t)
    /// dest bits set using index in mnem, i.e. (1<<i), i=index of mnemonic in mnem
    /// used for parsing licor config/output flags
    /// @param[out]  dest flag value pointer (uint32_t *)
    /// @param[in]  src  source mnemonic string (e.g. a|b...)
    /// @param[in]  mnem mnemonic values ("name|name...")
    /// @return 0 on success, -1 otherwise
    int config_mnem2flag(void *dest, char *src, const char *mnem);

    /// @fn config_flag2mnem(char *dest, void *src, size_t len, const char *mnem, unsigned max_fields)
    /// @brief parse flags to mnemonic string
    /// used for parsing licor config/output flags
    /// @param[out] dest mnemonic output string pointer
    /// @param[in]  src  source flags
    /// @param[in]  mnem mnemonic values ("name|name...")
    /// @param[in]  max_fields maximum number of bits (<=32)
     /// @return 0 on success, -1 otherwise
    int config_flag2mnem(char *dest, void *src, size_t len, const char *mnem, unsigned max_fields);

#ifdef __cplusplus
}
#endif

#endif // THIS_FILE
