#include "image_cache.h"
#include <SDL_image.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>

bool image_resolve_path(const char *docFilePath, const char *imageRef, char *outBuf, int outBufSize) {
    if (!imageRef || imageRef[0] == '\0') return false;
    int n;
    if (imageRef[0] == '/') {
        n = snprintf(outBuf, (size_t)outBufSize, "%s", imageRef);
        return n > 0 && n < outBufSize;
    }
    if (!docFilePath) return false;
    const char *slash = strrchr(docFilePath, '/');
    if (slash) {
        int dirLen = (int)(slash - docFilePath) + 1; /* includes the trailing '/' */
        n = snprintf(outBuf, (size_t)outBufSize, "%.*s%s", dirLen, docFilePath, imageRef);
    } else {
        /* docFilePath has no directory component -- it's a bare filename in the CWD. */
        n = snprintf(outBuf, (size_t)outBufSize, "%s", imageRef);
    }
    return n > 0 && n < outBufSize;
}

typedef struct {
    char *path;
    SDL_Texture *texture;
    int width, height;
    time_t mtime;
    bool loaded; /* true once a load has been attempted for the current mtime (success or fail) */
    bool failed; /* meaningful only when loaded == true */
} ImageCacheEntry;

struct ImageCache {
    ImageCacheEntry *items;
    int count, cap;
};

static char *dup_str(const char *s) {
    size_t n = strlen(s);
    char *copy = malloc(n + 1);
    memcpy(copy, s, n + 1);
    return copy;
}

void image_cache_init(ImageCache **cache) {
    ImageCache *c = malloc(sizeof(ImageCache));
    c->items = NULL;
    c->count = 0;
    c->cap = 0;
    *cache = c;
}

void image_cache_free(ImageCache *cache) {
    if (!cache) return;
    for (int i = 0; i < cache->count; i++) {
        if (cache->items[i].texture) SDL_DestroyTexture(cache->items[i].texture);
        free(cache->items[i].path);
    }
    free(cache->items);
    free(cache);
}

static ImageCacheEntry *find_entry(ImageCache *cache, const char *path) {
    for (int i = 0; i < cache->count; i++) {
        if (strcmp(cache->items[i].path, path) == 0) return &cache->items[i];
    }
    return NULL;
}

static ImageCacheEntry *add_entry(ImageCache *cache, const char *path) {
    if (cache->count >= cache->cap) {
        int newCap = cache->cap > 0 ? cache->cap * 2 : 8;
        cache->items = realloc(cache->items, (size_t)newCap * sizeof(ImageCacheEntry));
        cache->cap = newCap;
    }
    ImageCacheEntry *e = &cache->items[cache->count++];
    memset(e, 0, sizeof(*e));
    e->path = dup_str(path);
    return e;
}

static void load_entry(ImageCacheEntry *e, SDL_Renderer *renderer, time_t mtime) {
    if (e->texture) { SDL_DestroyTexture(e->texture); e->texture = NULL; }
    e->loaded = true;
    e->mtime = mtime;

    SDL_Surface *surface = IMG_Load(e->path);
    if (!surface) { e->failed = true; return; }

    SDL_Texture *tex = SDL_CreateTextureFromSurface(renderer, surface);
    int w = surface->w, h = surface->h;
    SDL_FreeSurface(surface);
    if (!tex) { e->failed = true; return; }

    e->texture = tex;
    e->width = w;
    e->height = h;
    e->failed = false;
}

bool image_cache_get(ImageCache *cache, SDL_Renderer *renderer, const char *resolvedPath,
                      SDL_Texture **outTexture, int *outW, int *outH) {
    struct stat st;
    if (stat(resolvedPath, &st) != 0) {
        ImageCacheEntry *e = find_entry(cache, resolvedPath);
        if (e) {
            if (e->texture) { SDL_DestroyTexture(e->texture); e->texture = NULL; }
            e->loaded = false;
        }
        return false;
    }

    ImageCacheEntry *e = find_entry(cache, resolvedPath);
    if (!e) e = add_entry(cache, resolvedPath);

    if (!e->loaded || e->mtime != st.st_mtime) {
        load_entry(e, renderer, st.st_mtime);
    }

    if (e->failed || !e->texture) return false;
    *outTexture = e->texture;
    *outW = e->width;
    *outH = e->height;
    return true;
}
