#include <utility>

#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,
        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, std::vector<string> arguments) {
    Log(DiagnosticSeverity::Error, code, start, length, std::move(arguments));
}

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

void DiagnosticsHolder::LogInfo(DiagnosticCode code, unsigned int start, unsigned int length, std::vector<string> arguments) {
    Log(DiagnosticSeverity::Info, code, start, length, std::move(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 topLimit = _lineStarts.size() - 1;
    size_t bottomLimit = 0;
    while (true){
        if (bottomLimit == topLimit){
            return bottomLimit;
        }
        size_t half = (topLimit - bottomLimit) / 2;
        size_t pos = _lineStarts[half];
        size_t length = _lineLength[half];
        if (pos < i && pos + length > i){
            return half;
        }
        if (pos > i){
            bottomLimit = half;
        } else if (pos < i){
            topLimit = half;
        } else{
            return half;
        }
    }
}

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

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());
    }
}

const string PrettyDiagnostic ( const char * format, vector<string> arguments)
{
    string result = string(format);
    for (int 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) {
    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());
    }
    return stream.str();
}

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();
    }
}