#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <mspack.h>
#include <system.h>

#define FILENAME ".chminfo-temp"

unsigned char *load_sys_data(struct mschm_decompressor *chmd,
                             struct mschmd_header *chm,
			     const char *filename,
			     off_t *length_ptr)
{
  struct mschmd_file *file;
  unsigned char *data;
  FILE *fh;

  for (file = chm->sysfiles; file; file = file->next) {
    if (strcmp(file->filename, filename) == 0) break;
  }
  if (!file || file->section->id != 0) return NULL;
  if (chmd->extract(chmd, file, FILENAME)) return NULL;
  if (length_ptr) *length_ptr = file->length;
  if (!(data = (unsigned char *) malloc((size_t) file->length))) return NULL;
  if ((fh = fopen(FILENAME, "rb"))) {
    fread(data, (size_t) file->length, 1, fh);
    fclose(fh);
  }
  else {
    free(data);
    data = NULL;
  }
  unlink(FILENAME);
  return data;
}

char *guid(unsigned char *data) {
  static char result[43];
  snprintf(result, sizeof(result),
           "{%08X-%04X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X}",
           EndGetI32(&data[0]),
           data[4] | (data[5] << 8),
           data[6] | (data[7] << 8),
           data[8] | (data[9] << 8),
           data[10], data[11], data[12], data[13],
           data[14], data[15], data[16], data[17]);
  return result;
}

#define READ_ENCINT(var, label) do {	       		\
    (var) = 0;						\
    do {						\
	if (p > &chunk[chm->chunk_size-2]) goto label;	\
	(var) = ((var) << 7) | (*p & 0x7F);		\
    } while (*p++ & 0x80);				\
} while (0)

void print_dir(struct mschmd_header *chm, char *filename) {
  unsigned char dir[0x54], *chunk;
  unsigned int i;
  FILE *fh;

  if (!(chunk = (unsigned char *) malloc(chm->chunk_size))) return;
  
  if ((fh = fopen(filename, "rb"))) {
#if HAVE_FSEEKO
    fseeko(fh, chm->dir_offset - 84, SEEK_SET);
#else
    fseek(fh, chm->dir_offset - 84, SEEK_SET);
#endif
    fread(&dir[0], 84, 1, fh);
    printf("  chmhs1_Signature  = %4.4s\n", &dir[0]);
    printf("  chmhs1_Version    = %d\n", EndGetI32(&dir[4]));
    printf("  chmhs1_HeaderLen  = %d\n", EndGetI32(&dir[8]));
    printf("  chmhs1_Unknown1   = %d\n", EndGetI32(&dir[12]));
    printf("  chmhs1_ChunkSize  = %d\n", EndGetI32(&dir[16]));
    printf("  chmhs1_Density    = %d\n", EndGetI32(&dir[20]));
    printf("  chmhs1_Depth      = %d\n", EndGetI32(&dir[24]));
    printf("  chmhs1_IndexRoot  = %d\n", EndGetI32(&dir[28]));
    printf("  chmhs1_FirstPMGL  = %d\n", EndGetI32(&dir[32]));
    printf("  chmhs1_LastPMGL   = %d\n", EndGetI32(&dir[36]));
    printf("  chmhs1_Unknown2   = %d\n", EndGetI32(&dir[40]));
    printf("  chmhs1_NumChunks  = %d\n", EndGetI32(&dir[44]));
    printf("  chmhs1_LanguageID = %d\n", EndGetI32(&dir[48]));
    printf("  chmhs1_GUID       = %s\n", guid(&dir[52]));
    printf("  chmhs1_Unknown3   = %d\n", EndGetI32(&dir[68]));
    printf("  chmhs1_Unknown4   = %d\n", EndGetI32(&dir[72]));
    printf("  chmhs1_Unknown5   = %d\n", EndGetI32(&dir[76]));
    printf("  chmhs1_Unknown6   = %d\n", EndGetI32(&dir[80]));

    for (i = 0; i < chm->num_chunks; i++) {
      unsigned int num_entries, quickref_size, j, k;
      unsigned char *p, *name;
      printf("  CHUNK %u:\n", i);
      fread(chunk, chm->chunk_size, 1, fh);

      if ((chunk[0] == 'P') && (chunk[1] == 'M') &&
          (chunk[2] == 'G') && (chunk[3] == 'L'))
      {
        k = chm->chunk_size - 2;
        num_entries = chunk[k] | (chunk[k+1] << 8);
        quickref_size = EndGetI32(&chunk[4]);
	if (quickref_size > (chm->chunk_size - 20)) {
	    printf("    QR size of %d too large (max is %d)\n",
		   quickref_size, chm->chunk_size - 20);
	    quickref_size = chm->chunk_size - 20;
	}
        printf("    PMGL entries=%u qrsize=%u zero=%u prev=%d next=%d\n",
               num_entries, quickref_size, EndGetI32(&chunk[8]),
               EndGetI32(&chunk[12]), EndGetI32(&chunk[16]));

        printf("    QR: entry %4u = offset %u\n", 0, 20);
        j = (1 << chm->density) + 1;
        while (j < num_entries) {
          k -= 2;
	  if (k < (chm->chunk_size - quickref_size)) break;
          printf("    QR: entry %4u = offset %u\n",
                 j, (chunk[k] | (chunk[k+1] << 8)) + 20);
          j += (1 << chm->density) + 1;
        }

        p = &chunk[20];
        for (j = 0; j < num_entries; j++) {
          unsigned int name_len = 0, section = 0, offset = 0, length = 0;
          printf("    %4d: ", (int) (p - &chunk[0]));
	  READ_ENCINT(name_len, PMGL_end); name = p; p += name_len;
	  READ_ENCINT(section, PMGL_end);
	  READ_ENCINT(offset, PMGL_end);
	  READ_ENCINT(length, PMGL_end);
          printf("sec=%u off=%-10u len=%-10u name=\"",section,offset,length);
          if (name_len) fwrite(name, 1, name_len, stdout);
          printf("\"\n");
	}
      PMGL_end:
	if (j != num_entries) printf("premature end of chunk\n");

      }
      else if  ((chunk[0] == 'P') && (chunk[1] == 'M') &&
		(chunk[2] == 'G') && (chunk[3] == 'I'))
      {
	k = chm->chunk_size - 2;
	num_entries = chunk[k] | (chunk[k+1] << 8);
	quickref_size = EndGetI32(&chunk[4]);
	printf("    PMGI entries=%u free=%u\n", num_entries, quickref_size);

        printf("    QR: entry %4u = offset %u\n", 0, 8);
        j = (1 << chm->density) + 1;
        while (j < num_entries) {
          k -= 2;
          printf("    QR: entry %4u = offset %u\n",
                 j, (chunk[k] | (chunk[k+1] << 8)) + 8);
          j += (1 << chm->density) + 1;
        }

        p = &chunk[8];
        for (j = 0; j < num_entries; j++) {
          unsigned int name_len, section;
          printf("    %4d: ", (int) (p - &chunk[0]));
          READ_ENCINT(name_len, PMGI_end); name = p; p += name_len;
          READ_ENCINT(section, PMGI_end);
          printf("chunk=%-4u name=\"",section);
          if (name_len) fwrite(name, 1, name_len, stdout);
          printf("\"\n");
	}
      PMGI_end: 
	if (j != num_entries) printf("premature end of chunk\n");
      }
      else {
	printf("    unknown format\n");
      }
    }

    fclose(fh);
  }
}


int main(int argc, char *argv[]) {
  struct mschm_decompressor *chmd;
  struct mschmd_header *chm;
  struct mschmd_file *file;
  unsigned int numf, i;
  unsigned char *data;
  off_t pos, len;

  setbuf(stdout, NULL);
  setbuf(stderr, NULL);

  MSPACK_SYS_SELFTEST(i);
  if (i) return 0;

  if ((chmd = mspack_create_chm_decompressor(NULL))) {
    for (argv++; *argv; argv++) {
      printf("%s\n", *argv);
      if ((chm = chmd->open(chmd, *argv))) {
	printf("  chmhead_Version     %u\n",      chm->version);
	printf("  chmhead_Timestamp   %u\n",      chm->timestamp);
	printf("  chmhead_LanguageID  %u\n", 	 chm->language);
	printf("  chmhs0_FileLen      %" LD "\n", chm->length);
	printf("  chmhst_OffsetHS1    %" LD "\n", chm->dir_offset);
	printf("  chmhst3_OffsetCS0   %" LD "\n", chm->sec0.offset);

	print_dir(chm, *argv);

	if ((data = load_sys_data(chmd, chm,
	     "::DataSpace/Storage/MSCompressed/ControlData", &len)))
        {
	  printf("  lzxcd_Length        %u\n",    EndGetI32(&data[0]));
	  printf("  lzxcd_Signature     %4.4s\n", &data[4]);
	  printf("  lzxcd_Version       %u\n",    EndGetI32(&data[8]));
	  printf("  lzxcd_ResetInterval %u\n",    EndGetI32(&data[12]));
	  printf("  lzxcd_WindowSize    %u\n",    EndGetI32(&data[16]));
	  printf("  lzxcd_CacheSize     %u\n",    EndGetI32(&data[20]));
	  printf("  lzxcd_Unknown1      %u\n",    EndGetI32(&data[24]));
	  free(data);
	}

	if ((data = load_sys_data(chmd, chm,
	     "::DataSpace/Storage/MSCompressed/Transform/{7FC28940-"
	     "9D31-11D0-9B27-00A0C91E9C7C}/InstanceData/ResetTable", &len)))
        {
	  off_t contents = chm->sec0.offset;
	  printf("  lzxrt_Unknown1      %u\n",      EndGetI32(&data[0]));
	  printf("  lzxrt_NumEntries    %u\n",      EndGetI32(&data[4]));
	  printf("  lzxrt_EntrySize     %u\n",      EndGetI32(&data[8]));
	  printf("  lzxrt_TableOffset   %u\n",      EndGetI32(&data[12]));
	  printf("  lzxrt_UncompLen     %" LU "\n", EndGetI64(&data[16]));
	  printf("  lzxrt_CompLen       %" LU "\n", EndGetI64(&data[24]));
	  printf("  lzxrt_FrameLen      %u\n",      EndGetI32(&data[32]));

	  for (file = chm->sysfiles; file; file = file->next) {
	    if (strcmp(file->filename,
		       "::DataSpace/Storage/MSCompressed/Content") == 0)
	    {
	      contents += file->offset;
	      break;
	    }
	  }

	  printf("  - reset table (uncomp offset -> stream offset "
		 "[real offset, length in file]\n");

	  numf = EndGetI32(&data[4]);
	  pos = ((unsigned int) EndGetI32(&data[12]));
	  switch (EndGetI32(&data[8])) {
	  case 4:
	    for (i = 0; i < numf && pos < len; i++, pos += 4) {
	      unsigned int rtdata = EndGetI32(&data[pos]);
	      printf("    %-10u -> %-10u [ %" LU " %u ]\n",
		     i * EndGetI32(&data[32]),
		     rtdata,
		     contents + rtdata,
		     (i == (numf-1))
		     ? (EndGetI32(&data[24]) - rtdata)
		     : (EndGetI32(&data[pos + 4]) - rtdata)
		     );
	    }
	    break;
	  case 8:
	    for (i = 0; i < numf && pos < len; i++, pos += 8) {
	      unsigned long long int rtdata = EndGetI64(&data[pos]);
	      printf("    %-10" LU " -> %-10" LU " [ %" LU " %" LU " ]\n",
		     i * EndGetI64(&data[32]), rtdata, contents + rtdata,
		     (i == (numf-1))
		     ? (EndGetI64(&data[24]) - rtdata)
		     : (EndGetI64(&data[pos + 8]) - rtdata)
		     );
	    }
	    break;
	  }
	  free(data);
	}
	chmd->close(chmd, chm);
      }
    }
    mspack_destroy_chm_decompressor(chmd);
  }
  return 0;
}