/**
 *
 * @file src/common/Errors.cpp
 *
 * @copyright 2008-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @author Camille Ordronneau
 * @author Samuel Thibault
 * @author Johnny Jazeix
 * @author Mathieu Faverge
 * @author Olivier Lagrasse
 * @author Augustin Degomme
 * @author Clement Vuchener
 *
 * @date 2024-07-17
 */

#include <fstream>
#include <iostream>
#include <string>
#include <sstream>
#include <queue>
/* -- */
#include "common.hpp"
/* -- */
#include "core/Core.hpp"
/* -- */
#include "common/Message.hpp"
#include "common/Errors.hpp"
/* -- */
#ifdef WIN32
#define snprintf sprintf_s
#endif

using namespace std;

queue<string> Error::_errors;
queue<string> Error::_warnings;

const int Error::VITE_ERRCODE_EVERYTHING = 0;
const int Error::VITE_ERRCODE_WARNING = 1;
const int Error::VITE_ERRCODE_ERROR = 2;

string Error::_content = "";

const string Error::VITE_ERR_PARSE = "expected \" before end of file";
const string Error::VITE_ERR_MMAP = "mmap error";
const string Error::VITE_ERR_EMPTY_FILE = "empty file";
const string Error::VITE_ERR_FSTAT = "status file error";
const string Error::VITE_ERR_OPEN = "open file error";
const string Error::VITE_ERR_MUNMAP = "munmap error";
const string Error::VITE_ERR_EXPECT_END_DEF = "expected %EndEventDef";
const string Error::VITE_ERR_EXPECT_EVENT_DEF = "expected %EventDef";
const string Error::VITE_ERR_EXPECT_NAME_DEF = "the definition is not named";
const string Error::VITE_ERR_EXPECT_ID_DEF = "the definition is not identified";
const string Error::VITE_ERR_UNKNOWN_ID_DEF = "there is no definition with the identity: ";
const string Error::VITE_ERR_EXTRA_TOKEN = "extra token(s) ignored";
const string Error::VITE_ERR_UNKNOWN_EVENT_DEF = "the following event doesn't match with any event known: ";
const string Error::VITE_ERR_FIELD_TYPE_MISSING = "a field type is missing ";
const string Error::VITE_ERR_FIELD_TYPE_UNKNOWN = "the following field type is unknown: ";
const string Error::VITE_ERR_EMPTY_DEF = "a definition line is empty";
const string Error::VITE_ERR_INCOMPATIBLE_VALUE_IN_EVENT = "incompatible value: ";
const string Error::VITE_ERR_BAD_FILE_EXTENSION = "the extension of the file is not .trace";
const string Error::VITE_ERR_LINE_TOO_SHORT_EVENT = "missing field value(s) in an event";

const string Error::VITE_ERR_UNKNOWN_CONTAINER_TYPE = "Unknown container type: ";
const string Error::VITE_ERR_UNKNOWN_CONTAINER = "Unknown container: ";
const string Error::VITE_ERR_UNKNOWN_EVENT_TYPE = "Unknown event type: ";
const string Error::VITE_ERR_UNKNOWN_STATE_TYPE = "Unknown state type: ";
const string Error::VITE_ERR_UNKNOWN_VARIABLE_TYPE = "Unknown variable type: ";
const string Error::VITE_ERR_UNKNOWN_LINK_TYPE = "Unknown link type: ";
const string Error::VITE_ERR_UNKNOWN_ENTITY_TYPE = "Unknown entity type: ";
const string Error::VITE_ERR_UNKNOWN_ENTITY_VALUE = "Unknown entity value: ";

const string Error::VITE_ERR_EVENT_ALREADY_DEF = "Event trid is already defined";
const string Error::VITE_ERR_FIELD_NAME_MISSING = "Field's name is missing";
const string Error::VITE_ERR_FIELD_NOT_ALLOWED = "This type is not allowed for this field";
const string Error::VITE_ERR_EVENT_NOT_CORRECT = "The definition of this event is not correct : ";

void Error::set(const string &kind_of_error, const int priority) {
    Error::_content = kind_of_error;

    switch (priority) {
    case VITE_ERRCODE_WARNING:
        Error::_warnings.push(Error::_content);
        break;
    default: // Include the _ERROR
        Error::_errors.push(Error::_content);
        break;
    }
}

void Error::set(const string &kind_of_error, const unsigned int line_number, const int priority) {
    char line[sizeof(line_number) * 3 + 3 + 1];
    snprintf(line, sizeof(line), "%d : ", line_number);
    set(line + kind_of_error, priority);
}

void Error::set_and_print(const string &kind_of_error, const int priority) {
    set(kind_of_error, priority);
    print(priority);
}

void Error::set_and_print(const string &kind_of_error, const unsigned int line_number, const int priority) {
    char line[sizeof(line_number) * 3 + 1];
    snprintf(line, sizeof(line), "%d", line_number);
    set(kind_of_error + " on line " + line, priority);
    print(priority);
}

bool Error::set_if(bool condition, const string &kind_of_error, const unsigned int line_number, const int priority) {
    if (condition) {
        char line[sizeof(line_number) * 3 + 1];
        snprintf(line, sizeof(line), "%d", line_number);
        set(kind_of_error + " on line " + line, priority);
        return true;
    }
    return false;
}

void Error::print(const int priority) {
    *Message::get_instance() << _content;
    switch (priority) {
    case VITE_ERRCODE_WARNING:
        *Message::get_instance() << Message::endw;
        break;
    default: // Include the _ERROR
        *Message::get_instance() << Message::ende;
        break;
    }
}

void Error::print(const string &content, const int priority) {
    *Message::get_instance() << content;
    switch (priority) {
    case VITE_ERRCODE_WARNING:
        *Message::get_instance() << Message::endw;
        break;
    default: // Include the _ERROR
        *Message::get_instance() << Message::ende;
        break;
    }
}

void Error::print_numbers() {
    *Message::get_instance() << Error::_errors.size() << " errors and " << Error::_warnings.size() << " warnings were found during parsing.";
    if (Error::_warnings.size() == 0 && Error::_errors.size() == 0) {
        *Message::get_instance() << Message::endi;
    }
    else if (Error::_errors.size() == 0) {
        *Message::get_instance() << Message::endw;
    }
    else {
        *Message::get_instance() << Message::ende;
    }
}

void Error::flush(const std::string &log_file_name, const std::string &trace_file_name) {

    if (_errors.empty() && _warnings.empty()) {
        return;
    }
    else {

        print("Errors and warnings can be found in " + log_file_name, VITE_ERRCODE_ERROR);

        ofstream outfile(log_file_name.c_str(), ios::out | ios::trunc);

        const int number_of_errors = Error::_errors.size();
        const int number_of_warnings = Error::_warnings.size();

        if (!outfile.is_open()) {
            cerr << "unable to open " << log_file_name << " to print the errors encountered in the file opening" << endl;
            return;
        }
        else {
            outfile << "File " << trace_file_name << endl
                    << endl;

            if (!_errors.empty()) {
                outfile << "Errors :" << endl;
            }

            // Print the errors
            while (!_errors.empty()) {
                outfile << _errors.front() << endl;
                // print(_errors.front(), _ERROR);
                _errors.pop();
            }

            // Print the warnings
            if (!_warnings.empty()) {
                outfile << endl
                        << "Warnings :" << endl;
            }
            while (!_warnings.empty()) {
                outfile << _warnings.front() << endl;
                // print(_warnings.front(), _WARNING);
                _warnings.pop();
            }

            outfile << endl
                    << "Your trace has " << number_of_errors << " errors and " << number_of_warnings << " warnings." << endl;

            outfile.close();
        }
    }
}
