/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright 2012-2016 Danny Robson <danny@nerdcruft.net>
 */

#include "level.hpp"

#include "../parse/enum.hpp"

#include <iostream>

#include <cstring>


///////////////////////////////////////////////////////////////////////////////
/// convert a string representation of a log-level into an enumeration value.
///
/// conversion is case insensitive
/// throws std::range_error if unable to convert
cruft::log::level_t
cruft::log::to_level (std::string_view name)
{
    if (std::empty (name))
        return cruft::log::EMERGENCY;

    std::string upper (name.size (), char{});
    std::transform (
        name.cbegin (),
        name.cend   (),
        upper.begin (),
        ::toupper
    );

    #define ITEM(NAME)                  \
    if (!strcmp (#NAME, upper.data ())) \
        return NAME;
    MAP_LEVEL_T(ITEM)
    #undef ITEM

    throw std::invalid_argument (std::string (name));
}


///////////////////////////////////////////////////////////////////////////////
const std::string&
cruft::log::to_string (level_t l)
{
    switch (l) {
    #define CASE(L)                             \
        case cruft::log::L: {                   \
            static const std::string STR = #L;  \
            return STR;                         \
        }
    MAP_LEVEL_T(CASE)
    #undef CASE
    }

    unreachable ();
}


//-----------------------------------------------------------------------------
std::ostream&
cruft::log::operator<< (std::ostream& os, level_t l)
{
    return os << to_string (l);
}


///////////////////////////////////////////////////////////////////////////////
// Determine what the value for LOG_LEVEL should be at the beginning of
// execution given the system environment.
//
// Note that the LOG macros _cannot_ be used from within this function as it
// will likely result in infinite recursion.
static cruft::log::level_t
initial_log_level (void)
{
    const char *env = getenv ("LOG_LEVEL");
    if (!env)
        return cruft::log::DEFAULT_LOG_LEVEL;

    try {
        return cruft::log::to_level (env);
    } catch (...) {
        std::clog << "Invalid environment LOG_LEVEL: '" << env << "'\n";
        return cruft::log::DEFAULT_LOG_LEVEL;
    }
}


//-----------------------------------------------------------------------------
// We shouldn't ever actually get to use the default value, but we set it to
// the most verbose option just in case we've made a mistake elsewhere.

static bool                s_log_level_done;
static cruft::log::level_t s_log_level_value;

//-----------------------------------------------------------------------------
cruft::log::level_t
cruft::log::log_level (level_t _level)
{
    s_log_level_value = _level;
    s_log_level_done  = true;
    return s_log_level_value;
}


//-----------------------------------------------------------------------------
cruft::log::level_t
cruft::log::log_level (void)
{
    if (!s_log_level_done) {
        s_log_level_value = initial_log_level ();
        s_log_level_done  = true;
    }

    return s_log_level_value;
}


///////////////////////////////////////////////////////////////////////////////
std::size_t
cruft::log::level_width (void)
{
    return cruft::max (
        #define ITEM(NAME) strlen(#NAME),
        MAP_LEVEL_T(ITEM)
        #undef ITEM

        0u
    );
}


///////////////////////////////////////////////////////////////////////////////
cruft::parse::enumeration::cookie
cruft::log::setup_level_reflection (void)
{
#define ITEM(NAME) { #NAME, NAME },
    return parse::enumeration::setup (
        std::map<std::string_view, level_t> {
            MAP_LEVEL_T(ITEM)
        }
    );
#undef ITEM
}