// Autor: Monika Jung
// Temat: std::stacktrace - dodatkowe zadanie (C++23)

#include <iostream>
#include <stacktrace>
#include <string>
#include <vector>
#include <stdexcept>


/*
    DOKUMENTACJA: std::stacktrace (C++23)

    Biblioteka <stacktrace> wprowadza do standardu C++ możliwość uzyskiwania informacji o
    bieżącym stosie wywołań. Jest to szczególnie przydatne przy debugowaniu i diagnostyce błędów oraz
    przy analizie i optymalizacji kodu.

    Kluczowe elementy:
    1. std::stacktrace_entry -> reprezentuje pojedynczą ramkę stosu
       Pozwala pobrać nazwę funkcji (description()), plik źródłowy (source_file())
       oraz numer linii (source_line()).
    2. std::stacktrace (alias dla std::basic_stacktrace) -> kontener przechowujący
       całą sekwencję wywołań.
    3. std::stacktrace::current()
        - Jest to statyczna metoda, która pobiera bieżący ślad stosu lub jego określoną część.
        - przyjmuje argument skip, który pozwala pominąć określoną liczbę ramek od góry stosu (domyślnie 0, czyli pełny stos).

    Architektura biblioteki:
        - W rzeczywistości głównym silnikiem biblioteki jest szablon klasy: std::basic_stacktrace<Allocator>
        Pozwala on na zaawansowane zarządzanie pamięcią (np. w systemach embedded).
        Użyty w programie typ `std::stacktrace` to po prostu standardowy alias: using stacktrace = std::basic_stacktrace<std::allocator<std::stacktrace_entry>>;
        Dzięki temu w codziennym kodzie możemy używać wygodnej, skróconej nazwy.
        - Projekt biblioteki opiera się na strategii leniwej ewaluacji (lazy evaluation).


    ODNOŚNIKI:
    - https://en.cppreference.com/w/cpp/header/stacktrace
    - https://www.geeksforgeeks.org/cpp/cpp-23-stacktrace-header/
    - https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2020/p0881r7.html

    Uwaga dot. kompilacji (GCC):
    Wymaga flagi -std=c++23 oraz dolinkowania biblioteki pomocniczej: -lstdc++_libbacktrace
    Przykład: g++ additional_task.cpp -std=c++23 -lstdc++_libbacktrace -g
*/


// Przykład 1: Klasa wyjątku, która przechowuje ślad stosu w momencie rzucenia wyjątku
class TraceableException : public std::runtime_error {
    std::stacktrace trace;
public:
    TraceableException(const std::string& msg)
        : std::runtime_error(msg), trace(std::stacktrace::current()) {}

    void printFullReport() const {
        std::cerr << "--- RAPORT BLEDU ---\n";
        std::cerr << "Komunikat: " << what() << "\n";
        std::cerr << "Sciezka wywolan (Stacktrace):\n";


        for (const auto& entry : trace) {
            std::cerr << " Funkcja: " << entry.description()
                      << " \nPlik: " << entry.source_file()
                      << " \nLinia: " << entry.source_line()
                      << " \n--------------------\n";

        }

        // Stos wywołań można również wypisać w formie skróconej:  std::cerr << trace << "\n";
    }
};

// Symulacja logiki biznesowej, która może rzucić wyjątek
void procesuj_zamowienie(int id) {
    if (id < 0) {
        throw TraceableException("Nieprawidlowe ID zamowienia (ID ujemne)");
    }
    std::cout << "Zamowienie " << id << " przetworzone pomyślnie.\n";
}

void warstwa_logiki(int id) {
    procesuj_zamowienie(id);
}

void warstwa_api(int id) {
    warstwa_logiki(id);
}

//-------------------------------------------------------------------------

// Przykład 2: Porównanie metod logowania: stara vs nowa (std::stacktrace)
// Stara metoda: Makro preprocesora.
#define LOG_STARY(msg) std::cerr << "[STARE] Błąd w: " << __FILE__ << ":" << __LINE__ << " -> " << msg << "\n"

void log_nowy(const std::string& msg) {
    std::cerr << "[NOWE] Komunikat: " << msg << "\n";
    std::cerr << "Pełna ścieżka wywołań:\n" << std::stacktrace::current(1) << "\n";
}

void funkcja_gleboka() {
    LOG_STARY("Wystąpił problem z pamięcią podręczną.");

    std::cout << "--------------------------------------\n";

    log_nowy("Wystąpił problem z pamięcią podręczną.");
}

void funkcja_srednia() {
    funkcja_gleboka();
}

void funkcja_glowna() {
    funkcja_srednia();
}

int main() {
    std::vector<int> testowe_id = {10, 25, -1, 40};

    for (int id : testowe_id) {
        try {
            std::cout << "Probuję przetworzyc ID: " << id << "...\n";
            warstwa_api(id);
        }
        catch (const TraceableException& e) {
            // Zamiast tylko komunikatu, dostajemy pełną historię wywołań
            e.printFullReport();
        }
    }

    std::cout << "\n--------------------------------------\n";
    std::cout << "\nPorównanie  (Stare vs Nowe podejście):\n";
    funkcja_glowna();
    return 0;
}
