/* $Id$ */
/* Copyright (c) 2006-2022 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Devel cpp */
/* This program 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, version 3 of the License.
 *
 * This program 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 program.  If not, see <http://www.gnu.org/licenses/>. */



#include <sys/stat.h>
#include <libgen.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "CPP.h"
#include "common.h"


/* Cpp */
/* private */
/* types */
struct _CppDefine /* FIXME use a hash table */
{
	char * name;
	char * value;
};


/* public */
/* functions */
/* cpp_new */
Cpp * cpp_new(CppPrefs * prefs)
{
	Cpp * cpp;
	String * p;
	int r = 0;

	if((cpp = object_new(sizeof(*cpp))) == NULL)
		return NULL;
	memset(cpp, 0, sizeof(*cpp));
	cpp->options = prefs->options;
	cpp->parser = cppparser_new(cpp, NULL, prefs->filename,
			prefs->filters);
	if((p = string_new(prefs->filename)) != NULL)
	{
		r |= cpp_path_add(cpp, dirname(p)); /* FIXME inclusion order */
		string_delete(p);
	}
	if((p = getcwd(NULL, 0)) != NULL)
	{
		r |= cpp_path_add(cpp, p);
		free(p);
	}
	else
		error_set("%s%s", "getcwd: ", strerror(errno));
	if(r != 0 || cpp->parser == NULL || cpp->paths_cnt != 2)
	{
		cpp_delete(cpp);
		return NULL;
	}
	return cpp;
}


/* cpp_new_string */
Cpp * cpp_new_string(CppPrefs * prefs, char const * string)
{
	/* FIXME really implement */
	Cpp * cpp;
	String * p;
	int r = 0;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s()\n", __func__);
#endif
	if((cpp = object_new(sizeof(*cpp))) == NULL)
		return NULL;
	memset(cpp, 0, sizeof(*cpp));
	cpp->options = prefs->options;
	cpp->parser = cppparser_new_string(cpp, NULL, string, prefs->filters);
	if((p = string_new(prefs->filename)) != NULL)
	{
		r |= cpp_path_add(cpp, dirname(p)); /* FIXME inclusion order */
		string_delete(p);
	}
	if((p = getcwd(NULL, 0)) != NULL)
	{
		r |= cpp_path_add(cpp, p);
		free(p);
	}
	else
		error_set("%s%s", "getcwd: ", strerror(errno));
	if(r != 0 || cpp->parser == NULL || cpp->paths_cnt != 2)
	{
		cpp_delete(cpp);
		return NULL;
	}
	return cpp;
}


/* cpp_delete */
void cpp_delete(Cpp * cpp)
{
	size_t i;

	for(i = 0; i < cpp->defines_cnt; i++)
	{
		free(cpp->defines[i].name);
		free(cpp->defines[i].value);
	}
	free(cpp->defines);
	for(i = 0; i < cpp->paths_cnt; i++)
		free(cpp->paths[i]);
	free(cpp->paths);
	if(cpp->parser != NULL)
		cppparser_delete(cpp->parser);
	if(cpp->scopes != NULL)
		free(cpp->scopes);
	object_delete(cpp);
}


/* accessors */
/* cpp_get_filename */
char const * cpp_get_filename(Cpp * cpp)
{
	return cppparser_get_filename(cpp->parser);
}


/* useful */
/* cpp_define_add */
int cpp_define_add(Cpp * cpp, char const * name, char const * value)
{
	size_t i;
	CppDefine * p;
	char const * q;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(cpp, \"%s\", \"%s\")\n", __func__, name,
			value);
#endif
	if(name == NULL || name[0] == '\0')
		return error_set_code(1, "%s", strerror(EINVAL));
	if(value == NULL)
		value = "";
	for(i = 0; i < cpp->defines_cnt; i++)
		if(strcmp(cpp->defines[i].name, name) == 0)
			break;
	if(i != cpp->defines_cnt)
		return error_set_code(1, "%s is already defined", name);
	if(value != NULL) /* XXX not sure if this should be done here */
		for(q = cpp_define_get(cpp, value); q != NULL;
				q = cpp_define_get(cpp, value))
			value = q; /* XXX may deadloop */
	if((p = realloc(cpp->defines, sizeof(*p) * (cpp->defines_cnt + 1)))
			== NULL)
		return error_set_code(1, "%s", strerror(errno));
	cpp->defines = p;
	p = &p[cpp->defines_cnt];
	p->name = string_new(name);
	p->value = string_new(value);
	if(p->name == NULL || p->value == NULL)
	{
		string_delete(p->name);
		string_delete(p->value);
		return 1;
	}
	cpp->defines_cnt++;
	return 0;
}


/* cpp_define_get */
char const * cpp_define_get(Cpp * cpp, char const * name)
{
	size_t i;

	for(i = 0; i < cpp->defines_cnt; i++)
		if(strcmp(cpp->defines[i].name, name) == 0)
			return cpp->defines[i].value;
	return NULL;
}


/* cpp_define_remove */
int cpp_define_remove(Cpp * cpp, char const * name)
	/* FIXME should verify validity of name */
{
	size_t i;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(cpp, \"%s\")\n", __func__, name);
#endif
	for(i = 0; i < cpp->defines_cnt; i++)
		if(strcmp(cpp->defines[i].name, name) == 0)
			break;
	if(i == cpp->defines_cnt)
		return error_set_code(1, "%s is not defined", name);
	free(cpp->defines[i].name);
	free(cpp->defines[i].value);
	cpp->defines_cnt--;
	for(; i < cpp->defines_cnt; i++)
	{
		cpp->defines[i].name = cpp->defines[i + 1].name;
		cpp->defines[i].value = cpp->defines[i + 1].value;
	}
	return 0;
}


/* cpp_path_add */
int cpp_path_add(Cpp * cpp, char const * path)
{
	char ** p;

#ifdef DEBUG
	fprintf(stderr, "DEBUG: %s(cpp, \"%s\")\n", __func__, path);
#endif
	if((p = realloc(cpp->paths, sizeof(*p) * (cpp->paths_cnt + 1))) == NULL)
		return -error_set_code(1, "%s", strerror(errno));
	cpp->paths = p;
	if((p[cpp->paths_cnt] = strdup(path)) == NULL)
		return -error_set_code(1, "%s", strerror(errno));
	cpp->paths_cnt++;
	return 0;
}


/* cpp_path_lookup */
static char * _lookup_error(char const * path, int system);

String * cpp_path_lookup(Cpp * cpp, char const * filename, int system)
{
	size_t len = strlen(filename);
	size_t i;
	char * buf = NULL;
	char * p;
	struct stat st;

	for(i = 0; i < cpp->paths_cnt; i++)
	{
		if((p = realloc(buf, strlen(cpp->paths[i]) + len + 2)) == NULL)
		{
			error_set("%s", strerror(errno));
			free(buf);
			return NULL;
		}
		buf = p;
		sprintf(buf, "%s/%s", cpp->paths[i], filename);
#ifdef DEBUG
		fprintf(stderr, "DEBUG: stat(\"%s\", %p)\n", buf, (void *)&st);
#endif
		if(stat(buf, &st) == 0)
			return buf;
		if(errno != ENOENT)
			break;
	}
	free(buf);
	return _lookup_error(filename, system);
}

static char * _lookup_error(char const * filename, int system)
{
	char lquote = system ? '<' : '"';
	char rquote = system ? '>' : '"';

	error_set("%s%c%s%c%s%s", "Cannot include ", lquote, filename, rquote,
			": ", strerror(errno));
	return NULL;
}
