#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <ctype.h>
//
#include "proto.h"
#include "flash.h"
#include "main.h"
#include "shape.h"
#include "bbox.h"
#include "bucket.h"
#include "bitcount.h"
#include "matrix.h"
#include "cxform.h"
#include "bitmap.h"
#include "rectangle.h"
#include "gradient.h"
#include "action.h"
#include "button.h"
#include "fonttext.h"
#include "parser.h"
#include "sound.h"
#include "preprocess.h"
#include "evaluate.h"
#include "dictionary.h"
#include "utils.h"


static S32 get_token(char *out, S32 max);
static S32 get_tokenl(char *out, S32 max);
static S32 get_valueS8(S8 *result);
static S32 get_valueU8(U8 *result);
static S32 get_valueS16(S16 *result);
static S32 get_valueU16(U16 *result);
static S32 get_valueU32(U32 *result);
static S32 get_valueS32(S32 *result);
static S32 get_value(S32 *result);
static S32 get_token_bm(char *out, S32 max);
static S32 getchr(void);
static S32 get_rgb(U32 *rgb);
static S32 get_id(U16 *id);

static S32 place_object(PLACEOBJECT *place);
static S32 remove_object(U16 depth, U16 frame);
static S32 show_frame(void);

static S32 parse_file_version(void);
static S32 parse_button(void);
static S32 parse_frame_area(void);
static S32 parse_bg_colour(void);
static S32 parse_actions(ACTION **list, U32 *count);
static S32 parse_define_shape(void);
static S32 parse_bitmap(S32 bmtype);
static S32 parse_place_object(void);
static S32 parse_remove_object(void);
static S32 parse_show_frame(void);
static S32 parse_cxform(CXFORM *cxform);
static S32 parse_fillstyles(SHAPE *shape);
static S32 parse_linestyles(SHAPE *shape);
static S32 parse_gradient(GRADIENT *gradient);
static S32 parse_matrix(MATRIX *matrix);
static S32 parse_rect(RECT *rect);
static S32 parse_font(void);
static S32 parse_text(void);
static S32 parse_define_sound(void);
static S32 parse_play_sound(void);
static S32 parse_text_style_record(TEXTREC *rec);
static S32 parse_glyph(GLYPH *glyph);
static S32 parse_buttonsound_transition(BUTTONSOUND *bs, S32 state);
static S32 parse_button_sound(void);
static S32 parse_button_state(BUTTON *button, U8 which);
static S32 read_inline_file(char **ptr, S32 *size);

static S32 parse_initialise(void);


#define MAXFRAMECOUNT         8192
#define RESERVED_FRAMENO      65535

#define TOKEN_NOT_FOUND       -1
#define TOKEN                 0
#define TOKEN_STRING          1
#define TOKEN_LB              2
#define TOKEN_RB              3

// local to this file
static U32 framecount, framerate, bgcolour, protected;
static U8 fileversion;
static RECT framesize;
static S32 scalex, scaley, scaleboth;   // 1000 = 100%

static FRAME frames[MAXFRAMECOUNT];
static char linebuffer[MAXLINELENGTH];
static U32 bufferpos;



S32 parse_initialise() {

  // reset everything
  memset(frames+0, 0, sizeof(FRAME));
  scalex = scaley = scaleboth = 1000;
  framesize.minx = framesize.miny = 0;
  framesize.maxx = framesize.maxy = 4000;
  dictionary_init();

  // default values
  framecount  = 0;
  fileversion = 1;
  bgcolour    = 0x00fffff;
  framerate   = 0x0a00;
  protected   = 0;

  if (create_variable("frameno"))       return 1;
  if (set_variable_value("frameno", 0)) return 1;

  return 0;
}



S32 get_rgb(U32 *rgb) {

  S32 i, type;
  char token[100];

  type = get_token(token, 100);
  if (type == TOKEN_LB) {
    S32 r, g, b, a;
    type = get_value(&r);
    if (type != TOKEN)                  return type;
    type = get_value(&g);
    if (type != TOKEN)                  return type;
    type = get_value(&b);
    if (type != TOKEN)                  return type;
    type = get_value(&a);
    if (type == TOKEN_RB)
      a = 255;
    else if (type != TOKEN)
      return type;
    else if (get_token(token, 100) != TOKEN_RB)
      return type;

    if (r < 0)     r = 0;
    if (r > 255)   r = 255;
    if (g < 0)     g = 0;
    if (g > 255)   g = 255;
    if (b < 0)     b = 0;
    if (b > 255)   b = 255;
    if (a < 0)     a = 0;
    if (a > 255)   a = 255;

    *rgb = r | (g<<8) | (b<<16) | (a<<24);
    return TOKEN;

  } else if (type != TOKEN)             return type;

  *rgb = 0;
  i = 0;
  while (isxdigit(token[i])) {
    S32 c;

    c = token[i];
    if (c >= 'a')  c -= 'a' - 'A';
    if (c >= 'A')
      c -= 'A'-10;
    else
      c -= '0';
    *rgb = (*rgb<<4) | c;
    i++;
  }

  return TOKEN;
}


S32 update_record_header(S32 tag, U32 ptr) {

  U32 ptr2, size;
  U16 rechdr;
  U8 *buf;

  if (flush_bucket())                             return 1;
  ptr2 = read_position(&buf);
  buf += ptr;
  size = ptr2 - ptr - 2;
  if (size >= 63) {
    U8 hdr[4];
    rechdr = (tag<<6) | 0x3f;
    buf[0] =  rechdr     & 0xff;
    buf[1] = (rechdr>>8) & 0xff;
    hdr[0] =  size       & 0xff;
    hdr[1] = (size>> 8)  & 0xff;
    hdr[2] = (size>>16)  & 0xff;
    hdr[3] = (size>>24)  & 0xff;
    if (bucket_insert(ptr+2, hdr, 4))               return 1;
  } else {
    rechdr = (tag<<6) | size;
    buf[0] = rechdr      & 0xff;
    buf[1] = (rechdr>>8) & 0xff;
  }

  return 0;
}


S32 getchr() {

  while (!linebuffer[bufferpos]) {
    if (get_line(linebuffer))   return -1;
    if (debug_print_lines)  printf("%s\n", linebuffer);
    bufferpos = 0;
  }

  return linebuffer[bufferpos++];
}


S32 get_tokenl(char *out, S32 max) {

  S32 type, i;

  type = get_token(out, max);
  if ((type == TOKEN) || (type == TOKEN_STRING)) {
    i = 0;
    while (out[i]) {
      out[i] = tolower(out[i]);
      i++;
    }
  }
  return type;
}


S32 get_valueS8(S8 *result) {

  S32 res;
  S32 type;

  type = get_value(&res);
  if (type != TOKEN)          return type;
  *result = (S8)res;

  return TOKEN;
}


S32 get_valueU8(U8 *result) {

  S32 res;
  S32 type;

  type = get_value(&res);
  if (type != TOKEN)          return type;
  *result = (U8)res;

  return TOKEN;
}


S32 get_valueS16(S16 *result) {

  S32 res;
  S32 type;

  type = get_value(&res);
  if (type != TOKEN)          return type;
  *result = (S16)res;

  return TOKEN;
}


S32 get_id(U16 *id) {

  S32 type;

  type = get_valueU16(id);
  if (!type != TOKEN)         return type;
  if (*id == 0) {
    fprintf(stderr, "Id 0 is reserved\n");
    return TOKEN_NOT_FOUND;
  }
  if (*id == RESERVED_ID) {
    fprintf(stderr, "Id %d (%04x) is reserved\n", RESERVED_ID, RESERVED_ID);
    return TOKEN_NOT_FOUND;
  }

  return TOKEN;
}


S32 get_valueU16(U16 *result) {

  S32 res;
  S32 type;

  type = get_value(&res);
  if (type != TOKEN)          return type;
  *result = (U16)res;

  return TOKEN;
}


S32 get_valueU32(U32 *result) {

  return get_value((S32 *)result);
}


S32 get_value(S32 *result) {

  return get_valueS32(result);
}


S32 get_valueS32(S32 *result) {

  char token[100];
  S32 type;

  type = get_token_bm(token, 100);
  if (type != TOKEN)                    return type;
  if (evaluate(token, result))          return TOKEN_NOT_FOUND;
  return TOKEN;
}


S32 get_token_bm(char *out, S32 max) {  // bracket-matching

  S32 len, type, bm, i;
  char token[MAXLINELENGTH];

  type = get_token(out, max);
  if (type != TOKEN)          return type;
  if (out[0] != '(')          return type;

  bm = 0;
  len = strlen(out);
  for (i = 0; i < len; i++) {
    if (out[i] == '(')
      bm++;
    else if (out[i] == ')')
      bm--;
  }

  while (bm) {
    type = get_token(token, max-len);
    if (type != TOKEN) {
      fprintf(stderr, "Unmatched brackets or expression is too long\n");
      return type;
    }

    strcat(out+len, token);
    len = strlen(out);

    bm = 0;
    for (i = 0; i < len; i++) {
      if (out[i] == '(')
        bm++;
      else if (out[i] == ')')
        bm--;
    }
  }

  return TOKEN;
}


S32 get_token(char *out, S32 max) {
// disgusting to say the least - not the prettiest piece
// of code I've ever had to take credit for...
  S32 len, c, token;

  len = 0;
  max -= 1;

  token = TOKEN_NOT_FOUND;

  do {
    // skip white spaces
    do {
      c = getchr();
      if (c == -1)                      return TOKEN_NOT_FOUND;
      if ((c <= ' ') && (len > 0)) {
        // end of token
        out[len] = '\0';
        return TOKEN;
      }
    } while (c <= ' ');

    if ((c == '{') && (len == 0))        return TOKEN_LB;
    if ((c == '}') && (len == 0))        return TOKEN_RB;

    // is it a comment??
    if (c == '/') {
      S32 c2;
      c2 = getchr();
      if (c2 == -1)                     return TOKEN_NOT_FOUND;
      if (c2 == '/') {
        // it is a comment, so skip to end of line
        bufferpos = 0; linebuffer[bufferpos] ='\0';
        if (len > 0) {
          // if we had already read something,
        }
      } else {
        if (len+2 >= max)               return TOKEN_NOT_FOUND;
        out[len++] = c;
        out[len++] = c2;
      }
      // mark char as processed
      c = -1;
    }

    if (c > 0) {
      if (len+1 >= max)                 return TOKEN_NOT_FOUND;
      out[len++] = c;
      if (c == '"') {         // string, read to end of string
        len--;
        while ((c != -1) && (len < max)) {
          c = getchr();
          if (c == -1)                  return TOKEN_NOT_FOUND;
          if (c == '"') {
            // end of string
            out[len] = '\0';
            return TOKEN_STRING;
          } else if (c == '\\') {
            // escaped char
            if (len+1 >= max)           return TOKEN_NOT_FOUND;
            c = getchr();
            if (c == -1)                return TOKEN_NOT_FOUND;
            switch (c) {
            case 'r':
              out[len++] = '\r';
              break;
            case 'n':
              out[len++] = '\n';
              break;
            case 't':
              out[len++] = '\t';
              break;
            case '"':
              out[len++] = '"';
              break;
            case '\\':
              out[len++] = '\\';
              break;
            default:
              break;
            }
          } else
            // normal char
            out[len++] = c;
        }

      } else {                // normal token, read to next space
      }
    }

  } while (1);

  return TOKEN_NOT_FOUND;
}

/* S32 get_token(char *out, S32 max) {
  S32 n;
  n = get_token2(out, max);
  fprintf(stderr, "TOKEN: %s\n", out);
  return n;
} */


void report_error(char *err) {

  fprintf(stderr, "%s\n", err);
  exit(-1);
}


S32 parse_rect(RECT *rect) {

  char token[100];
  S32 val;

  if (get_token(token, 100) != TOKEN_LB)  return 1;

  if (get_value(&val) != TOKEN)           return 1;
  rect->minx = (scalex*val)/1000;

  if (get_value(&val) != TOKEN)           return 1;
  rect->miny = (scaley*val)/1000;

  if (get_value(&val) != TOKEN)           return 1;
  rect->maxx = (scalex*val)/1000;

  if (get_value(&val) != TOKEN)           return 1;
  rect->maxy = (scaley*val)/1000;

  if (get_token(token, 100) != TOKEN_RB)  return 1;

  return 0;
}


S32 parse_gradient(GRADIENT *gradient) {

  char token[100];
  S32 type, i;
  U8 grad;

  memset(gradient, 0, sizeof(GRADIENT));
  gradient->usealpha = 1;

  if (get_token(token, 100) != TOKEN_LB)  return 1;

  do {
    type = get_valueU8(&grad);
    if (type == TOKEN) {
      gradient->ratio[gradient->n] = grad;
      if (get_rgb(&gradient->rgba[gradient->n]) != TOKEN)  return 1;
      gradient->n++;

    } else if (type != TOKEN_RB)
      return 1;

  } while (type != TOKEN_RB);

  for (i = 0; i < gradient->n-1; i++)
    if (gradient->ratio[i] >= gradient->ratio[i+1])         return 1;

  return 0;
}


S32 parse_matrix(MATRIX *matrix) {

  char token[100];
  S32 v[7], val;
  S32 n, type;

  matrix->scalex = matrix->scaley = (scaleboth*65536)/1000;
  matrix->rotate0 = matrix->rotate1 = 0;
  matrix->tx = matrix->ty = 0;

  if (get_token(token, 100) != TOKEN_LB)          return 1;

  n = 0;
  do {
    type = get_value(&val);
    if (type == TOKEN)
      v[n++] = val;
    else if (type != TOKEN_RB)                    return 1;
  } while ((type != TOKEN_RB) && (n <= 6));
  if (type != TOKEN_RB)                           return 1;

  switch (n) {
  case 1:
    matrix->scalex = matrix->scaley = (scaleboth*v[0])/1000;
    break;
  case 2:
    matrix->scalex = (scaleboth*v[0])/1000;
    matrix->scaley = (scaleboth*v[1])/1000;
    break;
  case 3:
    matrix->scalex = matrix->scaley = (scaleboth*v[0])/1000;
    matrix->tx = (scalex*v[1])/1000;
    matrix->ty = (scaley*v[2])/1000;
    break;
  case 4:
    matrix->scalex = (scaleboth*v[0])/1000;
    matrix->scaley = (scaleboth*v[1])/1000;
    matrix->tx = (scalex*v[2])/1000;
    matrix->ty = (scaley*v[3])/1000;
    break;
  case 6:
    matrix->scalex = (scaleboth*v[0])/1000;
    matrix->scaley = (scaleboth*v[1])/1000;
    matrix->rotate0 = (scaleboth*v[2])/1000;
    matrix->rotate1 = (scaleboth*v[3])/1000;
    matrix->tx = (scalex*v[4])/1000;
    matrix->ty = (scaley*v[5])/1000;
    break;
  case 5:
    report_error("Bad matrix");
    break;
  }

  return 0;
}


S32 parse_actions(ACTION **list, U32 *count) {

  char token[MAXLINELENGTH];
  S32 type;
  ACTION action;

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  do {
    memset(&action, 0, sizeof(ACTION));
    type = get_tokenl(token, 100);
    if (type == TOKEN_RB)                         return 0;
    if (type != TOKEN)                            return 1;
    if (strcmp(token, "gotoframe") == 0) {
      U16 frame;
      if (get_valueU16(&frame) != TOKEN)          return 1;
      action.action = ACTION_GOTOFRAME;
      action.data.gotoframe = frame;
      if (add_action(list, count, &action))       return 1;

    } else if (strcmp(token, "play") == 0) {
      action.action = ACTION_PLAY;
      if (add_action(list, count, &action))       return 1;

    } else if (strcmp(token, "stop") == 0) {
      action.action = ACTION_STOP;
      if (add_action(list, count, &action))       return 1;

    } else if (strcmp(token, "stopsounds") == 0) {
      action.action = ACTION_STOPSOUNDS;
      if (add_action(list, count, &action))       return 1;

    } else if (strcmp(token, "nextframe") == 0) {
      action.action = ACTION_NEXTFRAME;
      if (add_action(list, count, &action))       return 1;

    } else if (strcmp(token, "previousframe") == 0) {
      action.action = ACTION_PREVIOUSFRAME;
      if (add_action(list, count, &action))       return 1;

    } else if (strcmp(token, "geturl") == 0) {
      S32 type;

      if (get_token(token, 100) != TOKEN_LB)      return 1;
      do {
        type = get_tokenl(token, 100);
        if (type == TOKEN) {
          if (strcmp(token, "url") == 0) {
            type = get_token(token, MAXLINELENGTH);
            if (type != TOKEN_STRING)             return 1;
            action.data.geturl.url = malloc(strlen(token)+1);
            if (!action.data.geturl.url)          return 1;
            strcpy(action.data.geturl.url, token);
          } else if (strcmp(token, "target") == 0) {
            type = get_token(token, MAXLINELENGTH);
            if (type != TOKEN_STRING)             return 1;
            action.data.geturl.target = malloc(strlen(token)+1);
            if (!action.data.geturl.target)       return 1;
            strcpy(action.data.geturl.target, token);
          }
        } else if (type != TOKEN_RB)              return 1;
      } while (type != TOKEN_RB);

      if (!action.data.geturl.url)  report_error("No URL specified");
      if (!action.data.geturl.target) {
        action.data.geturl.target = malloc(1);
        if (!action.data.geturl.target)           return 1;
        action.data.geturl.target[0] = '\0';
      }
      action.action = ACTION_GETURL;
      if (add_action(list, count, &action))       return 1;

    } else {
      fprintf(stderr, "Unsupported field '%s'\n", token);
      return 1;
    }
  } while (1);

  return 1;
}


S32 parse_play_sound() {

  char token[MAXLINELENGTH];
  PLAYSOUND *play;
  S32 type;
  U16 framei;
  FRAME *frame;

  play = malloc(sizeof(PLAYSOUND));
  if (!play)                                                return 1;
  memset(play, 0, sizeof(PLAYSOUND));
  play->id = RESERVED_ID;
  play->loops = 1;
  framei = RESERVED_FRAMENO;

  if (get_token(token, 100) != TOKEN_LB)                    return 1;
  do {
    type = get_tokenl(token, 100);

    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&play->id) != TOKEN)                     return 1;
        if (!is_id_used(play->id)) {
          fprintf(stderr, "Unknown character (id=%d)\n", play->id);
          return 1;
        }

      } else if (strcmp(token, "loops") == 0) {
        if (get_valueU16(&play->loops) != TOKEN)            return 1;

      } else if (strcmp(token, "frame") == 0) {
        if (get_valueU16(&framei) != TOKEN)                 return 1;
      }

    } else if (type != TOKEN_RB)                            return 1;
  } while (type != TOKEN_RB);

  if (framei == RESERVED_FRAMENO)
    frame = frames+framecount;
  else {
    if (framei >= framecount)
      report_error("Illegal frame no. in PlaySound { }");
    frame = frames+framei;
  }

  if (!frame->sounds) {
    frame->sounds = malloc(sizeof(PLAYSOUND));
    if (!frame->sounds)                                     return 1;
  } else {
    PLAYSOUND *newsounds;
    newsounds = realloc(frame->sounds, (frame->soundcount+1)*sizeof(PLAYSOUND));
    if (!newsounds)                                         return 1;
    frame->sounds = newsounds;
  }
  memcpy(frame->sounds + frame->soundcount, play, sizeof(PLAYSOUND));
  frame->soundcount++;

  return 0;
}


S32 parse_define_sound() {

  char token[MAXLINELENGTH];
  S32 type;
  SOUND *sound;

  if (sound_create(&sound))                                 return 1;

  if (get_token(token, 100) != TOKEN_LB)                    return 1;

  do {
    type = get_tokenl(token, MAXLINELENGTH);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&sound->id) != TOKEN)                    return 1;
        if (is_id_used(sound->id))   report_error("Dublicate character ID");

      } else if (strcmp(token, "format") == 0) {
        if (get_tokenl(token, 100) != TOKEN_STRING)          return 1;
        if (strcmp(token, "lin8") == 0)
          sound->format = SOUNDFORMAT_LIN8   + (SOUNDFORMAT_LIN8<<SOUND_INPUT);
        else if (strcmp(token, "lin16") == 0)
          sound->format = SOUNDFORMAT_LIN16  + (SOUNDFORMAT_LIN16<<SOUND_INPUT);
        else if ((strcmp(token, "adpcm2") == 0) || (strcmp(token, "adpcm2-8") == 0))
          sound->format = SOUNDFORMAT_ADPCM2 + (SOUNDFORMAT_LIN8<<SOUND_INPUT);
        else if ((strcmp(token, "adpcm3") == 0) || (strcmp(token, "adpcm3-8") == 0))
          sound->format = SOUNDFORMAT_ADPCM3 + (SOUNDFORMAT_LIN8<<SOUND_INPUT);
        else if ((strcmp(token, "adpcm4") == 0) || (strcmp(token, "adpcm4-8") == 0))
          sound->format = SOUNDFORMAT_ADPCM4 + (SOUNDFORMAT_LIN8<<SOUND_INPUT);
        else if ((strcmp(token, "adpcm5") == 0) || (strcmp(token, "adpcm5-8") == 0))
          sound->format = SOUNDFORMAT_ADPCM5 + (SOUNDFORMAT_LIN8<<SOUND_INPUT);
        else if (strcmp(token, "adpcm2-16") == 0)
          sound->format = SOUNDFORMAT_ADPCM2 + (SOUNDFORMAT_LIN16<<SOUND_INPUT);
        else if (strcmp(token, "adpcm3-16") == 0)
          sound->format = SOUNDFORMAT_ADPCM3 + (SOUNDFORMAT_LIN16<<SOUND_INPUT);
        else if (strcmp(token, "adpcm4-16") == 0)
          sound->format = SOUNDFORMAT_ADPCM4 + (SOUNDFORMAT_LIN16<<SOUND_INPUT);
        else if (strcmp(token, "adpcm5-16") == 0)
          sound->format = SOUNDFORMAT_ADPCM5 + (SOUNDFORMAT_LIN16<<SOUND_INPUT);
        else {
          fprintf(stderr, "Unknown sound-format '%s'\n", token);
          return 1;
        }

      } else if (strcmp(token, "file") == 0) {
        S32 type;
        type = get_token(token, 100);
        if (type == TOKEN_STRING) {
          sound->file = malloc(strlen(token)+1);
          if (!sound->file)                                 return 1;
          strcpy(sound->file, token);
          sound->sizeinmemory = NOT_INLINE_FILE;
        } else if (type == TOKEN_LB) {
          char *ptr;
          S32 size;

          if (read_inline_file(&ptr, &size)) {
            fprintf(stderr, "Failed to read inline-file\n");
            return 1;
          }
          sound->file = ptr;
          sound->sizeinmemory = size;
        } else
          return 1;

      } else if (strcmp(token, "channels") == 0) {
        S32 chns;
        if (get_value(&chns) != TOKEN)                      return 1;
        if (chns == 1)
          sound->stereo = 0;
        else
          sound->stereo = 1;

      } else if (strcmp(token, "freq") == 0) {
        if (get_value(&sound->freq) != TOKEN)               return 1;

      }

    } else if (type != TOKEN_RB)
      return 1;

  } while (type != TOKEN_RB);

  return add_character(sound->id, sound, CHARACTER_SOUND);
}


S32 read_inline_file(char **ptr, S32 *size) {

  S32 type, allocated, bucket, bits;
  char token[MAXLINELENGTH], *p;

  *ptr = malloc(4096);
  if (!*ptr)   return 1;
  allocated = 4096;
  *size = 0;

  bucket = 0;
  bits = 0;
  do {
    // get sequence of hex digits
    type = get_tokenl(token, MAXLINELENGTH);
    if (type == TOKEN_NOT_FOUND)        return 1;
    if (type == TOKEN_RB)
      if (bits)     // if ncessary, flush bucket by writing '0'
        token[0] = '0', token[1] = '\0';

    p = token;
    do {
      if (isxdigit(*p)) {
        S32 v;

        // convert hex digit to value
        if (*p > '9')
          v = *p - 'a' + 10;
        else
          v = *p - '0';
        // add value to bucket
        bucket = (bucket<<4) | v;
        // increase no. of nibbles in the bucket
        bits += 1;
        // if the buck is full (2 nibbles = 1 byte), flush it
        if (bits == 2) {
          if (*size == allocated) {
            // we've filled the buffer, so extend it
            char *p2;

            allocated += 4096;
            p2 = realloc(*ptr, allocated);
            if (!p2)  return 1;
            *ptr = p2;
          }
          // write new byte
          (*ptr)[*size] = bucket;
          // increment size
          *size += 1;
          // reset bucket
          bits = 0;
        }
      }
      p++;
    } while (*p);

  } while (type != TOKEN_RB);

  return 0;
}


S32 parse_bitmap(S32 bmtype) {

  char token[MAXLINELENGTH];
  S32 type;
  BITMAP *bitmap;

  if (bitmap_create(&bitmap))                               return 1;
  bitmap->alphatype = BITMAP_RAW;

  if (get_token(token, 100) != TOKEN_LB)                    return 1;

  switch (bmtype) {
  case BITMAP_JPEG:
    bitmap->type = BITMAP_JPEG;
    do {
      type = get_tokenl(token, MAXLINELENGTH);
      if (type == TOKEN) {
        if (strcmp(token, "id") == 0) {
          if (get_id(&bitmap->id) != TOKEN)                 return 1;
          if (is_id_used(bitmap->id))   report_error("Dublicate character ID");

        } else if (strcmp(token, "width") == 0) {
          if (get_valueU16(&bitmap->width) != TOKEN)        return 1;

        } else if (strcmp(token, "height") == 0) {
          if (get_valueU16(&bitmap->height) != TOKEN)       return 1;

        } else if (strcmp(token, "file") == 0) {
          S32 type;
          type = get_token(token, 100);
          if (type == TOKEN_STRING) {
            bitmap->file = malloc(strlen(token)+1);
            if (!bitmap->file)                              return 1;
            strcpy(bitmap->file, token);
            bitmap->sizeinmemory = NOT_INLINE_FILE;
          } else if (type == TOKEN_LB) {
            char *ptr;
            S32 size;

            if (read_inline_file(&ptr, &size))              return 1;
            bitmap->file = ptr;
            bitmap->sizeinmemory = size;
          } else
            return 1;

        } else if (strcmp(token, "alphatype") == 0) {
          if (get_token(token, 100) != TOKEN_STRING)        return 1;
          if (strcmp(token, "compressed") == 0)
            bitmap->alphatype = BITMAP_RAW;
          else if (strcmp(token, "uncompressed") == 0)
            bitmap->alphatype = BITMAP_UNCOMPRESSED;
          else
            return 1;

        } else if (strcmp(token, "alphafile") == 0) {
          S32 type;
          type = get_token(token, 100);
          if (type == TOKEN_STRING) {
            bitmap->alphafile = malloc(strlen(token)+1);
            if (!bitmap->alphafile)                         return 1;
            strcpy(bitmap->alphafile, token);
            bitmap->alphasizeinmemory = NOT_INLINE_FILE;
          } else if (type == TOKEN_LB) {
            char *ptr;
            S32 size;

            if (read_inline_file(&ptr, &size))              return 1;
            bitmap->alphafile = ptr;
            bitmap->alphasizeinmemory = size;
          } else
            return 1;

        } else                                              return 1;

      } else if (type != TOKEN_RB)
        return 1;
    } while (type != TOKEN_RB);
    break;

  case BITMAP_LOSSLESS:
    bitmap->type = BITMAP_LOSSLESS;
    do {
      type = get_tokenl(token, MAXLINELENGTH);
      if (type == TOKEN) {
        if (strcmp(token, "id") == 0) {
          if (get_id(&bitmap->id) != TOKEN)                 return 1;
          if (is_id_used(bitmap->id))   report_error("Dublicate character ID");

        } else if (strcmp(token, "width") == 0) {
          if (get_valueU16(&bitmap->width) != TOKEN)        return 1;

        } else if (strcmp(token, "height") == 0) {
          if (get_valueU16(&bitmap->height) != TOKEN)       return 1;

        } else if (strcmp(token, "file") == 0) {
          S32 type;
          type = get_token(token, 100);
          if (type == TOKEN_STRING) {
            bitmap->file = malloc(strlen(token)+1);
            if (!bitmap->file)                                return 1;
            strcpy(bitmap->file, token);
            bitmap->sizeinmemory = NOT_INLINE_FILE;
          } else if (type == TOKEN_LB) {
            char *ptr;
            S32 size;

            if (read_inline_file(&ptr, &size))              return 1;
            bitmap->file = ptr;
            bitmap->sizeinmemory = size;
          } else
            return 1;

        } else if (strcmp(token, "alphafile") == 0) {
          S32 type;
          type = get_token(token, 100);
          if (type == TOKEN_STRING) {
            bitmap->alphafile = malloc(strlen(token)+1);
            if (!bitmap->alphafile)                         return 1;
            strcpy(bitmap->alphafile, token);
            bitmap->alphasizeinmemory = NOT_INLINE_FILE;
          } else if (type == TOKEN_LB) {
            char *ptr;
            S32 size;

            if (read_inline_file(&ptr, &size))              return 1;
            bitmap->alphafile = ptr;
            bitmap->alphasizeinmemory = size;
          } else
            return 1;

        } else if (strcmp(token, "type") == 0) {
          if (get_token(token, 100) != TOKEN_STRING)        return 1;
          if (strcmp(token, "8") == 0)
            bitmap->bits = BITMAP_8BPP;
          else if (strcmp(token, "16") == 0)
            bitmap->bits = BITMAP_16BPP;
          else if (strcmp(token, "32") == 0)
            bitmap->bits = BITMAP_32BPP;
          else if (strcmp(token, "RAW8") == 0)
            bitmap->bits = BITMAP_8BPP  | BITMAP_UNCOMPRESSED;
          else if (strcmp(token, "RAW16") == 0)
            bitmap->bits = BITMAP_16BPP | BITMAP_UNCOMPRESSED;
          else if (strcmp(token, "RAW32") == 0)
            bitmap->bits = BITMAP_32BPP | BITMAP_UNCOMPRESSED;
#ifdef __riscos__
          else if (strcmp(token, "spritefile") == 0)
            bitmap->bits = BITMAP_SPRITEFILE;
#endif
          else {
            fprintf(stderr, "Illegal 'type' field in bitmap\n");
            return 1;
          }

        } else                                              return 1;

      } else if (type != TOKEN_RB)
        return 1;
    } while (type != TOKEN_RB);
    break;

  }

  return add_character(bitmap->id, bitmap, CHARACTER_BITMAP);
}


S32 parse_fillstyles(SHAPE *shape) {

  char token[100];
  S32 type;
  FILLSTYLE style;

  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "solid") == 0) {
        style.type = FILLSTYLE_SOLID;
        if (get_rgb(&style.fill.solid) != TOKEN)            return 1;

      } else if ((strcmp(token, "linear") == 0) || (strcmp(token, "radial") == 0)) {
        S32 type;

        matrix_reset(&style.fill.gradient.matrix);

        if (strcmp(token, "radial") == 0)
          style.type = FILLSTYLE_RADIAL;
        else
          style.type = FILLSTYLE_LINEAR;
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        do {
          type = get_tokenl(token, 100);
          if (type == TOKEN) {
            if (strcmp(token, "matrix") == 0) {
              if (parse_matrix(&style.fill.gradient.matrix))  return 1;
            } else if (strcmp(token, "gradient") == 0) {
              if (parse_gradient(&style.fill.gradient.gradient))  return 1;
            }
          } else if (type != TOKEN_RB)                      return 1;
        } while (type != TOKEN_RB);

      } else if ((strcmp(token, "tiled") == 0) || (strcmp(token, "clipped") == 0)) {
        S32 type;

        matrix_reset(&style.fill.gradient.matrix);

        if (strcmp(token, "clipped") == 0)
          style.type = FILLSTYLE_CLIPPED;
        else
          style.type = FILLSTYLE_TILED;
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        do {
          type = get_tokenl(token, 100);
          if (type == TOKEN) {
            if (strcmp(token, "matrix") == 0) {
              if (parse_matrix(&style.fill.bitmap.matrix))  return 1;

            } else if (strcmp(token, "id") == 0) {
              if (get_id(&style.fill.bitmap.id) != TOKEN)   return 1;
              if (get_character_type(style.fill.bitmap.id) != CHARACTER_BITMAP) {
                fprintf(stderr, "Character (id=%d) is unknown or does not refer to a bitmap\n", style.fill.bitmap.id);
                return 1;
              }
            }
          } else if (type != TOKEN_RB)                      return 1;

        } while (type != TOKEN_RB);
      }
      if (shape_add_fillstyle(shape, &style))               return 1;

    } else if (type != TOKEN_RB)                            return 1;

  } while (type != TOKEN_RB);

  return 0;
}


S32 parse_linestyles(SHAPE *shape) {

  S32 type;
  U16 width;
  LINESTYLE style;

  do {
    type = get_valueU16(&width);
    if (type == TOKEN) {
      style.width = (scaleboth*width)/1000;

      if (get_rgb(&style.rgba) != TOKEN)          return 1;

      if (shape_add_linestyle(shape, &style))     return 1;

    } else if (type != TOKEN_RB)
      return 1;

  } while (type != TOKEN_RB);

  return 0;
}


S32 parse_file_version() {

  if (get_valueU8(&fileversion) != TOKEN)         return 1;

  return 0;
}


S32 parse_frame_area() {

  char token[100];
  S32 type;

  if (get_token(token, 100) != TOKEN_LB)          return 1;

  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "width") == 0) {
        if (get_value(&framesize.maxx) != TOKEN)  return 1;
        if (create_variable("framesizex"))        return 1;
        if (set_variable_value("framesizex", framesize.maxx)) return 1;

      } else if (strcmp(token, "height") == 0) {
        if (get_value(&framesize.maxy) != TOKEN)  return 1;
        if (create_variable("framesizey"))        return 1;
        if (set_variable_value("framesizey", framesize.maxy)) return 1;

      } else if (strcmp(token, "scalex") == 0) {
        if (get_value(&scalex) != TOKEN)          return 1;

      } else if (strcmp(token, "scaley") == 0) {
        if (get_value(&scaley) != TOKEN)          return 1;

      } else {
        fprintf(stderr, "Unknown token '%s'\n", token);
        return 1;
      }
    } else if (type != TOKEN_RB)                  return 1;
  } while (type != TOKEN_RB);

  scaleboth = (S32)sqrt((int)abs(scalex*scaley));

  return 0;
}


S32 parse_frame_rate() {

  char token[100], *p;
  U8 i, f;

  if (get_token(token, 100))     return 1;
  p = strchr(token, '.');
  if (p) {
    *p = '\0';
    f = atoi(p+1);
  } else
    f = 0;

  i = atoi(token);

  if ((i > 255) || (i < 0) || (f > 255) || (f < 0))  return 1;

  framerate = (i<<8) | f;

  return 0;
}


S32 parse_cxform(CXFORM *cxform) {

  char token[100];
  S16 values[8];
  S32 n, type;

  cxform->mula = 255;
  cxform->adda = 0;

  if (get_token(token, 100) != TOKEN_LB)          return 1;

  n = 0;
  do {
    type = get_valueS16(values+n);
    if (type == TOKEN)
      n++;
    else if (type != TOKEN_RB)
      return 1;
  } while ((n < 8) && (type != TOKEN_RB));

  if (n == 8)
    if (get_token(token, 100) != TOKEN_RB)        return 1;

  if ((n != 6) && (n != 8)) {
    fprintf(stderr, "Bad value-count in cxform\n");
    return 1;
  }

  if (n == 6) {
    cxform->mulr = values[0];
    cxform->mulg = values[1];
    cxform->mulb = values[2];
    cxform->addr = values[3];
    cxform->addg = values[4];
    cxform->addb = values[5];
    cxform->mula = 255;
    cxform->adda = 0;
  } else {
    cxform->mulr = values[0];
    cxform->mulg = values[1];
    cxform->mulb = values[2];
    cxform->mula = values[3];
    cxform->addr = values[4];
    cxform->addg = values[5];
    cxform->addb = values[6];
    cxform->adda = values[7];
  }

  return 0;
}


S32 parse_bg_colour() {

  return get_rgb(&bgcolour);
}


S32 parse_define_shape(void) {

  char token[100];
  S32 type;
  S32 x, y;
  SHAPE *shape;
  SHAPERECORD rec;

  if (shape_create(&shape))                                 return 1;

  if (get_token(token, 100) != TOKEN_LB)                    return 1;

  x = y = 0;

  do {
    // repeatedly read a token and parse the arguments
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&shape->id) != TOKEN)                    return 1;
        if (is_id_used(shape->id))  report_error("Dublicate character ID");

      } else if (strcmp(token, "bbox") == 0) {
        // bbox { } is not used from version 0.02 onwards
        RECT unused;
        if (parse_rect(&unused))                            return 1;

      } else if (strcmp(token, "fillstyle") == 0) {
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        if (parse_fillstyles(shape))                        return 1;

      } else if (strcmp(token, "linestyle") == 0) {
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        if (parse_linestyles(shape))                        return 1;

      } else if ((strcmp(token, "selectfillstyle0") == 0) ||
                 (strcmp(token, "fill0") == 0))              {
        rec.type = SHAPERECORD_STYLE;
        rec.flags = SHAPERECORD_STYLE_FILL0;
        if (get_valueU8(&rec.fillstyle0) != TOKEN)          return 1;
        if (shape_add_record(shape, &rec))                  return 1;

      } else if ((strcmp(token, "selectfillstyle1") == 0) ||
                 (strcmp(token, "fill1") == 0))              {
        rec.type = SHAPERECORD_STYLE;
        rec.flags = SHAPERECORD_STYLE_FILL1;
        if (get_valueU8(&rec.fillstyle1) != TOKEN)          return 1;
        if (shape_add_record(shape, &rec))                  return 1;

      } else if (strcmp(token, "selectlinestyle") == 0) {
        rec.type = SHAPERECORD_STYLE;
        rec.flags = SHAPERECORD_STYLE_LINE;
        if (get_valueU8(&rec.linestyle) != TOKEN)           return 1;
        if (shape_add_record(shape, &rec))                  return 1;

      } else if (strcmp(token, "moveto") == 0) {
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        rec.type = SHAPERECORD_STYLE;
        rec.flags = SHAPERECORD_STYLE_MOVE;
        if (get_value(&x) != TOKEN)                         return 1;
        x = (scalex*x)/1000;
        if (get_value(&y) != TOKEN)                         return 1;
        y = (scaley*y)/1000;
        rec.x = x;
        rec.y = y;
        if (shape_add_record(shape, &rec))                  return 1;
        if (get_token(token, 100) != TOKEN_RB)              return 1;

      } else if (strcmp(token, "moveby") == 0) {
        S32 dx, dy;
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        rec.type = SHAPERECORD_STYLE;
        rec.flags = SHAPERECORD_STYLE_MOVE;
        if (get_value(&dx) != TOKEN)                        return 1;
        dx = (scalex*dx)/1000;
        if (get_value(&dy) != TOKEN)                        return 1;
        dy = (scaley*dy)/1000;
        if ((dx != 0) || (dy != 0)) {
          x += dx;
          y += dy;
          rec.x = x;
          rec.y = y;
          if (shape_add_record(shape, &rec))                return 1;
        }
        if (get_token(token, 100) != TOKEN_RB)              return 1;

      } else if (strcmp(token, "lineby") == 0) {
        S32 dx, dy;
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        rec.type = SHAPERECORD_STRAIGHT;
        if (get_value(&dx) != TOKEN)                        return 1;
        dx = (scalex*dx)/1000;
        if (get_value(&dy) != TOKEN)                        return 1;
        dy = (scaley*dy)/1000;
        if ((dx != 0) || (dy != 0)) {
          x += dx;
          y += dy;
          rec.x = dx;
          rec.y = dy;
          if (shape_add_record(shape, &rec))                return 1;
        }
        if (get_token(token, 100) != TOKEN_RB)              return 1;

      } else if (strcmp(token, "curveby") == 0) {
        S32 dxc, dyc, dx, dy;
        if (get_token(token, 100) != TOKEN_LB)              return 1;
        rec.type = SHAPERECORD_CURVE;
        if (get_value(&dxc) != TOKEN)                       return 1;
        dxc = (scalex*dxc)/1000;
        if (get_value(&dyc) != TOKEN)                       return 1;
        dyc = (scaley*dyc)/1000;
        if (get_value(&dx) != TOKEN)                        return 1;
        dx = (scalex*dx)/1000;
        if (get_value(&dy) != TOKEN)                        return 1;
        dy = (scaley*dy)/1000;
        if ((dx != 0) || (dy != 0) || (dxc != 0) || (dyc != 0)) {
          x += dxc + dx;
          y += dyc + dy;
          rec.x = dx;
          rec.y = dy;
          rec.ctrlx = dxc;
          rec.ctrly = dyc;
          if (shape_add_record(shape, &rec))                return 1;
        }
        if (get_token(token, 100) != TOKEN_RB)              return 1;

      }
    } else if (type != TOKEN_RB)                            return 1;
  } while (type != TOKEN_RB);

  rec.type = SHAPERECORD_END;
  if (shape_add_record(shape, &rec))                        return 1;

  return add_character(shape->id, shape, CHARACTER_SHAPE);
}


S32 parse_buttonsound_transition(BUTTONSOUND *bs, S32 transition) {

  char token[100];

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  if (get_id(&bs->soundids[transition]))          return 1;
  if (get_valueU16(&bs->loops[transition]))       return 1;
  if (get_token(token, 100) != TOKEN_RB)          return 1;

  return 0;
}


S32 parse_button_sound() {

  char token[100];
  S32 type;
  BUTTONSOUND bs;

  bs.soundids[BUTTONTRANSITION_LEAVE] =
  bs.soundids[BUTTONTRANSITION_ENTER] =
  bs.soundids[BUTTONTRANSITION_DOWN] =
  bs.soundids[BUTTONTRANSITION_UP] = 0;
  bs.buttonid = RESERVED_ID;

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "buttonid") == 0) {
        if (get_id(&bs.buttonid) != TOKEN)        return 1;

      } else if (strcmp(token, "enter") == 0) {
        if (parse_buttonsound_transition(&bs, BUTTONTRANSITION_ENTER))
          return 1;

      } else if (strcmp(token, "leave") == 0) {
        if (parse_buttonsound_transition(&bs, BUTTONTRANSITION_LEAVE))
          return 1;

      } else if (strcmp(token, "up") == 0) {
        if (parse_buttonsound_transition(&bs, BUTTONTRANSITION_UP))
          return 1;

      } else if (strcmp(token, "down") == 0) {
        if (parse_buttonsound_transition(&bs, BUTTONTRANSITION_DOWN))
          return 1;

      } else
        return 1;
    } else if (type != TOKEN_RB)                  return 1;
  } while (type != TOKEN_RB);

  return buttonsound_add(&bs);
}


S32 parse_button_state(BUTTON *button, U8 which) {

  char token[100];
  S32 type;
  BUTTONSTATE state;

  memset(&state, 0, sizeof(BUTTONSTATE));
  state.state = which;
  matrix_reset(&state.matrix);
  cxform_reset(&state.cxform);

  if (get_token(token, 100) != TOKEN_LB)                    return 1;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        S32 type;
        if (get_id(&state.id) != TOKEN)                     return 1;
        type = get_character_type(state.id);
        if (type == CHARACTER_NOT_FOUND) {
          fprintf(stderr, "Character (id=%d) is undefined\n", state.id);
          return 1;
        }
        if ((type != CHARACTER_BITMAP) &&
            (type != CHARACTER_SHAPE) &&
            (type != CHARACTER_TEXT)) {
          fprintf(stderr, "Character (id=%d) can not be used for a button-state\n", state.id);
          return 1;
        }

      } else if (strcmp(token, "depth") == 0) {
        if (get_valueU16(&state.depth) != TOKEN)            return 1;

      } else if (strcmp(token, "matrix") == 0) {
        if (parse_matrix(&state.matrix))                    return 1;

      } else if (strcmp(token, "cxform") == 0) {
        if (parse_cxform(&state.cxform))                    return 1;

      }
    } else if (type != TOKEN_RB)                            return 1;
  } while (type != TOKEN_RB);

  if (button->count == 0) {
    button->states = malloc(sizeof(BUTTONSTATE));
    if (!button->states) {
      fprintf(stderr, "No room\n");
      return 1;
    }
  } else {
    BUTTONSTATE *newstates;
    newstates = realloc(button->states, (button->count+1)*sizeof(BUTTONSTATE));
    if (!newstates) {
      fprintf(stderr, "No room\n");
      return 1;
    }
    button->states = newstates;
  }

  memcpy(button->states + button->count, &state, sizeof(BUTTONSTATE));
  button->count++;

  return 0;
}


S32 parse_button() {

  char token[100];
  S32 type;
  BUTTON *button;

  // create and reset structure
  button = malloc(sizeof(BUTTON));
  if (!button)                                              return 1;
  memset(button, 0, sizeof(BUTTON));

  if (get_token(token, 100) != TOKEN_LB)                    return 1;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&button->id) != TOKEN)                   return 1;
        if (is_id_used(button->id))   report_error("Dublicate character ID");

      } else if (strcmp(token, "actions") == 0) {
        if (parse_actions(&button->actions, &button->actioncount))
                                                            return 1;
      } else {
        U32 which;

        which = 0;
        if (strstr(token, "up"))          which |= BUTTONSTATE_UP;
        if (strstr(token, "over"))        which |= BUTTONSTATE_OVER;
        if (strstr(token, "down"))        which |= BUTTONSTATE_DOWN;
        if (strstr(token, "activearea"))  which |= BUTTONSTATE_SHAPE;
        if (which) {
          if (parse_button_state(button, which)) {
            fprintf(stderr, "Failed to parse 'up' state\n");
            return 1;
          }
        } else {
          fprintf(stderr, "Unsupported field '%s'\n", token);
          return 1;
        }
      }
    } else if (type != TOKEN_RB)                            return 1;
  } while (type != TOKEN_RB);

  return add_character(button->id, button, CHARACTER_BUTTON);
}


S32 parse_place_object() {

  char token[100];
  S32 type;
  PLACEOBJECT place;

  // reset the structure
  memset(&place, 0, sizeof(PLACEOBJECT));
  cxform_reset(&place.cxform);
  matrix_reset(&place.matrix);
  place.usealpha = 1;
  place.flags = PLACEOBJECT_MOVE | PLACEOBJECT_MATRIX;
  place.frameno = RESERVED_FRAMENO;

  if (get_token(token, 100) != TOKEN_LB)                    return 1;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&place.id) != TOKEN)                     return 1;
        if (!is_id_used(place.id)) {
          fprintf(stderr, "Unknown character (id=%d)\n", place.id);
          return 1;
        }
        place.flags &= ~PLACEOBJECT_MOVE;
        place.flags |= PLACEOBJECT_CHARACTER;

      } else if (strcmp(token, "depth") == 0) {
        if (get_valueU16(&place.depth) != TOKEN)            return 1;

      } else if (strcmp(token, "matrix") == 0) {
        if (parse_matrix(&place.matrix))                    return 1;
        place.flags |= PLACEOBJECT_MATRIX;

      } else if (strcmp(token, "cxform") == 0) {
        if (parse_cxform(&place.cxform))                    return 1;
        place.flags |= PLACEOBJECT_CXFORM;

      } else if (strcmp(token, "clip") == 0) {
        if (get_valueU16(&place.clip) != TOKEN)             return 1;
        place.flags |= PLACEOBJECT_CLIP;

      } else if (strcmp(token, "frame") == 0) {
        if (get_valueU16(&place.frameno) != TOKEN)          return 1;

      }

    } else if (type != TOKEN_RB)                            return 1;
  } while (type != TOKEN_RB);

  place_object(&place);

  return 0;
}



S32 parse_remove_object() {
// handles both RemoveObject and RemoveObject2
  char token[100];
  S32 type;
  U16 depth, frameno;

  frameno = RESERVED_FRAMENO;

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_token(token, 100) != TOKEN)       return 1;

      } else if (strcmp(token, "depth") == 0) {
        if (get_valueU16(&depth) != TOKEN)        return 1;

      } else if (strcmp(token, "frame") == 0) {
        if (get_valueU16(&frameno) != TOKEN)      return 1;
      }

    } else if (type != TOKEN_RB)
      return 1;
  } while (type != TOKEN_RB);

  remove_object(depth, frameno);

  return 0;
}


S32 parse_show_frame() {

  char token[100];

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  if (get_token(token, 100) != TOKEN_RB)          return 1;
  if (show_frame())                               return 1;
  return 0;
}



S32 parse_text_style_record(TEXTREC *rec) {

  char token[100];
  S32 type;

  rec->flags = 0;

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "font") == 0) {
        rec->flags |= TEXTREC_FONT;
        if (get_id(&rec->data.style.font) != TOKEN)  return 1;
        if (find_font(rec->data.style.font) == -1) {
          fprintf(stderr, "Font (id=%d) not defined\n", rec->data.style.font);
          return 1;
        }

      } else if (strcmp(token, "size") == 0) {
        rec->flags |= TEXTREC_SIZE;
        if (get_valueU16(&rec->data.style.size) != TOKEN)
          return 1;

      } else if (strcmp(token, "colour") == 0) {
        rec->flags |= TEXTREC_COLOUR;
        if (get_rgb(&rec->data.style.colour) != TOKEN)
          return 1;

      } else if (strcmp(token, "move") == 0) {
        rec->flags |= TEXTREC_MOVE;
        if (get_token(token, 100) != TOKEN_LB)    return 1;
        if (get_value(&rec->data.style.x) != TOKEN)  return 1;
        if (get_value(&rec->data.style.y) != TOKEN)  return 1;
        if (get_token(token, 100) != TOKEN_RB)    return 1;
      }

    } else if (type != TOKEN_RB)                  return 1;

  } while (type != TOKEN_RB);

  if (rec->flags == 0) {
    fprintf(stderr, "Empty text-record\n");
    return 1;
  }

  return 0;
}


S32 parse_text() {

  char token[MAXLINELENGTH];
  S32 type;
  TEXT *text;
  S32 fontindex;

  // reset structure
  text = malloc(sizeof(TEXT));
  if (!text) {
    fprintf(stderr, "No room\n");
    return 1;
  }
  memset(text, 0, sizeof(TEXT));
  matrix_reset(&text->matrix);

  if (get_token(token, 100) != TOKEN_LB)          return 1;

  fontindex = -1;

  do {
    type = get_tokenl(token, MAXLINELENGTH);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&text->id) != TOKEN)           return 1;
        if (is_id_used(text->id)) {
          fprintf(stderr, "Dublicate character id (%d)\n", text->id);
          return 1;
        }

      } else if (strcmp(token, "matrix") == 0) {
        if (parse_matrix(&text->matrix))          return 1;

      } else if (strcmp(token, "style") == 0) {
        TEXTREC *rec;
        rec = malloc(sizeof(TEXTREC));
        if (!rec) {
          fprintf(stderr, "No room\n");
          return 1;
        }
        if (parse_text_style_record(rec))         return 1;
        if (rec->flags & TEXTREC_FONT)
          fontindex = find_font(rec->data.style.font);
        if (add_text_record(text, rec))           return 1;

      } else if (strcmp(token, "text") == 0) {
        TEXTREC *rec;

        // make sure a font has been selected
        if (fontindex == -1) {
          fprintf(stderr, "No font has been selected\n");
          return 1;
        }
        // reset record
        rec = malloc(sizeof(TEXTREC));
        if (!rec) {
          fprintf(stderr, "No room\n");
          return 1;
        }
        memset(rec, 0, sizeof(TEXTREC));
        rec->flags = 0;
        // read string to display
        if (get_token(token, MAXLINELENGTH) != TOKEN_STRING)
          return 1;
        if (strlen(token) >= 127) {
          fprintf(stderr, "String too long\n");
          return 1;
        }
        rec->data.text.text = malloc(strlen(token)+1);
        if (!rec->data.text.text) {
          fprintf(stderr, "No room\n");
          return 1;
        }
        strcpy((char *)rec->data.text.text, token);
        rec->data.text.length = strlen((char *)rec->data.text.text);
        if (add_text_record(text, rec))           return 1;
        // mark the glyphs as being used
        mark_glyphs_as_used(fontindex, rec->data.text.text, rec->data.text.length);
      }

    } else if (type != TOKEN_RB)                  return 1;

  } while (type != TOKEN_RB);

  return add_character(text->id, text, CHARACTER_TEXT);
}


S32 parse_glyph(GLYPH *glyph) {

  char token[100];
  S32 type;
  GLYPHSHAPE shape;
  S32 x, y;

  if (get_token(token, 100) != TOKEN_LB)          return 1;
  x = y = 0;
  do {
    type = get_tokenl(token, 100);
    if (type == TOKEN) {
      if (strcmp(token, "char") == 0) {
        type = get_token(token, 100);
        if (type == TOKEN_STRING) {
          if (strlen(token) != 1) {
            fprintf(stderr, "Bad glyph-specifier (argument to 'char' is too long)\n");
            return 1;
          }
          glyph->letter = token[0];
        } else if (type == TOKEN) {
          S32 v;
          if (evaluate(token, &v))                return 1;
          glyph->letter = v;
        } else {
          fprintf(stderr, "Bad glyph-specifier (bad argument to 'char')\n");
          return 1;
        }

      } else if (strcmp(token, "index") == 0) {
        if (get_valueU8(&glyph->letter) != TOKEN) return 1;

      } else if (strcmp(token, "moveby") == 0) {
        S32 dx, dy;
        shape.type = GLYPHSHAPE_MOVE;
        if (get_token(token, 100) != TOKEN_LB)    return 1;
        if (get_value(&dx) != TOKEN)              return 1;
        if (get_value(&dy) != TOKEN)              return 1;
        if (get_token(token, 100) != TOKEN_RB)    return 1;
        x += dx;
        y += dy;
        shape.x = x;
        shape.y = y;
        if (add_glyph_shape(glyph, &shape))       return 1;

      } else if (strcmp(token, "moveto") == 0) {
        shape.type = GLYPHSHAPE_MOVE;
        if (get_token(token, 100) != TOKEN_LB)    return 1;
        if (get_value(&x) != TOKEN)               return 1;
        if (get_value(&y) != TOKEN)               return 1;
        if (get_token(token, 100) != TOKEN_RB)    return 1;
        shape.x = x;
        shape.y = y;
        if (add_glyph_shape(glyph, &shape))       return 1;

      } else if (strcmp(token, "lineby") == 0) {
        shape.type = GLYPHSHAPE_LINE;
        if (get_token(token, 100) != TOKEN_LB)    return 1;
        if (get_value(&shape.x) != TOKEN)         return 1;
        if (get_value(&shape.y) != TOKEN)         return 1;
        if (get_token(token, 100) != TOKEN_RB)    return 1;
        x += shape.x;
        y += shape.y;
        if (add_glyph_shape(glyph, &shape))       return 1;

      } else if (strcmp(token, "curveby") == 0) {
        shape.type = GLYPHSHAPE_CURVE;
        if (get_token(token, 100) != TOKEN_LB)    return 1;
        if (get_value(&shape.ctrlx) != TOKEN)     return 1;
        if (get_value(&shape.ctrly) != TOKEN)     return 1;
        if (get_value(&shape.x) != TOKEN)         return 1;
        if (get_value(&shape.y) != TOKEN)         return 1;
        if (get_token(token, 100) != TOKEN_RB)    return 1;
        x += shape.ctrlx + shape.x;
        y += shape.ctrly + shape.y;
        if (add_glyph_shape(glyph, &shape))       return 1;
      }

    } else if (type != TOKEN_RB)                  return 1;

  } while (type != TOKEN_RB);

  return 0;
}


S32 parse_font() {

  char token[MAXLINELENGTH];
  S32 type, i;
  S32 scalex, scaley;
  FONT *font;

  font = malloc(sizeof(FONT));
  if (!font) {
    fprintf(stderr, "No room\n");
    return 1;
  }
  memset(font, 0, sizeof(FONT));

  scalex = scaley = 65536;

  if (get_token(token, 100) != TOKEN_LB)          return 1;

  do {
    type = get_tokenl(token, MAXLINELENGTH);
    if (type == TOKEN) {
      if (strcmp(token, "id") == 0) {
        if (get_id(&font->id) != TOKEN)           return 1;
        if (is_id_used(font->id)) {
          fprintf(stderr, "Dublicate character id (%d)\n", font->id);
          return 1;
        }

      } else if (strcmp(token, "spacing") == 0) {
        if (get_valueS16(&font->spacing))         return 1;

      } else if (strcmp(token, "name") == 0) {
        if (get_token(token, 100) != TOKEN_STRING) return 1;
        if (strlen(token) > 255) {
          fprintf(stderr, "Fontname too long, max 255 chars\n");
          return 1;
        }
        strcpy(font->fontname, token);

      } else if (strcmp(token, "scale") == 0) {
        if (get_token(token, 100) != TOKEN_LB)    return 1;
        if (get_value(&scalex) != TOKEN)          return 1;
        if (get_value(&scaley) != TOKEN)          return 1;
        if (get_token(token, 100) != TOKEN_RB)    return 1;

      } else if (strcmp(token, "glyph") == 0) {
        GLYPH *glyph;
        glyph = malloc(sizeof(GLYPH));
        if (!glyph) {
          fprintf(stderr, "No room\n");
          return 1;
        }
        memset(glyph, 0, sizeof(GLYPH));
        if (parse_glyph(glyph))                   return 1;
        if (add_glyph(font, glyph))               return 1;
      }

    } else if (type != TOKEN_RB)                  return 1;

  } while (type != TOKEN_RB);

  // if scaling or offset is specified for the font...
  if ((scalex != 65536) || (scaley != 65536)) {
    for (i = 0; i < font->glyphcount; i++) {
      GLYPH *glyph;
      S32 shape;

      glyph = font->glyphs + i;
      for (shape = 0; shape < glyph->shapecount; shape++) {
        GLYPHSHAPE *gs;
        gs = glyph->shapes + shape;
        gs->x     = (gs->x     * scalex)>>16;
        gs->y     = (gs->y     * scaley)>>16;
        gs->ctrlx = (gs->ctrlx * scalex)>>16;
        gs->ctrly = (gs->ctrly * scaley)>>16;
      }
    }
  }
  // calculate glyph bbox
  for (i = 0; i < font->glyphcount; i++) {
    GLYPH *glyph;
    S32 shape, context;
    S32 x, y;

    glyph = font->glyphs + i;

    bbox_init(&glyph->bbox, &context);
    x = y = 0;
    for (shape = 0; shape < glyph->shapecount; shape++) {
      GLYPHSHAPE *gs;

      gs = glyph->shapes + shape;
      if (gs->type == GLYPHSHAPE_MOVE) {
        x = gs->x;
        y = gs->y;
        bbox_add_point(&glyph->bbox, &context, x, y, 0);
      } else if (gs->type == GLYPHSHAPE_LINE) {
        x += gs->x;
        y += gs->y;
        bbox_add_point(&glyph->bbox, &context, x, y, 0);
      } else if (gs->type == GLYPHSHAPE_CURVE) {
        x += gs->ctrlx;
        y += gs->ctrly;
        bbox_add_point(&glyph->bbox, &context, x, y, 0);
        x += gs->x;
        y += gs->y;
        bbox_add_point(&glyph->bbox, &context, x, y, 0);
      }
    }
  }

  if (add_font(font))                             return 1;

  return add_character(font->id, font, CHARACTER_FONT);
}

// ---------------------------------------------------------------------


S32 place_object(PLACEOBJECT *place) {

  FRAME *frame;

  use_depth(place->depth, 1);
  use_id(place->id);

  if (place->frameno == RESERVED_FRAMENO)
    frame = frames+framecount;
  else {
    if (place->frameno >= framecount)
      report_error("Illegal frame no. in PlaceObject { }");
    frame = frames+place->frameno;
  }

  if (!frame->place) {
    frame->place = malloc(sizeof(PLACEOBJECT));
    if (!frame->place)        return 1;
  } else {
    PLACEOBJECT *newplace;
    newplace = realloc(frame->place, (frame->placecount+1)*sizeof(PLACEOBJECT));
    if (!newplace)            return 1;
    frame->place = newplace;
  }
  memcpy(frame->place + frame->placecount, place, sizeof(PLACEOBJECT));
  frame->placecount++;

  return 0;
}


S32 remove_object(U16 depth, U16 frameno) {

  FRAME *frame;

  use_depth(depth, 0);

  if (frameno == RESERVED_FRAMENO)
    frame = frames+framecount;
  else {
    if (frameno >= framecount)
      report_error("Illegal frame number in RemoveObject { }");
    frame = frames+frameno;
  }
  if (!frame->removedepths) {
    frame->removedepths = malloc(sizeof(U16));
    if (!frame->removedepths)       return 1;
  } else {
    U16 *newremove;
    newremove = realloc(frame->removedepths, (frame->removecount+1)*sizeof(U16));
    if (!newremove)           return 1;
    frame->removedepths = newremove;
  }
  frame->removedepths[frame->removecount++] = depth;

  return 0;
}


S32 show_frame() {

  framecount++;
  if (framecount >= MAXFRAMECOUNT)                return 1;
  // clear next frame
  memset(frames+framecount, 0, sizeof(FRAME));

  if (set_variable_value("frameno", framecount))  return 1;
  return 0;
}


S32 write_tag(S32 tag, S32 size) {

  if (size >= 0x3f) {
    if (write_ushort((tag<<6) | 0x3f))  return 1;
    if (write_uint((U32)size))          return 1;
  } else
    if (write_ushort((tag<<6) | size))  return 1;

  return 0;
}


S32 write_place_object(PLACEOBJECT *place) {

  U32 ptr;

  if (cxform_is_empty(&place->cxform))
    place->flags &= ~PLACEOBJECT_CXFORM;

  if (flush_bucket())                                       return 1;
  ptr = read_position(NULL);
  if (write_ushort(0))                                      return 1;
  if (write_ubyte(place->flags))                            return 1;
  if (write_ushort(place->depth))                           return 1;
  if (place->flags & PLACEOBJECT_CHARACTER)
    if (write_ushort(place->id))                            return 1;
  if (place->flags & PLACEOBJECT_MATRIX)
    if (matrix_write(&place->matrix))                       return 1;
  if (place->flags & PLACEOBJECT_CXFORM)
    if (cxform_write(&place->cxform, place->usealpha))      return 1;
  if (place->flags & PLACEOBJECT_CLIP)
      if (write_ushort(place->clip))                        return 1;

  if (update_record_header(stagPlaceObject2, ptr))          return 1;

  return 0;
}


S32 write_flash_file() {

  U32 i;

  if (framecount < 1) {
    fprintf(stderr, "No frames defined\n");
    return 1;
  }

  if (write_ubyte('F'))                                     return 1;
  if (write_ubyte('W'))                                     return 1;
  if (write_ubyte('S'))                                     return 1;
  if (write_ubyte(fileversion))                             return 1;
  if (write_uint(0))                                        return 1;
  if (rect_write(&framesize))                               return 1;
  if (write_ushort(framerate))                              return 1;
  if (write_ushort(framecount))                             return 1;

  if (protected)
    if (write_tag(stagProtect, 0))                          return 1;

  // pass one - characters that may be used by other characters
  for (i = 0; i < used_characters; i++) {
    switch (dictionary[i]->type) {
    case CHARACTER_BITMAP:
      if (bitmap_write(dictionary[i]->data.bitmap)) {
        fprintf(stderr, "Failed to write bitmap\n");
        return 1;
      }
      break;
    case CHARACTER_FONT:
      if (fonttext_write_font(dictionary[i]->data.font)) {
        fprintf(stderr, "Failed to write font\n");
        return 1;
      }
      if (fonttext_write_fontinfo(dictionary[i]->data.font)) {
        fprintf(stderr, "Failed to write font info\n");
        return 1;
      }
      break;
    case CHARACTER_SOUND:
      if (sound_write(dictionary[i]->data.sound)) {
        fprintf(stderr, "Failed to write sound\n");
        return 1;
      }
      break;
    }
  }
  // pass two - characters that use other characters
  for (i = 0; i < used_characters; i++) {
    switch (dictionary[i]->type) {
    case CHARACTER_SHAPE:
      if (shape_write(dictionary[i]->data.shape)) {
        fprintf(stderr, "Failed to write shape\n");
        return 1;
      }
      break;
    case CHARACTER_TEXT:
      if (fonttext_write_text(dictionary[i]->data.text)) {
        fprintf(stderr, "Failed to write text\n");
        return 1;
      }
      break;
    }
  }
  // pass three - characters that use characters that use other characters
  for (i = 0; i < used_characters; i++) {
    switch (dictionary[i]->type) {
    case CHARACTER_BUTTON:
      if (button_write(dictionary[i]->data.button)) {
        fprintf(stderr, "Failed to write button\n");
        return 1;
      }
      break;
    }
  }

  buttonsound_write_all();

  if (write_tag(stagSetBackgroundColour, 3))                return 1;
  if (write_ubyte( bgcolour      &255))                     return 1;
  if (write_ubyte((bgcolour>>8 ) &255))                     return 1;
  if (write_ubyte((bgcolour>>16) &255))                     return 1;

  for (i = 0; i < framecount; i++) {
    U32 obj;
    // remove objects
    for (obj = 0; obj < frames[i].removecount; obj++) {
      if (write_tag(stagRemoveObject2, 2))                  return 1;
      if (write_ushort(frames[i].removedepths[obj]))        return 1;
    }
    // play sounds
    for (obj = 0; obj < frames[i].soundcount; obj++)
      if (write_play_sound(frames[i].sounds+obj))           return 1;
    // place objects
    for (obj = 0; obj < frames[i].placecount; obj++)
      if (write_place_object(frames[i].place+obj))          return 1;
    // actions
    if (action_write(frames[i].actions, frames[i].actioncount))  return 1;

    if (write_tag(stagShowFrame, 0))                        return 1;
  }

  if (write_tag(stagEnd, 0))                                return 1;
  return 0;
}



S32 parse_file() {

  char token[MAXLINELENGTH];
  S32 type;

  if (parse_initialise())  return 1;

  linebuffer[0] = '\0';
  bufferpos = 0;

  do {
    type = get_tokenl(token, MAXLINELENGTH-1);
    if (type == TOKEN) {
      if (strcmp(token, "fileversion") == 0) {
        if (parse_file_version())   report_error("Bad FileVersion");

      } else if (strcmp(token, "end") == 0) {
        return 0;

      } else if (strcmp(token, "framearea") == 0) {
        if (parse_frame_area())     report_error("Bad FrameArea");

      } else if (strcmp(token, "protect") == 0) {
        if (get_token(token, MAXLINELENGTH-1) != TOKEN_LB)
          report_error("Error in script-file");
        if (get_token(token, MAXLINELENGTH-1) != TOKEN_RB)
          report_error("Error in script-file");
        protected = 1;

      } else if (strcmp(token, "framerate") == 0) {
        if (parse_frame_rate())     report_error("Bad FrameRate");

      } else if ((strcmp(token, "backgroundcolour") == 0) ||
                 (strcmp(token, "bgcolour") == 0))           {
        if (parse_bg_colour())      report_error("Bad BackgroundColour");

      } else if (strcmp(token, "button") == 0) {
        if (parse_button())         report_error("Bad Button");

      } else if (strcmp(token, "buttonsound") == 0) {
        if (parse_button_sound())   report_error("Bad ButtonSound");

      } else if ((strcmp(token, "defineshape") == 0) ||
                 (strcmp(token, "shape") == 0))         {
        if (parse_define_shape())   report_error("Bad DefineShape");

      } else if (strcmp(token, "jpeg") == 0) {
        if (parse_bitmap(BITMAP_JPEG))  report_error("Bad JPEG");

      } else if (strcmp(token, "bitmap") == 0) {
        if (parse_bitmap(BITMAP_LOSSLESS))  report_error("Bad JPEG");

      } else if (strcmp(token, "font") == 0) {
        if (parse_font())  report_error("Bad font definition");

      } else if (strcmp(token, "text") == 0) {
        if (parse_text())  report_error("Bad text definition");

      } else if ((strcmp(token, "placeobject") == 0) ||
                 (strcmp(token, "place") == 0))         {
        if (parse_place_object())   report_error("Bad PlaceObject");

      } else if ((strcmp(token, "removeobject") == 0) ||
                 (strcmp(token, "remove") == 0))         {
        if (parse_remove_object())  report_error("Bad RemoveObject");

      } else if ((strcmp(token, "definesound") == 0) ||
                 (strcmp(token, "sound") == 0))         {
        if (parse_define_sound())  report_error("Bad DefineSound");

      } else if (strcmp(token, "playsound") == 0) {
        if (parse_play_sound())  report_error("Bad PlaySound");

      } else if (strcmp(token, "doaction") == 0) {
        FRAME *frame;
        frame = frames+framecount;
        if (parse_actions(&frame->actions, &frame->actioncount))
          report_error("Bad DoAction");

      } else if (strcmp(token, "showframe") == 0) {
        if (parse_show_frame())     report_error("Bad ShowFrame");

      } else
        fprintf(stderr, "Unknown tag '%s'\n", token);

    } else if (!end_of_file()) {
      fprintf(stderr, "Unexpected token :");
      switch (type) {
      case TOKEN_LB:
        fprintf(stderr, "'{'\n");
        break;
      case TOKEN_RB:
        fprintf(stderr, "'}'\n");
        break;
      case TOKEN:
      case TOKEN_STRING:
        fprintf(stderr, "'%s'\n", token);
        break;
      }
    }
  } while (!end_of_file());

  return 0;
}
