/* EXERCISE 02 — BROKEN ON PURPOSE. Do not copy this shape into real code.
 *
 * A map tile cache. Tiles arrive, are decoded into 32 KiB buffers, and are
 * evicted when the viewport moves. Eviction is interleaved: the tiles that
 * leave the viewport are scattered through the allocation order, not grouped
 * at the end of it.
 *
 * Symptom as reported by the field: "we evict half the cache and the process
 * footprint does not move at all."
 *
 * Build and run:
 *   clang -O2 -g -Wall -Wextra tilecache.c -o /tmp/tiles_broken
 *   /tmp/tiles_broken
 *
 * Nothing here leaks. Every byte is freed before exit. That is exactly what
 * makes the symptom confusing, and it is the point of the exercise.
 *
 * Every measurement is printed as key=value so a script can assert on it.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <malloc/malloc.h>
#include <mach/mach.h>

#define TILES      20000
#define TILE_BYTES (32 * 1024)
#define WATCHDOG_S 60

/* ------------------------------------------------------------------ safety */
/* A watchdog thread bounds this fixture even if it is run on a machine where
 * the allocation pattern behaves very differently. It reports through write(2)
 * and leaves through _exit(2) deliberately: a wedged process may hold a lock
 * that stdio or an atexit handler would need. */
static void *watchdog(void *unused) {
    (void)unused;
    sleep(WATCHDOG_S);
    const char m[] = "WATCHDOG: no progress within budget - forcing _exit(75).\n";
    ssize_t ignored = write(2, m, sizeof m - 1); (void)ignored;
    _exit(75);
}

/* --------------------------------------------------------------- footprint */
/* phys_footprint is the number macOS itself uses for per-process memory
 * limits. It is neither virtual size nor resident size. */
static double footprint_mb(void) {
    struct task_vm_info info;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    if (task_info(mach_task_self(), TASK_VM_INFO,
                  (task_info_t)&info, &count) != KERN_SUCCESS) return -1.0;
    return info.phys_footprint / 1048576.0;
}

static char *tiles[TILES];

static unsigned long long checksum(void) {
    unsigned long long sum = 0;
    for (int i = 0; i < TILES; i++)
        if (tiles[i]) sum += (unsigned char)tiles[i][0];
    return sum;
}

int main(void) {
    pthread_t wd;
    pthread_create(&wd, NULL, watchdog, NULL);
    pthread_detach(wd);

    double base = footprint_mb();
    printf("phase=start footprintMB=%.1f\n", base);

    /* Fill the cache. Every tile is touched, so every page is real. */
    for (int i = 0; i < TILES; i++) {
        tiles[i] = malloc(TILE_BYTES);
        if (!tiles[i]) { fprintf(stderr, "out of memory\n"); return 1; }
        memset(tiles[i], (i & 0x7f) + 1, TILE_BYTES);
    }
    double full = footprint_mb();
    unsigned long long sum = checksum();
    printf("phase=full footprintMB=%.1f requestedMB=%.1f\n",
           full, (double)TILES * TILE_BYTES / 1048576.0);

    /* Evict half the cache — interleaved, the way a moving viewport does it.
     * Every odd-indexed tile survives, so no large contiguous span of the
     * heap becomes free. */
    for (int i = 0; i < TILES; i += 2) { free(tiles[i]); tiles[i] = NULL; }
    double half = footprint_mb();
    printf("phase=evicted-half footprintMB=%.1f\n", half);

    /* Ask the allocator, explicitly, to give memory back. */
    size_t relieved = malloc_zone_pressure_relief(NULL, 0);
    double relief = footprint_mb();
    printf("phase=after-pressure-relief footprintMB=%.1f reliefBytes=%zu\n",
           relief, relieved);

    /* Free the rest. */
    for (int i = 1; i < TILES; i += 2) { free(tiles[i]); tiles[i] = NULL; }
    double empty = footprint_mb();
    printf("phase=empty footprintMB=%.1f\n", empty);

    double grewMB      = full  - base;
    double releasedMB  = full  - half;
    double retainedMB  = half  - base;

    printf("checksum=%llu\n", sum);
    printf("peakMB=%.1f\n", full);
    printf("grewMB=%.1f\n", grewMB);
    printf("releasedByHalfEvictionMB=%.1f\n", releasedMB);
    printf("stillHeldAfterHalfEvictionMB=%.1f\n", retainedMB);
    printf("finalMB=%.1f\n", empty);
    printf("recoveredFractionAtHalf=%.3f\n", grewMB > 0 ? releasedMB / grewMB : 0.0);
    return 0;
}
