#include "Utils.hpp"

#include <locale>
#include <ctime>

#ifdef WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif

#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>

#include <boost/locale.hpp>

#include <boost/algorithm/string/predicate.hpp>
#include <boost/date_time/local_time/local_time.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/fstream.hpp>
#include <boost/nowide/integration/filesystem.hpp>
#include <boost/nowide/convert.hpp>

namespace Slic3r {

static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;

void set_logging_level(unsigned int level)
{
    switch (level) {
    // Report fatal errors only.
    case 0: logSeverity = boost::log::trivial::fatal; break;
    // Report fatal errors and errors.
    case 1: logSeverity = boost::log::trivial::error; break;
    // Report fatal errors, errors and warnings.
    case 2: logSeverity = boost::log::trivial::warning; break;
    // Report all errors, warnings and infos.
    case 3: logSeverity = boost::log::trivial::info; break;
    // Report all errors, warnings, infos and debugging.
    case 4: logSeverity = boost::log::trivial::debug; break;
    // Report everyting including fine level tracing information.
    default: logSeverity = boost::log::trivial::trace; break;
    }

    boost::log::core::get()->set_filter
    (
        boost::log::trivial::severity >= logSeverity
    );
}

// Force set_logging_level(<=error) after loading of the DLL.
// Switch boost::filesystem to utf8.
static struct RunOnInit {
    RunOnInit() { 
        boost::nowide::nowide_filesystem();
        set_logging_level(1);
    }
} g_RunOnInit;

void trace(unsigned int level, const char *message)
{
    boost::log::trivial::severity_level severity = boost::log::trivial::trace;
    switch (level) {
    // Report fatal errors only.
    case 0: severity = boost::log::trivial::fatal; break;
    // Report fatal errors and errors.
    case 1: severity = boost::log::trivial::error; break;
    // Report fatal errors, errors and warnings.
    case 2: severity = boost::log::trivial::warning; break;
    // Report all errors, warnings and infos.
    case 3: severity = boost::log::trivial::info; break;
    // Report all errors, warnings, infos and debugging.
    case 4: severity = boost::log::trivial::debug; break;
    // Report everyting including fine level tracing information.
    default: severity = boost::log::trivial::trace; break;
    }

    BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\
        (::boost::log::keywords::severity = severity)) << message;
}

static std::string g_var_dir;

void set_var_dir(const std::string &dir)
{
    g_var_dir = dir;
}

const std::string& var_dir()
{
    return g_var_dir;
}

std::string var(const std::string &file_name)
{
    auto file = (boost::filesystem::path(g_var_dir) / file_name).make_preferred();
    return file.string();
}

static std::string g_resources_dir;

void set_resources_dir(const std::string &dir)
{
    g_resources_dir = dir;
}

const std::string& resources_dir()
{
    return g_resources_dir;
}

static std::string g_local_dir;

void set_local_dir(const std::string &dir)
{
    g_local_dir = dir;
}

const std::string& localization_dir()
{
	return g_local_dir;
}

static std::string g_data_dir;

void set_data_dir(const std::string &dir)
{
    g_data_dir = dir;
}

const std::string& data_dir()
{
    return g_data_dir;
}

} // namespace Slic3r

#include <xsinit.h>

void
confess_at(const char *file, int line, const char *func,
            const char *pat, ...)
{
    #ifdef SLIC3RXS
     va_list args;
     SV *error_sv = newSVpvf("Error in function %s at %s:%d: ", func,
         file, line);

     va_start(args, pat);
     sv_vcatpvf(error_sv, pat, &args);
     va_end(args);

     sv_catpvn(error_sv, "\n\t", 2);

     dSP;
     ENTER;
     SAVETMPS;
     PUSHMARK(SP);
     XPUSHs( sv_2mortal(error_sv) );
     PUTBACK;
     call_pv("Carp::confess", G_DISCARD);
     FREETMPS;
     LEAVE;
    #endif
}

void PerlCallback::register_callback(void *sv)
{ 
    if (! SvROK((SV*)sv) || SvTYPE(SvRV((SV*)sv)) != SVt_PVCV)
        croak("Not a Callback %_ for PerlFunction", (SV*)sv);
    if (m_callback)
        SvSetSV((SV*)m_callback, (SV*)sv);
    else
        m_callback = newSVsv((SV*)sv);
}

void PerlCallback::deregister_callback()
{
	if (m_callback) {
		sv_2mortal((SV*)m_callback);
		m_callback = nullptr;
	}
}

void PerlCallback::call() const
{
    if (! m_callback)
        return;
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    PUTBACK; 
    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
    FREETMPS;
    LEAVE;
}

void PerlCallback::call(int i) const
{
    if (! m_callback)
        return;
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSViv(i)));
    PUTBACK; 
    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
    FREETMPS;
    LEAVE;
}

void PerlCallback::call(int i, int j) const
{
    if (! m_callback)
        return;
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSViv(i)));
    XPUSHs(sv_2mortal(newSViv(j)));
    PUTBACK; 
    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
    FREETMPS;
    LEAVE;
}

void PerlCallback::call(const std::vector<int>& ints) const
{
    if (! m_callback)
        return;
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    for (int i : ints)
    {
        XPUSHs(sv_2mortal(newSViv(i)));
    }
    PUTBACK;
    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
    FREETMPS;
    LEAVE;
}

void PerlCallback::call(double x, double y) const
{
    if (!m_callback)
        return;
    dSP;
    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(sv_2mortal(newSVnv(x)));
    XPUSHs(sv_2mortal(newSVnv(y)));
    PUTBACK;
    perl_call_sv(SvRV((SV*)m_callback), G_DISCARD);
    FREETMPS;
    LEAVE;
}

void PerlCallback::call(bool b) const
{
    call(b ? 1 : 0);
}

#ifdef WIN32
    #ifndef NOMINMAX
    # define NOMINMAX
    #endif
    #include <windows.h>
#endif /* WIN32 */

namespace Slic3r {

// Encode an UTF-8 string to the local code page.
std::string encode_path(const char *src)
{    
#ifdef WIN32
    // Convert the source utf8 encoded string to a wide string.
    std::wstring wstr_src = boost::nowide::widen(src);
    if (wstr_src.length() == 0)
        return std::string();
    // Convert a wide string to a local code page.
    int size_needed = ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), nullptr, 0, nullptr, nullptr);
    std::string str_dst(size_needed, 0);
    ::WideCharToMultiByte(0, 0, wstr_src.data(), (int)wstr_src.size(), const_cast<char*>(str_dst.data()), size_needed, nullptr, nullptr);
    return str_dst;
#else /* WIN32 */
    return src;
#endif /* WIN32 */
}

// Encode an 8-bit string from a local code page to UTF-8.
std::string decode_path(const char *src)
{  
#ifdef WIN32
    int len = int(strlen(src));
    if (len == 0)
        return std::string();
    // Convert the string encoded using the local code page to a wide string.
    int size_needed = ::MultiByteToWideChar(0, 0, src, len, nullptr, 0);
    std::wstring wstr_dst(size_needed, 0);
    ::MultiByteToWideChar(0, 0, src, len, const_cast<wchar_t*>(wstr_dst.data()), size_needed);
    // Convert a wide string to utf8.
    return boost::nowide::narrow(wstr_dst.c_str());
#else /* WIN32 */
    return src;
#endif /* WIN32 */
}

std::string normalize_utf8_nfc(const char *src)
{
    static std::locale locale_utf8(boost::locale::generator().generate(""));
    return boost::locale::normalize(src, boost::locale::norm_nfc, locale_utf8);
}

namespace PerlUtils {
    // Get a file name including the extension.
    std::string path_to_filename(const char *src)       { return boost::filesystem::path(src).filename().string(); }
    // Get a file name without the extension.
    std::string path_to_stem(const char *src)           { return boost::filesystem::path(src).stem().string(); }
    // Get just the extension.
    std::string path_to_extension(const char *src)      { return boost::filesystem::path(src).extension().string(); }
    // Get a directory without the trailing slash.
    std::string path_to_parent_path(const char *src)    { return boost::filesystem::path(src).parent_path().string(); }
};

std::string timestamp_str()
{
    const auto now = boost::posix_time::second_clock::local_time();
    char buf[2048];
    sprintf(buf, "on %04d-%02d-%02d at %02d:%02d:%02d",
        // Local date in an ANSII format.
        int(now.date().year()), int(now.date().month()), int(now.date().day()),
        int(now.time_of_day().hours()), int(now.time_of_day().minutes()), int(now.time_of_day().seconds()));
    return buf;
}

unsigned get_current_pid()
{
#ifdef WIN32
    return GetCurrentProcessId();
#else
    return ::getpid();
#endif
}

}; // namespace Slic3r