/* ============================================================================
 *  Temat: std::mdspan (C++23)
 *
 *  Pelna dokumentacja zagadnienia: plik "dokumentacja_mdspan.pdf"
 *
 *  Kompilacja (wymagany GCC 14+):
 *      g++   -std=c++23 -O2 -Wall -Wextra mdspan_demo.cpp -o mdspan_demo
 * Uruchomienie:
 *      ./mdspan_demo
 * ============================================================================
 */

#include <mdspan>
#include <iostream>
#include <iomanip>
#include <vector>
#include <array>
#include <numeric>
#include <string_view>
#include <cstddef>

// Drukuje 2D mdspan w formie tabelarycznej. Szablon, bo mdspan ma kilka
// parametrow typu (extents, layout, accessor).
template <typename MDSpan>
void print_2d(MDSpan m, std::string_view name)
{
    std::cout << "[ " << name << " ]  rozmiar = "
              << m.extent(0) << " x " << m.extent(1) << "\n";
    for (std::size_t i = 0; i < m.extent(0); ++i) {
        for (std::size_t j = 0; j < m.extent(1); ++j)
            std::cout << std::setw(6) << m[i, j];   // C++23: multi-arg op[]
        std::cout << '\n';
    }
    std::cout << '\n';
}

// Widok 2D o rozmiarach znanych w runtime na vector<int>.
//     Modyfikacja przez widok dziala na oryginalnych danych.
void example_basic_dynamic()
{
    std::cout << "===  mdspan z dynamicznymi rozmiarami ===\n\n";

    std::vector<int> data(12);
    std::iota(data.begin(), data.end(), 1);

    std::mdspan<int, std::dextents<std::size_t, 2>> mat(data.data(), 3, 4);
    print_2d(mat, "macierz 3x4 (row-major)");

    mat[1, 2] = 999;
    std::cout << "Po `mat[1, 2] = 999`, data[1*4 + 2] = "
              << data[1 * 4 + 2] << "\n\n";
}

// extents: rozmiary statyczne, dynamiczne i mieszane.
//     Statyczne extents daje  wiecej informacji dla optymalizatora.
void example_extents()
{
    std::cout << "=== extents statyczne, dynamiczne i mieszane ===\n\n";

    int buf[2 * 3];
    std::iota(std::begin(buf), std::end(buf), 100);

    std::mdspan<int, std::extents<std::size_t, 2, 3>> stat(buf);
    print_2d(stat, "extents<2, 3> (oba wymiary statyczne)");

    std::mdspan<int, std::extents<std::size_t,
                                  std::dynamic_extent, 3>> mix(buf, 2);
    print_2d(mix, "extents<dynamic, 3> (mieszane)");
}

//  Uklad: ten sam bufor, dwie interpretacje pamieci.
//     layout_right = row-major (C/C++), layout_left = column-major (Fortran).
void example_layouts()
{
    std::cout << "===  layout_right vs layout_left ===\n\n";

    std::array<int, 6> data = {1, 2, 3, 4, 5, 6};
    using Ext2 = std::extents<std::size_t, 2, 3>;

    std::mdspan<int, Ext2, std::layout_right> rmat(data.data());
    std::mdspan<int, Ext2, std::layout_left>  lmat(data.data());

    std::cout << "Te same dane w pamieci: [ 1 2 3 4 5 6 ]\n\n";
    print_2d(rmat, "layout_right (row-major)");
    print_2d(lmat, "layout_left  (column-major)");
}

// layout_stride: dowolne kroki w wymiarach pozwala na transpozycja bez kopiowania.
//     adres(i, j) = base + i*stride0 + j*stride1
void example_strided()
{
    std::cout << "===  layout_stride: transpozycja bez kopiowania ===\n\n";

    std::array<int, 6> data = {1, 2, 3, 4, 5, 6};

    std::mdspan<int, std::extents<std::size_t, 2, 3>> mat(data.data());
    print_2d(mat, "oryginal: macierz 2x3");

    using ExtT = std::extents<std::size_t, 3, 2>;
    std::array<std::size_t, 2> strides{1, 3};
    std::layout_stride::mapping<ExtT> mapping{ExtT{}, strides};

    std::mdspan<int, ExtT, std::layout_stride> transposed(data.data(), mapping);
    print_2d(transposed, "widok transponowany 3x2 (TE SAME dane)");
}

// Podwidok (slice): wiersz macierzy jako 1D mdspan, bez kopiowania
void example_subview()
{
    std::cout << "=== podwidok: wiersz macierzy jako 1D mdspan ===\n\n";

    std::vector<int> data(20);
    std::iota(data.begin(), data.end(), 1);

    std::mdspan<int, std::dextents<std::size_t, 2>> mat(data.data(), 4, 5);
    print_2d(mat, "macierz 4x5");

    int* row_ptr = &data[2 * 5];
    std::mdspan<int, std::dextents<std::size_t, 1>> row(row_ptr, 5);

    std::cout << "[ wiersz nr 2 ] : ";
    for (std::size_t i = 0; i < row.extent(0); ++i)
        std::cout << row[i] << ' ';
    std::cout << "\n\n";

    row[0] = -1;
    row[4] = -1;
    std::cout << "Po `row[0] = -1; row[4] = -1`:\n";
    print_2d(mat, "macierz 4x5 po modyfikacji przez podwidok");
}

// Zastosowanie praktyczne: generyczne mnozenie macierzy.
//     Funkcja nie zalezy od kontenera, ukladu pamieci i  extents.
template <typename A, typename B, typename C>
void matmul(A a, B b, C c)
{
    for (std::size_t i = 0; i < a.extent(0); ++i)
        for (std::size_t j = 0; j < b.extent(1); ++j) {
            int sum = 0;
            for (std::size_t k = 0; k < a.extent(1); ++k)
                sum += a[i, k] * b[k, j];
            c[i, j] = sum;
        }
}

void example_matmul()
{
    std::cout << "=== generyczne mnozenie macierzy ===\n\n";

    std::array<int, 6> a_data = {1, 2, 3,  4, 5, 6};
    std::array<int, 6> b_data = {7, 8,  9, 10,  11, 12};
    std::array<int, 4> c_data{};

    std::mdspan<int, std::extents<std::size_t, 2, 3>> A(a_data.data());
    std::mdspan<int, std::extents<std::size_t, 3, 2>> B(b_data.data());
    std::mdspan<int, std::extents<std::size_t, 2, 2>> C(c_data.data());

    print_2d(A, "A");
    print_2d(B, "B");
    matmul(A, B, C);
    print_2d(C, "C = A * B");
}

int main()
{
    std::cout << "================================================\n";
    std::cout << "  std::mdspan (C++23) przyklady \n";
    std::cout << "================================================\n\n";

    example_basic_dynamic();
    example_extents();
    example_layouts();
    example_strided();
    example_subview();
    example_matmul();
    return 0;
}
