/*++
Copyright (c) 2011 Microsoft Corporation

Module Name:

    params.cpp

Abstract:

    Parameters

Author:

    Leonardo (leonardo) 2011-05-09

Notes:

--*/
#include "util/params.h"
#include "util/rational.h"
#include "util/symbol.h"
#include "util/dictionary.h"
#include <atomic>

params_ref params_ref::g_empty_params_ref;

std::string norm_param_name(char const* n) {
    if (*n == ':')
        n++;
    std::string r = n;
    unsigned sz = static_cast<unsigned>(r.size());
    if (sz == 0)
        return "_";
        
    for (unsigned i = 0; i < sz; i++) {
        char curr = r[i];
        if ('A' <= curr && curr <= 'Z')
            r[i] = curr - 'A' + 'a';
        else if (curr == '-' || curr == ':')
            r[i] = '_';
    }
    return r;
}

std::string norm_param_name(symbol const & n) {
    if (n.is_null())
        return "_";
    return norm_param_name(n.bare_str());
}

struct param_descrs::imp {
    struct info {
        param_kind   m_kind;
        char const * m_descr;
        char const * m_default;
        char const * m_module;

        info(param_kind k, char const * descr, char const * def, char const* module):
            m_kind(k),
            m_descr(descr),
            m_default(def),
            m_module(module) {
        }

        info():
            m_kind(CPK_INVALID), 
            m_descr(nullptr),
            m_default(nullptr),
            m_module(nullptr) {
        }
    };

    dictionary<info> m_info;
    svector<symbol> m_names;

    void insert(symbol const & name, param_kind k, char const * descr, char const * def, char const* module) {
        SASSERT(!name.is_numerical());
        info i;
        if (m_info.find(name, i)) {
            SASSERT(i.m_kind == k);
            return;
        }
        m_info.insert(name, info(k, descr, def, module));
        m_names.push_back(name);
    }

    void erase(symbol const & name) {
        m_info.erase(name);
    }

    bool contains(symbol const & name) const {
        return m_info.contains(name);
    }
                                    
    param_kind get_kind(symbol const & name) const {
        info i;
        if (m_info.find(name, i))
            return i.m_kind;
        return CPK_INVALID;
    }

    bool split_name(symbol const& name, std::string_view & prefix, symbol & suffix) const {
        if (name.is_numerical()) return false;
        char const* str = name.bare_str();
        char const* period = strchr(str,'.');
        if (!period) return false;
        prefix = std::string_view(str, period - str);
        suffix = symbol(period + 1);
        return true;
    }

    param_kind get_kind_in_module(symbol & name) const {
        param_kind k = get_kind(name);
        std::string_view prefix;
        symbol suffix;
        if (k == CPK_INVALID && split_name(name, prefix, suffix)) {   
            k = get_kind(suffix);
            if (k != CPK_INVALID) {
                if (get_module(suffix) == prefix) {
                    name = suffix;
                }
                else {
                    k = CPK_INVALID;
                }
            }
        }
        return k;
    }

    char const* get_module(symbol const& name) const {
        info i;
        if (m_info.find(name, i)) 
            return i.m_module;
        return nullptr;
    }

    char const * get_descr(symbol const & name) const {
        info i;
        if (m_info.find(name, i))
            return i.m_descr;
        return nullptr;
    }

    char const * get_default(symbol const & name) const {
        info i;
        if (m_info.find(name, i))
            return i.m_default;
        return nullptr;
    }

    unsigned size() const {
        return m_names.size();
    }
    
    symbol get_param_name(unsigned idx) const {
        return m_names[idx];
    }

    struct symlt {
        bool operator()(symbol const & s1, symbol const & s2) const { return ::lt(s1, s2); }        
    };

    void display(std::ostream & out, unsigned indent, bool smt2_style, bool include_descr, bool markdown) const {
        svector<symbol> names;
        for (auto const& kv : m_info) {
            names.push_back(kv.m_key);
        }
        std::sort(names.begin(), names.end(), symlt());
        if (names.empty())
            return;
        if (markdown) {
            out << " Parameter | Type | Description | Default\n"
                   " ----------|------|-------------|--------\n";
        }
        for (symbol const& name : names) {
            for (unsigned i = 0; i < indent; i++) out << " ";
            if (smt2_style)
                out << ':';
            std::string s = name.str();
            unsigned n = static_cast<unsigned>(s.length());
            for (unsigned i = 0; i < n; i++) {
                if (smt2_style && s[i] == '_')
                    out << '-';
                else if (!smt2_style && s[i] == '-')
                    out << '_';
                else if (s[i] >= 'A' && s[i] <= 'Z')
                    out << (s[i] - 'A' + 'a');
                else 
                    out << s[i];
            }
            info d;
            m_info.find(name, d);
            SASSERT(d.m_descr);
            if (markdown) 
                out << " | " << d.m_kind << " ";
            else
                out << " (" << d.m_kind << ")";
            if (markdown) {
                out << " |  ";
                for (auto ch : std::string_view(d.m_descr)) {
                    switch (ch) {
                    case '<': out << "&lt;"; break;
                    case '>': out << "&gt;"; break;
                    default:  out << ch; break;
                    }
                }
            }
            else if (include_descr)
                out << " " << d.m_descr;
            if (markdown) {
                out << " | ";
                if (d.m_default)
                    out << d.m_default;
            }
            else if (d.m_default != nullptr)
                out << " (default: " << d.m_default << ")";
            out << "\n";
        }
    }

    void copy(param_descrs & other) {
        for (auto const& kv : other.m_imp->m_info) {
            insert(kv.m_key, kv.m_value.m_kind, kv.m_value.m_descr, kv.m_value.m_default, kv.m_value.m_module);
        }
    }

};

param_descrs::param_descrs() {
    m_imp = alloc(imp);
}

param_descrs::~param_descrs() {
    dealloc(m_imp);
}

void param_descrs::copy(param_descrs & other) {
    m_imp->copy(other);
}

void param_descrs::insert(symbol const & name, param_kind k, char const * descr, char const * def, char const* module) {
    m_imp->insert(name, k, descr, def, module);
}

void param_descrs::insert(char const * name, param_kind k, char const * descr, char const * def, char const* module) {
    insert(symbol(name), k, descr, def, module);
}

bool param_descrs::contains(char const * name) const {
    return contains(symbol(name));
}

bool param_descrs::contains(symbol const & name) const {
    return m_imp->contains(name);
}

char const * param_descrs::get_descr(char const * name) const {
    return get_descr(symbol(name));
}

char const * param_descrs::get_descr(symbol const & name) const {
    return m_imp->get_descr(name);
}

char const * param_descrs::get_default(char const * name) const {
    return get_default(symbol(name));
}

char const * param_descrs::get_default(symbol const & name) const {
    return m_imp->get_default(name);
}

void param_descrs::erase(symbol const & name) {
    m_imp->erase(name);
}

void param_descrs::erase(char const * name) {
    erase(symbol(name));
}

param_kind param_descrs::get_kind_in_module(symbol & name) const {
    return m_imp->get_kind_in_module(name);
}

param_kind param_descrs::get_kind(symbol const & name) const {
    return m_imp->get_kind(name);
}

param_kind param_descrs::get_kind(char const * name) const {
    return get_kind(symbol(name));
}

unsigned param_descrs::size() const {
    return m_imp->size();
}

symbol param_descrs::get_param_name(unsigned i) const {
    return m_imp->get_param_name(i);
}

char const* param_descrs::get_module(symbol const& name) const {
    return m_imp->get_module(name);
}

void param_descrs::display(std::ostream & out, unsigned indent, bool smt2_style, bool include_descr) const {
    return m_imp->display(out, indent, smt2_style, include_descr, false);
}

void param_descrs::display_markdown(std::ostream & out, bool smt2_style, bool include_descr) const {
    return m_imp->display(out, 0, smt2_style, include_descr, true);
}

void insert_max_memory(param_descrs & r) {
    r.insert("max_memory", CPK_UINT, "(default: infty) maximum amount of memory in megabytes.", "4294967295");
}

void insert_max_steps(param_descrs & r) {
    r.insert("max_steps", CPK_UINT, "(default: infty) maximum number of steps.", "4294967295");
}

void insert_produce_models(param_descrs & r) {
    r.insert("produce_models", CPK_BOOL, "model generation.", "false");
}

void insert_produce_proofs(param_descrs & r) {
    r.insert("produce_proofs", CPK_BOOL, "proof generation.", "false");
}

void insert_timeout(param_descrs & r) {
    r.insert("timeout", CPK_UINT, "(default: infty) timeout in milliseconds.", "4294967295");
}

void insert_rlimit(param_descrs & r) {
    r.insert("rlimit", CPK_UINT, "default resource limit used for solvers. Unrestricted when set to 0.", "0");
}

void insert_ctrl_c(param_descrs & r) {
    r.insert("ctrl_c", CPK_BOOL, "enable interrupts from ctrl-c", "true");
}

class params {
    friend class params_ref;
    struct value {
        param_kind m_kind;
        union {
            bool          m_bool_value;
            unsigned      m_uint_value;
            double        m_double_value;
            char const *  m_str_value;
            symbol        m_sym_value;
            rational *    m_rat_value;
        };
        value() : m_kind(CPK_BOOL), m_bool_value(false) {}
        value& operator=(value const& other) {
            m_kind = other.m_kind;
            switch (m_kind) {
            case CPK_BOOL: m_bool_value = other.m_bool_value; break;
            case CPK_UINT: m_uint_value = other.m_uint_value; break;
            case CPK_DOUBLE: m_double_value = other.m_double_value; break;
            case CPK_STRING: m_str_value = other.m_str_value; break;
            case CPK_SYMBOL: m_sym_value = other.m_sym_value; break;
            default: m_rat_value = other.m_rat_value; break;
            }
            return *this;
        }
    };
    typedef std::pair<symbol, value> entry;
    svector<entry>        m_entries;
    std::atomic<unsigned> m_ref_count = 0;
    void del_value(entry & e);
    void del_values();

public:
    ~params() {
        reset();
    }

    void inc_ref() { m_ref_count++; }
    void dec_ref() { 
        SASSERT(m_ref_count > 0);
        if (--m_ref_count == 0) dealloc(this); 
    }

    bool empty() const { return m_entries.empty(); }
    bool contains(symbol const & k) const;
    bool contains(char const * k) const;

    void reset();
    void reset(symbol const & k);
    void reset(char const * k);

    void validate(param_descrs const & p) {        
        for (params::entry& e : m_entries) {
            param_kind expected = p.get_kind_in_module(e.first);
            if (expected == CPK_INVALID) {
                std::stringstream strm;
                strm << "unknown parameter '" << e.first.str() << "'\n";    
                strm << "Legal parameters are:\n";
                p.display(strm, 2, false, false);
                throw default_exception(strm.str());
            }
            if (e.second.m_kind != expected && 
                !(e.second.m_kind == CPK_UINT && expected == CPK_NUMERAL)) {
                std::stringstream strm;
                strm << "Parameter " << e.first.str() << " was given argument of type ";
                strm << e.second.m_kind << ", expected " << expected;                
                throw default_exception(strm.str());
            }
        }
    }
    
    // getters
    bool get_bool(symbol const & k, bool _default) const;
    bool get_bool(char const * k, bool _default) const;
    unsigned get_uint(symbol const & k, unsigned _default) const;
    unsigned get_uint(char const * k, unsigned _default) const;
    double get_double(symbol const & k, double _default) const;
    double get_double(char const * k, double _default) const;
    char const * get_str(symbol const & k, char const * _default) const;
    char const * get_str(char const * k, char const * _default) const;
    rational get_rat(symbol const & k, rational const & _default) const;
    rational get_rat(char const * k, rational const & _default) const; 
    symbol get_sym(symbol const & k, symbol const & _default) const;
    symbol get_sym(char const * k, symbol const & _default) const;

    bool get_bool(char const * k, params_ref const & fallback, bool _default) const;
    unsigned get_uint(char const * k, params_ref const & fallback, unsigned _default) const;
    double get_double(char const * k, params_ref const & fallback, double _default) const;
    char const * get_str(char const * k, params_ref const & fallback, char const * _default) const;
    symbol get_sym(char const * k, params_ref const & fallback, symbol const & _default) const;

    // setters
    void set_bool(symbol const & k, bool v);
    void set_bool(char const * k, bool  v);
    void set_uint(symbol const & k, unsigned v);
    void set_uint(char const * k, unsigned v);
    void set_double(symbol const & k, double v);
    void set_double(char const * k, double v);
    void set_str(symbol const & k, char const * v);
    void set_str(char const * k, char const * v);
    void set_rat(symbol const & k, rational const & v);
    void set_rat(char const * k, rational const & v); 
    void set_sym(symbol const & k, symbol const & v);
    void set_sym(char const * k, symbol const & v);

    void display(std::ostream & out) const {
        out << "(params";
        for (params::entry const& e : m_entries) {
            out << " " << e.first;            
            switch (e.second.m_kind) {
            case CPK_BOOL:
                out << " " << (e.second.m_bool_value?"true":"false");
                break;
            case CPK_UINT:
                out << " " <<e.second.m_uint_value;
                break;
            case CPK_DOUBLE:
                out << " " << e.second.m_double_value;
                break;
            case CPK_NUMERAL:
                out << " " << *(e.second.m_rat_value);
                break;
            case CPK_SYMBOL:
                out << " " << e.second.m_sym_value;
                break;
            case CPK_STRING:
                out << " " << e.second.m_str_value;
                break;
            default:
                UNREACHABLE();
                break;
            }
        }
        out << ")";
    }

    void display_smt2(std::ostream & out, char const* module, param_descrs& descrs) const {
        for (params::entry const& e : m_entries) {
            if (!descrs.contains(e.first)) continue;
            out << "(set-option :";
            out << module << ".";        
            out << e.first;
            switch (e.second.m_kind) {
            case CPK_BOOL:
                out << " " << (e.second.m_bool_value?"true":"false");
                break;
            case CPK_UINT:
                out << " " <<e.second.m_uint_value;
                break;
            case CPK_DOUBLE:
                out << " " << e.second.m_double_value;
                break;
            case CPK_NUMERAL:
                out << " " << *(e.second.m_rat_value);
                break;
            case CPK_SYMBOL:
                out << " " << e.second.m_sym_value;
                break;
            case CPK_STRING:
                out << " " << e.second.m_str_value;
                break;
            default:
                UNREACHABLE();
                break;
            }
            out << ")\n";
        }
    }

    void display(std::ostream & out, symbol const & k) const {
        for (params::entry const& e : m_entries) {
            if (e.first != k)
                continue;
            switch (e.second.m_kind) {
            case CPK_BOOL:
                out << (e.second.m_bool_value?"true":"false");
                return;
            case CPK_UINT:
                out << e.second.m_uint_value;
                return;
            case CPK_DOUBLE:
                out << e.second.m_double_value;
                return;
            case CPK_NUMERAL:
                out << *(e.second.m_rat_value);
                return;
            case CPK_SYMBOL:
                out << e.second.m_sym_value;
                return;
            case CPK_STRING:
                out << e.second.m_str_value;
                return;
            default:
                out << "internal";
                return;
            }
        }
        out << "default";
    }
};

params_ref::~params_ref() {
    if (m_params)
        m_params->dec_ref();
}

params_ref::params_ref(params_ref const & p) {
    set(p);
}

void params_ref::display(std::ostream & out) const {
    if (m_params)
        m_params->display(out);
    else 
        out << "(params)";
}

void params_ref::display_smt2(std::ostream& out, char const* module, param_descrs& descrs) const {
    if (m_params)
        m_params->display_smt2(out, module, descrs);

}


void params_ref::display(std::ostream & out, char const * k) const {
    display(out, symbol(k));
}

void params_ref::display(std::ostream & out, symbol const & k) const {
    if (m_params)
        m_params->display(out, k);
    else
        out << "default";
}

void params_ref::validate(param_descrs const & p) {
    if (m_params)
        m_params->validate(p);
}


void params_ref::set(params_ref const & p) {
    if (p.m_params)
        p.m_params->inc_ref();
    if (m_params)
        m_params->dec_ref();
    m_params = p.m_params;
}

void params_ref::copy(params_ref const & src) {
    if (m_params == nullptr || m_params->empty())
        set(src);
    else if (src.empty())
        return;
    else {
        init();
        copy_core(src.m_params);
    }
}

void params_ref::copy_core(params const * src) {
    if (src == nullptr)
        return;
    for (auto const& p : src->m_entries) {
        switch (p.second.m_kind) {
        case CPK_BOOL:
            m_params->set_bool(p.first, p.second.m_bool_value);
            break;
        case CPK_UINT:
            m_params->set_uint(p.first, p.second.m_uint_value);
            break;
        case CPK_DOUBLE:
            m_params->set_double(p.first, p.second.m_double_value);
            break;
        case CPK_NUMERAL:
            m_params->set_rat(p.first, *(p.second.m_rat_value));
            break;
        case CPK_SYMBOL:
            m_params->set_sym(p.first, p.second.m_sym_value);
            break;
        case CPK_STRING:
            m_params->set_str(p.first, p.second.m_str_value);
            break;
        default:
            UNREACHABLE();
            break;
        }
    }
}

void params_ref::init() {
    if (!m_params) {
        m_params = alloc(params);
        m_params->inc_ref();
    }
    else if (m_params->m_ref_count > 1) {
        params * old = m_params;
        m_params = alloc(params);
        m_params->inc_ref();
        copy_core(old);
        old->dec_ref();
    }
    
    SASSERT(m_params->m_ref_count == 1);
}

bool params_ref::get_bool(symbol const & k, bool _default) const { return m_params ? m_params->get_bool(k, _default) : _default; }
bool params_ref::get_bool(char const * k, bool _default) const { return m_params ? m_params->get_bool(k, _default) : _default; }
unsigned params_ref::get_uint(symbol const & k, unsigned _default) const { return m_params ? m_params->get_uint(k, _default) : _default; }
unsigned params_ref::get_uint(char const * k, unsigned _default) const { return m_params ? m_params->get_uint(k, _default) : _default; }
double params_ref::get_double(symbol const & k, double _default) const { return m_params ? m_params->get_double(k, _default) : _default; }
double params_ref::get_double(char const * k, double _default) const { return m_params ? m_params->get_double(k, _default) : _default; }
char const * params_ref::get_str(symbol const & k, char const * _default) const { return m_params ? m_params->get_str(k, _default) : _default; }
char const * params_ref::get_str(char const * k, char const * _default) const { return m_params ? m_params->get_str(k, _default) : _default; }

rational params_ref::get_rat(symbol const & k, rational const & _default) const { 
    return m_params ? m_params->get_rat(k, _default) : _default; 
}

rational params_ref::get_rat(char const * k, rational const & _default) const { 
    return m_params ? m_params->get_rat(k, _default) : _default; 
}

symbol params_ref::get_sym(symbol const & k, symbol const & _default) const { 
    return m_params ? m_params->get_sym(k, _default) : _default; 
}

symbol params_ref::get_sym(char const * k, symbol const & _default) const { 
    return m_params ? m_params->get_sym(k, _default) : _default; 
}

bool params_ref::get_bool(char const * k, params_ref const & fallback, bool _default) const {
    return m_params ? m_params->get_bool(k, fallback, _default) : fallback.get_bool(k, _default);
}

unsigned params_ref::get_uint(char const * k, params_ref const & fallback, unsigned _default) const {
    return m_params ? m_params->get_uint(k, fallback, _default) : fallback.get_uint(k, _default);
}

double params_ref::get_double(char const * k, params_ref const & fallback, double _default) const {
    return m_params ? m_params->get_double(k, fallback, _default) : fallback.get_double(k, _default);
}

char const * params_ref::get_str(char const * k, params_ref const & fallback, char const * _default) const {
    return m_params ? m_params->get_str(k, fallback, _default) : fallback.get_str(k, _default);
}

symbol params_ref::get_sym(char const * k, params_ref const & fallback, symbol const & _default) const {
    return m_params ? m_params->get_sym(k, fallback, _default) : fallback.get_sym(k, _default);
}

bool params_ref::empty() const {
    if (!m_params)
        return true;
    return m_params->empty();
}

bool params_ref::contains(symbol const & k) const {
    if (!m_params)
        return false;
    return m_params->contains(k);
}

bool params_ref::contains(char const * k) const {
    if (!m_params)
        return false;
    return m_params->contains(k);
}

void params_ref::reset() {
    if (m_params)
        m_params->reset();
}

void params_ref::reset(symbol const & k) {
    if (m_params)
        m_params->reset(k);
}

void params_ref::reset(char const * k) {
    if (m_params)
        m_params->reset(k);
}

void params_ref::set_bool(symbol const & k, bool v) {
    init();
    m_params->set_bool(k, v);
}

void params_ref::set_bool(char const * k, bool  v) {
    init();
    m_params->set_bool(k, v);
}

void params_ref::set_uint(symbol const & k, unsigned v) {
    init();
    m_params->set_uint(k, v);
}

void params_ref::set_uint(char const * k, unsigned v) {
    init();
    m_params->set_uint(k, v);
}

void params_ref::set_double(symbol const & k, double v) {
    init();
    m_params->set_double(k, v);
}

void params_ref::set_double(char const * k, double v) {
    init();
    m_params->set_double(k, v);
}

void params_ref::set_str(symbol const & k, char const * v) {
    init();
    m_params->set_str(k, v);
}

void params_ref::set_str(char const * k, char const * v) {
    init();
    m_params->set_str(k, v);
}

void params_ref::set_rat(symbol const & k, rational const & v) {
    init();
    m_params->set_rat(k, v);
}

void params_ref::set_rat(char const * k, rational const & v) {
    init();
    m_params->set_rat(k, v);
}

void params_ref::set_sym(symbol const & k, symbol const & v) {
    init();
    m_params->set_sym(k, v);
}

void params_ref::set_sym(char const * k, symbol const & v) {
    init();
    m_params->set_sym(k, v);
}


void params::del_value(entry & e) {
    switch (e.second.m_kind) {
    case CPK_NUMERAL:
        if (e.second.m_kind == CPK_NUMERAL)
            dealloc(e.second.m_rat_value);
        break;
    default:
        return;
    }
}

#define TRAVERSE_ENTRIES(CODE) {                        \
    svector<entry>::iterator it  = m_entries.begin();   \
    svector<entry>::iterator end = m_entries.end();     \
    for (; it != end; ++it) {                           \
        CODE                                            \
    }                                                   \
}

#define TRAVERSE_CONST_ENTRIES(CODE) {                          \
    svector<entry>::const_iterator it  = m_entries.begin();     \
    svector<entry>::const_iterator end = m_entries.end();       \
    for (; it != end; ++it) {                                   \
        CODE                                                    \
    }                                                           \
}

void params::del_values() {
    TRAVERSE_ENTRIES(del_value(*it););
}

#define CONTAINS(k) {                                           \
    if (empty())                                                \
        return false;                                           \
    TRAVERSE_CONST_ENTRIES(if (it->first == k) return true;);   \
    return false;                                               \
}


bool params::contains(symbol const & k) const {
    CONTAINS(k);
}
 
bool params::contains(char const * k) const {
    CONTAINS(k);
}

void params::reset() {
    del_values();
    m_entries.finalize();
    SASSERT(empty());
}

#define RESET(k) {                              \
    if (empty()) return;                        \
    TRAVERSE_ENTRIES(if (it->first == k) {      \
        svector<entry>::iterator it2 = it;      \
        del_value(*it2);                        \
        ++it;                                   \
        for (; it != end; ++it, ++it2) {        \
            *it2 = *it;                         \
        }                                       \
        m_entries.pop_back();                   \
        return;                                 \
    });                                         \
}

void params::reset(symbol const & k) {
    RESET(k);
}

void params::reset(char const * k) {
    RESET(k);
}

#define GET_VALUE(MATCH_CODE, KIND) {                                           \
    if (empty()) return _default;                                                \
    TRAVERSE_CONST_ENTRIES(if (it->first == k && it->second.m_kind == KIND) {   \
        MATCH_CODE                                                              \
    });                                                                         \
    return _default;                                                             \
}
    
#define GET_SIMPLE_VALUE(FIELD_NAME, KIND) GET_VALUE(return it->second.FIELD_NAME;, KIND)

bool params::get_bool(symbol const & k, bool _default) const {
    GET_SIMPLE_VALUE(m_bool_value, CPK_BOOL);
}

bool params::get_bool(char const * k, bool _default) const {
    GET_SIMPLE_VALUE(m_bool_value, CPK_BOOL);
}

unsigned params::get_uint(symbol const & k, unsigned _default) const {
    GET_SIMPLE_VALUE(m_uint_value, CPK_UINT);
}

unsigned params::get_uint(char const * k, unsigned _default) const {
    GET_SIMPLE_VALUE(m_uint_value, CPK_UINT);
}

double params::get_double(symbol const & k, double _default) const {
    GET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE);
}

double params::get_double(char const * k, double _default) const {
    GET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE);
}

char const * params::get_str(symbol const & k, char const * _default) const {
    GET_SIMPLE_VALUE(m_str_value, CPK_STRING);
}

char const * params::get_str(char const * k, char const * _default) const {
    GET_SIMPLE_VALUE(m_str_value, CPK_STRING);
}

rational params::get_rat(symbol const & k, rational const & _default) const {
    if (empty()) return _default;                                               
    TRAVERSE_CONST_ENTRIES(if (it->first == k) {
            if (it->second.m_kind == CPK_NUMERAL) {   
                return *(it->second.m_rat_value);
            }
            if (it->second.m_kind == CPK_UINT) {
                return rational(static_cast<int>(it->second.m_uint_value));
            }
        });
    return _default;                                                            
}

rational params::get_rat(char const * k, rational const & _default) const {
    if (empty()) return _default;                                               
    TRAVERSE_CONST_ENTRIES(if (it->first == k) {
            if (it->second.m_kind == CPK_NUMERAL) {   
                return *(it->second.m_rat_value);
            }
            if (it->second.m_kind == CPK_UINT) {
                return rational(static_cast<int>(it->second.m_uint_value));
            }
        });
    return _default;                                                            
}

symbol params::get_sym(symbol const & k, symbol const & _default) const {
    GET_VALUE(return it->second.m_sym_value;, CPK_SYMBOL);
}

symbol params::get_sym(char const * k, symbol const & _default) const {
    GET_VALUE(return it->second.m_sym_value;, CPK_SYMBOL);
}

#define GET_VALUE2(MATCH_CODE, KIND) {                                  \
    if (!empty()) {                                                     \
        TRAVERSE_CONST_ENTRIES(if (it->first == k && it->second.m_kind == KIND) { \
                MATCH_CODE                                              \
        });                                                             \
    }                                                                   \
}

#define GET_SIMPLE_VALUE2(FIELD_NAME, KIND) GET_VALUE2(return it->second.FIELD_NAME;, KIND)

bool params::get_bool(char const * k, params_ref const & fallback, bool _default) const {
    GET_SIMPLE_VALUE2(m_bool_value, CPK_BOOL);
    return fallback.get_bool(k, _default);
}

unsigned params::get_uint(char const * k, params_ref const & fallback, unsigned _default) const {
    GET_SIMPLE_VALUE2(m_uint_value, CPK_UINT);
    return fallback.get_uint(k, _default);
}

double params::get_double(char const * k, params_ref const & fallback, double _default) const {
    GET_SIMPLE_VALUE2(m_double_value, CPK_DOUBLE);
    return fallback.get_double(k, _default);
}

char const * params::get_str(char const * k, params_ref const & fallback, char const * _default) const {
    GET_SIMPLE_VALUE2(m_str_value, CPK_STRING);
    return fallback.get_str(k, _default);
}

symbol params::get_sym(char const * k, params_ref const & fallback, symbol const & _default) const {
    GET_VALUE2(return it->second.m_sym_value;, CPK_SYMBOL);
    return fallback.get_sym(k, _default);
}

#define SET_VALUE(MATCH_CODE, ADD_CODE) {       \
    TRAVERSE_ENTRIES(if (it->first == k) {      \
        MATCH_CODE                              \
        return;                                 \
    });                                         \
    ADD_CODE                                    \
}

#define SET_SIMPLE_VALUE(FIELD_NAME, KIND) SET_VALUE({  \
    del_value(*it);                                     \
    it->second.m_kind = KIND;                           \
    it->second.FIELD_NAME = v;                          \
},                                                      \
{                                                       \
    entry new_entry;                                    \
    new_entry.first  = symbol(k);                       \
    new_entry.second.m_kind = KIND;                     \
    new_entry.second.FIELD_NAME = v;                    \
    m_entries.push_back(new_entry);                     \
})

// setters
void params::set_bool(symbol const & k, bool v) {
    SET_SIMPLE_VALUE(m_bool_value, CPK_BOOL);
}

void params::set_bool(char const * k, bool  v) {
    SET_SIMPLE_VALUE(m_bool_value, CPK_BOOL);
}
 
void params::set_uint(symbol const & k, unsigned v) {
    SET_SIMPLE_VALUE(m_uint_value, CPK_UINT);
}

void params::set_uint(char const * k, unsigned v) {
    SET_SIMPLE_VALUE(m_uint_value, CPK_UINT);
}

void params::set_double(symbol const & k, double v) {
    SET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE);
}

void params::set_double(char const * k, double v) {
    SET_SIMPLE_VALUE(m_double_value, CPK_DOUBLE);
}

void params::set_str(symbol const & k, char const * v) {
    SET_SIMPLE_VALUE(m_str_value, CPK_STRING);
}

void params::set_str(char const * k, char const * v) {
    SET_SIMPLE_VALUE(m_str_value, CPK_STRING);
}

#define SET_RAT_VALUE() SET_VALUE({                             \
    if (it->second.m_kind != CPK_NUMERAL) {                     \
        del_value(*it);                                         \
        it->second.m_kind = CPK_NUMERAL;                        \
        it->second.m_rat_value = alloc(rational);               \
    }                                                           \
    *(it->second.m_rat_value) = v;                              \
},                                                              \
{                                                               \
    entry new_entry;                                            \
    new_entry.first  = symbol(k);                               \
    new_entry.second.m_kind = CPK_NUMERAL;                      \
    new_entry.second.m_rat_value = alloc(rational);             \
    *(new_entry.second.m_rat_value) = v;                        \
    m_entries.push_back(new_entry);                             \
})

void params::set_rat(symbol const & k, rational const & v) {
    SET_RAT_VALUE();
}

void params::set_rat(char const * k, rational const & v) {
    SET_RAT_VALUE();
}

#define SET_SYM_VALUE() SET_VALUE({                     \
    del_value(*it);                                     \
    it->second.m_kind = CPK_SYMBOL;                     \
    it->second.m_sym_value = v.bare_str();              \
},                                                      \
{                                                       \
    entry new_entry;                                    \
    new_entry.first  = symbol(k);                       \
    new_entry.second.m_kind = CPK_SYMBOL;               \
    new_entry.second.m_sym_value = v.bare_str();        \
    m_entries.push_back(new_entry);                     \
})

void params::set_sym(symbol const & k, symbol const & v) {
    SET_SYM_VALUE();
}

void params::set_sym(char const * k, symbol const & v) {
    SET_SYM_VALUE();
}

#ifdef Z3DEBUG
#include <iostream>
void pp(params_ref const & p) {
    std::cout << p << std::endl;
}
#endif
