#include <compare>
#include <iostream>
#include <limits>
#include <cmath> // Include for NaN
#include <chrono>
#include <string>


//
// Autor: Ignacy Kosacki 318424
//
// Wszystkie informacje ktore zostaly uzyte do wykonania tego zadania zostaly wziete z :https://en.cppreference.com/w/cpp/utility
//
// Program pokazuje zasade dzialania "Three-way comparison", udogodnienie dodane w standardzie C++20
// Three-way comparison pozwala na porownanie dwoch elementow, za pomoca operatora "<=>", wyniki takiej operacji moga byc nastepujace:
// 1. std::strong_ordering: Ten typ jest używany, gdy porównywane obiekty zawsze mogą być porównane w sposób transytywny i przechodni. Możliwe wyniki to:
//    -std::strong_ordering::less: Oznacza, że pierwszy element jest mniejszy od drugiego.
//    -std::strong_ordering::equal: Oznacza, że oba elementy są równe.
//    -std::strong_ordering::greater: Oznacza, że pierwszy element jest większy od drugiego.
//
// 2. std::weak_ordering: Używany w sytuacjach, gdzie porównania są przechodnie, ale niekoniecznie transytywne. Możliwe wyniki są podobne do std::strong_ordering.
//    -std::weak_ordering::less:
//    -std::weak_ordering::equivalent:
//    -std::weak_ordering::greater:
//
// 3. std::partial_ordering: Stosowany, gdy nie wszystkie elementy mogą być porównywane. Możliwe wyniki to:
//    -std::partial_ordering::less
//    -std::partial_ordering::equivalent
//    -std::partial_ordering::greater
//    -std::partial_ordering::unordered: Oznacza, że elementy nie mogą być porównane w sposób uporządkowany.
//
//
//
// W ponizszym kodzie zademonstruje dzialanie tego operatora, oraz zweryfikuje jego wydajnosc.



//Nasza wlasna uproszczona implementacja 'three_way_compare'
template <typename T>
int three_way_compare(const T& a, const T& b) {
    if (a < b) {
        return -1;
    } else if (a > b) {
        return 1;
    } else {
        return 0;
    }
}


//Klasa Person ktorej uzyjemy do zademonstrowania sortowania nazwisk za pomoca 'weak_ordering'
class Person {
    std::string lastName;
public:
    Person(std::string ln) : lastName(std::move(ln)) {}

    std::weak_ordering operator<=>(const Person& other) const {
        return lastName <=> other.lastName; // Porównujemy tylko nazwiska
    }
};

int main() {


    int a = 1500, b = 5500, c = 1500;
    float d = NAN;
    std::cout << "a = " << a << ", b = " << b << ", c = " << c << ", d = NaN " << "\n";


    //Przyklady uzyc Three_way_comparison
    if (a <=> b == std::partial_ordering::less) {
        std::cout << "a is less than b\n";
    }
    if (b <=> c == std::partial_ordering::greater) {
        std::cout << "b is greater than c\n";
    }
    if (a <=> c == std::partial_ordering::equivalent) {
        std::cout << "a is equal to c\n";
    }

    // Porownywanie liczb z wartoscia 'NaN', jest mozliwe dzieki partial_ordering w nastepujacy sposob:
    auto result = d <=> a;
    if (result == std::partial_ordering::unordered) {
        std::cout << "d is unordered with a\n";
    }


    //Przyklad sortowania obiektow po dlugosci atrybutu
    Person p1("Kowalski"), p2("Jablonowski");
    if (auto cmp = p1 <=> p2; cmp < 0) {
        std::cout << "Kowalski przed Jablonowskim\n";
    } else if (cmp > 0) {
        std::cout << "Jablonowski przed Kowalskim\n";
    } else {
        std::cout << "Kowalski i Jablonowski to to samo nazwisko\n";
    }



    std::cout << "\n";
    //Testy wydajnosci dla intow
    // Początek pomiaru czasu
    auto start1 = std::chrono::high_resolution_clock::now();
    // Przeprowadzenie wielu porównań
    for (int i = 0; i < 5000000; ++i) {
       if (a <=> b == 0) {
    }
    }
    // Koniec pomiaru czasu
    auto end1 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration1 = end1 - start1;
    std::cout << "Time taken for 5,000,000 comparisons (int) a=b: " << duration1.count() << " ms\n";


    //Test
    auto start2 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (a <=> b == std::strong_ordering::equal) {
    }
    }
    auto end2 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration2 = end2 - start2;
    std::cout << "Time taken for 5,000,000 comparisons (int) a=b, using 'strong_ordering': " << duration2.count() << " ms\n";


    //Test weak_ordering::equivalent
    auto start3 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (a <=> b == std::weak_ordering::equivalent) {
    }
    }
    auto end3 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration3 = end3 - start3;
    std::cout << "Time taken for 5,000,000 comparisons (int) a=b, using 'weak_ordering': " << duration3.count() << " ms\n";



    //Test partial_ordering::equivalent
    auto start4 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (a <=> b == std::partial_ordering::equivalent) {
    }
    }
    auto end4 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration4 = end4 - start4;
    std::cout << "Time taken for 5,000,000 comparisons (int) a=b, using 'partial_ordering': " << duration4.count() << " ms\n";


    //Test
    auto start5 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (three_way_compare(a, b)==-1) {
    }
    }
    auto end5 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration5 = end5 - start5;
    std::cout << "Time taken for 5,000,000 comparisons (int) a=b, using our implementation: " << duration5.count() << " ms\n";






    std::cout << "\n";
    //Testy wydajnosci dla floatow
    float floatA = 10.0f, floatB = 20.0f;

    auto start6 = std::chrono::high_resolution_clock::now();
    // Przeprowadzenie wielu porównań
    for (int i = 0; i < 5000000; ++i) {
       if (floatA <=> floatB == 0) {
    }
    }
    // Koniec pomiaru czasu
    auto end6 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration6 = end6 - start6;
    std::cout << "Time taken for 5,000,000 comparisons (float) a=b: " << duration6.count() << " ms\n";


    //Test
    auto start7 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (floatA <=> floatB == std::strong_ordering::equal) {
    }
    }
    auto end7 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration7 = end7 - start7;
    std::cout << "Time taken for 5,000,000 comparisons (float) a=b, using 'strong_ordering': " << duration7.count() << " ms\n";


    //Test weak_ordering::equivalent
    auto start8 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (floatA <=> floatB == std::weak_ordering::equivalent) {
    }
    }
    auto end8 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration8 = end8 - start8;
    std::cout << "Time taken for 5,000,000 comparisons (float) a=b, using 'weak_ordering': " << duration8.count() << " ms\n";



    //Test partial_ordering::equivalent
    auto start9 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (floatA <=> floatB == std::partial_ordering::equivalent) {
    }
    }
    auto end9 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration9 = end9 - start9;
    std::cout << "Time taken for 5,000,000 comparisons (float) a=b, using 'partial_ordering': " << duration9.count() << " ms\n";


    //Test
    auto start10 = std::chrono::high_resolution_clock::now();
    for (int i = 0; i < 5000000; ++i) {
    if (three_way_compare(floatA, floatB)==-1) {
    }
    }
    auto end10 = std::chrono::high_resolution_clock::now();
    std::chrono::duration<double, std::milli> duration10 = end10 - start10;
    std::cout << "Time taken for 5,000,000 comparisons (float) a=b, using our implementation: " << duration10.count() << " ms\n";

    //Wyniki przykladowego testu:
    //Time taken for 5,000,000 comparisons (int) a=b: 9.9903 ms
    //Time taken for 5,000,000 comparisons (int) a=b, using 'strong_ordering': 14.2394 ms
    //Time taken for 5,000,000 comparisons (int) a=b, using 'weak_ordering': 35.9778 ms
    //Time taken for 5,000,000 comparisons (int) a=b, using 'partial_ordering': 34.9811 ms
    //Time taken for 5,000,000 comparisons (int) a=b, using our implementation: 9.8804 ms
    //
    //Time taken for 5,000,000 comparisons (float) a=b: 9.69538 ms
    //Time taken for 5,000,000 comparisons (float) a=b, using 'strong_ordering': 29.8846 ms
    //Time taken for 5,000,000 comparisons (float) a=b, using 'weak_ordering': 28.3784 ms
    //Time taken for 5,000,000 comparisons (float) a=b, using 'partial_ordering': 11.3456 ms
    //Time taken for 5,000,000 comparisons (float) a=b, using our implementation: 9.57783 ms
    //
    // Jak sie okazuje po wykonanych testach, nasza wlasna implementacja radzi sobie na rowni z podstawowym uzyciem potrojnego porownania.
    // Jednak operator '<=>' pozwala na wiecej operacji, ktore moglyby spowolnic nasza implemenctacje gdybymy dodali je w nieoptymalny sposob.
    // Trzeba w takim razie przyznac, ze operator ten jest duzym ulatwieniem.
    //
    // Wyniki testow wydajnosci pokazaly, ze najszybciej zostana wykonane porownania w ktorych nie sprecyzujemy jaki typ wyniku bedziemy porownywac.
    // Jesli chodzi o porownywanie z konkretnymi typami wynikow, 'strong_ordering' okazal sie byc najlepszy dla liczb typu 'int', natomiast to 'partial_ordering' poradzil sobie najlepiej z porownywaniem z typem 'float.
    // Pokazuje to, ze kazdy typ ma swoje zastosowania, mocniejsze i slabsze strony ktorych trzeba byc swiadomym, zeby napisac optymalny kod.
    //






    return 0;

}
