/* does not work on big endian systems */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

int nb_global_objs;
int nb_rooms;
int nb_scripts;
int nb_costumes;
int nb_sounds;

#define G(c, f, g) do { c = fgetc(f); if (c == EOF) goto g; c ^= 0x69; } while (0)
#define H(c, f, g) do { if (l == 0) goto g; c = fgetc(f); if (c == EOF) goto g; l--; c ^= 0x69; } while (0)
#define OOM do { printf("OOM\n"); exit(1); } while (0);

struct {
  int offset;
} *rooms;

struct {
  int offset;
} *lfls;

struct {
  int lfl;
  int offset;
} *scripts;

void read_index(char *filename)
{
  FILE *f = fopen(filename, "rb"); if (!f) { perror(filename); exit(1); }
  char name[5];
  int c;
  int len;
  name[4] = 0;

  while (1) {
    G(c, f, eof); name[0] = c;
    G(c, f, err); name[1] = c;
    G(c, f, err); name[2] = c;
    G(c, f, err); name[3] = c;

    G(c, f, err); len = c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c;

    if (!strcmp(name, "DOBJ")) {
      G(c, f, err); nb_global_objs = c;
      G(c, f, err); nb_global_objs |= c << 8;
      len -= 2;
    } else
    if (!strcmp(name, "DROO")) {
      G(c, f, err); nb_rooms = c;
      G(c, f, err); nb_rooms |= c << 8;
      len -= 2;
    } else
    if (!strcmp(name, "DSCR")) {
      int i;
      G(c, f, err); nb_scripts = c;
      G(c, f, err); nb_scripts |= c << 8;
      len -= 2;
      scripts = malloc(sizeof(*scripts) * nb_scripts); if (!scripts) OOM;
      for (i = 0; i < nb_scripts; i++) {
        G(c, f, err);
        scripts[i].lfl = c - 1;
      }
      for (i = 0; i < nb_scripts; i++) {
        G(c, f, err); scripts[i].offset = c;
        G(c, f, err); scripts[i].offset |= c << 8;
        G(c, f, err); scripts[i].offset |= c << 16;
        G(c, f, err); scripts[i].offset |= c << 24;
      }
      for (i = 0; i < nb_scripts; i++)
        printf("script %d: lfl %d, offset %d\n", i, scripts[i].lfl,
               scripts[i].offset);
      len -= 5 * nb_scripts;
    } else
    if (!strcmp(name, "DCOS")) {
      G(c, f, err); nb_costumes = c;
      G(c, f, err); nb_costumes |= c << 8;
      len -= 2;
    } else
    if (!strcmp(name, "DSOU")) {
      G(c, f, err); nb_sounds = c;
      G(c, f, err); nb_sounds |= c << 8;
      len -= 2;
    } else
    printf("**** WARNING: unknown chunck %s (%d)\n", name, len);

    len -= 8;

    while (len) { G(c, f, err); len--; }
  }

eof:
  fclose(f);
  return;

err:
  printf("error reading %s\n", filename);
  exit(1);
}

int room(int lfl_number, int l, FILE *f)
{
  char name[5];
  int len;
  int c;
  char file[64];
  FILE *out;
  int i;

  int nb_read = 0;

  lfl_number++;

  printf("    ROOM %d chunk proceeding...\n", lfl_number);

  name[4] = 0;
  l -= 8;

  while (l) {
    H(c, f, err); name[0] = c; nb_read++;
    H(c, f, err); name[1] = c; nb_read++;
    H(c, f, err); name[2] = c; nb_read++;
    H(c, f, err); name[3] = c; nb_read++;

    H(c, f, err); len = c; len <<= 8; nb_read++;
    H(c, f, err); len |= c; len <<= 8; nb_read++;
    H(c, f, err); len |= c; len <<= 8; nb_read++;
    H(c, f, err); len |= c; nb_read++;
    if (!strcmp(name, "ENCD")) {
      /* entry room script */
      printf("ENCD found (%d)\n", len);
      sprintf(file, "rooms/%d", lfl_number);
      mkdir("rooms", 0755);
      mkdir(file, 0755);
      sprintf(file, "rooms/%d/ENCD.bin", lfl_number);
      out = fopen(file, "wb"); if (!out) { perror(file); exit(1); }
      fwrite(name, 4, 1, out);
      fwrite(((char*)&len)+3, 1, 1, out);
      fwrite(((char*)&len)+2, 1, 1, out);
      fwrite(((char*)&len)+1, 1, 1, out);
      fwrite(((char*)&len)+0, 1, 1, out);
      len -= 8;
      for (i = 0; i < len; i++) {
        H(c, f, err); nb_read++;
        fwrite(&c, 1, 1, out);
      }
      fclose(out);
      continue;
    } else if (!strcmp(name, "EXCD")) {
      /* exit room script */
      printf("EXCD found (%d)\n", len);
      sprintf(file, "rooms/%d", lfl_number);
      mkdir("rooms", 0755);
      mkdir(file, 0755);
      sprintf(file, "rooms/%d/EXCD.bin", lfl_number);
      out = fopen(file, "wb"); if (!out) { perror(file); exit(1); }
      fwrite(name, 4, 1, out);
      fwrite(((char*)&len)+3, 1, 1, out);
      fwrite(((char*)&len)+2, 1, 1, out);
      fwrite(((char*)&len)+1, 1, 1, out);
      fwrite(((char*)&len)+0, 1, 1, out);
      len -= 8;
      for (i = 0; i < len; i++) {
        H(c, f, err); nb_read++;
        fwrite(&c, 1, 1, out);
      }
      fclose(out);
      continue;
    } else if (!strcmp(name, "LSCR")) {
      /* local room script */
      int script_number;
      printf("LSCR found (%d)\n", len);
      sprintf(file, "rooms/%d", lfl_number);
      mkdir("rooms", 0755);
      mkdir(file, 0755);
      H(c, f, err); nb_read++; script_number = c;
      sprintf(file, "rooms/%d/LSCR_%d.bin", lfl_number, script_number);
      out = fopen(file, "wb"); if (!out) { perror(file); exit(1); }
      fwrite(name, 4, 1, out);
      fwrite(((char*)&len)+3, 1, 1, out);
      fwrite(((char*)&len)+2, 1, 1, out);
      fwrite(((char*)&len)+1, 1, 1, out);
      fwrite(((char*)&len)+0, 1, 1, out);
      fwrite((char*)&script_number, 1, 1, out);
      len -= 9;
      for (i = 0; i < len; i++) {
        H(c, f, err); nb_read++;
        fwrite(&c, 1, 1, out);
      }
      fclose(out);
      continue;
    } else
    printf("**** WARNING: unknown chunck %s (len %d)\n", name, len);

    len -= 8;

    while (len) { H(c, f, err); len--; nb_read++; }
  }

  printf("    ROOM done.\n");
  return nb_read;

err:
  printf("error reading ROOM stuff\n");
  exit(1);
}

void lflf(int lfl_number, int l, FILE *f)
{
  char name[5];
  int len;
  int c;

  printf("  LFLF %d chunk proceeding...\n", lfl_number);

  name[4] = 0;
  l -= 8;

  while (l) {
    H(c, f, err); name[0] = c;
    H(c, f, err); name[1] = c;
    H(c, f, err); name[2] = c;
    H(c, f, err); name[3] = c;

    H(c, f, err); len = c; len <<= 8;
    H(c, f, err); len |= c; len <<= 8;
    H(c, f, err); len |= c; len <<= 8;
    H(c, f, err); len |= c;

    if (!strcmp(name, "ROOM")) {
      l -= room(lfl_number, len, f);
      continue;
    } else
    printf("**** WARNING: unknown chunck %s (len %d)\n", name, len);

    len -= 8;

    while (len) { H(c, f, err); len--; }
  }

  printf("  LFLF done.\n");
  return;

err:
  printf("error reading LFLF stuff\n");
  exit(1);
}

void loff(int l, FILE *f)
{
  int nb_lfl;
  int i;
  int off;
  int cur;
  int c;

  l -= 8;

  H(c, f, err); nb_lfl = c;

  printf("%d LFLF\n", nb_lfl);

  lfls = malloc(sizeof(*lfls) * nb_rooms); if (!lfls) OOM;
  for (i = 0; i < nb_rooms; i++) { lfls[i].offset = -1; }

  for (i = 0; i < nb_lfl; i++) {
    H(c, f, err); cur = c;

    H(c, f, err); off = c;
    H(c, f, err); off |= c<<8;
    H(c, f, err); off |= c<<16;
    H(c, f, err); off |= c<<24;

    if (cur < 1 || cur > nb_rooms)
      { printf("LOFF out of bound (%d)\n", cur); exit(1); }

    lfls[cur-1].offset = off;
    printf("  offset of LFLF %d = %d\n", cur-1, off);
  }

  if (!l) return;
  printf("what? l=%d, so l is not 0?\n", l);

err:
  printf("error reading LFLF stuff\n");
  exit(1);
}

void read_data(char *filename)
{
  FILE *f = fopen(filename, "rb"); if (!f) { perror(filename); exit(1); }
  int len;
  char name[5];
  int c;
  int nb_lfl = 0;
  int i;

  name[4] = 0;

  /* dumb LECF header */
  G(c, f, eof); G(c, f, eof); G(c, f, eof); G(c, f, eof);
  G(c, f, eof); G(c, f, eof); G(c, f, eof); G(c, f, eof);

  while (1) {
    G(c, f, eof); name[0] = c;
    G(c, f, err); name[1] = c;
    G(c, f, err); name[2] = c;
    G(c, f, err); name[3] = c;

    G(c, f, err); len = c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c;

    if (!strcmp(name, "LOFF")) {
      loff(len, f);
      continue;
    } else
    if (!strcmp(name, "LFLF")) {
      /* do nothing, LFLF chunks will be proceeded after, using
       * info from LOFF
       */
#if 0
      lflf(nb_lfl, len, f);
      nb_lfl++;
      continue;
#endif
    } else
    printf("**** WARNING: unknown chunck %s (%d)\n", name, len);

    len -= 8;

    while (len) { G(c, f, err); len--; }
  }

eof:
  /* proceed lfl by passing through LOFF stuff */
  for (i = 0; i < nb_rooms; i++) {
    if (lfls[i].offset == -1) continue;

    fseek(f, lfls[i].offset-8, SEEK_SET);

    G(c, f, err); name[0] = c;
    G(c, f, err); name[1] = c;
    G(c, f, err); name[2] = c;
    G(c, f, err); name[3] = c;

    G(c, f, err); len = c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c;

    lflf(i, len, f);
    nb_lfl++;
  }

  fclose(f);
  return;

err:
  printf("error reading %s\n", filename);
  fclose(f);
  exit(1);
}

void dump_scripts(char *filename)
{
  FILE *f = fopen(filename, "rb"); if (!f) { perror(filename); exit(1); }
  FILE *out;
  int i;
  char fn[64];
  char name[5];
  int c;
  int len;

  name[4] = 0;

  printf("dumping %d scripts\n", nb_scripts);

  mkdir("scripts", 0755);

  for (i = 0; i < nb_scripts; i++) {
    if (scripts[i].lfl == -1) {
      printf("skeeping non existing script %d\n", i);
      continue;
    }
    fseek(f, scripts[i].offset + lfls[scripts[i].lfl].offset, SEEK_SET);
    G(c, f, err); name[0] = c;
    G(c, f, err); name[1] = c;
    G(c, f, err); name[2] = c;
    G(c, f, err); name[3] = c;

    if (strcmp(name, "SCRP")) { printf("can't read script %d\n", i); exit(1); }

    G(c, f, err); len = c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c; len <<= 8;
    G(c, f, err); len |= c;

    len -= 8;

    sprintf(fn, "scripts/script%4.4d_lfl%d.bin", i, scripts[i].lfl);
    out = fopen(fn, "wb"); if (!out) { perror(fn); exit(1); }
    fwrite(name, 4, 1, out);
    fwrite(((char*)&len)+3, 1, 1, out);
    fwrite(((char*)&len)+2, 1, 1, out);
    fwrite(((char*)&len)+1, 1, 1, out);
    fwrite(((char*)&len)+0, 1, 1, out);
    while (len) {
      G(c, f, err);
      len--;
      fprintf(out, "%c", c);
    }
    fclose(out);
  }

  fclose(f);
  return;

err:
  printf("error reading %s\n", filename);
  exit(1);
}

int main(int n, char **v)
{
  if (n!=3) { printf("gimme index & data\n"); return 1; }

  read_index(v[1]);
  printf("%d global objects\n%d rooms\n%d scripts\n%d costumes\n%d sounds\n",
         nb_global_objs, nb_rooms, nb_scripts, nb_costumes, nb_sounds);

  rooms = malloc(sizeof(*rooms) * nb_rooms); if (!rooms) OOM;

  read_data(v[2]);

  dump_scripts(v[2]);
  return 0;
}
