/*
ZPR: Zadanie dodatkowe
Autor: Michał Szwejk
Kompilacja: g++ -std=c++23 -g -Wall -Wextra -Wpedantic -Werror main.cpp -lstdc++exp -o main

Uwaga: '-g' konieczne, by ramki miały nazwy funkcji, plik i numer linii,
'-lstdc++exp' linkuje implementację 'std::stacktrace' w libstdc++ (C++23).

Przedstawiony poniżej kod prezentuje przykładowe zastosowanie mechanizmu 'std::stacktrace' wprowadzonego w C++23.
Aplikacja definiuje własny wyjątek 'StacktraceException', który wykorzystując nadpisaną metodę 'what' zwraca
nie tylko wiadomość powiązaną z danym błędem, ale także stos wywołania prowadzący do wyjątku. Informacja dotycząca
stosu wywołań jest szczególnie istotna podczas diagnozy niedziałającego kodu. Stos wywołania pomaga 
programiście szybciej zidentyfikować przyczynę błędu.

Dodatkowo dodano klasę 'CrashReporter', która odpowiada za tworzenie raportu o przechwyconym wyjątku 
i zapis do zadanego pliku. Taki mechanizm - oczywiście w rozwiniętej formie - mógłby być przydatny
w środowisku produkcyjnym gdzie każde nieprzewidziane przerwanie działania aplikacji byłoby szczegółowo
raportowane.
*/
#include <chrono>
#include <exception>
#include <format>
#include <fstream>
#include <print>
#include <random>
#include <ranges>
// 'stacktrace' (C++23) - udostępnia migawkę stosu ('std::basic_stacktrace')
// jako kontener zawierający kolejne ramki wyowałań ('std::stacktrace_entry')
#include <stacktrace>
#include <string>


class StacktraceException : public std::exception {
public:
    // Klasa 'std::stacktrace' udostępnia metodę 'current', która zwraca obecny stos wywołań.
    // W przypadku tego wyjątku jest ona użyta jak argument, ponieważ w przeciwnym wypadku,
    // wpis związany z wywołaniem wyjątku także byłby uwzględniony w śladzie stosu.
    // Metoda 'current' dodatkowo przyjmuje następujące parametry (nie zostały one użyte w tym przykładzie):
    // - 'alloc' -> alokator, który zapisuje w pamięci obiekty typu 'std::stacktrace_entry';
    // - 'skip' -> liczba pomijanych ramek stosu;
    // - 'max_depth' -> maksymalna liczba zwracanych ramek.
    StacktraceException(std::string message, const std::stacktrace& trace = std::stacktrace::current()) :
        // 'std::stacktrace' dostarcza specjalizację 'std::formatter', zatem może zostać użyty w funkcji
        // 'std::format', co zostało pokazane w inicjalizacji 'what_'.
        message_(std::move(message)), trace_(format_trace(trace)), what_(std::format("{}\n{}", message_, trace_)) {}

    std::string_view trace() const { return trace_; }
    std::string_view message() const { return message_; }
    const char* what() const noexcept override { return what_.c_str(); }

private:
    std::string message_;
    std::string trace_;
    std::string what_;

    std::string format_trace(const std::stacktrace& trace) {
        std::string out;
        /*
        Klasa 'std::stacktrace' udostępnia iterator (typ iteratora to 'std::stacktrace::const_iterator')
        oraz metody pozwalające traktować go jako kontener tylko do odczytu.
        W kontenerze znajdują się obiekty typu 'std::stacktrace_entry', które dostarczają podstawowe
        operacje służące do pobierania informacji o wykonaniu funkcji wraz ze śladem stosu:
        - 'description' -> zwraca opis danego wywołania, które zostało 'odłożone' na stosie; 
        - 'source_file' -> zwraca nazwę pliku źródłowego, który zawiera kod danego wywołania; 
        - 'source_line' -> zwraca numer linii w kodzie źródłowym, który odpowiada danemu wywołaniu.
        */
        for (auto [i, entry]: std::views::enumerate(trace)) {
            out += std::format(
                    "#{:<2} {} at {}:{}\n", i, entry.description(), entry.source_file(), entry.source_line()
            );
        }
        return out;
    }
};

class CrashReporter {
public:
    CrashReporter(std::string filepath = "report.log") : filepath_(std::move(filepath)) {};
    void report(const StacktraceException& ex) {
        auto timestamp = now();
        auto message = ex.message();
        auto trace = ex.trace();
        std::ofstream file(filepath_);
        std::println(file, "{}", std::vformat(template_, std::make_format_args(timestamp, message, trace)));
    }

private:
    std::string filepath_;

    static std::string now() {
        const auto& now = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
        return std::format("{:%F %T}", now);
    }

    static constexpr const std::string_view template_ = "====CRASH REPORT====\n"
                                                        "Time: {}\n"
                                                        "Exception message: {}\n"
                                                        "----STACK TRACE-----\n"
                                                        "{}"
                                                        "====================";
};

// Przykładowe funkcje pozwalające badać jakie zachowują się ramki stosu w przypadku ich kolejnego wywoływania.
// Łańcuch wywołań: funcA -> (funcB1 | funcB2) -> funcC
// Celowo wprowadzono niedeterminizm, ponieważ śledzenie stosu może być szczególnie przydatne,
// jeżeli do danego wyjątku nie prowadzi jednoznaczna sekwencja wywołań.
void funcC(int i) { throw StacktraceException(std::format("Unexpected error for i={}", i)); }
void funcB1(int i) { funcC(i - 1); }
void funcB2(int i) { funcC(i + 1); }
void funcA(int i) { (i < 5) ? funcB1(i) : funcB2(i); }

int main() {
    CrashReporter reporter;
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<int> dist(0, 10);
    int i = dist(gen);

    try {
        funcA(i);
    } catch (const StacktraceException& ex) {
        reporter.report(ex);
        return 1;
    }

    return 0;
}
/* 
Przykładowy output w pliku 'report.log':
  ====CRASH REPORT====
  Time: 2026-06-09 09:44:58
  Exception message: Unexpected error for i=6
  ----STACK TRACE-----
  #0  funcC(int) at .../ZPR/stacktrace/main.cpp:104
  #1  funcB2(int) at .../ZPR/stacktrace/main.cpp:106
  #2  funcA(int) at .../ZPR/stacktrace/main.cpp:107
  #3  main at .../ZPR/stacktrace/main.cpp:117
  #4  __libc_start_call_main at :0
  #5  __libc_start_main@GLIBC_2.2.5 at :0
  #6  _start at :0
  ====================
*/

/*
Żródła:
- https://en.cppreference.com/cpp/header/stacktrace
- https://biowpn.github.io/bioweapon/2025/05/13/improve-diagnostics-with-std-stacktrace.html
- https://www.youtube.com/watch?v=dZzmtHXJN7A
*/
