#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <time.h>
#include <mpi.h>

// Definizione di alcune costanti globali
#define PI 3.14159265358979323
#define TEMPERATURE_DECAY_RATE 0.98
#define MIN_DISTANCE 10.0  // Distanza minima tra i nodi

// Strutture
typedef struct {
    double x;
    double y;
} Force;

typedef struct {
    double x;
    double y;
} Node;

typedef struct {
    size_t start;
    size_t end;
} Edge;

typedef struct {
    Node* nodes;
    Edge* edges;
    size_t node_count;
    size_t edge_count;
} SimpleGraph;


// Prototipi delle funzioni
SimpleGraph readGraphFile(char* file_name);
void calculateRepulsion(Force* net_forces, SimpleGraph* graph, size_t start, size_t end);
void calculateAttraction(Force* net_forces, SimpleGraph* graph, size_t start, size_t end);
Force* initializeForceVector(SimpleGraph graph);
void moveNodes(Force* net_forces, SimpleGraph* graph, size_t start, size_t end);
void getMaxNodeDimensions(SimpleGraph* graph, double* maxX, double* maxY);


int peso;
double temperature;
double k; // Distanza ideale
double scaleFactor; // Fattore di scala iniziale
double offsetX; // Offset iniziale
double offsetY;


int main(int argc, char* argv[]) {
    MPI_Init(&argc, &argv);

    int rank, size;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    MPI_Comm_size(MPI_COMM_WORLD, &size);

    if (argc < 5) {
        if (rank == 0) {
            printf("Uso corretto: %s nome_file iterazioni temperatura peso\n", argv[0]);
        }
        MPI_Finalize();
        return 1; // Esce dal programma con codice di errore
    }

    char* file_name = argv[1];
    int it = atoi(argv[2]);
    temperature = atof(argv[3]);
    peso = atoi(argv[4]);

    SimpleGraph graph;
    if (rank == 0) {
        graph = readGraphFile(file_name);
    }

    // Broadcast del numero di nodi e archi a tutti i processi
    MPI_Bcast(&graph.node_count, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD);
    MPI_Bcast(&graph.edge_count, 1, MPI_UNSIGNED_LONG, 0, MPI_COMM_WORLD);

    if (rank != 0) {
        graph.nodes = malloc(graph.node_count * sizeof(Node));
        graph.edges = malloc(graph.edge_count * sizeof(Edge));
    }
    MPI_Bcast(graph.nodes, graph.node_count * sizeof(Node), MPI_BYTE, 0, MPI_COMM_WORLD);
    MPI_Bcast(graph.edges, graph.edge_count * sizeof(Edge), MPI_BYTE, 0, MPI_COMM_WORLD);
    double screenWidth = 2240 - 10;
    double screenHeight = 1400 - 10;
    k = sqrt((screenWidth * screenHeight) / graph.node_count);


    double offsetX = 0.0;
    double offsetY = 0.0;
    Force* net_forces = initializeForceVector(graph);
    double maxX, maxY;
    getMaxNodeDimensions(&graph, &maxX, &maxY);
    double scaleFactor = fmin(screenWidth / (2 * maxX), screenHeight / (2 * maxY));
    if (!net_forces) {
        fprintf(stderr, "Error allocating memory for forces\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }
    for (int i = 0; i < it; i++) {
        calculateRepulsion(net_forces, &graph, rank * graph.node_count / size, (rank + 1) * graph.node_count / size);
        calculateAttraction(net_forces, &graph, rank * graph.edge_count / size, (rank + 1) * graph.edge_count / size);
        MPI_Barrier(MPI_COMM_WORLD);
        if (rank == 0) {
            Force* dati = initializeForceVector(graph);
            // Buffer per ricevere tutte le forze dai processi
            Force* all_forces = malloc(size * graph.node_count * sizeof(Force));

            // Array per i counts e i displacements
            int* counts = malloc(size * sizeof(int)); //Numero di elementi ricevuti da ogni processo
            int* displacements = malloc(size * sizeof(int)); //Index di partenza

            for (int i = 0; i < size; i++) {
                counts[i] = graph.node_count * sizeof(Force);
                displacements[i] = i * graph.node_count * sizeof(Force);
            }

            // Raccoglie le forze da tutti i processi
            MPI_Gatherv(net_forces, graph.node_count * sizeof(Force), MPI_BYTE,
                        all_forces, counts, displacements, MPI_BYTE,
                        0, MPI_COMM_WORLD);

            // Somma le forze ricevute
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < graph.node_count; j++) {
                    net_forces[j].x += all_forces[i * graph.node_count + j].x;
                    net_forces[j].y += all_forces[i * graph.node_count + j].y;
                }
            }

            moveNodes(net_forces, &graph, 0, graph.node_count);

            // Libera la memoria allocata
            free(all_forces);
            free(counts);
            free(displacements);
        } else {
            // Invio delle forze al processo radice
            MPI_Gatherv(net_forces, graph.node_count * sizeof(Force), MPI_BYTE,
                        NULL, NULL, NULL, MPI_BYTE,
                        0, MPI_COMM_WORLD);
        }
        MPI_Barrier(MPI_COMM_WORLD);
        if (temperature > 1.0) {
            temperature *= TEMPERATURE_DECAY_RATE;
        }

        MPI_Bcast(graph.nodes, graph.node_count * sizeof(Node), MPI_BYTE, 0, MPI_COMM_WORLD);
        for (int i = 0; i< graph.node_count; i++){
            net_forces[i].x = 0.0;
            net_forces[i].y = 0.0;
        }
        MPI_Barrier(MPI_COMM_WORLD);

    }

    if (rank == 0) {
        FILE* output = fopen("out.txt", "w");
        if (!output) {
            printf("Impossibile aprire il file di output per la scrittura.\n");
            MPI_Finalize();
            return 0;
        }
        fprintf(output, "%zu %d\n", graph.node_count, peso);

        // Scrivi le posizioni dei nodi
        for (size_t i = 0; i < graph.node_count; i++) {
            fprintf(output, "%zu %f %f\n", i, graph.nodes[i].x, graph.nodes[i].y);
        }

        fprintf(output, "%zu\n", graph.edge_count);

        // Scrivi gli archi
        for (size_t i = 0; i < graph.edge_count; i++) {
            fprintf(output, "%zu %zu\n", graph.edges[i].start, graph.edges[i].end);
        }

        fclose(output);
    }

    free(graph.nodes);
    free(graph.edges);
    MPI_Finalize();
    return 0;
}

SimpleGraph readGraphFile(char* file_name) {
    FILE* input = fopen(file_name, "r");
    if (!input) {
        fprintf(stderr, "Error opening file\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }
    SimpleGraph graph;
    fscanf(input, "%zu", &graph.node_count); // Legge il numero totale di nodi

    // Creazione di un array temporaneo per memorizzare i dati degli archi
    size_t temp_capacity = 100; // Capacità iniziale, aumenta dinamicamente se necessario
    size_t temp_edge_count = 0;
    Edge* temp_edges = malloc(temp_capacity * sizeof(Edge));

    if (!temp_edges) {
        fprintf(stderr, "Error allocating memory for edges\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }

    size_t node1, node2;
    double pesett;

    if (!peso) { // Se non è presente il peso, legge due alla volta
        while (fscanf(input, "%zu %zu", &node1, &node2) == 2) {
            if (temp_edge_count >= temp_capacity) {
                // Aumenta la capacità dell'array temporaneo
                temp_capacity *= 2;
                temp_edges = realloc(temp_edges, temp_capacity * sizeof(Edge));
                if (!temp_edges) {
                    fprintf(stderr, "Error reallocating memory for edges\n");
                    MPI_Abort(MPI_COMM_WORLD, 1);
                }
            }
            temp_edges[temp_edge_count].start = node1;
            temp_edges[temp_edge_count].end = node2;
            temp_edge_count++;
        }
    } else { // Se è presente il peso, legge tre alla volta
        while (fscanf(input, "%zu %zu %lf", &node1, &node2, &pesett) == 3) {
            if (temp_edge_count >= temp_capacity) {
                // Aumenta la capacità dell'array temporaneo
                temp_capacity *= 2;
                temp_edges = realloc(temp_edges, temp_capacity * sizeof(Edge));
                if (!temp_edges) {
                    fprintf(stderr, "Error reallocating memory for edges\n");
                    MPI_Abort(MPI_COMM_WORLD, 1);
                }
            }
            temp_edges[temp_edge_count].start = node1;
            temp_edges[temp_edge_count].end = node2;
            temp_edge_count++;
        }
    }

    fclose(input);

    graph.nodes = malloc(graph.node_count * sizeof(Node));
    if (!graph.nodes) {
        fprintf(stderr, "Error allocating memory for nodes\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }

    graph.edges = malloc(temp_edge_count * sizeof(Edge));
    if (!graph.edges) {
        fprintf(stderr, "Error allocating memory for edges\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }

    // Inizializza le posizioni dei nodi
    for (size_t i = 0; i < graph.node_count; i++) {
        graph.nodes[i].x = cos((2*PI*i)/graph.node_count);
        graph.nodes[i].y = sin((2*PI*i)/graph.node_count);
    }

    memcpy(graph.edges, temp_edges, temp_edge_count * sizeof(Edge));
    graph.edge_count = temp_edge_count;

    free(temp_edges);
    return graph;
}

void calculateRepulsion(Force* net_forces, SimpleGraph* graph, size_t start, size_t end) {
    for (size_t i = start; i < end; i++) {
        for (size_t j = 0; j < graph->node_count; j++) {
            if (i != j) {
                double dx = graph->nodes[i].x - graph->nodes[j].x;
                double dy = graph->nodes[i].y - graph->nodes[j].y;
                double distance = sqrt(dx * dx + dy * dy);

                if (distance < MIN_DISTANCE) {
                    distance = MIN_DISTANCE;
                }

                double force_magnitude = k * k / distance;
                double force_x = force_magnitude * (dx / distance);
                double force_y = force_magnitude * (dy / distance);

                net_forces[i].x += force_x;
                net_forces[i].y += force_y;

            }
        }
    }
}

void calculateAttraction(Force* net_forces, SimpleGraph* graph, size_t start, size_t end) {
    for (size_t i = start; i < end; i++) {
        size_t node1 = graph->edges[i].start;
        size_t node2 = graph->edges[i].end;
        double dx = graph->nodes[node2].x - graph->nodes[node1].x;
        double dy = graph->nodes[node2].y - graph->nodes[node1].y;
        double distance = sqrt(dx * dx + dy * dy);
        double force = distance * distance / k;
        if (distance < MIN_DISTANCE) {
            distance = MIN_DISTANCE;
        }
        net_forces[node1].x += force * dx / distance;
        net_forces[node1].y += force * dy / distance;
        net_forces[node2].x -= force * dx / distance;
        net_forces[node2].y -= force * dy / distance;

    }
}

Force* initializeForceVector(SimpleGraph graph) {
    Force* net_forces = malloc(graph.node_count * sizeof(Force));
    if (!net_forces) {
        fprintf(stderr, "Error allocating memory for forces\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }
    for (size_t i = 0; i < graph.node_count; i++) {
        net_forces[i].x = 0;
        net_forces[i].y = 0;
    }
    return net_forces;
}

void moveNodes(Force* net_forces, SimpleGraph* graph, size_t start, size_t end) {
    for (size_t i = start; i < end; i++) {
        double dx = net_forces[i].x;
        double dy = net_forces[i].y;


        double displacement = sqrt(dx * dx + dy * dy);
        if (displacement > temperature) {
            dx = dx / displacement * temperature;
            dy = dy / displacement * temperature;
        }

        graph->nodes[i].x += dx;
        graph->nodes[i].y += dy;
    }
}

void getMaxNodeDimensions(SimpleGraph* graph, double* maxX, double* maxY) {
    *maxX = 0.0;
    *maxY = 0.0;

    for (size_t i = 0; i < graph->node_count; i++) {
        if (fabs(graph->nodes[i].x) > *maxX) {
            *maxX = fabs(graph->nodes[i].x);
        }
        if (fabs(graph->nodes[i].y) > *maxY) {
            *maxY = fabs(graph->nodes[i].y);
        }
    }
}
