#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>
#include <pthread.h>

#define ACCOUNT_SIZE 16
#define MAX_NAME_LEN 10
#define FILENAME "../Data/accounts.db"

// Couleurs pour l'affichage
#define RESET   "\033[0m"
#define GREEN   "\033[32m"
#define RED     "\033[31m"
#define PINK    "\033[35m"
#define BLUE    "\033[34m"

typedef struct {
    int transactions;        // Nombre de transactions à effectuer
    int account_count;       // Nombre de comptes disponibles
    int transactions_done;   // Nombre de transactions effectuées
    int transactions_failed; // Nombre de transactions annulées
    int total_transfered;    // Montant total transféré par le thread
} ThreadData;

pthread_mutex_t file_mutex;  // Mutex pour protéger l'accès au fichier

void read_account(int fd, int index, char *name, int *balance) {
    char buffer[ACCOUNT_SIZE];

    // Se positionner à l'index du compte dans le fichier
    lseek(fd, index * ACCOUNT_SIZE, SEEK_SET);

    // Lire l'enregistrement du compte
    read(fd, buffer, ACCOUNT_SIZE);

    // Extraire le nom et le solde du compte
    sscanf(buffer, "%7s %d", name, balance);
}

void write_account(int fd, int index, const char *name, int balance) {
    char buffer[ACCOUNT_SIZE];

    // Formater la chaîne
    snprintf(buffer, ACCOUNT_SIZE, "%-7s%8d", name, balance);

    // S'assurer que le buffer est exactement de taille ACCOUNT_SIZE (16)
    for (int i = strlen(buffer); i < ACCOUNT_SIZE - 1; i++) {
        buffer[i] = ' ';
    }
    buffer[ACCOUNT_SIZE - 1] = '\n';  // Assurer la terminaison avec un saut de ligne

    // Se positionner à l'index du compte dans le fichier
    lseek(fd, index * ACCOUNT_SIZE, SEEK_SET);

    // Écrire l'enregistrement du compte
    write(fd, buffer, ACCOUNT_SIZE);
}

void perform_transaction(int fd, int account_count, ThreadData *data) {
    int from_idx = rand() % account_count;
    int to_idx = rand() % account_count;

    while (from_idx == to_idx) {
        to_idx = rand() % account_count;
    }

    char from_name[MAX_NAME_LEN], to_name[MAX_NAME_LEN];
    int from_balance, to_balance;

    pthread_mutex_lock(&file_mutex);  // Verrouiller l'accès au fichier

    read_account(fd, from_idx, from_name, &from_balance);
    read_account(fd, to_idx, to_name, &to_balance);

    int max_transfer = from_balance / 5;
    if (max_transfer > 0) {
        int transfer_amount = rand() % max_transfer + 1;

        from_balance -= transfer_amount;
        to_balance += transfer_amount;

        write_account(fd, from_idx, from_name, from_balance);
        write_account(fd, to_idx, to_name, to_balance);

        // Mise à jour des statistiques
        data->transactions_done++;
        data->total_transfered += transfer_amount;

        printf(GREEN "Transaction : %s -> %s : %d€\n" RESET, from_name, to_name, transfer_amount);
    } else {
        data->transactions_failed++;
        printf(RED "Transaction annulée : Fonds insuffisants pour %s\n" RESET, from_name);
    }

    pthread_mutex_unlock(&file_mutex);  // Déverrouiller l'accès au fichier
}

void *thread_transactions(void *arg) {
    ThreadData *data = (ThreadData *)arg;

    int fd = open(FILENAME, O_RDWR);
    if (fd < 0) {
        perror("Erreur : Impossible d'ouvrir le fichier");
        pthread_exit(NULL);
    }

    for (int i = 0; i < data->transactions; i++) {
        perform_transaction(fd, data->account_count, data);
        usleep(rand() % 100000);
    }

    close(fd);
    pthread_exit(NULL);
}

int calculate_total_balance() {
    int fd = open(FILENAME, O_RDONLY);
    if (fd < 0) {
        perror("Erreur : Impossible d'ouvrir le fichier");
        return -1;
    }

    off_t file_size = lseek(fd, 0, SEEK_END);
    int account_count = file_size / ACCOUNT_SIZE;

    int total_balance = 0;
    char name[MAX_NAME_LEN];
    int balance;

    for (int i = 0; i < account_count; i++) {
        read_account(fd, i, name, &balance);
        total_balance += balance;
    }

    close(fd);
    return total_balance;
}

void print_statistics(ThreadData *thread_data, int num_threads) {
    // En-tête du tableau avec une meilleure mise en forme
    printf("\n%s--- Statistiques détaillées ---%s\n", PINK, RESET);
    printf("%-10s%-25s%-25s%-20s\n", "Thread", "Transactions Effectuées", "Transactions Annulées", "Total Transféré (€)");
    
    // Affichage des statistiques de chaque thread
    for (int i = 0; i < num_threads; i++) {
        printf("%-10d%-25d%-25d%-20d\n", i+1, thread_data[i].transactions_done, thread_data[i].transactions_failed, thread_data[i].total_transfered);
    }

    // Ligne de séparation
    printf("%s----------------------------- %s\n", PINK, RESET);
}


int main(int argc, char *argv[]) {
    if (argc != 3) {
        fprintf(stderr, "Usage: %s <number_of_threads> <transactions_per_thread>\n", argv[0]);
        return 1;
    }

    int num_threads = atoi(argv[1]);
    int transactions_per_thread = atoi(argv[2]);

    int initial_balance = calculate_total_balance();
    if (initial_balance < 0) {
        return 1;
    }
    printf(PINK "Solde total initial : %10d€\n" RESET, initial_balance);

    int fd = open(FILENAME, O_RDONLY);
    if (fd < 0) {
        perror("Erreur : Impossible d'ouvrir le fichier");
        return 1;
    }
    off_t file_size = lseek(fd, 0, SEEK_END);
    close(fd);

    int account_count = file_size / ACCOUNT_SIZE;
    printf(BLUE "Nombre de comptes : %d\n" RESET, account_count);

    pthread_mutex_init(&file_mutex, NULL);

    pthread_t threads[num_threads];
    ThreadData thread_data[num_threads];

    for (int i = 0; i < num_threads; i++) {
        thread_data[i].transactions = transactions_per_thread;
        thread_data[i].account_count = account_count;
        thread_data[i].transactions_done = 0;
        thread_data[i].transactions_failed = 0;
        thread_data[i].total_transfered = 0;

        if (pthread_create(&threads[i], NULL, thread_transactions, &thread_data[i]) != 0) {
            perror("Erreur : Impossible de créer le thread");
            return 1;
        }
    }

    for (int i = 0; i < num_threads; i++) {
        pthread_join(threads[i], NULL);
    }

    pthread_mutex_destroy(&file_mutex);

    int final_balance = calculate_total_balance();
    if (final_balance < 0) {
        return 1;
    }
    printf(PINK "Solde total final   : %10d€\n" RESET, final_balance);

    if (initial_balance != final_balance) {
        printf(RED "Le solde final est différent du solde initial...\n" RESET);
    } else {
        printf(GREEN "Le solde final est identique au solde initial !\n" RESET);
    }

    print_statistics(thread_data, num_threads);

    printf("Tous les threads sont terminés !\n");

    return 0;
}