#include <algorithm>
#include <array>
#include <deque>
#include <vector>
#include <string>
#include <ranges>
#include <iostream>

/*
`std::ranges::views::zip` oraz `std::ranges::views::zip_transform` są elementami biblioteki standardowej C++ 
wprowadzonymi w standardzie C++ 23. Formalnie są one *obiektami punktowego dostosowania* (customization point
objects). Z perspektywy programisty zachowują się jak funkcje.

`views::zip` jako argumenty przyjmuje jeden lub więcej *widok* (view) i zwraca *adaptor zakresu* (range adaptor)
typu `views::zip_view`, który tworzy widok w którym i-ty element jest krotką zawierającą i-te elementy z każdego
wejściowego widoku. 

W przykładzie stworzono 3 listy różnego typu (vector, array, deque) - z numerami osób, wiekami osób i ich imionami.
Listy połączono za pomocą `views::zip` i posortowano wg. wieku.

`views::zip_transform` jako pierwszy argument przyjmuje *obiekt wywoływalny* (invocable object), np. funkcję, a jako
kolejne argumenty przyjmuje jeden lub więcej widoków. Zwraca adaptor zakresu typu `views::zip_transform_view` tworzący
widok, w którym każdy i-ty element jest wynikiem zastosowania obiektu wywoływalnego do krotki zawierającej każdy
i-ty element widoków wejściowych.

W przykładzie stworzono dwie listy różnego rodzaju (vector, array) - z liczbami i ich mnożnikami. Wykonano mnożenie 
liczb za pomocą funkcji mnożącej oraz `views::zip_transform`.

https://en.cppreference.com/w/cpp/ranges/zip_transform_view
https://en.cppreference.com/w/cpp/ranges/zip_view

*/

int main() {
    // Przykład views::zip
    std::cout << "views::zip" << std::endl;
    // Listy identyfikatorów, wieków i imion
    std::vector<int> id_nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::array<int, 10> ages = std::to_array({44, 16, 81, 78, 96, 48, 19, 33, 68, 30});
    std::deque<std::string> names = {
        "Norbert",
        "Urban",
        "Tomasz",
        "Borys",
        "Otylia",
        "Joachim",
        "Zenobia",
        "Kunegunda",
        "Jerzy",
        "Mieczysław"
    };

    // Wypisanie list przed sortowaniem
    std::cout << "Przed sortowaniem" << std::endl;
    for (int i = 0; i < 10; i++) {
        std::cout << id_nums.at(i) << ", "
        << names.at(i) << ", "
        << ages.at(i) << std::endl;
    }
    
    // Zipowanie
    auto zipped = std::views::zip(id_nums, names, ages);
    // Sortowanie wg. 3-ciego elementu krotki, tj. wieku 
    std::ranges::sort(zipped, [](const auto & lhs, const auto & rhs) {return std::get<2>(lhs) <  std::get<2>(rhs);});
    
    // Wypisanie posortowanych list
    std::cout << std::endl << "Po sortowaniu" << std::endl;
    for (std::tuple<int&, std::string&, int&> elem : zipped) {
        std::cout << std::get<0>(elem) << ", " 
        << std::get<1>(elem) << ", "
        << std::get<2>(elem) << std::endl;
    }

    // Przykład views::zip_transform
    std::cout << '\n' << "views::zip_transform" << std::endl;
     // Lista liczb i mnożników
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::array<int, 10> multipliers = std::to_array({44, 16, 81, 78, 96, 48, 19, 33, 68, 30});

    // Wypisanie działań
    for (int i = 0; i < 10; i++) {
        std::cout << nums.at(i) << " * " << multipliers.at(i) << std::endl;
    }

    // Funkcja która zostanie wykonana na każdej krotce elementów
    auto multiply = [](int num, int multiplier) {
        return num * multiplier;
    };

    // Wykonanie funkcji
    auto zip_transformed = std::views::zip_transform(multiply, nums, multipliers);

    // Wypisanie wyników
    std::cout << std::endl;
    for (auto elem : zip_transformed) {
        std::cout << elem << std::endl;
    }
}