/******************************************************************************
 * Copyright (C) 2006 Tetsuya Kimata <kimata@acapulco.dyndns.org>
 *
 * All rights reserved.
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any
 * damages arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any
 * purpose, including commercial applications, and to alter it and
 * redistribute it freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must
 *    not claim that you wrote the original software. If you use this
 *    software in a product, an acknowledgment in the product
 *    documentation would be appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must
 *    not be misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source
 *    distribution.
 *
 * $Id: ConfigReader.cpp 2085 2006-11-25 19:29:52Z svn $
 *****************************************************************************/

#include "Environment.h"

#include "apr_strings.h"

#include "ConfigReader.h"
#include "File.h"
#include "TemporaryPool.h"
#include "Message.h"
#include "Macro.h"
#include "SourceInfo.h"

SOURCE_INFO_ADD("$Id: ConfigReader.cpp 2085 2006-11-25 19:29:52Z svn $");

const char ConfigReader::COMMENT_CHAR       = '#';
const char ConfigReader::QUOTE_CHAR         = '"';
const char ConfigReader::ASSIGN_CHAR        = '=';

/******************************************************************************
 * public メソッド
 *****************************************************************************/
apr_table_t *ConfigReader::read(apr_pool_t *pool, const char *conf_file_path)
{
    static const apr_size_t INITIAL_ENTRY_COUNT = 16;

    const char *input;
    const char *name;
    const char *value;
    apr_table_t *conf_table;
    apr_mmap_t *conf_file_map;
    TemporaryPool temp_pool;
    File conf_file(temp_pool.get(), conf_file_path);

    conf_file.open(APR_READ|APR_BINARY);
    conf_file_map = conf_file.mmap(0, 0, APR_MMAP_READ);

    conf_table = apr_table_make(pool, INITIAL_ENTRY_COUNT);

    input = AS_CONST_CHAR(conf_file_map->mm);
    while (read_line(pool, &input, &name, &value)) {
        apr_table_setn(conf_table, name, value);
    }

    return conf_table;
}


/******************************************************************************
 * private メソッド
 *****************************************************************************/
bool ConfigReader::read_line(apr_pool_t *pool, const char **input,
                             const char **name, const char **value)
{
    skip_space(input);

    if (**input == '\0') {
        return false;
    }

    // コメント行または空行
    while ((**input == COMMENT_CHAR) || (**input == '\n')) {
        skip_line(input);
        return read_line(pool, input, name, value);
    }

    read_param(pool, input, name, value);

    skip_space(input);

    if (**input != '\n') {
        THROW(MESSAGE_CONF_RETURN_NEEDED);
    }

    skip_line(input);

    return true;
}

void ConfigReader::read_param(apr_pool_t *pool, const char **input,
                              const char **name, const char **value)
{
    read_word(pool, input, name);

    skip_space(input);

    if (**input != ASSIGN_CHAR) {
        THROW(MESSAGE_CONF_ASSIGN_NEEDED);
    }
    (*input)++;

    skip_space(input);

    read_word(pool, input, value);
}

void ConfigReader::read_word(apr_pool_t *pool, const char **input,
                             const char **word)
{
    const char *start;
    const char *end;

    if (**input == QUOTE_CHAR) {
        start = ++(*input);

        // QUOTE_CHAR のエスケープは未サポート
        while ((**input != '\0') && (**input != QUOTE_CHAR)) {
            (*input)++;
        }
        if (**input != QUOTE_CHAR) {
            THROW(MESSAGE_CONF_QUOTE_ENDED);
        }
    } else {
        start = *input;

        while (isalnum(static_cast<unsigned char>(**input))) {
            (*input)++;
        }
    }
    end = (*input)++;

    *word = apr_pstrmemdup(pool, start, end - start);
}

void ConfigReader::skip_space(const char **input)
{
    // 改行はスキップしない
    while ((**input != '\0') && (**input != '\n') &&
           isspace(static_cast<unsigned char>(**input))) {
        (*input)++;
    }
}

void ConfigReader::skip_line(const char **input)
{
    while ((**input != '\0') && (**input != '\n')) {
        (*input)++;
    }

    if (**input == '\n') {
        (*input)++;
    }
}


/******************************************************************************
 * テスト
 *****************************************************************************/
#ifdef DEBUG_ConfigReader

#include "TestRunner.h"

void show_usage(const char *prog_name)
{
    cerr << "Usage: " << prog_name << " <CONF_FIEL_PATH>" << endl;
}

void run_read(apr_pool_t *pool, const char *conf_file_path)
{
    show_test_name("read");

    ConfigReader::read(pool, conf_file_path);
}

void run_all(apr_pool_t *pool, int argc, const char * const *argv)
{
    const char *conf_file_path;

    if (argc != 2) {
        THROW(MESSAGE_ARGUMENT_INVALID);
    }

    conf_file_path = argv[1];

    if (!is_exist(pool, conf_file_path)) {
        THROW(MESSAGE_CONF_FILE_NOT_FOUND);
    }

    show_line();
    run_read(pool, conf_file_path);
}

#endif

// Local Variables:
// mode: c++
// coding: utf-8-dos
// End:
