#include <memory>    // For std::unique_ptr
#include "DiagnosticsHolder.hpp"
#include "ErrorMessages-EN-US.cpp"

using namespace Porygon::Diagnostics;
vector<Diagnostic> DiagnosticsHolder::GetDiagnostics() {
    return _diagnostics;
}

void DiagnosticsHolder::Log(DiagnosticSeverity severity, DiagnosticCode code, unsigned int start, unsigned int length,
        const std::vector<string>& arguments) {
    _diagnostics.emplace_back(severity, code, start, length, arguments);
    if (severity >= DiagnosticSeverity::Error){
        _hasErrors = true;
    }
}

void DiagnosticsHolder::LogError(DiagnosticCode code, unsigned int start, unsigned int length, const std::vector<string>& arguments) {
    Log(DiagnosticSeverity::Error, code, start, length, arguments);
}

void DiagnosticsHolder::LogWarning(DiagnosticCode code, unsigned int start, unsigned int length, const std::vector<string>& arguments) {
    Log(DiagnosticSeverity::Warning, code, start, length, arguments);
}

void DiagnosticsHolder::LogInfo(DiagnosticCode code, unsigned int start, unsigned int length, const std::vector<string>& arguments) {
    Log(DiagnosticSeverity::Info, code, start, length, arguments);
}

bool DiagnosticsHolder::HasErrors() {
    return _hasErrors;
}

int DiagnosticsHolder::DiagnosticsCount() {
    return _diagnostics.size();
}

Diagnostic *DiagnosticsHolder::GetDiagnosticAt(int position) {
    return &_diagnostics[position];
}

size_t DiagnosticsHolder::GetLineFromPosition(size_t i) {
    size_t bottomLimit = 0;
    size_t topLimit = _lineStarts.size() - 1;
    while (bottomLimit <= topLimit){
        size_t index = bottomLimit + (topLimit - bottomLimit) / 2;
        auto pos = _lineStarts[index];
        if (pos == i) return index;
        if (pos > i){
            topLimit = index - 1;
        } else{
            bottomLimit = index + 1;
        }
    }
    return bottomLimit - 1;
}

std::string SeverityToString(DiagnosticSeverity s){
    switch (s){
        case DiagnosticSeverity::Info: return "Info";
        case DiagnosticSeverity::Warning: return "Warning";
        case DiagnosticSeverity::Error: return "Error";
        default: throw;
    }
}

void findAndReplaceAll(std::string & data, const std::string& toSearch, const std::string& replaceStr)
{
    // Get the first occurrence
    size_t pos = data.find(toSearch);
    // Repeat till end is reached
    while( pos != std::string::npos)
    {
        // Replace this occurrence of Sub String
        data.replace(pos, toSearch.size(), replaceStr);
        // Get the next occurrence from the current position
        pos =data.find(toSearch, pos + replaceStr.size());
    }
}

string PrettyDiagnostic ( const char * format, vector<string> arguments)
{
    string result = string(format);
    for (size_t i = 0; i < arguments.size(); i++){
        auto keyToReplace = "{" + to_string(i) + "}";
        auto argument = arguments[i];
        findAndReplaceAll(result, keyToReplace, argument);
    }
    return result;
}

std::string* DiagnosticsHolder::GetFullDiagnostic(Diagnostic* diagnostic) {
    auto previousMsg = diagnostic->GetMessage();
    if (previousMsg != nullptr){
        return previousMsg;
    }
    stringstream stream;
    stream << "[" << SeverityToString(diagnostic->GetSeverity()) << "] ";
    auto startPos = diagnostic->GetStartPosition();
    auto line = this -> GetLineFromPosition(startPos);
    auto linePos = startPos - this ->GetStartPositionForLine(line);
    stream << " (" << line + 1 << ", " << linePos + 1 << ") ";
    auto unformatted = ErrorMessages.find(diagnostic->GetCode());
    if (unformatted != ErrorMessages.end()){
        stream << PrettyDiagnostic(unformatted->second, diagnostic->GetArguments());
    }
    auto s = new string(stream.str());
    diagnostic->SetMessage(s);
    return s;
}

extern "C" {
    int GetDiagnosticsCount (DiagnosticsHolder* diagnostics){
        return diagnostics->DiagnosticsCount();
    }

    Diagnostic* GetDiagnosticAt(DiagnosticsHolder* diagnostics, int position){
        return diagnostics->GetDiagnosticAt(position);
    }

    const char * GetFullDiagnostic(DiagnosticsHolder* diagnostics, Diagnostic* diag){
        return diagnostics ->GetFullDiagnostic(diag)->c_str();
    }
}
