#include <stdio.h>
#include <stdlib.h>

#define DICT_MAX_SIZE 100

/*
 * Definiujemy strukturę o nazwie `dictionary_element`, która zawiera dwa pola:
 * `key` - klucz, po którym będziemy szukali wpisów w słowniku
 * `value` - wartość, kóra jest przyporządkowana danemu kluczowi
 */
typedef struct {
    int key;
    float value;
} dictionary_element;

/*
 * Deklaracja to sam nagłówek funkcji zakończony średnikiem, tzn `typ_zwracany nazwa(lista argumentów);`
 *
 * Podział na deklaracje i definicje to podział na to CO dany program robi i JAK to robi.
 * Ten podział ułatwia czytanie i pisanie większych projektów - najpierw ustalamy interfejs,
 * czyli CO jakaś część programu robi, a dopiero później piszemy JAK to robi.
 *
 * Dużą zaletą tego rozwiązania jest wymuszenie na programistach, żeby polegali jedynie na tym CO dana funkcja robi.
 * Przykładowo używamy `scanf` i `printf`, a nie musimy wiedzieć, JAK one działają. Co więcej, ich implamentacja może
 * się zmieniać (np w zależności od systemu operacyjnego), a dla nas nie będzie to miało znaczenia. Tutaj jest podobnie,
 * tzn używając słownika nie musimy martwić się o to jak on działa, wystarczy, że znamy funkcje do jego obsługi.
 */

// Deklaracje funkcji do obsługi słownika

// Inicjalizuje nowy słownik
void dict_init(dictionary_element dict[], int *dict_fill);

// Dodaje element `elem` do słownika, jeśli element już istnieje, to wypisuje błąd
void dict_add(dictionary_element dict[], int *dict_fill, dictionary_element elem);

// Sprawdza, czy dany klucz `key` jest w słowniku, jeśli tak, to zwraca 1, w przeciwnym wypadku zwraca 0
int dict_contains(dictionary_element dict[], int *dict_fill, int key);

// Zwraca wartość przyporządkowaną danemu kluczowi `key`, lub 0.0, jeśli klucza nie ma w słowniku
float dict_get(dictionary_element dict[], int *dict_fill, int key);

// Usuwa wpis o danym kluczu `key` ze słownika
void dict_remove(dictionary_element dict[], int *dict_fill, int key);



// Definicje funkcji do obsługi słownika

void dict_init(dictionary_element dict[], int *dict_fill) {
    *dict_fill = 0; // ustawiamy wypełnienie słownika na 0, nie ma potrzeby czyścić pamięci używanej przez `dict`
}

void dict_add(dictionary_element dict[], int *dict_fill, dictionary_element elem) {
    if (dict_contains(dict, dict_fill, elem.key)) { // jeśli element już znajduje się w słowniku, to wypisujemy błąd
        printf("ERROR: dictionary already contains key: %d\n", elem.key);
        return;
    }
    dict[*dict_fill] = elem; // dodajemy nowy element do słownika na pierwszym wolnym miejscu
    (*dict_fill)++; // zwiększamy licznik wypełnienia słownika
    printf("SUCCESS: added %d -> %f to dictionary\n", elem.key, elem.value);
}

int dict_contains(dictionary_element dict[], int *dict_fill, int key) {
    for (int i = 0; i < *dict_fill; i++) { // musimy przeszukać całą tablicę w posukiwaniu elementu o danym kluczu
        if (dict[i].key == key) { // jeśli znaleźliśmy element o danym kluczu, to od razu zwracamy 1 - nie musimy szukać dalej
            return 1;
        }
    }
    return 0; // jeśli przeszukaliśmy całą tablicę i nie znaleźliśmy w niej danego klucza, to zwracamy 0
}

float dict_get(dictionary_element dict[], int *dict_fill, int key) {
    // Kod jest niemal identyczny do `dict_contains` z tą różnicą, że zwracamy znalezioną wartość
    // lub 0.0 jeśli danego klucza nie było w słowniku
    for (int i = 0; i < *dict_fill; i++) {
        if (dict[i].key == key) {
            return dict[i].value;
        }
    }
    printf("ERROR: dictionary does not contain key: %d\n", key);
    return 0.0;
}

void dict_remove(dictionary_element dict[], int *dict_fill, int key) {
    int index = -1; // ustawiamy wartość zmiennej `index` na -1 - żaden element w tablicy nie ma takiego indeksu
    for (int i = 0; i < *dict_fill; i++) {
        if (dict[i].key == key) {
            index = i;
            break; // jeśli znaleźliśmy dany klucz, to wychodzimy z pętli `for`
        }
    }
    if (index == -1) { // jeśli wartość zmiennej `index` to nadal -1, to znaczy, że nie znaleźliśmy danego klucza w słowniku
        printf("ERROR: dictionary does not contain key: %d\n", key);
        return;
    }
    printf("SUCCESS: removed %d -> %f from dictionary\n", dict[index].key, dict[index].value);
    dict[index] = dict[*dict_fill]; // przesuwamy element z końca tablicy na miejsce usuwanego elementu
    (*dict_fill)--; // usuwamy ostatni element tablicy
}

int main() {
    dictionary_element dict[DICT_MAX_SIZE];
    int dict_fill;

    dict_init(dict, &dict_fill);

    dictionary_element elem1 = {.key = 5, .value = 6.5};
    dictionary_element elem2 = {.key = 123456789, .value = 1.23};
    dictionary_element elem3 = {.key = -13, .value = 1.0};
    dictionary_element elem4 = {.key = -13, .value = 2.0};

    dict_add(dict, &dict_fill, elem1); // dodajemy pierwszy element
    dict_add(dict, &dict_fill, elem2); // dodajemy drugi element
    dict_add(dict, &dict_fill, elem3); // dodajemy trzeci element
    dict_add(dict, &dict_fill, elem4); // tutaj próbujemy dodać element z kluczem, który już jest w słowniku - dostaniemy błąd

    // wartość przyporządkowana kluczowi `-13` to nadal `1.0`
    printf("Dictionary element: %d -> %f\n", -13, dict_get(dict, &dict_fill, -13));

    // w słowniku mamy klucz `5`
    printf("Dictionary element: %d -> %f\n", 5, dict_get(dict, &dict_fill, 5));

    // usuwamy klucz `5`
    dict_remove(dict, &dict_fill, 5);

    // teraz w słowniku nie ma już klucza `5` - przy próbie znalezienia go dostaniemy błąd
    printf("Dictionary element: %d -> %f\n", 5, dict_get(dict, &dict_fill, 5));

    return 0;
}