#include <chrono>
#include <iostream>
#include <ranges>
#include <string>
#include <vector>

/*
ZADANIE DODATKOWE ZPR23L:
Autor: Mateusz Krakowski
*/

void rangesViewsDiffShowcase() {
    /*
    Lets say we have a vector of 10 000 000 integers, we want to transform it to a vector of
    those integers cubed and print out the first 10 elements of the vector
    */
    std::cout << "Views lazy evaluation vs Vector eager evaluation showcase" << std::endl;

    std::vector<int> vector;
    for (int i = 0; i < 10000000; ++i) {
        vector.push_back(i);
    }
    auto start = std::chrono::high_resolution_clock::now();

    // vector view that transforms the vector to a vector of cubed elements
    auto vector_view = vector | std::views::transform([](int i) { return i * i * i; });

    std::cout << "First 10 elements of the vector view: ";
    for (auto i : vector_view | std::views::take(10)) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    auto end = std::chrono::high_resolution_clock::now();

    std::cout << "Views time taken to print out 10 first cubed elements: "
              << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << "ns"
              << std::endl;

    // creating a new vector with the cubed elements
    start = std::chrono::high_resolution_clock::now();
    std::vector<int> vector_cubed;
    for (auto i : vector) {
        vector_cubed.push_back(i * i * i);
    }

    std::cout << "First 10 elements of the vector: ";
    for (auto i : vector_cubed | std::views::take(10)) {
        std::cout << i << " ";
    }
    end = std::chrono::high_resolution_clock::now();
    std::cout << std::endl
              << "New Vector time taken to print out 10 first cubed elements: "
              << std::chrono::duration_cast<std::chrono::nanoseconds>(end - start).count() << "ns"
              << std::endl;

    std::cout << "The difference is that the vector view is lazily evaluated, meaning that it \n"
                 "doesn't do the transformation until it is needed, while the new vector is \n"
                 "eagerly evaluated, meaning that it does the transformation immediately."
              << std::endl;
}

void rangesFilterShowcase() {
    /*
    Sources:
    - https://en.cppreference.com/w/cpp/ranges/filter_view
    */
    std::cout << "Ranges Filter" << std::endl;
    std::vector<int> vector{1, 2, 3, 4, 5, 6};

    std::cout << "Original vector: ";
    for (auto i : vector) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // lambda functions for filtering
    auto even = [](int i) { return i % 2 == 0; };
    auto odd = [](int i) { return i % 2 != 0; };

    // filter even numbers
    auto even_numbers = vector | std::views::filter(even);

    std::cout << "Filtered even numbers: ";
    for (auto i : even_numbers) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // filter odd numbers
    auto odd_numbers = vector | std::views::filter(odd);

    std::cout << "Filtered odd numbers: ";
    for (auto i : odd_numbers) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

void rangesTransformShowcase() {
    /*
    Sources:
    - https://en.cppreference.com/w/cpp/ranges/transform_view
    std::views::transform lets us make a view of a range that transforms the elements of the range
    using a lambda function, in this example we will transform a vector of integers to a view of
    these integers squared
    */
    std::cout << "Ranges Transform" << std::endl;
    std::vector<int> vector{1, 2, 3, 4, 5, 6};

    std::cout << "Original vector: ";
    for (auto i : vector) {
        std::cout << i << " ";
    }
    std::cout << std::endl;

    // lambda functions for transforming
    auto square = [](int i) { return i * i; };

    // transform vector
    auto transformed_vector = vector | std::views::transform(square);

    std::cout << "Transformed square view: ";
    for (auto i : transformed_vector) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

int calculateSplitPoint(int size, float ratio) {
    /*
    Helper function for calculating the split point of dataset
    */
    return size * ratio;
}

void rangesTakeDropDataShowcase(float ratio) {
    /*
    Sources:
    - https://en.cppreference.com/w/cpp/ranges/take_view
    - https://en.cppreference.com/w/cpp/ranges/drop_view
    Lets say that we have a vector of data that we want to split it into two subsets
    one for training and one for testing, we can do it using ranges Drop and Take and a helper
    function to calculate the split point
    */

    std::cout << "Ranges Take and Drop" << std::endl;
    std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};

    std::cout << "Original data: ";
    for (auto i : data) {
        std::cout << i << " ";
    }

    // split data into training and testing
    int split_point = calculateSplitPoint(data.size(), ratio);
    auto training_data = data | std::views::take(split_point);
    auto testing_data = data | std::views::drop(split_point);

    std::cout << std::endl << "Training data: ";
    for (auto i : training_data) {
        std::cout << i << " ";
    }

    std::cout << std::endl << "Testing data: ";
    for (auto i : testing_data) {
        std::cout << i << " ";
    }

    std::cout << std::endl;
}

void rangesReverseShowcase() {
    /*
    Sources:
    -https://en.cppreference.com/w/cpp/ranges/reverse_view
    reverse_view lets us easily make a view of reversed vector
    */
    std::cout << "Ranges Reverse" << std::endl;
    std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8, 9};

    std::cout << "Original data: ";
    for (auto i : data) {
        std::cout << i << " ";
    }

    std::ranges::reverse_view reversed_data{data};

    std::cout << std::endl << "Reversed data: ";
    for (auto i : reversed_data) {
        std::cout << i << " ";
    }
    std::cout << std::endl;
}

int main() {
    rangesViewsDiffShowcase();
    std::cout << std::endl;

    rangesFilterShowcase();
    std::cout << std::endl;

    rangesTransformShowcase();
    std::cout << std::endl;

    float ratio = 0.7;
    rangesTakeDropDataShowcase(ratio);
    std::cout << std::endl;

    rangesReverseShowcase();
    std::cout << std::endl;
    return 0;
}
