Simplify structures #include <mpi.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>

#define TRACKER_RANK 0
#define MAX_FILES 10
#define MAX_FILENAME 15
#define HASH_SIZE 32
#define MAX_CHUNKS 100

#define MSG_INIT_FILES 1
#define MSG_ACK 2
#define MSG_REQUEST_SWARM 3
#define MSG_SWARM_RESPONSE 4
#define MSG_CHUNK_REQUEST 5
#define MSG_CHUNK_RESPONSE 6
#define MSG_FILE_COMPLETE 7
#define MSG_ALL_COMPLETE 8
#define MSG_SHUTDOWN 9
#define MSG_CHUNK_MISSING 10

typedef struct {
    char hash[HASH_SIZE + 1];
    bool downloaded;
    int position;
} Chunk;

typedef struct{
    char filename[MAX_FILENAME];
    int num_chunks;
    Chunk chunks[MAX_CHUNKS];
} File;

typedef struct{
    int rank;
    File owned_files[MAX_FILES];
    int num_owned_files;
    char wanted_files[MAX_FILES][MAX_FILENAME];
    int num_wanted_files;
    File downloading_files[MAX_FILES];
    int num_downloading_files;
    bool should_stop;
    int chunks_downloaded;
} ClientData;

typedef struct{
    int client_rank;
    bool is_seed;
    bool is_complete;
    
} PeerInfo;

typedef struct{
    char filename[MAX_FILENAME];
    int num_chunks;
    char chunks_hash[MAX_CHUNKS][HASH_SIZE + 1];
    PeerInfo peers[MAX_FILES];
    int num_peers;
} SwarmInfo;

ClientData *client_data;

void initialize_client_data() {
    client_data = (ClientData *)malloc(sizeof(ClientData));
    if (!client_data) {
        fprintf(stderr, "Failed to allocate memory for client_data\n");
        exit(-1);
    }
    memset(client_data, 0, sizeof(ClientData));
}

void cleanup_client_data() {
    if (client_data) {
        free(client_data);
        client_data = NULL;
    }
}

void read_input_file(int rank, ClientData *client_data) {
    char filename[20];
    sprintf(filename, "in%d.txt", rank);
    FILE *input = fopen(filename, "r");
    if (!input) {
        printf("Error opening input file for rank %d\n", rank);
        exit(1);
    }

    fscanf(input, "%d", &client_data->num_owned_files);
    for (int i = 0; i < client_data->num_owned_files; i++) {
        fscanf(input, "%s %d", client_data->owned_files[i].filename,
               &client_data->owned_files[i].num_chunks);
        
        for (int j = 0; j < client_data->owned_files[i].num_chunks; j++) {
            fscanf(input, "%s", client_data->owned_files[i].chunks[j].hash);
            client_data->owned_files[i].chunks[j].downloaded = true;
            client_data->owned_files[i].chunks[j].position = j;
        }
    }

    fscanf(input, "%d", &client_data->num_wanted_files);
    for (int i = 0; i < client_data->num_wanted_files; i++) {
        fscanf(input, "%s", client_data->wanted_files[i]);
    }

    fclose(input);
}

void save_downloaded_file(int rank, const char* filename, File* file) {
    char output_filename[MAX_FILENAME];
    sprintf(output_filename, "client%d_%s", rank, filename);
    FILE *output = fopen(output_filename, "w");
    if (!output) {
        printf("Error creating output file %s\n", output_filename);
        return;
    }

    for (int i = 0; i < file->num_chunks; i++) {
        fprintf(output, "%s\n", file->chunks[i].hash);
    }
    fclose(output);
}

void* download_thread_func(void* arg) {
    int rank = (int)arg;

    client_data->chunks_downloaded = 0;

    // Send owned files to tracker
    MPI_Send(&client_data->num_owned_files, 1, MPI_INT, TRACKER_RANK, MSG_INIT_FILES, MPI_COMM_WORLD);
    for (int i = 0; i < client_data->num_owned_files; i++) {
        MPI_Send(&client_data->owned_files[i], sizeof(File), MPI_BYTE, TRACKER_RANK, MSG_INIT_FILES, MPI_COMM_WORLD);
    }

    // Wait for confirmation to start downloading from tracker
    int ack;
    MPI_Recv(&ack, 1, MPI_INT, TRACKER_RANK, MSG_ACK, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

    for (int i = 0; i < client_data->num_wanted_files && !client_data->should_stop; i++) {
        strcpy(client_data->downloading_files[i].filename, client_data->wanted_files[i]);
        
        MPI_Send(client_data->wanted_files[i], MAX_FILENAME, MPI_CHAR, TRACKER_RANK, MSG_REQUEST_SWARM, MPI_COMM_WORLD);
        
        SwarmInfo swarm;
        MPI_Recv(&swarm, sizeof(SwarmInfo), MPI_BYTE, TRACKER_RANK, MSG_SWARM_RESPONSE, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

        client_data->downloading_files[i].num_chunks = swarm.num_chunks;
        for (int j = 0; j < swarm.num_chunks; j++) {
            strcpy(client_data->downloading_files[i].chunks[j].hash, swarm.chunks_hash[j]);
            client_data->downloading_files[i].chunks[j].downloaded = false;
            client_data->downloading_files[i].chunks[j].position = j;
        }

        // Initialize round-robin index
        int peer_index = 0;

        for (int j = 0; j < swarm.num_chunks; j++) {
            if (client_data->should_stop) break;

            // Select peer using round-robin
            int peer_rank = swarm.peers[peer_index].client_rank;
            peer_index = (peer_index + 1) % swarm.num_peers;

            MPI_Send(&client_data->downloading_files[i].chunks[j], sizeof(Chunk), MPI_BYTE, peer_rank, MSG_CHUNK_REQUEST, MPI_COMM_WORLD);

            char response[10];
            MPI_Recv(response, 10, MPI_CHAR, peer_rank, MSG_CHUNK_RESPONSE, MPI_COMM_WORLD, MPI_STATUS_IGNORE);

            if (strcmp(response, "OK") == 0) {
                client_data->downloading_files[i].chunks[j].downloaded = true;
                client_data->chunks_downloaded++;

                if (client_data->chunks_downloaded % 10 == 0) {
                    MPI_Send(client_data->wanted_files[i], MAX_FILENAME, MPI_CHAR, TRACKER_RANK, MSG_REQUEST_SWARM, MPI_COMM_WORLD);
                    MPI_Recv(&swarm, sizeof(SwarmInfo), MPI_BYTE, TRACKER_RANK, MSG_SWARM_RESPONSE, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                    peer_index = 0; 
                }
            }
        }

        save_downloaded_file(rank, client_data->wanted_files[i], &client_data->downloading_files[i]);
        MPI_Send(client_data->wanted_files[i], MAX_FILENAME, MPI_CHAR, TRACKER_RANK, MSG_FILE_COMPLETE, MPI_COMM_WORLD);
    }

    MPI_Send(&rank, 1, MPI_INT, TRACKER_RANK, MSG_ALL_COMPLETE, MPI_COMM_WORLD);

    return NULL;
}

void* upload_thread_func(void* arg) {
    int rank = (int)arg;
    
    while (!client_data->should_stop) {
        MPI_Status status;
        Chunk requested_chunk;
        
        // Wait for chunk request
        MPI_Recv(&requested_chunk, sizeof(Chunk), MPI_BYTE, MPI_ANY_SOURCE, MSG_CHUNK_REQUEST, MPI_COMM_WORLD, &status);
        bool chunk_found = false;
        
        // Search for chunk in owned files
        for (int i = 0; i < client_data->num_owned_files; i++) {
            for (int j = 0; j < client_data->owned_files[i].num_chunks; j++) {
                if (strcmp(client_data->owned_files[i].chunks[j].hash, requested_chunk.hash) == 0) {
                    MPI_Send("OK", 3, MPI_CHAR, status.MPI_SOURCE, MSG_CHUNK_RESPONSE, MPI_COMM_WORLD);
                    chunk_found = true;
                    break;
                }
            }
            if (chunk_found) break;
        }
        
        if (!chunk_found) {
            MPI_Send("NO", 3, MPI_CHAR, status.MPI_SOURCE, MSG_CHUNK_RESPONSE, MPI_COMM_WORLD);
        }
    }

    return NULL;
}

void tracker(int numtasks, int rank) {
    SwarmInfo swarms[MAX_FILES];
    int num_swarms = 0;
    int completed_clients = 0;
    bool* client_completed = calloc(numtasks, sizeof(bool));

    // Wait for all clients to connect
    for (int i = 1; i < numtasks; i++) {
        int num_files;
        MPI_Recv(&num_files, 1, MPI_INT, i, MSG_INIT_FILES, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
        
        for (int j = 0; j < num_files; j++) {
            File received_file;
            MPI_Recv(&received_file, sizeof(File), MPI_BYTE, i, MSG_INIT_FILES, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            
            // Add client as seed for each file
            bool found = false;
            for (int k = 0; k < num_swarms; k++) {
                if (strcmp(swarms[k].filename, received_file.filename) == 0) {
                    swarms[k].peers[swarms[k].num_peers].client_rank = i;
                    swarms[k].peers[swarms[k].num_peers].is_seed = true;
                    swarms[k].num_peers++;
                    found = true;
                    break;
                }
            }
            
            if (!found) {
                strcpy(swarms[num_swarms].filename, received_file.filename);
                swarms[num_swarms].num_chunks = received_file.num_chunks;
                for (int k = 0; k < received_file.num_chunks; k++) {
                    strcpy(swarms[num_swarms].chunks_hash[k], received_file.chunks[k].hash);
                }
                swarms[num_swarms].peers[0].client_rank = i;
                swarms[num_swarms].peers[0].is_seed = true;
                swarms[num_swarms].num_peers = 1;
                num_swarms++;
            }
        }
    }

    // Send ack to all clients
    for (int i = 1; i < numtasks; i++) {
        int ack = 1;
        MPI_Send(&ack, 1, MPI_INT, i, MSG_ACK, MPI_COMM_WORLD);
    }

    while (completed_clients < numtasks - 1) {
        MPI_Status status;
        char filename[MAX_FILENAME];
        int client_rank;

        MPI_Probe(MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, &status);
        
        switch (status.MPI_TAG) {
            case MSG_REQUEST_SWARM:
                MPI_Recv(filename, MAX_FILENAME, MPI_CHAR, status.MPI_SOURCE, MSG_REQUEST_SWARM, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                
                // Send swarm info
                for (int i = 0; i < num_swarms; i++) {
                    if (strcmp(swarms[i].filename, filename) == 0) {
                        MPI_Send(&swarms[i], sizeof(SwarmInfo), MPI_BYTE, status.MPI_SOURCE, MSG_SWARM_RESPONSE, MPI_COMM_WORLD);
                        
                        // Add client to swarm if not already present
                        bool found = false;
                        for (int j = 0; j < swarms[i].num_peers; j++) {
                            if (swarms[i].peers[j].client_rank == status.MPI_SOURCE) {
                                found = true;
                                break;
                            }
                        }
                        if (!found) {
                            swarms[i].peers[swarms[i].num_peers].client_rank = status.MPI_SOURCE;
                            swarms[i].peers[swarms[i].num_peers].is_seed = false;
                            swarms[i].num_peers++;
                        }
                        break;
                    }
                }
                break;

            case MSG_FILE_COMPLETE:
                MPI_Recv(filename, MAX_FILENAME, MPI_CHAR, status.MPI_SOURCE, MSG_FILE_COMPLETE, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                
                // Add client as seed for downloaded file
                for (int i = 0; i < num_swarms; i++) {
                    if (strcmp(swarms[i].filename, filename) == 0) {
                        for (int j = 0; j < swarms[i].num_peers; j++) {
                            if (swarms[i].peers[j].client_rank == status.MPI_SOURCE) {
                                swarms[i].peers[j].is_seed = true;
                                break;
                            }
                        }
                        break;
                    }
                }
                break;

            case MSG_ALL_COMPLETE:
                MPI_Recv(&client_rank, 1, MPI_INT, status.MPI_SOURCE, MSG_ALL_COMPLETE, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
                
                client_completed[client_rank] = true;
                completed_clients++;

                // Send shutdown message to all clients
                if (completed_clients == numtasks - 1) {
                    for (int i = 1; i < numtasks; i++) {
                        MPI_Send(&i, 1, MPI_INT, i, MSG_SHUTDOWN, MPI_COMM_WORLD);
                    }
                }
                break;
        }
    }

    free(client_completed);
}

void peer(int numtasks, int rank) {
    pthread_t download_thread;
    pthread_t upload_thread;
    void *status;
    int r;

    initialize_client_data();

    client_data->rank = rank;
    client_data->should_stop = false;
    client_data->chunks_downloaded = 0;
    
    read_input_file(rank, client_data);
    
    r = pthread_create(&download_thread, NULL, download_thread_func, (void *)&rank);
    if (r) {
        printf("Error creating download thread\n");
        cleanup_client_data();
        exit(-1);
    }
    
    r = pthread_create(&upload_thread, NULL, upload_thread_func, (void *)&rank);
    if (r) {
        printf("Error creating upload thread\n");
        cleanup_client_data();
        exit(-1);
    }

    int shutdown_msg;
    MPI_Recv(&shutdown_msg, 1, MPI_INT, TRACKER_RANK, MSG_SHUTDOWN, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
    client_data->should_stop = true;

    r = pthread_join(download_thread, &status);
    if (r) {
        printf("Error joining download thread\n");
        cleanup_client_data();
        exit(-1);
    }
    r = pthread_join(upload_thread, &status);
    if (r) {
        printf("Error joining upload thread\n");
        cleanup_client_data();
        exit(-1);
    }

    cleanup_client_data();
}

int main(int argc, char *argv[]) {
    int numtasks, rank;
    int provided;
    
    MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided);
    if (provided < MPI_THREAD_MULTIPLE) {
        fprintf(stderr, "MPI nu are suport pentru multi-threading\n");
        exit(-1);
    }
    
    MPI_Comm_size(MPI_COMM_WORLD, &numtasks);
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    
    if (rank == TRACKER_RANK) {
        tracker(numtasks, rank);
    } else {
        peer(numtasks, rank);
    }
    
    MPI_Finalize();
}