// Autor: Dominik Łopatecki
// Structured bindings
// Źródła: https://en.cppreference.com/w/cpp/language/structured_binding,
// https://meetingcpp.com/mcpp/slides/2019/fun_with_structured_bindings7859.pdf
// https://blog.tartanllama.xyz/structured-bindings/
// Należy kompilować w standardzie c++17

/*
 Składnia: attr(optional) cv-auto ref-qualifier(optional) [identifier-list] =
 expresion;

 Structured bindings można stosować do expression o typie:
 - array
 - tuple-like
 - do struktur danych i klas

 Wykorzystanie structured bindings pozwala na pisanie kodu czystszego oraz
 czytelniejszego.
*/

#include <array>
#include <iostream>
#include <map>
#include <tuple>

void binding_an_array() {
  // Zastosowanie dla typu array.
  // Liczba identyfikatorów musi być taka sama jak liczba elementów w tablicy

  int arr[2] = {1, 2};

  /*
   Poprzednio:
   int a = arr[0];
   int b = arr[1];
  */

  // Używając structured bindings:
  auto [a, b] = arr;

  std::cout << "Przykład: auto [a, b] = arr" << std::endl;
  std::cout << "arr[0] = " << arr[0] << "   arr[1] = " << arr[1] << std::endl;
  std::cout << "a = " << a << "   b = " << b << std::endl << std::endl;

  // Identyfikatory mogą być referencjami

  auto &[r1, r2] = arr; // referencje

  std::cout << "Przed zmianami:" << std::endl;
  std::cout << "arr = {" << arr[0] << ", " << arr[1] << "}" << std::endl;
  std::cout << "a = " << a << "   b = " << b << std::endl;
  std::cout << "r1 = " << r1 << "   r2 = " << r2 << std::endl << std::endl;

  std::cout << "Zmieniamy zmienną a = 4:" << std::endl;
  a = 4;
  std::cout << "arr = {" << arr[0] << ", " << arr[1] << "}" << std::endl;
  std::cout << "a = " << a << "   b = " << b << std::endl << std::endl;

  std::cout << "Zmieniamy referencję r1 = 4:" << std::endl;
  r1 = 4;
  std::cout << "arr = {" << arr[0] << ", " << arr[1] << "}" << std::endl;
  std::cout << "r1 = " << r1 << "   r2 = " << r2 << std::endl << std::endl;

  // Dodatkowo identyfikatorom można nadać atrybuty.
  [[maybe_unused]] const auto [x, y] = arr;
  std::cout << y << std::endl;
}



// Sekcja tuple-like

const std::pair<int, int> get_coordinates() {
  // funckcja potrzebna do demonstracji
  return std::make_pair(1, 3);
}

void binding_a_tuple_like_type() {
  auto tup = std::make_tuple(std::string("abc"), 1.32, true);

  /*
   Poprzednio:
   std::string word = get<0>(tup);
   float number = get<1>(tup);
   bool b = get<2>(tup);
  */

  // Używając structured bindings:
  const auto [word, number, b] = tup;
  std::cout << word << " " << number << " " << b << std::endl;

  // Structured bindings można wykorzystać do pobrania argumentów zwracanych
  // przez funckję o zwracanym typie krotkowym

  /*
   Poprzednio:
   int x = 0;
   int y = 0;
   std::tie(x, y) = get_coordinates();
  */

  // Używając structured bindings:
  const auto [x, y] = get_coordinates();
  std::cout << "Coordinates: " << x << ", " << y << std::endl << std::endl;

  // Wykorzystanie w pętli iterującej po typie std::map
  const std::map<std::string, int> numbers{{"jeden", 1}, {"dwa", 2}};

  /* Poprzednio:
  for (const auto &n : numbers) {
    std::cout << n.first << " " << n.second << std::endl;
  }
  */

  // Używając structured bindings:
  for (const auto &[key, val] : numbers) {
    std::cout << key << " " << val << std::endl;
  }
}



// Sekcja data

class Product {
public:
  Product(std::string name, float price) : name_(name), price_(price) {}

  // szablon funkcji get
  template <std::size_t N> auto get() const {
    if constexpr (N == 0)
      return name_;
    else if constexpr (N == 1)
      return price_;
  }

private:
  std::string name_;
  float price_;
};

namespace std {
// zdefiniowanie liczby wartości w klasie
template <>
struct tuple_size<Product> : std::integral_constant<std::size_t, 2> {};

// zdefiniowanie typów poszczególnych wartości
template <std::size_t N> struct tuple_element<N, Product> {
  using type = decltype(std::declval<Product>().template get<N>());
};
} // namespace std

void binding_to_data_members() {

  // Strukura danych

  struct Person {
    std::string name;
    std::string surname;
    int age;
  };

  const Person p = {std::string("Jan"), std::string("Kowalski"), 56};

  /*
   Poprzednio:
   std::string n = p.name;
   std::string sn = p.surname;
   int a = p.age;
   */

  // Używając structured bindings:
  const auto [n, sn, a] = p;

  std::cout << "Name: " << n << " Surname: " << sn << " Age: " << a << std::endl
            << std::endl;

  // W przypadku klas wymagane jest zapewnienie "tuple-like access"
  // Nad funckją zdeklarowana została klasa oraz odpowiednie szablony
  // zapewniające "tuple-like access"
  const Product prod = Product(std::string("Buty"), 230.99);
  const auto [prod_name, prod_price] = prod;
  std::cout << "Product name: " << prod_name << " Price: " << prod_price
            << std::endl;
}

int main() {
  std::cout << "___________________"
            << "Binding an array"
            << "___________________" << std::endl
            << std::endl;
  binding_an_array();
  std::cout << std::endl;
  std::cout << "___________________"
            << "Binding a tuple like type"
            << "___________________" << std::endl
            << std::endl;
  binding_a_tuple_like_type();
  std::cout << std::endl;
  std::cout << "___________________"
            << "Binding to a data members"
            << "___________________" << std::endl
            << std::endl;
  binding_to_data_members();
  return 0;
}