/*
Autor: Aleksander Drwal
Kompilator: g++ 13.1

W bibliotece <format> ze standardu C++20 dostępny jest std::formatter
którego specjalizacja umożliwa zdefiniowanie własnego sposobu formatowania
dla konkretnego typu.
https://en.cppreference.com/w/cpp/utility/format/formatter
*/

#include <format> // std::format
#include <iostream>
#include <cmath> // std::hypot, std::atan2

// Zdefiniowana przez nas struktura danych
struct Point
{
    double x, y;
};

// Specjalizacja szablonu dla naszego typu
template <>
struct std::formatter<Point, char>
{
    // domyślna wartość formatowania - możliwe użycie samego {} bez podania argumentu formatowania
    char presentation = 'c';

    /*
     Metoda parse umożliwa odczyt przekazanych dodatkowo argumentów do formatowania.
     ctx to obiekt dostarczający informacje o przkazanych argumentach formatowania.
     {:c} - formatowanie kartezjańskie
     {:p} - formatowanie biegunowe
    */
    template <class ParseContext>
    constexpr ParseContext::iterator parse(ParseContext &ctx)
    {
        auto it = ctx.begin();
        auto end = ctx.end();
        if (it == end)
            return it;

        if (*it == 'c' || *it == 'p')
        {
            presentation = *it;
            ++it;
        }
        if (it != end && *it != '}')
            throw std::format_error("Invalid format args.");

        return it;
    }

    /*
     Metoda format jet wywoływana dla każdego wyspecjalizowanego obiektu przekazanego do funkcji std::format.
     Wartość w zwróconym iteratorze zostanie wstawiona w miejsce w formatowanym ciągu znaków.
    */
    template <class FmtContext>
    FmtContext::iterator format(Point s, FmtContext &ctx) const
    {
        if (presentation == 'c')
            return std::format_to(ctx.out(), "(x: {}, y: {})", s.x, s.y);
        else
            return std::format_to(ctx.out(), "(r: {}, theta: {})", std::hypot(s.x, s.y), std::atan2(s.y, s.x));
    }
};

int main()
{
    Point p1{1.0, 2.0};
    Point p2{3.0, 4.5};

    std::cout << std::format("{}", p1) << std::endl;
    std::cout << std::format("{:c}", p1) << std::endl;
    std::cout << std::format("{:p}", p2) << std::endl;
    std::cout << std::format("p1{0:c}, p1{0:p}, p2{1:c}, p2{1:p}", p1, p2) << std::endl;
}
