/******************************************************************************
 * qLibc
 *
 * Copyright (c) 2010-2015 Seungyoung Kim.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *****************************************************************************/

/**
 * @file qaconf.c Apache-style configuration file parser.
 *
 * Apache-style Configuration is a configuration file syntax and format
 * originally introduced by Apache HTTPd project. This format is powerful,
 * flexible and human friendly. Even though this code gets distributed
 * as a part of qLibc project, the code is written not to have any external
 * dependencies to make this single file stands alone for better portability.
 * It is purely written from the ground up and dedicated to the public
 * by Seungyoung Kim.
 *
 * Sample Apache-style Configuration Syntax:
 * @code
 *   # Lines that begin with the hash character "#" are considered comments.
 *   Listen 53
 *   Protocols UDP TCP
 *   IPSEC On
 *
 *   <Domain "qdecoder.org">
 *     TTL 86400
 *     MX 10 mail.qdecoder.org
 *
 *     <Host mail>
 *        IPv4 192.168.10.1
 *        TXT "US Rack-13D-18 \"San Jose's\""
 *     </Host>
 *
 *     <Host www>
 *        IPv4 192.168.10.2
 *        TXT 'KR Rack-48H-31 "Seoul\'s"'
 *        TTL 3600
 *     </Host>
 *   </Domain>
 *
 *   <Domain "ringfs.org">
 *     <Host www>
 *        CNAME www.qdecoder.org
 *     </Host>
 *   </Domain>
 * @endcode
 *
 * @code
 *   // THIS EXAMPLE CODE CAN BE FOUND IN EXAMPLES DIRECTORY.
 *
 *   // Define scope.
 *   //   QAC_SCOPE_ALL and QAC_SCOPE_ROOT are predefined.
 *   //   Custum scope should be defined from 2(1 << 1).
 *   //   Note) These values are ORed(bit operation), so the number should be
 *   //         2(1<<1), 4(1<<2), 6(1<<3), 8(1<<4), ...
 *   enum {
 *     OPT_SECTION_ALL        = QAC_SECTION_ALL,   // pre-defined
 *     OPT_SECTION_ROOT       = QAC_SECTION_ROOT,  // pre-defined
 *     OPT_SECTION_DOMAIN     = (1 << 1),    // user-defined section
 *     OPT_SECTION_HOST       = (1 << 2),    // user-defined section
 *   };
 *
 *   // Define callback proto-types.
 *   static QAC_CB(confcb_debug);
 *
 *   // Define options and callbacks.
 *   static qaconf_option_t options[] = {
 *     {"Listen", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_ALL},
 *     {"Protocols", QAC_TAKEALL, confcb_debug, 0, OPT_SECTION_ROOT},
 *     {"IPSEC", QAC_TAKE_BOOL, confcb_debug, 0, OPT_SECTION_ROOT},
 *     {"Domain", QAC_TAKE_STR, confcb_debug, OPT_SECTION_DOMAIN, OPT_SECTION_ROOT},
 *     {  "TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST},
 *     {  "MX", QAC_TAKE2 | QAC_A1_INT, confcb_debug, 0, OPT_SECTION_DOMAIN},
 *     {  "Host", QAC_TAKE_STR, confcb_debug, OPT_SECTION_HOST, OPT_SECTION_DOMAIN},
 *     {    "IPv4", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
 *     {    "TXT", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
 *     {    "CNAME", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
 *     QAC_OPTION_END
 *   };
 *
 *   int user_main(void)
 *   {
 *     // Create a userdata structure.
 *     struct MyConf myconf;
 *
 *     // Initialize and create a qaconf object.
 *     qaconf_t *conf = qaconf();
 *     if (conf == NULL) {
 *       printf("Failed to open '" CONF_PATH "'.\n");
 *       return -1;
 *     }
 *
 *     // Register options.
 *     conf->addoptions(conf, options);
 *
 *     // Set callback userdata
 *     // This is a userdata which will be provided on callback
 *     conf->setuserdata(conf, &myconf);
 *
 *     // Run parser.
 *     int count = conf->parse(conf, CONF_PATH, QAC_CASEINSENSITIVE);
 *     if (count < 0) {
 *       printf("Error: %s\n", conf->errmsg(conf));
 *     } else {
 *       printf("Successfully loaded.\n");
 *     }
 *
 *     // Verify userdata structure.
 *     if (conf->errmsg(conf) == NULL) {  // another way to check parsing error.
 *       // codes here
 *     }
 *
 *     // Release resources.
 *     conf->free(conf);
 *   }
 *
 *   static QAC_CB(confcb_debug)
 *   {
 *     int i;
 *     for (i = 0; i < data->level; i++) {
 *       printf ("    ");
 *     }
 *
 *     // Print option name
 *     if (data->otype == QAC_OTYPE_SECTIONOPEN) {
 *       printf("<%s>", data->argv[0]);
 *     } else if (data->otype == QAC_OTYPE_SECTIONCLOSE) {
 *       printf("</%s>", data->argv[0]);
 *     } else {  // This is QAC_OTYPE_OPTION type.
 *       printf("%s", data->argv[0]);
 *     }
 *
 *     // Print parent names
 *     qaconf_cbdata_t *parent;
 *     for (parent = data->parent; parent != NULL; parent = parent->parent) {
 *       printf(" ::%s(%s)", parent->argv[0], parent->argv[1]);
 *     }
 *
 *     // Print option arguments
 *     for (i = 1; i < data->argc; i++) {
 *       printf(" [%d:%s]", i, data->argv[i]);
 *     }
 *     printf("\n");
 *
 *     // Return OK
 *     return NULL;
 *   }
 * @endcode
 *
 * @code
 *   [Output]
 *   Listen [1:53]
 *   Protocols [1:UDP] [2:TCP]
 *   IPSEC [1:1]
 *   <Domain> [1:qdecoder.org]
 *       TTL ::Domain(qdecoder.org) [1:86400]
 *       MX ::Domain(qdecoder.org) [1:10] [2:mail.qdecoder.org]
 *       <Host> ::Domain(qdecoder.org) [1:mail]
 *           IPv4 ::Host(mail) ::Domain(qdecoder.org) [1:192.168.10.1]
 *           TXT ::Host(mail) ::Domain(qdecoder.org) [1:US Rack-13D-18 "San Jose's"]
 *       </Host> ::Domain(qdecoder.org) [1:mail]
 *       <Host> ::Domain(qdecoder.org) [1:www]
 *           IPv4 ::Host(www) ::Domain(qdecoder.org) [1:192.168.10.2]
 *           TXT ::Host(www) ::Domain(qdecoder.org) [1:KR Rack-48H-31 "Seoul's"]
 *           TTL ::Host(www) ::Domain(qdecoder.org) [1:3600]
 *       </Host> ::Domain(qdecoder.org) [1:www]
 *   </Domain> [1:qdecoder.org]
 *   <Domain> [1:ringfs.org]
 *       <Host> ::Domain(ringfs.org) [1:www]
 *           CNAME ::Host(www) ::Domain(ringfs.org) [1:www.qdecoder.org]
 *       </Host> ::Domain(ringfs.org) [1:www]
 *   </Domain> [1:ringfs.org]
 *   Successfully loaded.
 * @endcode
 */

#ifndef DISABLE_QACONF

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include "qinternal.h"
#include "utilities/qstring.h"
#include "extensions/qaconf.h"

#ifndef _DOXYGEN_SKIP
#define MAX_LINESIZE    (1024*4)

/* internal functions */
static int addoptions(qaconf_t *qaconf, const qaconf_option_t *options);
static void setdefhandler(qaconf_t *qaconf, qaconf_cb_t *callback);
static void setuserdata(qaconf_t *qaconf, const void *userdata);
static int parse(qaconf_t *qaconf, const char *filepath, uint8_t flags);
static const char *errmsg(qaconf_t *qaconf);
static void reseterror(qaconf_t *qaconf);
static void free_(qaconf_t *qaconf);

static int _parse_inline(qaconf_t *qaconf, FILE *fp, uint8_t flags,
                         enum qaconf_section sectionid,
                         qaconf_cbdata_t *cbdata_parent);
static void _seterrmsg(qaconf_t *qaconf, const char *format, ...);
static void _free_cbdata(qaconf_cbdata_t *cbdata);
static int _is_str_number(const char *s);
static int _is_str_bool(const char *s);
#endif

/**
 * Create a new configuration object.
 *
 * @return a pointer of new qaconf_t object.
 *
 * @code
 *   qaconf_t *conf = qaconf();
 *   if (conf == NULL) {
 *     // Insufficient memory.
 *   }
 * @endcode
 */
qaconf_t *qaconf(void) {
    // Malloc qaconf_t structure
    qaconf_t *qaconf = (qaconf_t *) malloc(sizeof(qaconf_t));
    if (qaconf == NULL)
        return NULL;

    // Initialize the structure
    memset((void *) (qaconf), '\0', sizeof(qaconf_t));

    // member methods
    qaconf->addoptions = addoptions;
    qaconf->setdefhandler = setdefhandler;
    qaconf->setuserdata = setuserdata;
    qaconf->parse = parse;
    qaconf->errmsg = errmsg;
    qaconf->reseterror = reseterror;
    qaconf->free = free_;

    return qaconf;
}

/**
 * qaconf_t->addoptions(): Register option directives.
 *
 * @param qaconf qaconf_t object.
 * @param options array pointer of qaconf_option_t.
 *
 * @return a number of options registered(added).
 *
 * @code
 *  qaconf_option_t options[] = {
 *     {"Listen", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_ALL},
 *     {"Protocols", QAC_TAKEALL, confcb_debug, 0, OPT_SECTION_ROOT},
 *     {"IPSEC", QAC_TAKE_BOOL, confcb_debug, 0, OPT_SECTION_ROOT},
 *     {"Domain", QAC_TAKE_STR, confcb_debug, OPT_SECTION_DOMAIN, OPT_SECTION_ROOT},
 *     {  "TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST},
 *     {  "MX", QAC_TAKE2 | QAC_A1_INT, confcb_debug, 0, OPT_SECTION_DOMAIN},
 *     {  "Host", QAC_TAKE_STR, confcb_debug, OPT_SECTION_HOST, OPT_SECTION_DOMAIN},
 *     {    "IPv4", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
 *     {    "TXT", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
 *     {    "CNAME", QAC_TAKE_STR, confcb_debug, 0, OPT_SECTION_HOST},
 *     QAC_OPTION_END
 *   };
 *
 *   // Register options.
 *   qaconf_t *conf = qaconf();
 *   conf->addoptions(conf, options);
 *   (...codes goes...)
 * @endcode
 *
 * It takes an array of options as provided in the sample codes.
 * Each option consists of 5 parameters as below
 *
 * @code
 *   1st) Option Name : A option directive name.
 *   2nd) Arguments   : A number of arguments this option takes and their types.
 *   3rd) Callback    : A function pointer for callback.
 *   4th) Section ID  : Section ID if this option is a section like <Option>.
 *                 Otherwise 0 for regular option.
 *   5th) Sections    : ORed section IDs where this option can be specified.
 * @endcode
 *
 * Example:
 *
 * @code
 * {"TTL", QAC_TAKE_INT, confcb_debug, 0, OPT_SECTION_DOMAIN | OPT_SECTION_HOST}
 *   1st) Option name is "TTL"
 *   2nd) It takes 1 argument and its type must be integer.
 *   3rd) Callback function, confcb_debug, will be called.
 *   4th) This is a regular option and does not have section id.
 *   5th) This option can be specified in OPT_SECTION_DOMAIN and OPT_SECTION_HOST.
 * @endcode
 *
 * OPTION NAME field:
 *
 * Option name is a unique string. Even an option is section type like <option>
 * only name part without bracket needs to be specifed.
 *
 * ARGUMENT field:
 *
 * This field is for providing argument checking in parser level. So in user's
 * callback routine can go simple. This provides checking of number of arguments
 * this option can take and those argument type.
 *
 * In terms of argument types. There are 4 argument types as below.
 * And first 5 arguments can be checked individually with different types.
 *
 * @code
 *   STR type   : any type
 *   INT type   : integer type. ex) 23, -12, 0
 *   FLOAT type : integer + floating point type. ex) 1.32, -32.5, 23, -12, 0
 *   BOOL type  : bool type ex) 1/0, true/false, on/off, yes/no
 * @endcode
 *
 * When a BOOL type is specified, the argument passed to callback will be
 * replaced to "1" or "0" for convenience use. For example, if "On" is specified
 * as a argument and if BOOL type checking is specified, then actual argument
 * which will be passed to callback will have "1". So we can simply determine it
 * like "bool enabled = atoi(data->argv[1])".
 *
 * If original input argument needs to be passed to callback, specify STR type.
 *
 * Here is some examples of how to specify "Arguments" field.
 *
 * @code
 *  An option takes 1 argument.
 *    QAC_TAKE_STR    <= String(any) type
 *    QAC_TAKE_INT    <= Integer type
 *    QAC_TAKE_FLOAT  <= Float type
 *    QAC_TAKE_BOOL   <= Bool type
 *
 *    QAC_TAKE1               <= Equavalent to QAC_TAKE_STR
 *    QAC_TAKE1 | QAC_A1_BOOL <= Equavalent to QAC_TAKE_BOOL
 *
 *  An option takes 2 arguments, bool and float.
 *    QAC_TAKE2 | QAC_A1_BOOL | QAC_A2_FLOAT
 *
 *  An option takes any number of arguments in any type.
 *    QAC_TAKEALL
 *
 *  An option takes any number of arguments but 1st one must be bool and
 *  2nd one must be integer and rest of them must be float.
 *    QAC_TAKEALL | QAC_A1_BOOL | QAC_A2_INT | QAC_AA_FLOAT
 * @endcode
 *
 * CALLBACK field:
 *
 * User defined callback function. We provide a macro, QAC_CB, for function
 * proto type. Always use QAC_CB macro.
 *
 * @code
 *   QAC_CB(sample_cb) {
 *     (...codes...)
 *   }
 *
 *   is equavalent to
 *
 *   char *sample_cb(qaconf_cbdata_t *data, void *userdata) {
 *     (...codes...)
 *   }
 * @endcode
 *
 * Callback function will be called with 2 arguments. One is callback data and
 * the other one is userdata. Userdata is the data pointer set by setuserdata().
 *
 * Here is data structure. Arguments belong to the option can be accessed via
 * argv variables like data->argv[1]. argv[0] is for the option name.
 *
 * @code
 *   struct qaconf_cbdata_s {
 *     enum qaconf_otype otype;  // option type
 *     uint64_t section;         // current section where this option is located
 *     uint64_t sections;        // ORed all parent's sectionid(s) including current sections
 *     uint8_t level;            // number of parents(level), root level is 0
 *     qaconf_cbdata_t *parent;  // upper parent link
 *
 *     int argc;       // number arguments. always equal or greater than 1.
 *     char **argv;    // argument pointers. argv[0] is option name.
 *   }
 * @endcode
 *
 * SECTION ID field:
 *
 * If an option is an section like <Option>, section id can be assigned.
 * This section id can be used to limit some other option directives to be
 * located only inside of that section. So this is your choice. If it doesn't
 * require to check directory scope, we can just specify 0 here.
 *
 * There are 2 pre-defined section id, QAC_SECTION_ALL and QAC_SECTION_ROOT.
 * When we define user section, it has to be defined from 2(1 << 1)as below.
 *
 * @code
 *   enum {
 *     OPT_SECTION_ALL        = QAC_SECTION_ALL,   // pre-defined
 *     OPT_SECTION_ROOT       = QAC_SECTION_ROOT,  // pre-defined
 *     OPT_SECTION_DOMAIN     = (1 << 1),    // user-defined section
 *     OPT_SECTION_HOST       = (1 << 2),    // user-defined section
 *   };
 * @endcode
 *
 * Please note that this section IDs are ORed. So the section id should be
 * assigned in bit operation manner as 2(1<<1), 4(1<<2), 6(1<<3), 8(1<<4), ...
 *
 * SECTION IDS field:
 *
 * This field is to limit the scope where an option is allowed to be specified.
 * Multiple section IDs can be ORed.
 *
 * QAC_SECTION_ALL means an option can be appeared in anywhere.
 *
 * QAC_SECTION_ROOT means an option can be appeared only in top level and not
 * inside of any sections.
 */
static int addoptions(qaconf_t *qaconf, const qaconf_option_t *options) {
    if (qaconf == NULL || options == NULL) {
        _seterrmsg(qaconf, "Invalid parameters.");
        return -1;
    }

    // Count a number of options
    uint32_t numopts;
    for (numopts = 0; options[numopts].name != NULL; numopts++)
        ;
    if (numopts == 0)
        return 0;

    // Realloc
    size_t newsize = sizeof(qaconf_option_t) * (qaconf->numoptions + numopts);
    qaconf->options = (qaconf_option_t *) realloc(qaconf->options, newsize);
    memcpy(&qaconf->options[qaconf->numoptions], options,
           sizeof(qaconf_option_t) * numopts);
    qaconf->numoptions += numopts;

    return numopts;
}

/**
 * Set default callback function.
 *
 * Default callback function will be called for unregistered option directives.
 * QAC_IGNOREUNKNOWN flag will be ignored when default callback has set.
 *
 * @param qaconf qaconf_t object.
 * @param callback callback function pointer
 */
static void setdefhandler(qaconf_t *qaconf, qaconf_cb_t *callback) {
    qaconf->defcb = callback;
}

/**
 * qaconf_t->setuserdata(): Set userdata which will be provided on callback.
 *
 * @param qaconf qaconf_t object.
 * @param userdata a pointer of userdata.
 *
 * @code
 *   // Define an example userdata
 *   struct MyConf {
 *     int sample;
 *   };
 *
 *   int user_main(void) {
 *     struct MyConf myconf;
 *
 *     (...codes...)
 *
 *     // Set callback userdata.
 *     conf->setuserdata(conf, &myconf);
 *     (...codes...)
 *    }
 *
 *   QAC_CB(confcb_callback_func) {
 *     (...codes...)
 *      // Type casting userdata for convenient use.
 *      struct MyConf *myconf = (struct MyConf *)userdata;
 *      myconf->sample++;
 *     (...codes...)
 *     return NULL;
 *   }
 * @endcode
 */
static void setuserdata(qaconf_t *qaconf, const void *userdata) {
    qaconf->userdata = (void *) userdata;
}

/**
 * qaconf_t->parse(): Run parser.
 *
 * @param qaconf qaconf_t object.
 * @param filepath configuration file path.
 * @param flags parser options. (0 for default)
 *
 * @return A number of option directives parsed. -1 will be returned in case of
 *         error.
 *
 * Here is a list of flags. Multiple flags can be ORed.
 *
 *   QAC_CASEINSENSITIVE: Option name is case-insensitive.
 *
 *   QAC_IGNOREUNKNOWN : Ignore unknown option directives.
 *   This flag will be ignored if setdefhandler() has set.
 *
 * @code
 *   int c;
 *   c = conf->parse(conf, "sm1.conf", 0);
 *   c = conf->parse(conf, "sm2.conf", QAC_CASEINSENSITIVE);
 *   c = conf->parse(conf, "sm3.conf", QAC_CASEINSENSITIVE | QAC_IGNOREUNKNOWN);
 * @endcode
 */
static int parse(qaconf_t *qaconf, const char *filepath, uint8_t flags) {
    // Open file
    FILE *fp = fopen(filepath, "r");
    if (fp == NULL) {
        _seterrmsg(qaconf, "Failed to open file '%s'.", filepath);
        return -1;
    }

    // Set info
    if (qaconf->filepath != NULL)
        free(qaconf->filepath);
    qaconf->filepath = strdup(filepath);
    qaconf->lineno = 0;

    // Parse
    int optcount = _parse_inline(qaconf, fp, flags, QAC_SECTION_ROOT, NULL);

    // Clean up
    fclose(fp);

    return optcount;
}

/**
 * qaconf_t->errmsg(): Get last error message.
 *
 * @param qaconf qaconf_t object.
 *
 * @return A const pointer of error message string.
 *
 * @code
 *   int c = conf->parse(conf, "sample.conf", 0);
 *   if (c < 0) {
 *     // ERROR
 *     printf("%s\n", conf->errmsg(conf));
 *   }
 * @endcode
 */
static const char *errmsg(qaconf_t *qaconf) {
    return (const char*) qaconf->errstr;
}

/**
 * qaconf_t->reseterror(): Clear error message.
 *
 * @param qaconf qaconf_t object.
 *
 * @code
 *   conf->reseterror(conf);
 *   conf->parse(conf, "sample.conf", 0);
 *   if (conf->errmsg(conf) != NULL) {
 *     // ERROR
 *   }
 * @endcode
 */
static void reseterror(qaconf_t *qaconf) {
    if (qaconf->errstr != NULL) {
        free(qaconf->errstr);
        qaconf->errstr = NULL;
    }
}

/**
 * qaconf_t->free(): Release resources.
 *
 * @param qaconf qaconf_t object.
 *
 * @code
 *   conf->free(conf);
 * @endcode
 */
static void free_(qaconf_t *qaconf) {
    if (qaconf->filepath != NULL)
        free(qaconf->filepath);
    if (qaconf->errstr != NULL)
        free(qaconf->errstr);
    if (qaconf->options != NULL)
        free(qaconf->options);
    free(qaconf);
}

#ifndef _DOXYGEN_SKIP

#define ARGV_INIT_SIZE  (4)
#define ARGV_INCR_STEP  (8)
#define MAX_TYPECHECK   (5)
static int _parse_inline(qaconf_t *qaconf, FILE *fp, uint8_t flags,
                         enum qaconf_section sectionid,
                         qaconf_cbdata_t *cbdata_parent) {
    // Assign compare function.
    int (*cmpfunc)(const char *, const char *) = strcmp;
    if (flags & QAC_CASEINSENSITIVE)
        cmpfunc = strcasecmp;

    char buf[MAX_LINESIZE];
    bool doneloop = false;
    bool exception = false;
    int optcount = 0;  // number of option entry processed.
    int newsectionid = 0;  // temporary store
    void *freethis = NULL;  // userdata to free
    while (doneloop == false && exception == false) {

#define EXITLOOP(fmt, args...) do {                                         \
    _seterrmsg(qaconf, "%s:%d " fmt,                                        \
               qaconf->filepath, qaconf->lineno, ##args);                   \
    exception = true;                                                       \
    goto exitloop;                                                          \
} while (0);

        if (fgets(buf, MAX_LINESIZE, fp) == NULL) {
            // Check if section was opened and never closed
            if (cbdata_parent != NULL) {
                EXITLOOP("<%s> section was not closed.", cbdata_parent->argv[0]);
            }
            break;
        }

        // Increase line number counter
        qaconf->lineno++;

        // Trim white spaces
        qstrtrim(buf);

        // Skip blank like and comments.
        if (IS_EMPTY_STR(buf) || *buf == '#') {
            continue;
        }

        DEBUG("%s (line=%d)", buf, qaconf->lineno);

        // Create a callback data
        qaconf_cbdata_t *cbdata = (qaconf_cbdata_t*) malloc(
                sizeof(qaconf_cbdata_t));
        ASSERT(cbdata != NULL);
        memset(cbdata, '\0', sizeof(qaconf_cbdata_t));
        if (cbdata_parent != NULL) {
            cbdata->section = sectionid;
            cbdata->sections = cbdata_parent->sections | sectionid;
            cbdata->level = cbdata_parent->level + 1;
            cbdata->parent = cbdata_parent;
        } else {
            cbdata->section = sectionid;
            cbdata->sections = sectionid;
            cbdata->level = 0;
            cbdata->parent = NULL;
        }

        // Escape section option
        char *sp = buf;
        if (*sp == '<') {
            if (ENDING_CHAR(sp) != '>') {
                EXITLOOP("Missing closing bracket. - '%s'.", buf);
            }

            sp++;
            if (*sp == '/') {
                cbdata->otype = QAC_OTYPE_SECTIONCLOSE;
                sp++;
            } else {
                cbdata->otype = QAC_OTYPE_SECTIONOPEN;
            }

            // Remove tailing bracket
            ENDING_CHAR(sp) = '\0';
        } else {
            cbdata->otype = QAC_OTYPE_OPTION;
        }

        // Brackets has removed at this point
        // Copy data into cbdata buffer.
        cbdata->data = strdup(sp);
        ASSERT(cbdata->data != NULL);

        // Parse and tokenize.
        int argvsize = 0;
        char *wp1, *wp2;
        bool doneparsing = false;
        for (wp1 = (char *) cbdata->data; doneparsing == false; wp1 = wp2) {
            // Allocate/Realloc argv array
            if (argvsize == cbdata->argc) {
                argvsize += (argvsize == 0) ? ARGV_INIT_SIZE : ARGV_INCR_STEP;
                cbdata->argv = (char**) realloc((void *) cbdata->argv,
                                                sizeof(char*) * argvsize);
                ASSERT(cbdata->argv != NULL);
            }

            // Skip whitespaces
            for (; (*wp1 == ' ' || *wp1 == '\t'); wp1++)
                ;

            // Quote handling
            int qtmark = 0;  // 1 for singlequotation, 2 for doublequotation
            if (*wp1 == '\'') {
                qtmark = 1;
                wp1++;
            } else if (*wp1 == '"') {
                qtmark = 2;
                wp1++;
            }

            // Parse a word
            for (wp2 = wp1;; wp2++) {
                if (*wp2 == '\0') {
                    doneparsing = true;
                    break;
                } else if (*wp2 == '\'') {
                    if (qtmark == 1) {
                        qtmark = 0;
                        break;
                    }
                } else if (*wp2 == '"') {
                    if (qtmark == 2) {
                        qtmark = 0;
                        break;
                    }
                } else if (*wp2 == '\\') {
                    if (qtmark > 0) {
                        size_t wordlen = wp2 - wp1;
                        if (wordlen > 0)
                            memmove(wp1 + 1, wp1, wordlen);
                        wp1++;
                        wp2++;
                    }
                } else if (*wp2 == ' ' || *wp2 == '\t') {
                    if (qtmark == 0)
                        break;
                }
            }
            *wp2 = '\0';
            wp2++;

            // Check quotations has paired.
            if (qtmark > 0) {
                EXITLOOP("Quotation hasn't properly closed.");
            }

            // Store a argument
            cbdata->argv[cbdata->argc] = wp1;
            cbdata->argc++;
            DEBUG("  argv[%d]=%s", cbdata->argc - 1, wp1);

            // For quoted string, this case can be happened.
            if (*wp2 == '\0') {
                doneparsing = true;
            }
        }

        // Check mismatch sectionclose
        if (cbdata->otype == QAC_OTYPE_SECTIONCLOSE) {
            if (cbdata_parent == NULL
                    || cmpfunc(cbdata->argv[0], cbdata_parent->argv[0])) {
                EXITLOOP("Trying to close <%s> section that wasn't opened.",
                         cbdata->argv[0]);
            }
        }

        // Find matching option
        bool optfound = false;
        int i;
        for (i = 0; optfound == false && i < qaconf->numoptions; i++) {
            qaconf_option_t *option = &qaconf->options[i];

            if (!cmpfunc(cbdata->argv[0], option->name)) {
                // Check sections
                if ((cbdata->otype != QAC_OTYPE_SECTIONCLOSE)
                        && (option->sections != QAC_SECTION_ALL)
                        && (option->sections & sectionid) == 0) {
                    EXITLOOP("Option '%s' is in wrong section.", option->name);
                }

                // Check argument types
                if (cbdata->otype != QAC_OTYPE_SECTIONCLOSE) {
                    // Check number of arguments
                    int numtake = option->take & QAC_TAKEALL;
                    if (numtake != QAC_TAKEALL
                            && numtake != (cbdata->argc - 1)) {
                        EXITLOOP("'%s' option takes %d arguments.",
                                 option->name, numtake);
                    }

                    // Check argument types
                    int deftype;  // 0:str, 1:int, 2:float, 3:bool
                    if (option->take & QAC_AA_INT)
                        deftype = 1;
                    else if (option->take & QAC_AA_FLOAT)
                        deftype = 2;
                    else if (option->take & QAC_AA_BOOL)
                        deftype = 3;
                    else
                        deftype = 0;

                    int j;
                    for (j = 1; j < cbdata->argc && j <= MAX_TYPECHECK; j++) {
                        int argtype;
                        if (option->take & (QAC_A1_INT << (j - 1)))
                            argtype = 1;
                        else if (option->take & (QAC_A1_FLOAT << (j - 1)))
                            argtype = 2;
                        else if (option->take & (QAC_A1_BOOL << (j - 1)))
                            argtype = 3;
                        else
                            argtype = deftype;

                        if (argtype == 1) {
                            // integer type
                            if (_is_str_number(cbdata->argv[j]) != 1) {
                                EXITLOOP(
                                        "%dth argument of '%s' must be integer type.",
                                        j, option->name);
                            }
                        } else if (argtype == 2) {
                            // floating point type
                            if (_is_str_number(cbdata->argv[j]) == 0) {
                                EXITLOOP(
                                        "%dth argument of '%s' must be floating point. type",
                                        j, option->name);
                            }
                        } else if (argtype == 3) {
                            // bool type
                            if (_is_str_bool(cbdata->argv[j]) != 0) {
                                // Change argument to "1".
                                strcpy(cbdata->argv[j], "1");
                            } else {
                                EXITLOOP(
                                        "%dth argument of '%s' must be bool type.",
                                        j, option->name);
                            }
                        }
                    }
                }

                // Callback
                //DEBUG("Callback %s", option->name);
                qaconf_cb_t *usercb = option->cb;
                if (usercb == NULL)
                    usercb = qaconf->defcb;
                if (usercb != NULL) {
                    char *cberrmsg = NULL;

                    if (cbdata->otype != QAC_OTYPE_SECTIONCLOSE) {
                        // Normal option and sectionopen
                        cberrmsg = usercb(cbdata, qaconf->userdata);
                    } else {
                        // QAC_OTYPE_SECTIONCLOSE

                        // Change otype
                        ASSERT(cbdata_parent != NULL);
                        enum qaconf_otype orig_otype = cbdata_parent->otype;
                        cbdata_parent->otype = QAC_OTYPE_SECTIONCLOSE;

                        // Callback
                        cberrmsg = usercb(cbdata_parent, qaconf->userdata);

                        // Restore type
                        cbdata_parent->otype = orig_otype;
                    }

                    // Error handling
                    if (cberrmsg != NULL) {
                        freethis = cberrmsg;
                        EXITLOOP("%s", cberrmsg);
                    }
                }

                if (cbdata->otype == QAC_OTYPE_SECTIONOPEN) {
                    // Store it for later
                    newsectionid = option->sectionid;
                }

                // Set found flag
                optfound = true;
            }
        }

        // If not found.
        if (optfound == false) {
            if (qaconf->defcb != NULL) {
                qaconf->defcb(cbdata, qaconf->userdata);
            } else if ((flags & QAC_IGNOREUNKNOWN) == 0) {
                EXITLOOP("Unregistered option '%s'.", cbdata->argv[0]);
            }
        }

        // Section handling
        if (cbdata->otype == QAC_OTYPE_SECTIONOPEN) {
            // Enter recursive call
            DEBUG("Entering next level %d.", cbdata->level+1);
            int optcount2 = _parse_inline(qaconf, fp, flags, newsectionid,
                                          cbdata);
            if (optcount2 >= 0) {
                optcount += optcount2;
            } else {
                exception = true;
            }DEBUG("Returned to previous level %d.", cbdata->level);
        } else if (cbdata->otype == QAC_OTYPE_SECTIONCLOSE) {
            // Leave recursive call
            doneloop = true;
        }

        exitloop:
        // Release resources
        if (freethis != NULL) {
            free(freethis);
        }

        if (cbdata != NULL) {
            _free_cbdata(cbdata);
            cbdata = NULL;
        }

        if (exception == true) {
            break;
        }

        // Go up and down
        // if (otype

        // Increase process counter
        optcount++;
    }

    return (exception == false) ? optcount : -1;
}

static void _seterrmsg(qaconf_t *qaconf, const char *format, ...) {
    if (qaconf->errstr != NULL)
        free(qaconf->errstr);
    DYNAMIC_VSPRINTF(qaconf->errstr, format);
}

static void _free_cbdata(qaconf_cbdata_t *cbdata) {
    if (cbdata->argv != NULL)
        free(cbdata->argv);
    if (cbdata->data != NULL)
        free(cbdata->data);
    free(cbdata);
}

// return 2 for floating point .
// return 1 for integer
// return 0 for non number
static int _is_str_number(const char *s) {
    char *op = (char *) s;
    if (*op == '-') {
        op++;
    }

    char *cp, *dp;
    for (cp = op, dp = NULL; *cp != '\0'; cp++) {
        if ('0' <= *cp && *cp <= '9') {
            continue;
        }

        if (*cp == '.') {
            if (cp == op)
                return 0;  // dot can't be at the beginning.
            if (dp != NULL)
                return 0;  // dot can't be appeared more than once.
            dp = cp;
            continue;
        }

        return 0;
    }

    if (cp == op) {
        return 0;  // empty string
    }

    if (dp != NULL) {
        if (dp + 1 == cp)
            return 0;  // dot can't be at the end.
        return 2;  // float point
    }

    // integer
    return 1;
}

static int _is_str_bool(const char *s) {
    if (!strcasecmp(s, "true"))
        return 1;
    else if (!strcasecmp(s, "on"))
        return 1;
    else if (!strcasecmp(s, "yes"))
        return 1;
    else if (!strcasecmp(s, "1"))
        return 1;
    return 0;
}

#endif /* _DOXYGEN_SKIP */

#endif /* DISABLE_QACONF */

