#include "fesa/app/fesa_application.hpp"

#include "fesa/analysis/linear_static_analysis.hpp"
#include "fesa/assembly/parallel_for.hpp"
#include "fesa/core/diagnostic.hpp"
#include "fesa/io/hdf5/hdf5_results_writer.hpp"
#include "fesa/solvers/linear/mkl_pardiso_solver.hpp"

#include <filesystem>
#include <iostream>
#include <string>
#include <vector>

namespace fesa {
namespace {

constexpr int kSuccessExitCode = 0;
constexpr int kUsageExitCode = 2;
constexpr int kInputExitCode = 3;
constexpr int kModelExitCode = 4;
constexpr int kSolverExitCode = 5;
constexpr int kOutputExitCode = 6;

bool startsWithOption(const std::string& argument) {
    return !argument.empty() && argument.front() == '-';
}

Diagnostic usageDiagnostic() {
    return {
        Severity::error,
        "cli-usage",
        {{}, 0U},
        "",
        "",
        "Usage: fesa.exe <model.inp> [--output <results.h5>]."};
}

const char* severityName(const Severity severity) {
    return severity == Severity::warning ? "warning" : "error";
}

void writeDiagnostics(std::vector<Diagnostic> diagnostics) {
    sortDiagnostics(diagnostics);
    for (const auto& diagnostic : diagnostics) {
        // Stable field labels and tab separators keep empty source fields
        // explicit without depending on locale-specific formatting.
        std::cerr
            << "severity=" << severityName(diagnostic.severity)
            << '\t' << "code=" << diagnostic.code
            << '\t' << "file="
            << diagnostic.location.file.generic_u8string()
            << '\t' << "line=" << diagnostic.location.line
            << '\t' << "keyword=" << diagnostic.keyword
            << '\t' << "entity_identity=" << diagnostic.entityIdentity
            << '\t' << "message=" << diagnostic.message
            << '\n';
    }
}

int exitCodeFor(const Status& status) {
    switch (status.failureCategory().value_or(FailureCategory::input)) {
    case FailureCategory::input:
        return kInputExitCode;
    case FailureCategory::model:
        return kModelExitCode;
    case FailureCategory::solver:
        return kSolverExitCode;
    case FailureCategory::output:
        return kOutputExitCode;
    }
    return kInputExitCode;
}

} // namespace

int FesaApplication::run(const std::vector<std::string>& arguments) {
    const bool defaultOutputForm =
        arguments.size() == 1U &&
        !arguments[0U].empty() &&
        !startsWithOption(arguments[0U]);
    const bool explicitOutputForm =
        arguments.size() == 3U &&
        !arguments[0U].empty() &&
        !startsWithOption(arguments[0U]) &&
        arguments[1U] == "--output" &&
        !arguments[2U].empty() &&
        !startsWithOption(arguments[2U]);
    if (!defaultOutputForm && !explicitOutputForm) {
        writeDiagnostics({usageDiagnostic()});
        return kUsageExitCode;
    }

    AnalysisRequest request;
    request.inputPath = arguments[0U];
    request.outputPath = explicitOutputForm
        ? std::filesystem::path{arguments[2U]}
        : std::filesystem::current_path() / "results.h5";

    TbbParallelFor parallelFor;
    MklPardisoSolver linearSolver;
    Hdf5ResultsWriter resultsWriter;
    LinearStaticAnalysis analysis{
        parallelFor, linearSolver, resultsWriter};
    const Status status = analysis.run(request);
    if (status.isOk()) {
        return kSuccessExitCode;
    }

    writeDiagnostics(status.diagnostics());
    return exitCodeFor(status);
}

} // namespace fesa
