/*
 * scen.c - scenario compiler
 */

#include <stdio.h>
#include "config.h"
#include "game.h"

FILE *ifp, *ofp;
int dfd;
int dfpos;
int movenum;
int phrnum;
int fxnum;
int fsize;
int efsize;
char *xbp;
char ifn[20];
char ofn[20];
char dfn[20];
char buff[200];

char *byp(s)
char *s;
 {
    while (*s && isspace(*s))
      s++;
    return(s);
 }

char *unbyp(s)
char *s;
 {
    while (*s && !isspace(*s))
      s++;
    return(s);
 }

main(n, a)
char **a;
 {
    int num;
    int f;
    int l;
    char type;

    printf("SCEN  V%d.%d%c\n\n", MAJOR, MINOR, REV);
    if (n != 2)
     {
	printf("Usage: SCEN FILE\n");
	exit();
     }
    sprintf(ifn, "%s.src", a[1]);
    sprintf(ofn, "%s.scn", a[1]);
    sprintf(dfn, "%s.$$$", a[1]);
    if ((ifp = fopen(ifn, RTEXT)) == NULL)
     {
	printf("Can't find %s\n", ifn);
	exit();
     }
    if ((ofp = fopen(ofn, WBIN)) == NULL)
     {
	printf("Can't write %s\n", ofn);
	exit();
     }
    if ((dfd = CREAT(dfn)) == ERROR)
     {
	printf("Can't write %s\n", dfn);
	fclose(ofp);
	unlink(ofn);
	exit();
     }
    fsize = maxscor = movenum = phrnum = 0;
    dfpos = 1;
    while (getline())
     {
	if (buff[0] && *(byp(buff)) != ';' && *(byp(buff)) != '#')
	 {
	    f = sscanf(buff, "%c%d", &type, &num);
	    switch (type)
	     {
		case '#':
		case ';':
		break;
		case 'l':
		  if (f == 2)
		    ploc(num);
		  else
		    printf("Location record with no number\n");
		break;
		case 'i':
		  if (f == 2)
		    pitem(num);
		  else
		    printf("Item record with no number\n");
		break;
		case 'f':
		  if (f == 2)
		    pfx(num);
		  else
		    printf("FX record with no number\n");
		break;
		case 'm':
		  pmove();
		break;
		case 'p':
		  pphr();
		break;
		case '"':
		  if (f == 2)
		    pstring(num);
		  else
		    printf("String record with no number\n");
		break;
		case 'a':
		  passt();
		break;
		case 'g':
		  pguard();
		break;
		case 'x':
		  pmaxsc();
		break;
		case 's':
		  start = getlong();
		break;
		case 'e':
		  good = getlong();
		break;
		case 'b':
		  bad = getlong();
		break;
		default:
		  printf("Unknown record \"%s\"\n", buff);
		break;
	     }
	 }
     }
    efsize = 14333;
    for (f = 0; f < MAXSTR; f++)
      if (strings[f])
	efsize += strlen(strings[f]);
    output();
    close(dfd);
    if (efsize != fsize)
     {
	printf("filesize error: %d / %d\n", efsize, fsize);
	exit(0);
     }
    while (fsize & 127)
      putcc(0, ofp);
    if ((dfd = OPEN(dfn)) == ERROR)
     {
	printf("error: %s lost\n", dfn);
	exit(0);
     }
    while (read(dfd, buff, 128 / RWBSIZ) == 128 / RWBSIZ)
     {
	for (f = l = 0; f < 128; f++)
	 {
	    if (buff[f] == 0x1a)
	      putc(0, ofp);
	    else
	      l = putc((buff[f] ^ l) | 0x80, ofp);
	 }
     }
    close(dfd);
    unlink(dfn);
    fclose(ofp);
 }

ploc(num)
 {
    int ch;
    int i, j;
    struct _loc_ *locp;

    if (num < 1 || num >= MAXLOC)
     {
	printf("Illegal location number %d\n", num);
	skip();
	return;
     }
    locp = &locs[num];
    while ((ch = gci()) != EOF && ch != '.')
     {
	if (!isspace(ch))
	 {
	    switch (ch)
	     {
		case '%':
		  if (!(locp->_ldesc = fstring(num)))
		    printf("Description error in location %d\n", num);
		break;
		case '{':
		  locp->_lflags |= SEEN;
		case '[':
		  locp->_longdesc = getlong();
		break;
		case ',':
		  locp->_lflags |= DARK;
		break;
		case 'c':
		 {
		    if ((i = number(NULL)) > 100)
		      i = 100;
		    else if (i < 0)
		      i = 0;
		    locp->_lscore = i;
		 }
		break;
		case 'n':
		case 's':
		case 'e':
		case 'w':
		case 'u':
		case 'd':
		 {
		    if ((j = inchr("pdsxn", (i = gci()))) == ERROR)
		      printf("Location %d direction %c: bad way %c\n",
								num, ch, i);
		    else if (j == 4)
		      locp->_ways[inchr("nsewud", ch)] = string() + (j << 8);
		    else if (fscanf(ifp, "%d", &i) != 1)
		      printf("Location %d direction %c: no number\n",
								num, ch);
		    else if (i < 1 || i >= MAXLOC)
		      printf("Location %d direction %c: %d invalid\n",
								num, ch, i);
		    else
		      locp->_ways[inchr("nsewud", ch)] = i + (j << 8);
		 }
		break;
		default:
		  printf("unknown object '%c' in loc record (%d)\n", ch, num);
		case ';':
		case '#':
		  eoln();
		break;
	     }
	 }
     }
 }

pitem(num)
 {
    int ch;
    int i;
    struct _item_ *itemp;

    if (num < 1 || num >= MAXITEM)
     {
	printf("Illegal item number %d\n", num);
	skip();
	return;
     }
    itemp = &items[num];
    while ((ch = gci()) != EOF && ch != '.')
     {
	if (!isspace(ch))
	 {
	    switch (ch)
	     {
		case '%':
		  itemp->_idesc = string();
		break;
		case 'h':
		  itemp->_iflags |= HIDDEN;
		break;
		case 'a':
		  itemp->_iflags |= NOTALL;
		break;
		case 'w':
		  itemp->_weight = number(NULL);
		break;
		case 'l':
		  itemp->_iloc = number(NULL);
		break;
		case 'c':
		 {
		    if ((i = number(NULL)) > 100)
		      i = 100;
		    else if (i < 0)
		      i = 0;
		    itemp->_iscore = i;
		 }
		break;
		case 'x':
		  itemp->_exam = string();
		break;
		case '\'':
		  itemp->_iwords[itemp->_iwords[0] ? 1 : 0] = getword(NULL);
		break;
		default:
		  printf("unknown object '%c' in item record (%d)\n", ch,
									num);
		case ';':
		case '#':
		  eoln();
		break;
	     }
	 }
     }
 }

pfx(num)
 {
    int t;
    int a;
    int n;
    struct _fx_ *fxp;
    char *bp;

    if (num < 1 || num >= MAXFX)
     {
	printf("Illegal FX number %d\n", num);
	skip();
	return;
     }
    while (getline() && *(bp = byp(buff)) != '.')
     {
	switch (*bp)
	 {
	    case 0:
	    case ';':
	    case '#':
	    break;
	    case 'i':
	    case 'e':
	     {
		if (*bp == 'e')
		  t = FTRUE;
		else
		  t = scantest(unbyp(bp));
		if (!getline() || *(bp = byp(buff)) != 't')
		 {
		    printf("Expecting 't' record after 'e' / 'i' in FX %d\n",
									num);
		    if (*bp != '.')
		      skip();
		    return;
		 }
		a = scanact(unbyp(bp));
		if (fxp = &fxs[n = newfx()])
		 {
		    fxp->_ftype = FTA;
		    fxp->_fa1 = t;
		    fxp->_fa2 = a;
		 }
		(fxp = &fxs[num])->_ftype = FCH;
		fxp->_fa1 = n;
		fxp->_fa2 = n = newfx();
		fxs[num = n]._ftype = FIDLE;
	     }
	    break;
	    default:
	      printf("Unknown record in FX %d: \"%s\"\n", num, buff);
	    break;
	 }
     }
 }

pmove()
 {
    int i;
    struct _move_ *movep;
    char *bp;

    if (movenum >= MAXMOVE)
     {
	printf("To many 'm' records\n");
	return;
     }
    bp = byp(&buff[1]);
    (movep = &moves[++movenum])->_mfrom = number(&bp);
    movep->_mto = number(&bp);
    movep->_mfx = number(&bp);
 }

pphr()
 {
    int i;
    struct _phrase_ *phrp;
    char *bp;

    if (phrnum >= MAXMOVE)
     {
	printf("To many 'p' records\n");
	return;
     }
    bp = byp(&buff[1]);
    (phrp = &phrases[++phrnum])->_pw1 = getword(&bp);
    if (*(bp = byp(bp)) == '\'')
     {
	bp++;
	phrp->_pw2 = getword(&bp);
     }
    phrp->_pfx = number(&bp);
 }

pstring(num)
 {
    int ch;
    int i;
    char *bp;
    char newstr[160];

    if ((ch = gci()) == '"')
      ch = gci();
    i = 0;
    while (ch != '"' && ch != EOF)
     {
	if (i < 158)
	  newstr[i++] = ch;
	ch = gci();
     }
    newstr[i] = 0;
    if ((bp = alloc(i + 2)) == NULL)
     {
	printf("Out of string space\n");
	return;
     }
    strcpy(bp, newstr);
    strings[num] = bp;
 }

passt()
 {
    strncpy(asst, byp(&buff[1]), 39);
 }

pguard()
 {
    char *bp;

    bp = byp(&buff[1]);
    guards = number(&bp);
    gdprob = number(&bp);
    gdact = number(&bp);
    strncpy(guard, byp(bp), 59);
 }

pmaxsc()
 {
    maxscor = atoi(byp(&buff[1]));
 }

string()
 {
    int ch;
    int i;
    int j;
    char newstr[160];

    while (isspace(ch = gci()))
      ;
    if (isdigit(ch))
      for (i = ch - '0'; isdigit(ch = gci()); i = i * 10 + ch - '0')
	;
    else if (ch == '"')
     {
	j = 0;
	while ((ch = gci()) != '"' && ch != EOF)
	  if (j < 158)
	    newstr[j++] = ch;
	newstr[j] = 0;
	for (i = MAXSTR - 1; i; i--)
	  if (strings[i] == NULL)
	    break;
	if (i == 0 || (strings[i] = alloc(j + 2)) == NULL)
	 {
	    printf("Out of string space\n");
	    exit();
	 }
	strcpy(strings[i], newstr);
     }
    else
     {
	eoln();
	i = -1;
     }
    return(i);
 }

fstring(num)
 {
    int ch;
    int i;
    int j;
    char newstr[256];

    while (isspace(ch = gci()))
      ;
    if (isdigit(ch))
     {
        for (i = ch - '0'; isdigit(ch = gci()); i = i * 10 + ch - '0')
	  ;
	return(locs[i]._ldesc);
     }
    else if (ch == '"')
     {
	j = 0;
	memset(newstr, 256, 0x1a);
	while ((ch = gci()) != '"' && ch != EOF)
	  if (j < 255)
	    newstr[j++] = ch;
	i = dfpos;
	dfpos += (j = (j + 127) / 128);
	write(dfd, newstr, j * (128 / RWBSIZ));
     }
    else
      printf("Error: bad description in location %d\n", num);
    return(i);
 }

number(bpp)
char **bpp;
 {
    int ch;
    int i;
    int sign;

    xbp = bpp ? *bpp : NULL;
    while (isspace(ch = gcb()))
      ;
    sign = 1;
    i = 0;
    if (ch == '-')
     {
	sign = -1;
	ch = gcb();
     }
    while (isdigit(ch))
     {
	i = i * 10 + ch - '0';
	ch = gcb();
     }
    if (bpp)
      *bpp = &xbp[-1];
    return(i * sign);
 }

getword(bpp)
char **bpp;
 {
    int ch;
    int i;
    int j;
    char newword[6];

    xbp = bpp ? *bpp : NULL;
    while (isspace(ch = gcb()))
      ;
    if (ch == '\'')
      ch = gcb();
    j = 1;
    newword[0] = ch;
    while (!isspace(ch = gcb()) && ch != EOF)
      if (j < 4)
	newword[j++] = ch;
    while (j <= 4)
      newword[j++] = 0;
    for (i = MAXWORD - 1; i; i--)
      if (strncmp(words[i], newword, 4) == 0)
	break;
    if (i == 0)
     {
	for (i = MAXWORD - 1; i; i--)
	  if (words[i][0] == '\0')
	    break;
	if (i == 0)
	 {
	    printf("Out of word space\n");
	    exit();
	 }
	memcpy(words[i], newword, 4);
     }
    if (bpp)
      *bpp = xbp;
    return(i);
 }

newfx()
 {
    int i;

    for (i = MAXFX - 1; i; i--)
      if (fxs[i]._ftype == 0)
	return(i);
    printf("Out of FX space\n");
    return(0);
  }

skip()
 {
    while (getline() && buff[0] != '.')
      ;
 }

getline()
 {
    char *s;

    while ((s = fgets(buff, 198, ifp)) && (*(byp(buff)) == ';' ||
							*(byp(buff)) == '#'))
      ;
    if (s)
     {
	buff[strlen(buff) - 1] = 0;
	return(TRUE);
     }
    return(FALSE);
 }

getlong()
 {
    int ch;
    int l;
    int r;
    char ldesc[128];

    memset(ldesc, 128, 0x1a);
    l = 0;
    r = dfpos;
    while ((ch = gci()) != ']' && ch != '}' && ch != EOF)
     {
	ldesc[l++] = ch;
	if (l == 128)
	 {
	    write(dfd, ldesc, 128 / RWBSIZ);
	    dfpos++;
	    memset(ldesc, 128, 0x1a);
	    l = 0;
	 }
     }
    write(dfd, ldesc, 128 / RWBSIZ);
    dfpos++;
    return(r);
 }

scantest(bp)
char *bp;
 {
    int n;
    int l;
    struct _fx_ *fxp;

    if (!(*(bp = byp(bp))))
      return(0);
    l = 0;
    while (*(bp = byp(bp)) && *bp != ';' && *bp != '#' && (n = newfx()))
     {
	fxp = &fxs[n];
	fxp->_ftype = FTT;
	fxp->_fa1 = gettest(&bp);
	fxp->_fa2 = l;
	l = n;
     }
    return(n);
 }

gettest(bpp)
char **bpp;
 {
    char *bp;
    int i;
    int j;
    int k;

    i = 0;
    bp = *bpp;
    *bpp = unbyp(bp);
    switch (*bp)
     {
	case 'v':
	 {
	    if ((k = (*++bp | 0x20) - 'a') < 0 || k > 25)
	     {
		printf("Bad variable in test: %c\n", *bp);
		break;
	     }
	    if ((j = inchr("=#<>", *++bp)) == ERROR)
	     {
		printf("Bad test condition: %c\n", *bp);
		break;
	     }
	    bp++;
	    i = 0x8000 + (j << 12) + (k << 7) + (number(&bp) & 127);
	 }
	break;
	case 'l':
	 {
	    bp++;
	    k = number(&bp) & 63;
	    if ((j = *bp++) != '@' && j != '!')
	     {
		printf("'@' or '!' expected in location test\n");
		break;
	     }
	    i = (j == '!' ? 0xe000 : 0xc000) + (k << 7) + (number(&bp) & 127);
	 }
	break;
	case 'g':
	case 'a':
	  if (*++bp == '-' || *bp == '+')
	    i = 0x4000 + 2 * inchr("ag", bp[-1]) + inchr("+-", *bp);
	  else
	    printf("Bad a/g test: %c\n", *bp);
	break;
	case 'r':
	 {
	    bp++;
	    if ((j = number(&bp)) >= -1 && j <= 100)
	      i = 0x6000 + (j & 127);
	    else
	      printf("Bad number in 'r' test: %d\n", j);
	 }
	break;
	default:
	  printf("Unknown test: %c\n", *bp);
	break;
     }
    return(i);
 }

scanact(bp)
char *bp;
 {
    int n;
    int l;
    struct _fx_ *fxp;

    if (!(*(bp = byp(bp))))
      return(0);
    l = 0;
    while (*(bp = byp(bp)) && *bp != ';' && *bp != '#' && (n = newfx()))
     {
	fxp = &fxs[n];
	fxp->_ftype = FAA;
	fxp->_fa1 = getact(&bp);
	fxp->_fa2 = l;
	l = n;
     }
    return(n);
 }

getact(bpp)
char **bpp;
 {
    char *bp;
    int i;
    int j;
    int k;

    i = 0;
    bp = *bpp;
    *bpp = unbyp(bp);
    switch (*bp)
     {
	case 'v':
	 {
	    if ((k = (*++bp | 0x20) - 'a') < 0 || k > 25)
	     {
		printf("Bad variable in action: %c\n", *bp);
		break;
	     }
	    if ((j = inchr("=+-", *++bp)) == ERROR)
	     {
		printf("Bad assignment type: %c\n", *bp);
		break;
	     }
	    bp++;
	    i = 0x8000 + (j << 12) + (k << 7) + (number(&bp) & 127);
	 }
	break;
	case 'l':
	 {
	    bp++;
	    if ((k = number(&bp)) < 1 || k >= MAXULOC)
	     {
		printf("Bad location in change: %d\n", k);
		break;
	     }
	    if (*bp++ != '=')
	     {
		printf("'=' missing in location change: %d\n", k);
		break;
	     }
	    if ((j = number(&bp)) < 1 || j >= MAXULOC)
	     {
		printf("Bad location in change: %d\n", j);
		break;
	     }
	    i = 0xc000 + (k << 7) + j;
	 }
	break;
	case 'i':
	 {
	    bp++;
	    if ((k = number(&bp)) < 1 || k >= MAXITEM)
	     {
		printf("Bad item in change: %d\n", k);
		break;
	     }
	    if (*bp++ != '=')
	     {
		printf("'=' missing in item change: %d\n", k);
		break;
	     }
	    if ((j = number(&bp)) < 1 || j >= MAXITEM)
	     {
		printf("Bad item in change: %d\n", j);
		break;
	     }
	    i = 0x4000 + (k << 6) + j;
	 }
	break;
	case 'g':
	case 'a':
	  if (*++bp == '-' || *bp == '+')
	    i = 0x5000 + 2 * inchr("ag", bp[-1]) + inchr("+-", *bp);
	  else if (*bp == 'k' && bp[-1] == 'g')
	    i = 0x5004;
	  else
	    printf("Bad a/g action: %c\n", *bp);
	break;
	case 'e':
	  i = 0x5005;
	break;
	case 'b':
	  i = 0x5006;
	break;
	case 'n':
	  i = 0x5007;
	break;
	case 'u':
	  i = 0x5008;
	break;
	case 'f':
	  i = 0x5009;
	break;
	case '"':
	 {
	    bp++;
	    if ((j = number(&bp)) < 1 || j >= MAXSTR)
	      printf("Bad string number in action: %d\n", j);
	    else
	      i = 0x7000 + j;
	 }
	break;
	case 'c':
	 {
	    bp++;
	    i = 0x6000 + (number(&bp) & 255);
	 }
	break;
	case 't':
	 {
	    bp++;
	    if ((k = number(&bp)) < 1 || k >= MAXULOC)
	     {
		printf("Bad location in teleport: %d\n", k);
		break;
	     }
	    i = 0x6800 + k;
	 }
	break;
	default:
	  printf("Unknown action: %c\n", *bp);
	break;
     }
    return(i);
 }

output()
 {
    int i;
    int j;
    int l;
    struct _loc_ *locp;
    struct _item_ *itemp;
    struct _fx_ *fxp;
    struct _move_ *movep;
    struct _phrase_ *phrp;

    for (j = l = 0; asst[j]; j++)
      l = asst[j] = (asst[j] ^ l) | 0x80;
    for (j = l = 0; guard[j]; j++)
      l = guard[j] = (guard[j] ^ l) | 0x80;
    for (i = 0; i < MAXWORD; i++)
      if (words[i][0])
	for (j = l = 0; j < 4; j++)
	  l = words[i][j] = (words[i][j] ^ l) | 0x80;
    putcc(0x1a, ofp);
    putcc(0xe5, ofp);
    putcc(0xff, ofp);
    putcc(0x64, ofp);
    putww((MAJOR << 12) + (MINOR << 8) + REV, ofp);
    putww(0, ofp);
    putww(0, ofp);
    putww(start, ofp);
    putww(good, ofp);
    putww(bad, ofp);
    putww(gdact, ofp);
    putww(maxscor, ofp);
    putww((efsize - 1) / 128, ofp);
    for (i = 0; i < 40; i++)
      putcc(asst[i], ofp);
    for (i = 0; i < 60; i++)
      putcc(guard[i], ofp);
    putcc(NOGUARD, ofp);
    putcc(guards, ofp);
    putcc(gdprob, ofp);
    putcc(1, ofp);
    putcc(0, ofp);
    for (i = 0, locp = locs; i < MAXLOC; i++, locp++)
     {
	putww(locp->_ldesc, ofp);
	putcc(locp->_lflags, ofp);
	putww(locp->_longdesc, ofp);
	putcc(locp->_lscore, ofp);
	for (j = 0; j < 6; j++)
	  putww(locp->_ways[j], ofp);
     }
    for (i = 0, itemp = items; i < MAXITEM; i++, itemp++)
     {
	putcc(itemp->_idesc, ofp);
	putcc(itemp->_iflags, ofp);
	putcc(itemp->_weight, ofp);
	putcc(itemp->_iloc, ofp);
	putcc(itemp->_exam, ofp);
	putcc(itemp->_iscore, ofp);
	putcc(itemp->_iwords[0], ofp);
	putcc(itemp->_iwords[1], ofp);
     }
    for (i = 0; i < MAXVAR; i++)
      putcc(vars[i], ofp);
    for (i = 0, fxp = fxs; i < MAXFX; i++, fxp++)
     {
	putww(fxp->_ftype, ofp);
	putww(fxp->_fa1, ofp);
	putww(fxp->_fa2, ofp);
     }
    for (i = 0, movep = moves; i < MAXMOVE; i++, movep++)
     {
	putcc(movep->_mfrom, ofp);
	putcc(movep->_mto, ofp);
	putww(movep->_mfx, ofp);
     }
    for (i = 0, phrp = phrases; i < MAXPHR; i++, phrp++)
     {
	putcc(phrp->_pw1, ofp);
	putcc(phrp->_pw2, ofp);
	putww(phrp->_pfx, ofp);
     }
    for (i = 0; i < MAXWORD; i++)
      for (j = 0; j < 4; j++)
	putcc(words[i][j], ofp);
    for (i = 0; i < MAXSTR; i++)
     {
	if (strings[i])
	  for (j = l = 0; strings[i][j]; j++)
	    putcc((l = (strings[i][j] ^ l) | 0x80), ofp);
	putcc(0, ofp);
     }
    for (i = l = 0; i < MAXLOC; i++)
      if (locs[i]._ldesc)
	l++;
    printf("%3d locations\n", l);
    for (i = l = 0; i < MAXITEM; i++)
      if (items[i]._idesc)
	l++;
    printf("%3d items\n", l);
    for (i = l = 0; i < MAXFX; i++)
      if (fxs[i]._ftype)
	l++;
    printf("%3d FX records\n", l);
    for (i = l = 0; i < MAXMOVE; i++)
      if (moves[i]._mfrom)
	l++;
    printf("%3d move records\n", l);
    for (i = l = 0; i < MAXPHR; i++)
      if (phrases[i]._pw1)
	l++;
    printf("%3d phrases\n", l);
    for (i = l = 0; i < MAXWORD; i++)
      if (words[i][0])
	l++;
    printf("%3d words\n", l);
    for (i = l = 0; i < MAXSTR; i++)
     {
	if (strings[i])
	  l++;
	/* if (lstrings[i])
	  l++; */
     }
    printf("%3d strings\n", l);
 }

putww(w, fp)
FILE *fp;
 {
    putcc(w, fp);
    putcc(w >> 8, fp);
 }

putcc(c, fp)
FILE *fp;
 {
    putc(c, fp);
    fsize++;
 }

memcpy(s, t, n)
char *s, *t;
 {
    while (n--)
      *s++ = *t++;
 }

memset(s, n, v)
char *s;
 {
    while (n--)
      *s++ = v;
 }

gci()
 {
    return(getc(ifp));
 }

gcb()
 {
    return(xbp ? *xbp++ : gci());
 }

eoln()
 {
    int i;

    while ((i = gci()) != '\n' && i != EOF)
      ;
 }

inchr(s, c)
char *s;
 {
    int i;

    for (i = 0; s[i]; i++)
      if (s[i] == c)
	return(i);
    return(ERROR);
 }
