//
// Automated Testing Framework (atf)
//
// Copyright (c) 2007 The NetBSD Foundation, Inc.
// 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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.
//

#include <cstdlib>
#include <fstream>
#include <vector>

#include "config.hpp"
#include "config_file.hpp"
#include "defs.hpp"
#include "env.hpp"
#include "fs.hpp"
#include "parser.hpp"

namespace impl = tools::config_file;
namespace detail = tools::config_file::detail;

namespace {

typedef std::map< std::string, std::string > vars_map;

namespace atf_config {

static const tools::parser::token_type eof_type = 0;
static const tools::parser::token_type nl_type = 1;
static const tools::parser::token_type text_type = 2;
static const tools::parser::token_type dblquote_type = 3;
static const tools::parser::token_type equal_type = 4;
static const tools::parser::token_type hash_type = 5;

class tokenizer : public tools::parser::tokenizer< std::istream > {
public:
    tokenizer(std::istream& is, size_t curline) :
        tools::parser::tokenizer< std::istream >
            (is, true, eof_type, nl_type, text_type, curline)
    {
        add_delim('=', equal_type);
        add_delim('#', hash_type);
        add_quote('"', dblquote_type);
    }
};

} // namespace atf_config

class config_reader : public detail::atf_config_reader {
    vars_map m_vars;

    void
    got_var(const std::string& var, const std::string& name)
    {
        m_vars[var] = name;
    }

public:
    config_reader(std::istream& is) :
        atf_config_reader(is)
    {
    }

    const vars_map&
    get_vars(void)
        const
    {
        return m_vars;
    }
};

template< class K, class V >
static
void
merge_maps(std::map< K, V >& dest, const std::map< K, V >& src)
{
    for (typename std::map< K, V >::const_iterator iter = src.begin();
         iter != src.end(); iter++)
        dest[(*iter).first] = (*iter).second;
}

static
void
merge_config_file(const tools::fs::path& config_path,
                  vars_map& config)
{
    std::ifstream is(config_path.c_str());
    if (is) {
        config_reader reader(is);
        reader.read();
        merge_maps(config, reader.get_vars());
    }
}

static
std::vector< tools::fs::path >
get_config_dirs(void)
{
    std::vector< tools::fs::path > dirs;
    dirs.push_back(tools::fs::path(tools::config::get("atf_confdir")));
    if (tools::env::has("HOME"))
        dirs.push_back(tools::fs::path(tools::env::get("HOME")) / ".atf");
    return dirs;
}

} // anonymous namespace

detail::atf_config_reader::atf_config_reader(std::istream& is) :
    m_is(is)
{
}

detail::atf_config_reader::~atf_config_reader(void)
{
}

void
detail::atf_config_reader::got_var(
    const std::string& var ATF_DEFS_ATTRIBUTE_UNUSED,
    const std::string& val ATF_DEFS_ATTRIBUTE_UNUSED)
{
}

void
detail::atf_config_reader::got_eof(void)
{
}

void
detail::atf_config_reader::read(void)
{
    using tools::parser::parse_error;
    using namespace atf_config;

    std::pair< size_t, tools::parser::headers_map > hml =
        tools::parser::read_headers(m_is, 1);
    tools::parser::validate_content_type(hml.second,
        "application/X-atf-config", 1);

    tokenizer tkz(m_is, hml.first);
    tools::parser::parser< tokenizer > p(tkz);

    for (;;) {
        try {
            tools::parser::token t = p.expect(eof_type, hash_type, text_type,
                                              nl_type,
                                              "eof, #, new line or text");
            if (t.type() == eof_type)
                break;

            if (t.type() == hash_type) {
                (void)p.rest_of_line();
                t = p.expect(nl_type, "new line");
            } else if (t.type() == text_type) {
                std::string name = t.text();

                t = p.expect(equal_type, "equal sign");

                t = p.expect(text_type, "word or quoted string");
                ATF_PARSER_CALLBACK(p, got_var(name, t.text()));

                t = p.expect(nl_type, hash_type, "new line or comment");
                if (t.type() == hash_type) {
                    (void)p.rest_of_line();
                    t = p.expect(nl_type, "new line");
                }
            } else if (t.type() == nl_type) {
            } else
                std::abort();
        } catch (const parse_error& pe) {
            p.add_error(pe);
            p.reset(nl_type);
        }
    }

    ATF_PARSER_CALLBACK(p, got_eof());
}

vars_map
impl::merge_configs(const vars_map& lower,
                    const vars_map& upper)
{
    vars_map merged = lower;
    merge_maps(merged, upper);
    return merged;
}

vars_map
impl::read_config_files(const std::string& test_suite_name)
{
    vars_map config;

    const std::vector< tools::fs::path > dirs = get_config_dirs();
    for (std::vector< tools::fs::path >::const_iterator iter = dirs.begin();
         iter != dirs.end(); iter++) {
        merge_config_file((*iter) / "common.conf", config);
        merge_config_file((*iter) / (test_suite_name + ".conf"), config);
    }

    return config;
}
