/*
    load_cache - cache loading/parsing files
    Copyright (C) 2020  Tibor 'Igor2' Palinkas

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    Project page:  http://repo.hu/projects/libminuid
    Source code:   svn://repo.hu/libminuid/trunk
    Author:        emailto: libminuid [at] igor2.repo.hu
*/

#include <assert.h>
#include <genht/htsp.h>
#include <genht/hash.h>
#include <genvector/vtp0.h>

/* These are required for stat() and are C89. */
#include <sys/types.h>
#include <sys/stat.h>

#include "load_cache.h"

char *ldch_strdup(const char *s)
{
	int l = strlen(s);
	char *o = malloc(l+1);
	return memcpy(o, s, l+1);
}

double ldch_file_mtime(const char *path)
{
	struct stat st;
	if (stat(path, &st) != 0)
		return -1;
	return st.st_mtime;
}


void ldch_init(ldch_ctx_t *ctx)
{
	memset(ctx, 0, sizeof(ldch_ctx_t));
	htsp_init(&ctx->low_parsers, strhash, strkeyeq);
	htsp_init(&ctx->high_parsers, strhash, strkeyeq);
	htsp_init(&ctx->files, strhash, strkeyeq);
	ctx->next_low_uid = 1;
}

ldch_low_parser_t *ldch_reg_low_parser(ldch_ctx_t *ctx, const char *name)
{
	ldch_low_parser_t *p;

	p = htsp_get(&ctx->low_parsers, name);
	if (p != NULL)
		return NULL;

	p = calloc(sizeof(ldch_low_parser_t), 1);
	p->ctx = ctx;
	p->name = ldch_strdup(name);
	p->uid = ctx->next_low_uid++;
	htsp_set(&ctx->low_parsers, p->name, p);
	return p;
}

ldch_high_parser_t *ldch_reg_high_parser(ldch_ctx_t *ctx, const char *name)
{
	ldch_high_parser_t *p;

	p = htsp_get(&ctx->high_parsers, name);
	if (p != NULL)
		return NULL;

	p = calloc(sizeof(ldch_high_parser_t), 1);
	p->ctx = ctx;
	p->name = ldch_strdup(name);
	htsp_set(&ctx->high_parsers, p->name, p);
	return p;
}

ldch_low_parser_t *ldch_lookup_low_parser(ldch_ctx_t *ctx, const char *name)
{
	return htsp_get(&ctx->low_parsers, name);
}

ldch_high_parser_t *ldch_lookup_high_parser(ldch_ctx_t *ctx, const char *name)
{
	return htsp_get(&ctx->high_parsers, name);
}

void ldch_data_free(ldch_ctx_t *ctx, ldch_data_t *data)
{
	if ((data->high_parser != NULL) && (data->high_parser->free_payload != NULL))
		data->high_parser->free_payload(data);
	free(data);
}

static void ldch_free_file_low(ldch_ctx_t *ctx, ldch_file_t *file)
{
	long n;

	for(n = 0; n < file->data.used; n++)
		if (file->data.array[n] != NULL)
			ldch_data_free(ctx, file->data.array[n]);

	vtp0_uninit(&file->data);

	if (file->low_parser->free_payload != NULL)
		file->low_parser->free_payload(file->low_parser, file);
}

static void ldch_free_file(ldch_ctx_t *ctx, ldch_file_t *file)
{
	ldch_free_file_low(ctx, file);
	free(file->load_name);
	free(file->real_name);
	free(file);
}


static char *get_real_name(ldch_ctx_t *ctx, const char *load_name, ldch_low_parser_t *low, ldch_high_parser_t *high, void *low_call_ctx, void *high_call_ctx)
{
	if (ctx->load_name_to_real_name != NULL)
		return ctx->load_name_to_real_name(ctx, load_name, low, high, low_call_ctx, high_call_ctx);
	return ldch_strdup(load_name);
}

ldch_data_t *ldch_load_(ldch_ctx_t *ctx, const char *load_name, ldch_low_parser_t *low, ldch_high_parser_t *high, void *low_call_ctx, void *high_call_ctx)
{
	ldch_file_t *file = htsp_get(&ctx->files, load_name);
	void **d;
	ldch_data_t *data;

	/* get low level */
	if (file == NULL) {
		char *real_name = get_real_name(ctx, load_name, low, high, low_call_ctx, high_call_ctx);
		if (real_name == NULL)
			return NULL;
		file = low->parse_alloc(low, low_call_ctx, real_name);
		if (file == NULL) {
			free(real_name);
			return NULL;
		}
		file->real_name = real_name;
		file->load_name = ldch_strdup(load_name);
		if (low->parse(low, low_call_ctx, file, file->real_name) != 0) {
			ldch_unload(ctx, file);
			return NULL;
		}
		file->last_low_parsed = ldch_file_mtime(file->real_name);
		htsp_set(&ctx->files, file->load_name, file);
	}
	else {
		double fmd = ldch_file_mtime(file->real_name);
		if (fmd < 0)
			return NULL;
		if (low->uid != file->low_parser->uid)
			return NULL; /* can't parse the same file twice, with different low level */
		if (fmd > file->last_low_parsed) { /* changed on disk */
			ldch_free_file_low(ctx, file);
			low->parse(low, low_call_ctx, file, file->real_name);
		}
	}

	/* get high level parsing donw */
	d = vtp0_get(&file->data, low->uid, 1);
	if (d == NULL)
		return NULL; /* allocation error */

	if (*d == NULL) {
		data = high->parse(high, high_call_ctx, file);
		if (data == NULL)
			return NULL;
		*d = data;
		data->high_parser = high;
	}
	else
		data = *d;

	return data;
}


ldch_data_t *ldch_load(ldch_ctx_t *ctx, const char *load_name, const char *lows, const char *highs, void *low_call_ctx, void *high_call_ctx)
{
	ldch_low_parser_t *low;
	ldch_high_parser_t *high;

	low = ldch_lookup_low_parser(ctx, lows);
	if (low == NULL)
		return NULL;

	high = ldch_lookup_high_parser(ctx, highs);
	if (high == NULL)
		return NULL;

	return ldch_load_(ctx, load_name, low, high, low_call_ctx, high_call_ctx);
}

void ldch_unload(ldch_ctx_t *ctx, ldch_file_t *file)
{
	htsp_pop(&ctx->files, file->load_name);
	ldch_free_file(ctx, file);
}

void ldch_unreg_low_parser(ldch_low_parser_t *low)
{
	if (low->unreg != NULL)
		low->unreg(low);
	free(low->name);
	free(low);
}

void ldch_unreg_high_parser(ldch_ctx_t *ctx, ldch_high_parser_t *high)
{
	if (high->unreg != NULL)
		high->unreg(high);
	free(high->name);
	free(high);
}

void ldch_uninit(ldch_ctx_t *ctx)
{
	htsp_entry_t *e;

	for(e = htsp_first(&ctx->files); e != NULL; e = htsp_next(&ctx->files, e))
		ldch_unload(ctx, e->value);
	htsp_uninit(&ctx->files);

	for(e = htsp_first(&ctx->low_parsers); e != NULL; e = htsp_next(&ctx->low_parsers, e))
		ldch_unreg_low_parser(e->value);
	htsp_uninit(&ctx->low_parsers);

	for(e = htsp_first(&ctx->high_parsers); e != NULL; e = htsp_next(&ctx->high_parsers, e))
		ldch_unreg_high_parser(ctx, e->value);
	htsp_uninit(&ctx->high_parsers);
}

ldch_data_t *ldch_data_alloc(ldch_file_t *file, ldch_high_parser_t *high_parser, size_t payload_size)
{
	ldch_data_t *data = calloc(sizeof(ldch_data_t) - 1 + payload_size, 1);
	data->parent = file;
	data->high_parser = high_parser;
	data->payload_size = payload_size;
	return data;
}

ldch_file_t *ldch_file_alloc(ldch_ctx_t *ctx, ldch_low_parser_t *low_parser, size_t low_payload_size)
{
	ldch_file_t *file = calloc(sizeof(ldch_file_t) - 1 + low_payload_size, 1);
	file->parent = ctx;
	file->low_parser = low_parser;
	file->low_payload_size = low_payload_size;
	return file;
}

