/*
 * Scenario 4b: Array vs Linked List Traversal
 * ============================================
 * Arrays have excellent cache locality; linked lists do not.
 * This demonstrates why "O(n) vs O(n)" can have very different constants.
 *
 * Compile: gcc -O2 -o list_vs_array list_vs_array.c
 */

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

#define N 10000000  /* 10 million elements */

struct node {
    int value;
    struct node *next;
};

double get_time(void) {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec + ts.tv_nsec / 1e9;
}

/* Sum array elements */
long sum_array(int *arr, int n) {
    long sum = 0;
    for (int i = 0; i < n; i++) {
        sum += arr[i];
    }
    return sum;
}

/* Sum linked list elements */
long sum_list(struct node *head) {
    long sum = 0;
    struct node *curr = head;
    while (curr != NULL) {
        sum += curr->value;
        curr = curr->next;
    }
    return sum;
}

/* Create array */
int *create_array(int n) {
    int *arr = malloc(n * sizeof(int));
    if (!arr) {
        perror("malloc array");
        exit(1);
    }
    for (int i = 0; i < n; i++) {
        arr[i] = i % 100;
    }
    return arr;
}

/* Create linked list - nodes allocated sequentially (best case for list) */
struct node *create_list_sequential(int n) {
    struct node *nodes = malloc(n * sizeof(struct node));
    if (!nodes) {
        perror("malloc list");
        exit(1);
    }
    
    for (int i = 0; i < n - 1; i++) {
        nodes[i].value = i % 100;
        nodes[i].next = &nodes[i + 1];
    }
    nodes[n - 1].value = (n - 1) % 100;
    nodes[n - 1].next = NULL;
    
    return nodes;
}

/* Create linked list - nodes allocated randomly (worst case for cache) */
struct node *create_list_scattered(int n) {
    /* Allocate nodes individually to scatter them in memory */
    struct node **nodes = malloc(n * sizeof(struct node *));
    if (!nodes) {
        perror("malloc");
        exit(1);
    }
    
    /* Allocate each node separately */
    for (int i = 0; i < n; i++) {
        nodes[i] = malloc(sizeof(struct node));
        if (!nodes[i]) {
            perror("malloc node");
            exit(1);
        }
        nodes[i]->value = i % 100;
    }
    
    /* Shuffle the order (Fisher-Yates) */
    srand(42);
    for (int i = n - 1; i > 0; i--) {
        int j = rand() % (i + 1);
        struct node *tmp = nodes[i];
        nodes[i] = nodes[j];
        nodes[j] = tmp;
    }
    
    /* Link them in shuffled order */
    for (int i = 0; i < n - 1; i++) {
        nodes[i]->next = nodes[i + 1];
    }
    nodes[n - 1]->next = NULL;
    
    struct node *head = nodes[0];
    free(nodes);  /* Free the pointer array, not the nodes */
    return head;
}

void free_scattered_list(struct node *head) {
    while (head != NULL) {
        struct node *next = head->next;
        free(head);
        head = next;
    }
}

int main(void) {
    printf("Comparing array vs linked list traversal (%d elements)\n\n", N);

    double start, elapsed;
    long result;

    /* Array */
    printf("Creating array...\n");
    int *arr = create_array(N);
    
    start = get_time();
    result = sum_array(arr, N);
    elapsed = get_time() - start;
    printf("Array sum:             %ld in %.4f seconds\n", result, elapsed);
    double array_time = elapsed;
    free(arr);

    /* Sequential linked list (best case for list) */
    printf("\nCreating sequential linked list...\n");
    struct node *list_seq = create_list_sequential(N);
    
    start = get_time();
    result = sum_list(list_seq);
    elapsed = get_time() - start;
    printf("List sum (sequential): %ld in %.4f seconds\n", result, elapsed);
    double list_seq_time = elapsed;
    free(list_seq);

    /* Scattered linked list (worst case for cache) */
    printf("\nCreating scattered linked list (this takes a while)...\n");
    struct node *list_scat = create_list_scattered(N);
    
    start = get_time();
    result = sum_list(list_scat);
    elapsed = get_time() - start;
    printf("List sum (scattered):  %ld in %.4f seconds\n", result, elapsed);
    double list_scat_time = elapsed;
    free_scattered_list(list_scat);

    printf("\n--- Summary ---\n");
    printf("Array:              %.4fs (baseline)\n", array_time);
    printf("List (sequential):  %.4fs (%.1fx slower)\n", 
           list_seq_time, list_seq_time / array_time);
    printf("List (scattered):   %.4fs (%.1fx slower)\n", 
           list_scat_time, list_scat_time / array_time);

    printf("\nTo see cache behavior:\n");
    printf("  perf stat -e cache-misses,cache-references ./list_vs_array\n");

    return 0;
}
