/* SPDX-License-Identifier: copyleft-next-0.3.1 */
/* Copyright 2024 Kim Kuparinen < kimi.h.kuparinen@gmail.com > */

/**
 * @file compiler.c
 *
 * Top level compiler implementation.
 */

#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <stdlib.h>
#include <limits.h>
#include <errno.h>

#include <fwd/compiler.h>
#include <fwd/analyze.h>
#include <fwd/parser.h>
#include <fwd/debug.h>
#include <fwd/scope.h>
#include <fwd/lower.h>

/**
 * Read whole file into a buffer and return pointer to buffer.
 * Possibly kind of silly to have both \p file and \p f.
 * Apparently there's no standardized way to get the file name of a
 * file pointer.
 *
 * @param file Name of file to read.
 * @param f File pointer.
 * @return Pointer to buffer with file contents.
 */
static char *read_file(const char *file, FILE *f)
{
	fseek(f, 0, SEEK_END);
	/** @todo check how well standardized this actually is */
	long s = ftell(f);
	if (s == LONG_MAX) {
		error("%s might be a directory", file);
		return NULL;
	}

	fseek(f, 0, SEEK_SET);

	char *buf = malloc((size_t)(s + 1));
	if (!buf)
		return NULL;

	fread(buf, (size_t)(s + 1), 1, f);
	/* remember terminating null */
	buf[s] = 0;
	return buf;
}

/**
 * Helper for process_file(), actually processes the file
 * after process_file() has done path lookups and working directory
 * changes and whatnot.
 *
 * @param parent Parent file context. \c NULL if root file.
 * @param public \c 1 if file is being imported publicly, \c 0 otherwise.
 * @param file File name to process.
 * @return \c 0 if processing was succesful, non-zero value otherwise.
 */
static int process(struct scope **parent, const char *file)
{
	FILE *f = fopen(file, "rb");
	if (!f) {
		error("failed opening %s: %s\n", file, strerror(errno));
		return -1;
	}

	const char *buf = read_file(file, f);
	fclose(f);

	if (!buf)
		return -1;

	struct parser *p = create_parser();
	if (!p) {
		free((void *)buf);
		return -1;
	}

	parse(p, file, buf);
	struct ast *tree = p->tree;
	bool failed = p->failed;
	destroy_parser(p);

	if (failed) {
		free((void*)buf);
		return -1;
	}

	ast_dump_list(0, tree);

	struct scope *scope = create_scope();
	if (!scope) {
		free((void *)buf);
		return -1;
	}

	scope->fctx.fbuf = buf;
	scope->fctx.fname = strdup(file);
	if (*parent)
		scope_add_scope(*parent, scope);
	else
		*parent = scope;

	if (analyze_root(scope, tree))
		return -1;

	return 0;
}

int compile(const char *input) {
	int ret = -1;
	struct scope *root = NULL;
	if (process(&root, input)) {
		destroy_scope(root);
		destroy_allocs();
		error("processing of %s stopped due to errors", input);
		return ret;
	}

	if ((ret = lower(root))) {
		destroy_scope(root);
		destroy_allocs();
		error("lowering of %s stopped due to errors", input);
		return ret;
	}

	destroy_scope(root);
	destroy_allocs();
	return 0;
}