// vim: set ts=8 sw=8 noet tw=80:

/**
 * gallery.c - Claudio Maggioni
 *
 * External sources of information used:
 * - man section 3 for the several library functions;
 * - libpng.org documentation of PNG header an IHDR;
 * - https://codereview.stackexchange.com/q/149751 for inspiration of ntohl
 *   function implementation.
 */

#include "gallery.h"
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

struct image {
	uint32_t width;
	uint32_t height;
	char * name;
};

struct image_ll {
	struct image_ll* prev;
	struct image first;
	struct image_ll* next;
};

struct gallery {
	size_t size;
	struct image_ll* images;
};

static struct image_ll* find_by_name(struct gallery*, const char*);
static uint32_t my_ntohl (uint32_t);
static struct image_ll* ll_remove(struct gallery*, struct image_ll*);

const size_t INIT_CAP = 128;
const uint8_t HEADER[8] = { '\211', 'P', 'N', 'G', '\r', '\n', '\032', '\n' };

struct image_ll* find_by_name(struct gallery* g, const char* name) {
	for (struct image_ll* i = g->images; i; i = i->next) {
		if (i->first.name == name || !strcmp(i->first.name, name)) {
			return i;
		}
	}

	return NULL;
}

struct image_ll* ll_remove(struct gallery* g, struct image_ll* i) {
	if (i->next) {
		i->next->prev = i->prev;
	}

	if (i->prev) {
		i->prev->next = i->next;
	} else {
		g->images = i->next;
	}

	struct image_ll* n = i->next;
	free(i);
	g->size--;

	return n;
}

// inspired by https://codereview.stackexchange.com/q/149751
uint32_t my_ntohl (const uint32_t nlong) {
	uint8_t* data = (uint8_t*) &nlong;

	return ((uint32_t) data[3]) |
		((uint32_t) data[2] << 8) |
		((uint32_t) data[1] << 16) |
		((uint32_t) data[0] << 24);
}

struct gallery *gallery_new() {
	struct gallery* g = malloc(sizeof(struct gallery));

	if (!g) {
		return NULL;
	}

	g->images = NULL;
	g->size = 0;
	return g;
}

void gallery_destroy(struct gallery *g) {
	while (g->images) {
		struct image_ll* i = g->images;
		g->images = g->images->next;
		free(i);
	}
	free(g);
}

/*
 * remark: gallery_add(...) returns undocumented error code -3 when the malloc
 * for the new image data struct fails
 */
int gallery_add(struct gallery *g, char* filename) {
	if (find_by_name(g, filename)) {
		return -1;
	}

	FILE* f = fopen(filename, "r");

	if (!f) {
		return -2;
	}

	uint8_t file_head[8];
	if (fread(&file_head, 1, 8, f) != 8) {
		fclose(f);
		return -2;
	}

	if (memcmp(file_head, HEADER, sizeof(HEADER))) {
		return -2;
	}

	if (fseek(f, 8, SEEK_CUR) == -1) {
		perror("fseek failed");
		fclose(f);
		return -2;
	}

	struct image_ll* new = malloc(sizeof(struct image_ll));
	if (!new) {
		fclose(f);
		return -3;
	}

	if (fread(&new->first, 8, 1, f) != 1) {
		free(new);
		fclose(f);
		return -2;
	}

	new->first.width = my_ntohl(new->first.width);
	new->first.height = my_ntohl(new->first.height);
	new->first.name = filename;
	new->prev = NULL;
	new->next = g->images;

	if (g->images) {
		g->images->prev = new;
	}

	g->images = new;
	g->size++;

	return 1;
}

int gallery_rm(struct gallery* g, char* filename) {
	struct image_ll* i = find_by_name(g, filename);

	if (!i) {
		return 0;
	}

	ll_remove(g, i);
	return 1;
}

int gallery_count(struct gallery* g) {
	return g->size;
}

int gallery_filter(struct gallery *g, int (*f)(char *name, int w, int h)) {
	for (struct image_ll* i = g->images; i;) {
		if (!f(i->first.name, i->first.width, i->first.height)) {
			i = ll_remove(g, i);
		} else {
			i = i->next;
		}
	}

	return g->size;
}

char* gallery_bestfit(struct gallery* g, int max_width, int max_height) {
	struct image* best = NULL;

	for (struct image_ll* i = g->images; i; i = i->next) {
		uint32_t w = i->first.width;
		uint32_t h = i->first.height;
		if (w > max_width || h > max_height) {
			continue;
		}

		if (!best || w * h > best->width * best->height) {
			best = &i->first;
		}
	}

	return best ? best->name : NULL;
}

