#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "obj.h"
/* from names.h */
#define L_C	0		/* first and foremost on UNIX */
#define	L_FORT	1		/* the oldest one */
#define L_MAKE	2		/* Makefiles */
#define L_PLI	3		/* PL/1 */
#define L_MACH	4		/* some kinda assembler */
#define L_ENG	5		/* English */
#define	L_PAS	6		/* Pascal */
#define	L_MAIL	7		/* Electronic mail */
#define	L_NEWS	8		/* Usenet Netnews */
#define  L_GER    9   /* German */ 


#define MAXSTR		500
#define	EATAB {while (isascii(*l) && isspace(*l))  ++l;}

extern char *progname;
extern char *magicfile;
extern char *execbuf;
extern int debug;		/* option */
extern int nmagic;		/* number of valid magic[]s */

extern long strtol();
char *getstr();

struct magic magic[MAXMAGIS];

int apprentice(char *fn)
		/* name of magic file */
 {
	FILE *f;
	char line[MAXSTR+1];
	int errs = 0;

	f = fopen(fn, "r");
	if (f == NULL) 
    {
		(void) fprintf(stderr, "%s: can't read magic file %s\n", progname, fn);
		exit(1);
 	 }

	/* parse it */
	while (fgets(line, MAXSTR, f) != NULL) 
    {
		if (line[0]=='#')	/* comment, do not parse */
			continue;
		if (strlen(line) <= 1)	/* null line, garbage, etc */
			continue;
		line[strlen(line)-1] = '\0'; /* delete newline */
		if (parse(line, &nmagic) != 0)
			++errs;
	 }

	(void) fclose(f);
	return errs ? -1 : 0;
 }

/*
 * parse one line from magic file, put into magic[index++] if valid
 */

int parse(char *l, int *ndx)
// char *l;
// int *ndx;
 {
	int i = 0, nd = *ndx;
	int slen;
	static int warned = 0;
	struct magic *m;
	extern int errno;

	/*
	 * TODO malloc the magic structures (linked list?) so this can't happen
	 */
	if (nd+1 >= MAXMAGIS)
    {
		if (warned++ == 0)
			warning("magic table overflow - increase MAXMAGIS beyond 500 in file/apprentice.c\n", "");
//			itoa(MAXMAGIS));
		return -1;
	 }
	m = &magic[*ndx];

	if (*l == '>') {
		++l;		/* step over */
		m->contflag = 1;
	} else
		m->contflag = 0;

   if (isalpha(*l))   /* extended keywords */
     {
		if (strnicmp(l, "ASCII", 5) == 0) 
        {
			 m->type = ASCII;
			 l += 5;
		  } 
      else if (strnicmp(l, "DIRECTORY", 9) == 0) 
        {
			 m->type = OBJ_DIRECTORY;
			 l += 9;
		  } 
      else if (strnicmp(l, "TROFF", 5) == 0)
        {
			 m->type = TROFF;
			 l += 5;
		  } 
      else if (strnicmp(l, "FORTRAN", 7) == 0) 
        {
			 m->type = FORTRAN;
			 l += 7;
		  } 
      else if (strnicmp(l, "TAR", 3) == 0) 
        {
			 m->type = TAR_ARCH;
			 l += 3;
		  } 
      else if (strnicmp(l, "ELSE", 4) == 0) 
        {
			 m->type = ELSE;
			 l += 4;
		  } 
      else if (strnicmp(l, "EMPTY", 5) == 0) 
        {
			 m->type = EMPTY;
			 l += 5;
		  } 
      else if (strnicmp(l, "C-SOURCE", 8) == 0) 
        {
			 m->type = L_C + 13;   /* see names.h */
			 l += 8;
		  } 
      else if (strnicmp(l, "MAKE", 4) == 0) 
        {
			 m->type = L_MAKE + 13;
			 l += 4;
		  } 
      else if (strnicmp(l, "ENGLISH", 7) == 0) 
        {
			 m->type = L_ENG + 13;
			 l += 7;
		  } 
      else if (strnicmp(l, "GERMAN", 6) == 0) 
        {
			 m->type = L_GER + 13;
			 l += 6;
		  } 
      else if (strnicmp(l, "PASCAL", 6) == 0) 
        {
			 m->type = L_PAS + 13;
			 l += 6;
		  } 
     }       
   else
     {
		/* get offset, then skip over it */
		m->offset = atoi(l);
		while (isascii(*l) && isdigit(*l))
			++l;
		EATAB;

#define NBYTE 4
#define NSHORT 5
#define NLONG 4
#define NSTRING 6
		/* get type, skip it */
		if (strncmp(l, "byte", NBYTE)==0) {
			m->type = OBJ_BYTE;
			l += NBYTE;
		} else if (strncmp(l, "short", NSHORT)==0) {
			m->type = OBJ_SHORT;
			l += NSHORT;
		} else if (strncmp(l, "long", NLONG)==0) {
			m->type = OBJ_LONG;
			l += NLONG;
		} else if (strncmp(l, "string", NSTRING)==0) {
			m->type = OBJ_STRING;
			l += NSTRING;
		} else {
			errno = 0;
			warning("type %s invalid", l);
			return -1;
		}
		EATAB;

		if (*l == '>' || *l == '<' || *l == '&' || *l == '=') {
			m->reln = *l;
			++l;
		} else
			m->reln = '=';
		EATAB;

/*/*
 *  * TODO finish this macro and start using it!
 *  * #define offsetcheck {if (offset > HOWMANY-1) warning("offset too big", ""); }
 */
		switch(m->type) {
		/*
		 * Do not remove the casts below.  They are vital.
		 * When later compared with the data, the sign extension must
		 * have happened.
		 */
		case OBJ_BYTE:
			m->value.l = (char) strtol(l,&l,0);
			break;
		case OBJ_SHORT:
			m->value.l = (short) strtol(l,&l,0);
			break;
		case OBJ_LONG:
			m->value.l = (long) strtol(l,&l,0);
			break;
		case OBJ_STRING:
			l = getstr(l, m->value.s, sizeof(m->value.s), &slen);
			m->vallen = slen;
			break;
//	//	default:
//	//		warning("can't happen: m->type = %s\n", itoa(m->type));
//	//		return -1;
		}
   }

	/*
	 * now get last part - the description
	 */
	EATAB;
	while ((m->desc[i++] = *l++) != '\0' && i<MAXDESC)
		/* NULLBODY */;

	++(*ndx);		/* make room for next */
	return 0;
 }

/*
 * Convert a string containing C character escapes.  Stop at an unescaped
 * space or tab.
 * Copy the converted version to "p", returning its length in *slen.
 * Return updated scan pointer as function result.
 */
char *getstr(char *s, char *p, int plen, int *slen)
// register char	*s;
// register char	*p;
// int	plen, *slen;
 {
	char	*origs = s, *origp = p;
	char	*pmax = p + plen - 1;
	register int	c;
	register int	val;

	while((c = *s++) != '\0') {
		if (isspace(c)) break;
		if (p >= pmax) {
			fprintf(stderr, "String too long: %s\n", origs);
			break;
		}
		if(c == '\\') {
			switch(c = *s++) {

			case '\0':
				goto out;

			default:
				*p++ = c;
				break;

			case 'n':
				*p++ = '\n';
				break;

			case 'r':
				*p++ = '\r';
				break;

			case 'b':
				*p++ = '\b';
				break;

			case 't':
				*p++ = '\t';
				break;

			case 'f':
				*p++ = '\f';
				break;

			case 'v':
				*p++ = '\v';
				break;

			/* \ and up to 3 octal digits */
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
				val = c - '0';
				c = *s++;  /* try for 2 */
				if(c >= '0' && c <= '7') {
					val = (val<<3) | (c - '0');
					c = *s++;  /* try for 3 */
					if(c >= '0' && c <= '7')
						val = (val<<3) | (c-'0');
					else
						--s;
				}
				else
					--s;
				*p++ = val;
				break;

			/* \x and up to 3 hex digits */
			case 'x':
				val = 'x';	/* Default if no digits */
				c = hextoint(*s++);	/* Get next char */
				if (c >= 0) {
					val = c;
					c = hextoint(*s++);
					if (c >= 0) {
						val = (val << 4) + c;
						c = hextoint(*s++);
						if (c >= 0) {
							val = (val << 4) + c;
						} else
							--s;
					} else
						--s;
				} else
					--s;
				*p++ = val;
				break;
			}
		} else
			*p++ = c;
	}
out:
	*p = '\0';
	*slen = p - origp;
	return(s);
 }


/* Single hex char to int; -1 if not a hex char. */
int hextoint(char c)
 {
	if (!isascii(c))	return -1;
	if (isdigit(c))		return c - '0';
	if ((c>='a')&(c<='f'))	return c + 10 - 'a';
	if ((c>='A')&(c<='F'))	return c + 10 - 'A';
				return -1;
 }


/*
 * Print a string containing C character escapes.
 */
void showstr(char *s)
 {
	register char	c;

	while((c = *s++) != '\0') {
		if(c >= 040 && c <= 0176)
			putchar(c);
		else {
			putchar('\\');
			switch (c) {
			
			case '\n':
				putchar('n');
				break;

			case '\r':
				putchar('r');
				break;

			case '\b':
				putchar('b');
				break;

			case '\t':
				putchar('t');
				break;

			case '\f':
				putchar('f');
				break;

			case '\v':
				putchar('v');
				break;

			default:
				printf("%.3o", c & 0377);
				break;
			}
		}
	}
	putchar('\t');
 }
