/**************************************************************************
* Copyright  (c) 2001 by Acunia N.V. All rights reserved.                 *
*                                                                         *
* This software is copyrighted by and is the sole property of Acunia N.V. *
* and its licensors, if any. All rights, title, ownership, or other       *
* interests in the software remain the property of Acunia N.V. and its    *
* licensors, if any.                                                      *
*                                                                         *
* This software may only be used in accordance with the corresponding     *
* license agreement. Any unauthorized use, duplication, transmission,     *
*  distribution or disclosure of this software is expressly forbidden.    *
*                                                                         *
* This Copyright notice may not be removed or modified without prior      *
* written consent of Acunia N.V.                                          *
*                                                                         *
* Acunia N.V. reserves the right to modify this software without notice.  *
*                                                                         *
*   Acunia N.V.                                                           *
*   Vanden Tymplestraat 35      info@acunia.com                           *
*   3000 Leuven                 http://www.acunia.com                     *
*   Belgium - EUROPE                                                      *
**************************************************************************/

/*
** $Id: resource.c,v 1.4 2002/07/23 12:48:46 buytaert Exp $
*/

#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <zlib.h>
#include <time.h>

#include "jamjar.h"
#include "resource.h"
#include "argument.h"
#include "class.h"

#define NUM_BUCKETS  (2879)

static j_res hashtable[NUM_BUCKETS];

int res_compare(j_res f1, j_res f2) {

  int result = 1;
  
  if (f1->z_name && f2->z_name) {
    if (strcmp(f1->z_name, f2->z_name) != 0) {
      result = 0;
    }
  }
  else {
    if (f1->z_name != f2->z_name) {
      result = 0;
    }
  }

  return result;
  
}

void res_iterate(j_it iterator, void * arg) {

  size_t j;
  j_res res;
  
  for (j = 0; j < NUM_BUCKETS; j++) {
    for (res = hashtable[j]; res; res = res->next) {
      iterator(res, arg);
    }
  }
  
}

j_res res_exists(j_res res) {

  j_res found = NULL;
  j_res cursor;
  size_t indexx;
  
  indexx = res->hash % NUM_BUCKETS;

  for (cursor = hashtable[indexx]; cursor; cursor = cursor->next) {
    if (res_compare(res, cursor)) {
      found = cursor;
      break;
    }
  }

  logmsg(3, "name '%s' was %s in hashtable (index %d).\n", res->z_name, found ? "allready" : "not yet", indexx);
    
  return found;

}

void res_add(j_res res) {

  j_res cursor;
  size_t indexx;
  
  indexx = res->hash % NUM_BUCKETS;
  res->next = NULL;

  for (cursor = hashtable[indexx]; cursor; cursor = cursor->next) {
    if (cursor->next == NULL) {
      cursor->next = res;
      break;
    }
  }
  
  if (cursor == NULL) {
    hashtable[indexx] = res;
  }
  
  logmsg(3, "added '%s' in slot %d.\n", res->z_name, indexx);

}

/*
** Find a resources. If not found, add it to the hashtable. If found, return the
** old one and free the new one.
*/

j_res res_find(j_res new) {

  j_res old = res_exists(new);
  
  if (old) {
    res_free(new);
    return old;
  }
  else {
    res_add(new);
    return new;
  }
  
}

unsigned int hash(j_res res) {

  unsigned int h = 0;
  unsigned int g;
  char * string = res->z_name;

  if (string == NULL) {
    printf("for '%s', string is NULL\n", res->r_name);
    exit(0);
  }

  while (*string) {
    h = (h << 4) + *string++;
    g = h & 0xf0000000;
    if (g) {
      h ^= g >> 24;
    }
    h &= ~g;
  }

  return h;
  
}

void res_hash(j_res res) {
  res->hash = hash(res);
}

void res_free(j_res res) {

}

/*
** Create a resource from a j_arg structure. Note that we don't allocate new
** r_name or z_name memory. We use the references from the arg structure as
** these are not freed before jamjar stops completely.
*/

j_res res_arg(j_arg arg) {

  j_res res = jj_calloc(1, sizeof(j_Res));
  
  res->r_name = arg->file;
  res->u_size = arg->size;
  res->time = arg->time;
  res->z_name = arg->z_name;
  res->hash = hash(res);
    
  return res;
  
}

/*
** Create a resource with the name 'name' but add a ".class" extension
** at the same time. The result should go into the z_path field.
** We don't set any other fields but the z_name field for the hashtable.
** Later, when we read the data of the inner class, the missing links will be
** filled in with res_attach_class.
*/

j_res res_class(const char * name) {

  j_res res = jj_calloc(1, sizeof(j_Res));
  
  res->z_name = jj_calloc(strlen(name) + 7, sizeof(char));
  strcpy(res->z_name, name);
  strcat(res->z_name, ".class");
  res->hash = hash(res);
  setFlag(res->flags, RES_CLASS);
  
  return res;
  
}

/*
** Attach class and argument information to the resource.
*/

void res_attach_class(j_res res, j_arg arg, j_class class) {

  res->r_name = arg->file;
  res->u_data = class->data;
  res->class = class;
  class->res = res;
  res->time = arg->time;
  res->u_size = arg->size;
  arg->res = res;
  res->arg = arg;
  
}

void res_attach_other(j_res res, j_arg arg) {

  res->r_name = arg->file;
  res->time = arg->time;
  res->u_size = arg->size;
  arg->res = res;
  res->arg = arg;
  
}

/*
** The same buf for an entry. We don't add anything. The name is just fine
** as it came from the jarfile we are reading.
*/

j_res res_entry(const char * name, size_t len) {

  j_res res = jj_calloc(1, sizeof(j_Res));
  
  res->z_name = jj_calloc(len + 1, sizeof(char));
  memcpy(res->z_name, name, len);
  res->hash = hash(res);
  
  return res;
  
}

/*
** Time functions; convert time_t of unix to MSDOS time and vice versa. MSDOS time is
** expressed in 2 shorts, one holds the time and the other the date. For MSDOS, the epoch
** is January the first, 1980, for UNIX it is Janunary the first 1970.
** Oh yes, MSDOS is a registered trademark of Microsoft and UNIX is a trademark of the respective
** trademark owner (I didn't keep track).
*/

time_t dos2unix(j_dosdate date, j_dostime time) {

  struct tm s;
  
  s.tm_sec  = (time << 1)  & 0x3e;
  s.tm_min  = (time >> 5)  & 0x3f;
  s.tm_hour = (time >> 11) & 0x1f;
  
  s.tm_mday = date & 0x1f;
  s.tm_mon  = ((date >> 5) & 0x0f) - 1;
  s.tm_year = ((date >> 9) & 0x7f) + 80;
  
  return mktime(&s);
  
}

void unix2dos(time_t datetime, j_dosdate * date, j_dostime * time) {

  struct tm *s;
  time_t t = datetime;

  *date = 0;
  *time = 0;

  s = localtime(&t);

  *date |= (s->tm_year + 1900 - 1980) << 9;
  *date |= (s->tm_mon + 1) << 5;
  *date |= (s->tm_mday);

  *time |= (s->tm_hour) << 11;
  *time |= (s->tm_min) << 5;
  *time |= (s->tm_sec) >> 1;

}

#define BUFSIZE 512
static char buffer[BUFSIZE];

const char * res_timefmt(j_res res) {

  time_t time = res->time;
  struct tm * s;

  s = localtime(&time);
  memset(buffer, 0x00, BUFSIZE);
  strftime(buffer, BUFSIZE, "%a %b %d %H:%M:%S %Z %Y", s);
  
  return buffer;
  
}

/*
** Compression - decompression functions
*/

static z_stream * zs = NULL;

void compress_cleanup(void) {

  if (zs) {
    deflateEnd(zs);
  }
  
}

void res_compress(j_res res) {

  size_t out_len;
  unsigned char * out;
  int status;
  unsigned int crc;
  int percentage;

  if (zs == NULL) {
    zs = jj_calloc(1, sizeof(z_stream));
    zs->zalloc = z_calloc;
    zs->zfree = z_free;
    zs->opaque = Z_NULL;
    zs->data_type = Z_UNKNOWN;

    status = deflateInit2(zs, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 9, Z_DEFAULT_STRATEGY);
    if (status != Z_OK) {
      logmsg(11, "could not initialize the compressor.\n");
      exit(1);
    }
  }

  logmsg(5, "compressing file '%s'\n", res->r_name ? res->r_name : res->z_name);
    
  out_len = res->u_size + 1024;
  out = jj_calloc(out_len, sizeof(unsigned char));

  zs->avail_in = res->u_size;
  zs->next_in = res->u_data;
  zs->next_out = out;
  zs->avail_out = out_len;
  zs->data_type = Z_UNKNOWN;

  crc = crc32(0, Z_NULL, 0);
  crc = crc32(crc, res->u_data, res->u_size);

  status = deflate(zs, Z_FINISH);
  if (status != Z_STREAM_END) {
    logmsg(11, "Problem compressing '%s'.\n", zs->msg);
    exit(1);
  }

  percentage = 100 - ((zs->total_in * 100 - zs->total_out * 100) / zs->total_in);
  logmsg(4, "in = %d out = %d ratio = %d%% crc = 0x%08x.\n", zs->total_in, zs->total_out, percentage, crc);
  out = jj_realloc(out, zs->total_out);
  res->c_data = out;
  res->c_size = zs->total_out;
  res->crc = crc;

  status = deflateReset(zs);
  if (status != Z_OK) {
    logmsg(11, "Problem resetting '%s'.\n", zs->msg);
    exit(1);
  }

}

typedef int (*qs_compare)(const void *, const void *);

j_res res_sort(int (*cf)(const j_res * res1r, const j_res * res2r)) {

  size_t j;
  size_t i;
  size_t count = 0;
  j_res * array;
  j_res res;
  
  for (j = 0; j < NUM_BUCKETS; j++) {
    for (res = hashtable[j]; res; res = res->next) {
      count += 1;
    }
  }

  array = jj_calloc(count, sizeof(j_res));

  i = 0;
  for (j = 0; j < NUM_BUCKETS; j++) {
    for (res = hashtable[j]; res; res = res->next) {
      array[i++] = res;
    }
  }

  qsort(array, count, sizeof(j_res), (qs_compare)cf);

  array[count - 1]->sorted = NULL;
  for (i = 0; i < count - 1; i++) {
    array[i]->sorted = array[i + 1];
  }

  res = array[0];
  
  jj_free(array);

  return res;

}

#define FBSIZE (256)

static char * fbuffer = NULL;

const char * res_flagsfmt(j_res res) {

  if (fbuffer == NULL) {
    jj_calloc(FBSIZE, sizeof(char));
  }
  
  memset(fbuffer, 0x00, FBSIZE);
  
  if (isSet(res->flags, RES_CLASS)) {
    sprintf(fbuffer, "class ");
  }
  else if (isSet(res->flags, RES_WRITE)) {
    sprintf(fbuffer + strlen(buffer), "write ");
  }

  return fbuffer;
  
}
