#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdint.h>

#include <pthread.h>
#include <semaphore.h>

#include "fonctions.h"

/*
Initialise une liste de node_t
@my_list : la list_t qui est initialisée
*/
void init_list_t(list_t *my_list) {
    my_list->first = NULL;
    my_list->last = NULL;
    my_list->length = 0;
}

/*
Initialise une liste de node_lst
@my_list : la list_lst qui est initialisée
*/
void init_list_lst(list_lst *my_list) {
    my_list->first = NULL;
    my_list->last = NULL;
    my_list->length = 0;
}

/*
Ajoute un node_t à la fin d'une list_t
@my_list : la list_t a laquelle on va ajouter un node_t
@value : la valeur (uint64_t) que contiendra le node_t
*/
void put_node_t(list_t *my_list, uint64_t value) {
    node_t *new = malloc(sizeof(node_t));
    if (!new) return;
    new->next = NULL;
    new->value = value;
    if (my_list->length == 0) {
        my_list->first = new;
        my_list->last = new;
    } else {
        my_list->last->next = new;
        my_list->last = new;
    }
    my_list->length += 1;
}

/*
Ajoute un node_lst à la fin d'une list_lst
@my_list : la list_lst a laquelle on va ajouter un node_lst
@put_list : la list_t que contiendra le node_lst
*/
void put_node_lst(list_lst *my_list, list_t *put_list) {
    node_lst *new = malloc(sizeof(node_lst));
    if (!new) return;
    new->next = NULL;
    new->diviseurs = put_list;
    if (my_list->length == 0) {
        my_list->first = new;
        my_list->last = new;
    } else {
        my_list->last->next = new;
        my_list->last = new;
    }
    my_list->length += 1;
}

/*
Retire le premier node_t d'une list_t et renvoit son entier
@my_list : la list_t de laquelle on va retier le node_t pointé par first
@return : la valeur (uint64_t) que contenait le node_t retiré
*/
uint64_t get_node_t(list_t *my_list) {
    if (my_list->length == 0) return -1;
    int value = my_list->first->value;
    node_t *temp = my_list->first;
    my_list->first = my_list->first->next;
    my_list->length -= 1;
    free(temp);
    return value;
}

/*
Retire le premier node_lst d'une list_lst et renvoit sa liste
@my_list ; la list_lst de laquelle on va retirer le node_lst pointé par first
@return : la liste (list_t) que contenait le node_lst retiré
*/
list_t *get_node_lst(list_lst *my_list) {
    if (my_list->length == 0) return NULL;
    list_t *get_list = my_list->first->diviseurs;
    node_lst *temp = my_list->first;
    my_list->first = my_list->first->next;
    my_list->length -= 1;
    free(temp);
    return get_list;
}

/*
Vide une list_t de node_t
@my_list : la list_t qui va être vidée
*/
void clear_list_t(list_t *my_list) {
    if (my_list == NULL || my_list->length == 0) return;
    node_t *temp = my_list->first->next;
    while(my_list->first != NULL) {
        free(my_list->first);
        my_list->first = temp;
        if (temp != NULL) {
            temp = temp->next;
        }
    }
    my_list->first = NULL;
}

/*
Renvoie une list_t contenant un nombre suivi de ses diviseurs premiers
@value : le nombre (uint64_t) dont on veut trouver les diviseurs premiers
@return : une list_t contenant value dans le node_t pointé par first ainsi que ses diviseurs premiers
*/
list_t *prime_list(uint64_t value) {
    list_t *prime = malloc(sizeof(list_t));
    if (!prime) return NULL;
    init_list_t(prime);
    put_node_t(prime, value);

    uint64_t in_node = 0;
    uint64_t chiffre = value;
    uint64_t div = 2;

    while (value % div == 0) {
        if (div != in_node && value != div) {
            put_node_t(prime, div);
            in_node = div;
        }
        value = value/div;
    }
    for (uint64_t i = 3; i*i <= value; i += 2) {
        while (value % i == 0) {
            if (i != in_node) {
                put_node_t(prime, i);
                in_node = i;
            }
            value = value / i;
        }
    }
    if (value != chiffre && value != 1){
        put_node_t(prime, value);
    }
    
    return prime;
}

/*
Compte les lignes d'un fichier
@filename : un pointeur vers le nom du fichier à ouvrir
@return : le nombre (int) de lignes dans le fichier donné
*/
int count_lines(char *filename){
    uint64_t number;
    int lines = 0;
    FILE *file = fopen(filename, "r");
    while (fscanf(file, "%lu", &number) != EOF) {
        lines++;
    }
    fclose(file);
    return lines;
}

/*
Initialise un buffer1
@nthreads : le nombre (int) de threads passé en argument du programme
@return : un pointeur vers un buffer1 initialisé
*/
buffer1 *init_buffer_1(int nthreads) {
    buffer1 *buffer_1 = malloc(sizeof(buffer1));
    if (!buffer_1) {
        free(buffer_1);
        printf ("Error with buffer_1.\n");
        return NULL;
    }

    if (pthread_mutex_init(&(buffer_1->mutex), NULL) == -1) {
        free(buffer_1);
        return NULL;
    }
    if (sem_init(&(buffer_1->free), 0, nthreads + 2) == -1) {
        free(buffer_1);
        return NULL;
    }
    if (sem_init(&(buffer_1->full), 0, 0) == -1) {
        free(buffer_1);
        return NULL;
    }

    buffer_1->can_stop = false;
    buffer_1->numbers = malloc(sizeof(list_t));
    if (!buffer_1->numbers) {
        free(buffer_1->numbers);
        free(buffer_1);
        return NULL;
    }

    init_list_t(buffer_1->numbers);

    printf ("buffer_1 created.\n");
    return buffer_1;
}

/*
Initialise un buffer2
@nthreads : le nombre (int) de threads passé en argument du programme
@return : un pointeur vers un buffer2 initialisé
*/
buffer2 *init_buffer_2(int nthreads) {
    buffer2 *buffer_2 = malloc(sizeof(buffer2));
    if (!buffer_2) {
        free(buffer_2);
        printf ("Error with buffer_2.\n");
        return NULL;
    }

    if (pthread_mutex_init(&(buffer_2->mutex), NULL) == -1) {
        free(buffer_2);
        return NULL;
    }
    if (sem_init(&(buffer_2->free), 0, nthreads + 2) == -1) {
        free(buffer_2);
        return NULL;
    }
    if (sem_init(&(buffer_2->full), 0, 0) == -1) {
        free(buffer_2);
        return NULL;
    }

    buffer_2->prime_numbers = malloc(sizeof(list_lst));
    if (!buffer_2->prime_numbers) {
        free(buffer_2->prime_numbers);
        free(buffer_2);
        return NULL;
    }

    init_list_lst(buffer_2->prime_numbers);

    printf ("buffer_2 created.\n");
    return buffer_2;
}