/*
 * Copyright 2018 Google, Inc.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * 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;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT
 * OWNER 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.
 *
 * Authors: Gabe Black
 */

#include <fstream>
#include <map>
#include <sstream>
#include <string>

#include "base/logging.hh"
#include "systemc/core/process.hh"
#include "systemc/core/scheduler.hh"
#include "systemc/ext/core/sc_main.hh"
#include "systemc/ext/utils/sc_report_handler.hh"

namespace sc_core
{

namespace
{

std::unique_ptr<std::string> logFileName;
std::unique_ptr<std::ofstream> logFile;

struct ReportCatInfo
{
    explicit ReportCatInfo(sc_actions actions) :
        actions(actions), count(0), limit(-1)
    {}
    ReportCatInfo() : ReportCatInfo(SC_UNSPECIFIED) {}

    bool
    checkLimit(sc_actions &actions)
    {
        if (limit == 0 || count < limit)
            return false;
        if (limit != -1)
            actions |= SC_STOP;
        return true;
    }

    sc_actions actions;
    int count;
    int limit;
};

const char *severityNames[] = {
    [SC_INFO] = "Info",
    [SC_WARNING] = "Warning",
    [SC_ERROR] = "Error",
    [SC_FATAL] = "Fatal"
};

ReportCatInfo catForSeverity[SC_MAX_SEVERITY] =
{
    [SC_INFO] = ReportCatInfo(SC_DEFAULT_INFO_ACTIONS),
    [SC_WARNING] = ReportCatInfo(SC_DEFAULT_WARNING_ACTIONS),
    [SC_ERROR] = ReportCatInfo(SC_DEFAULT_ERROR_ACTIONS),
    [SC_FATAL] = ReportCatInfo(SC_DEFAULT_FATAL_ACTIONS)
};

std::map<std::string, ReportCatInfo> catForMsgType;
std::map<std::pair<std::string, sc_severity>, ReportCatInfo>
    catForSeverityAndMsgType;

int verbosityLevel = SC_MEDIUM;

sc_actions suppressedActions = SC_UNSPECIFIED;
sc_actions forcedActions = SC_UNSPECIFIED;
sc_actions catchActions = SC_UNSPECIFIED;

sc_report_handler_proc reportHandlerProc = &sc_report_handler::default_handler;

sc_actions maxAction = SC_ABORT;

std::unique_ptr<sc_report> globalReportCache;

} // anonymous namespace

void
sc_report_handler::report(sc_severity severity, const char *msg_type,
                          const char *msg, const char *file, int line)
{
    report(severity, msg_type, msg, SC_MEDIUM, file, line);
}

void
sc_report_handler::report(sc_severity severity, const char *msg_type,
                          const char *msg, int verbosity, const char *file,
                          int line)
{
    if (severity == SC_INFO && verbosity > verbosityLevel)
        return;

    ReportCatInfo &sevInfo = catForSeverity[severity];
    ReportCatInfo &msgInfo = catForMsgType[msg_type];
    ReportCatInfo &sevMsgInfo = catForSeverityAndMsgType[
        std::make_pair(std::string(msg_type), severity)];

    sevInfo.count++;
    msgInfo.count++;
    sevMsgInfo.count++;

    sc_actions actions = SC_UNSPECIFIED;
    if (sevMsgInfo.actions != SC_UNSPECIFIED)
        actions = sevMsgInfo.actions;
    else if (msgInfo.actions != SC_UNSPECIFIED)
        actions = msgInfo.actions;
    else if (sevInfo.actions != SC_UNSPECIFIED)
        actions = sevInfo.actions;

    actions &= ~suppressedActions;
    actions |= forcedActions;

    if (sevMsgInfo.checkLimit(actions) && msgInfo.checkLimit(actions))
        sevInfo.checkLimit(actions);

    ::sc_gem5::Process *current = ::sc_gem5::scheduler.current();
    sc_report report(severity, msg_type, msg, verbosity, file, line,
            sc_time::from_value(::sc_gem5::scheduler.getCurTick()),
            current ? current->name() : nullptr, -1);

    if (actions & SC_CACHE_REPORT) {
        if (current) {
            current->lastReport(&report);
        } else {
            globalReportCache =
                std::unique_ptr<sc_report>(new sc_report(report));
        }
    }

    reportHandlerProc(report, actions);
}

void
sc_report_handler::report(sc_severity, int id, const char *msg,
                          const char *file, int line)
{
    warn("%s:%d %s\n", file, line, msg);
    warn("%s not implemented.\n", __PRETTY_FUNCTION__);
}

sc_actions
sc_report_handler::set_actions(sc_severity severity, sc_actions actions)
{
    ReportCatInfo &info = catForSeverity[severity];
    sc_actions previous = info.actions;
    info.actions = actions;
    return previous;
}

sc_actions
sc_report_handler::set_actions(const char *msg_type, sc_actions actions)
{
    ReportCatInfo &info = catForMsgType[msg_type];
    sc_actions previous = info.actions;
    info.actions = actions;
    return previous;
}

sc_actions
sc_report_handler::set_actions(
        const char *msg_type, sc_severity severity, sc_actions actions)
{
    ReportCatInfo &info = catForSeverityAndMsgType[
        std::make_pair(std::string(msg_type), severity)];
    sc_actions previous = info.actions;
    info.actions = actions;
    return previous;
}

int
sc_report_handler::stop_after(sc_severity severity, int limit)
{
    ReportCatInfo &info = catForSeverity[severity];
    int previous = info.limit;
    info.limit = limit;
    return previous;
}

int
sc_report_handler::stop_after(const char *msg_type, int limit)
{
    ReportCatInfo &info = catForMsgType[msg_type];
    int previous = info.limit;
    info.limit = limit;
    return previous;
}

int
sc_report_handler::stop_after(
        const char *msg_type, sc_severity severity, int limit)
{
    ReportCatInfo &info = catForSeverityAndMsgType[
        std::make_pair(std::string(msg_type), severity)];
    int previous = info.limit;
    info.limit = limit;
    return previous;
}

int
sc_report_handler::get_count(sc_severity severity)
{
    return catForSeverity[severity].count;
}

int
sc_report_handler::get_count(const char *msg_type)
{
    return catForMsgType[msg_type].count;
}

int
sc_report_handler::get_count(const char *msg_type, sc_severity severity)
{
    return catForSeverityAndMsgType[
        std::make_pair(std::string(msg_type), severity)].count;
}

int
sc_report_handler::set_verbosity_level(int vl)
{
    int previous = verbosityLevel;
    verbosityLevel = vl;
    return previous;
}

int
sc_report_handler::get_verbosity_level()
{
    return verbosityLevel;
}


sc_actions
sc_report_handler::suppress(sc_actions actions)
{
    sc_actions previous = suppressedActions;
    suppressedActions = actions;
    return previous;
}

sc_actions
sc_report_handler::suppress()
{
    return suppress(SC_UNSPECIFIED);
}

sc_actions
sc_report_handler::force(sc_actions actions)
{
    sc_actions previous = forcedActions;
    forcedActions = actions;
    return previous;
}

sc_actions
sc_report_handler::force()
{
    return force(SC_UNSPECIFIED);
}


sc_actions
sc_report_handler::set_catch_actions(sc_actions actions)
{
    sc_actions previous = catchActions;
    catchActions = actions;
    return previous;
}

sc_actions
sc_report_handler::get_catch_actions()
{
    return catchActions;
}


void
sc_report_handler::set_handler(sc_report_handler_proc proc)
{
    reportHandlerProc = proc;
}

void
sc_report_handler::default_handler(
        const sc_report &report, const sc_actions &actions)
{
    if (actions & SC_DISPLAY)
        cprintf("\n%s\n", sc_report_compose_message(report));

    if ((actions & SC_LOG) && logFile) {
        ccprintf(*logFile, "%s: %s\n", report.get_time().to_string(),
                 sc_report_compose_message(report));
    }
    if (actions & SC_STOP) {
        sc_stop_here(report.get_msg_type(), report.get_severity());
        sc_stop();
    }
    if (actions & SC_INTERRUPT)
        sc_interrupt_here(report.get_msg_type(), report.get_severity());
    if (actions & SC_ABORT)
        sc_abort();
    if (actions & SC_THROW) {
        ::sc_gem5::Process *current = ::sc_gem5::scheduler.current();
        if (current)
            current->isUnwinding(false);
        throw report;
    }
}

sc_actions
sc_report_handler::get_new_action_id()
{
    maxAction = maxAction << 1;
    return maxAction;
}

sc_report *
sc_report_handler::get_cached_report()
{
    ::sc_gem5::Process *current = ::sc_gem5::scheduler.current();
    if (current)
        return current->lastReport();
    return globalReportCache.get();
}

void
sc_report_handler::clear_cached_report()
{
    ::sc_gem5::Process *current = ::sc_gem5::scheduler.current();
    if (current) {
        current->lastReport(nullptr);
    } else {
        globalReportCache = nullptr;
    }
}

bool
sc_report_handler::set_log_file_name(const char *new_name)
{
    if (!new_name) {
        logFile = nullptr;
        logFileName = nullptr;
        return false;
    } else {
        if (logFileName)
            return false;
        logFileName = std::unique_ptr<std::string>(new std::string(new_name));
        logFile = std::unique_ptr<std::ofstream>(new std::ofstream(new_name));
        return true;
    }
}

const char *
sc_report_handler::get_log_file_name()
{
    if (!logFileName)
        return nullptr;
    else
        return logFileName->c_str();
}

void
sc_interrupt_here(const char *msg_type, sc_severity)
{
    // Purposefully empty, for setting breakpoints supposedly.
}

void
sc_stop_here(const char *msg_type, sc_severity)
{
    // Purposefully empty, for setting breakpoints supposedly.
}

const std::string
sc_report_compose_message(const sc_report &report)
{
    std::ostringstream str;

    const char *sevName = severityNames[report.get_severity()];
    int id = report.get_id();

    str << sevName << ": ";
    if (id >= 0) {
        ccprintf(str, "(%c%d) ", sevName[0], id);
    }
    str << report.get_msg_type();

    const char *msg = report.get_msg();
    if (msg && msg[0])
        str << ": " << msg;

    if (report.get_severity() > SC_INFO) {
        ccprintf(str, "\nIn file: %s:%d", report.get_file_name(),
                 report.get_line_number());

        ::sc_gem5::Process *current = ::sc_gem5::scheduler.current();
        const char *name = report.get_process_name();
        if (current && sc_is_running() && name) {
            ccprintf(str, "\nIn process: %s @ %s", name,
                    report.get_time().to_string());
        }
    }

    return str.str();
}

bool
sc_report_close_default_log()
{
    if (logFile) {
        logFile = nullptr;
        logFileName = nullptr;
        return false;
    }
    return true;
}

} // namespace sc_core