#include "apoconv.h"
#include <helper.h>

#include <stdio.h>
#include <string.h>

#ifndef DIR_SEPERATOR
#define DIR_SEPERATOR '/'
#endif

void apo_main_info_init (struct apo_main_info* this) {
  this->version = 0; /* 0 is an invalid version */
  this->files = NULL;
}

void apo_main_decode_v2 (struct mmapped_file* apo,
			 const char* prefix,
			 struct apo_main_info* info)
{
  char* buffer = NULL;
  int hsize = 0;
  uint16_t max = 0;
  uint8_t* ptr = PTR8(apo->ptr);
  uint8_t bitpos = 0;
  uint16_t bytepos = 0;
  uint8_t level1 = 0;
  uint8_t level2 = 0;
  uint8_t entry = 0;

  if (PTR8(apo->ptr)[1] != 0x33)
    errexit("Unexpected version id %#02x\n",PTR8(apo->ptr)[1]);
  
  hsize = 6; /* SL75 */
  max = PTR16(ptr+4)[0];
  ptr += hsize;

  info->version = 3;
  info->files = NULL;

  if (max == 0) return;

  buffer = mem_alloc(strlen(prefix)+4+6+3+1,1);
  while (bytepos*8+bitpos < max &&
	 level1*level2*entry <= 0xFFFF) {
    if (ptr[bytepos]&(1<<(7-bitpos))) {
      sprintf(buffer,"%s%s%c%02u%c%02u%c%02u",
	      prefix,"data",DIR_SEPERATOR,
	      level1,DIR_SEPERATOR,
	      level2,DIR_SEPERATOR,
	      entry);
      info->files = str_list_append(info->files,str_list_create(str_dup(buffer)));
    }
    if (++bitpos > 7) {
      bitpos = 0;
      ++bytepos;
    }
    if (++entry > 40) {
      entry = 0;
      ++level2;
    }
    if (level2 > 40) {
      level2 = 0;
      ++level1;
    }
  }
  mem_realloc(buffer,0);
  info->files = str_list_rewind(info->files);
}

void apo_main_decode_v1 (struct mmapped_file* apo,
			 const char* prefix,
			 struct apo_main_info* info)
{
  char* buffer = NULL;
  int i = 0;
  int k = 0;;
  uint32_t* ptr;
  int hsize = 0;

  switch (PTR8(apo->ptr)[1]) {
  case 0x31:
    hsize = 2; /* S55 */
    info->version = 1;
    break;
  case 0x32:
    hsize = 4; /* S65 */
    info->version = 2;
    break;
  default:
    errexit("Unexpected version id %#02x\n",PTR8(apo->ptr)[1]);
  }

  ptr = (uint32_t*)(PTR8(apo->ptr) + hsize);
  buffer = mem_alloc(strlen(prefix)+2+1+2+1,1);
  for (; i < (apo->size-hsize)/4; ++i)
    for (k = 0; k < 32; ++k)
      if ((letohl(ptr[i])>>k)&1) {
	sprintf(buffer,"%s%02X%c%02X",prefix,i,DIR_SEPERATOR,k);
	info->files = str_list_append(info->files,str_list_create(str_dup(buffer)));
      }
  mem_realloc(buffer,0);
  info->files = str_list_rewind(info->files);
}


void apo_main_decode (char* file, struct apo_main_info* info) {
  struct mmapped_file apo;
  char* prefix;

  if (mem_map(file,&apo) < 0) {
    fprintf(stderr,"Failed to open %s\n",file);
    exit(EXIT_FAILURE);
  }

  prefix = strrchr(file,DIR_SEPERATOR);
  if (prefix == NULL) prefix = str_dup("");
  else prefix = strn_dup(file,prefix-file+1);

  switch (PTR8(apo.ptr)[0]) {
  case 0x61:
    apo_main_decode_v1(&apo,prefix,info);
    break;
  case 0x62:
    apo_main_decode_v2(&apo,prefix,info);
    break;
  default:
    errexit("Unknown format id %#02x\n",PTR8(apo.ptr)[1]);
  }
  mem_realloc(prefix,0);
}
