/*
 * Copyright 1998 Bertho A. Stultiens (BS)
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "wrc.h"
#include "utils.h"
#include "preproc.h"
#include "parser.h"


extern void set_pp_ignore(int);	/* From parser.l */

static char *current_define;

#define HASHKEY		2039
static struct pp_entry *pp_defines[HASHKEY];

#define MAXIFSTACK	64
static struct if_state ifstack[MAXIFSTACK];
static int ifstackidx = 0;

#if 0
void pp_status(void)
{
	int i;
	int sum;
	int total = 0;
	struct pp_entry *ppp;

	printf("Defines statistics:\n");
	for(i = 0; i < HASHKEY; i++)
	{
		sum = 0;
		for(ppp = pp_defines[i]; ppp; ppp = ppp->next)
			sum++;
		total += sum;
		printf("%4d, %3d\n", i, sum);
	}
	printf("Total defines: %d\n", total);
}
#pragma exit pp_status
#endif

/* Don't comment on the hash, its primitive but functional... */
int pp_hash(char *str)
{
	int sum = 0;
	while(*str)
		sum += *str++;
	return sum % HASHKEY;
}

struct pp_entry *pp_lookup(char *ident)
{
	int index = pp_hash(ident);
	struct pp_entry *ppp;
	for(ppp = pp_defines[index]; ppp; ppp = ppp->next)
	{
		if(!strcmp(ident, ppp->ident))
			return ppp;
	}
	return NULL;
}

void set_define(char *name)
{
	current_define = xstrdup(name);
}

void del_define(char *name)
{
	int index;
	struct pp_entry *ppp;

	if((ppp = pp_lookup(name)) == NULL)
	{
		if(pedantic)
			yywarning("%s was not defined", name);
		return;
	}

	index = pp_hash(name);
	if(pp_defines[index] == ppp)
	{
		pp_defines[index] = ppp->next;
		if(pp_defines[index])
			pp_defines[index]->prev = NULL;
	}
	else
	{
		ppp->prev->next = ppp->next;
		if(ppp->next)
			ppp->next->prev = ppp->prev;
	}
	free(ppp);
}

void add_define(char *text)
{
	int len;
	char *cptr;
	int index = pp_hash(current_define);
	struct pp_entry *ppp;
	if(pp_lookup(current_define) != NULL)
	{
		if(pedantic)
			yywarning("Redefinition of %s", current_define);
		del_define(current_define);
	}
	ppp = (struct pp_entry *)xmalloc(sizeof(struct pp_entry));
	ppp->ident = current_define;
	ppp->subst = xstrdup(text);
	ppp->next = pp_defines[index];
	pp_defines[index] = ppp;
	if(ppp->next)
		ppp->next->prev = ppp;
	/* Strip trailing white space from subst text */
	len = strlen(ppp->subst);
	while(len && strchr(" \t\r\n", ppp->subst[len-1]))
	{
		ppp->subst[--len] = '\0';
	}
	/* Strip leading white space from subst text */
	for(cptr = ppp->subst; *cptr && strchr(" \t\r", *cptr); cptr++)
		;
	if(ppp->subst != cptr)
		memmove(ppp->subst, cptr, strlen(cptr)+1);
	if(yydebug)
		printf("Added (%s, %d) <%s> to <%s>\n", input_name, line_number, ppp->ident, ppp->subst);
}

void add_cmdline_define(char *set)
{
	char *cpy = xstrdup(set);	/* Because gcc passes a R/O string */
	char *cptr = strchr(cpy, '=');
	if(cptr)
		*cptr = '\0';
	set_define(cpy);
	add_define(cptr ? cptr+1 : "");
	free(cpy);
}

#if defined(_Windows) || defined(__MSDOS__)
#define INCLUDESEPARATOR	";"
#else
#define INCLUDESEPARATOR	":"
#endif

static char **includepath;
static int nincludepath = 0;

void add_include_path(char *path)
{
	char *tok;
	char *cpy = xstrdup(path);

	tok = strtok(cpy, INCLUDESEPARATOR);
	while(tok)
	{
		char *dir;
		char *cptr;
		if(strlen(tok) == 0)
			continue;
		dir = xstrdup(tok);
		for(cptr = dir; *cptr; cptr++)
		{
			/* Convert to forward slash */
			if(*cptr == '\\')
				*cptr = '/';
		}
		/* Kill eventual trailing '/' */
		if(*(cptr = dir + strlen(dir)-1) == '/')
			*cptr = '\0';

		/* Add to list */
		nincludepath++;
		includepath = (char **)xrealloc(includepath, nincludepath * sizeof(*includepath));
		includepath[nincludepath-1] = dir;
		tok = strtok(NULL, INCLUDESEPARATOR);
	}
	free(cpy);
}

FILE *open_include(const char *name, int search)
{
	char *cpy = xstrdup(name);
	char *cptr;
	FILE *fp;
	int i;

	for(cptr = cpy; *cptr; cptr++)
	{
		/* kill double backslash */
		if(*cptr == '\\' && *(cptr+1) == '\\')
			memmove(cptr, cptr+1, strlen(cptr));
		/* Convert to forward slash */
		if(*cptr == '\\')
			*cptr = '/';
	}

	if(search)
	{
		/* Search current dir and then -I path */
		fp = fopen(name, "rt");
		if(fp)
		{
			if(yydebug)
				printf("Going to include <%s>\n", name);
			free(cpy);
			return fp;
		}
	}
	/* Search -I path */
	for(i = 0; i < nincludepath; i++)
	{
		char *path;
		path = (char *)xmalloc(strlen(includepath[i]) + strlen(cpy) + 2);
		strcpy(path, includepath[i]);
		strcat(path, "/");
		strcat(path, cpy);
		fp = fopen(path, "rt");
		if(fp && yydebug)
			printf("Going to include <%s>\n", path);
		free(path);
		if(fp)
		{
			free(cpy);
			return fp;
		}

	}
	free(cpy);
	return NULL;
}

void push_if(int truecase, int wastrue, int nevertrue)
{
	if(ifstackidx >= MAXIFSTACK-1)
		internal_error(__FILE__, __LINE__, "#if stack overflow");
	ifstack[ifstackidx].current = truecase && !wastrue;
	ifstack[ifstackidx].hasbeentrue = wastrue;
	ifstack[ifstackidx].nevertrue = nevertrue;
	if(nevertrue || !(truecase && !wastrue))
		set_pp_ignore(1);
	if(yydebug)
		printf("push_if: %d %d %d (%d %d %d)\n",
			truecase,
			wastrue,
			nevertrue,
			ifstack[ifstackidx].current,
			ifstack[ifstackidx].hasbeentrue,
			ifstack[ifstackidx].nevertrue);
	ifstackidx++;
}

int pop_if(void)
{
	if(ifstackidx <= 0)
		yyerror("#endif without #if|#ifdef|#ifndef (#if stack underflow)");
	ifstackidx--;
	if(yydebug)
		printf("pop_if: %d %d %d\n",
			ifstack[ifstackidx].current,
			ifstack[ifstackidx].hasbeentrue,
			ifstack[ifstackidx].nevertrue);
	if(ifstack[ifstackidx].nevertrue || !ifstack[ifstackidx].current)
		set_pp_ignore(0);
	return ifstack[ifstackidx].hasbeentrue || ifstack[ifstackidx].current;
}

int isnevertrue_if(void)
{
	return ifstackidx > 0 && ifstack[ifstackidx-1].nevertrue;
}

