#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "utfchar.h"
#include "hhentry.h"

/* hh basic functions */
void print_utfchar_hex_value (unsigned char *str, FILE *fp);
void put_int24_to_buffer (int i, char *buffer);
void put_int24_to_file (int i, FILE *fp);
void get_int24_from_buffer (int *val, char *buffer);
void get_int24_from_file (int *val, FILE *fp);

HH *
hh_new ()
{
  HH *new_hh;
  new_hh = (HH *) calloc (1, sizeof (HH));
  return new_hh;
}

void
hh_free (HH *hh)
{
  assert (hh != NULL);
  if (hh == NULL)
    return;
  free (hh->utf_hangul);
  free (hh->utf_hanja);
  free (hh);
  hh = NULL;
}

HH *
hh_new_with_data (unsigned char *utf_hangul, unsigned char *utf_hanja)
{
  
  HH *new_hh;
  
  assert (utf_hangul != NULL);
  assert (utf_hanja != NULL);

  if (!utf_hangul || !utf_hanja)
    return NULL;

  new_hh = (HH *) calloc (1, sizeof (HH));
  new_hh->utf_hangul = (unsigned char *) strdup (utf_hangul);
  new_hh->utf_hanja =  (unsigned char *) strdup (utf_hanja);
  
  return new_hh;
}

/* hhitem basic functions */
HHItem *
hhitem_new ()
{
  HHItem *new_entry;
  new_entry = (HHItem *) calloc (1, sizeof (HHItem));
  new_entry->n_hanja = 0;
  new_entry->hanja_list = NULL;
  new_entry->hangul = NULL;

  return new_entry;
}

void
hhitem_free (HHItem *entry)
{
  int i;

  assert (entry != NULL);
  
  if (entry == NULL){
    fprintf (stderr, "hhitem_free error: entry or *entry is null\n");
    return;
  }
  if (entry->hangul){
    free (entry->hangul);
  }
  for (i = 0 ; i < entry->n_hanja; i++){
    free (entry->hanja_list[i]);
  }
  free (entry->hanja_list);
  free (entry);
}

HHItem *
hhitem_new_with_data (HH *hh)
{
  HHItem *new_entry;

  assert (hh != NULL);
  assert (hh->utf_hangul != NULL);
  assert (hh->utf_hanja != NULL);
  
  new_entry = (HHItem *) calloc (1, sizeof (HHItem));
  new_entry->n_hanja = 1;
  new_entry->hangul = (unsigned char *) strdup (hh->utf_hangul);
  new_entry->hanja_list =
    (unsigned char **) calloc (1, sizeof (unsigned char *));
  new_entry->hanja_list[0] =
    (unsigned char *) strdup (hh->utf_hanja);;

  return new_entry;
}

void
hhitem_init (HHItem *hhentry)
{
  int n;
  assert (hhentry != NULL);
  
  free (hhentry->hangul);
  hhentry->hangul = NULL;
  
  for (n = 0 ; n < hhentry->n_hanja; n++){
    free (hhentry->hanja_list[n]);
  }
  free (hhentry->hanja_list);
  hhentry->hanja_list = NULL;
  hhentry->n_hanja = 0;
}

int
hhitem_comp (HHItem *a, HHItem *b)
{
  return strcmp (a->hangul, b->hangul);

}

HHItem *
hhitem_add_hanja (HHItem *hhentry, unsigned char *utf_hanja)
{
  int n_hanja;
  unsigned char **tmp;
  
  assert (hhentry != NULL);
  assert (utf_hanja != NULL);
  if (hhentry == NULL || utf_hanja == NULL)
    return NULL;
  n_hanja = hhentry->n_hanja;

  tmp = (unsigned char **) calloc (n_hanja + 1, sizeof (unsigned char *));
  memcpy (tmp, hhentry->hanja_list, n_hanja * sizeof (unsigned char *));
  tmp[n_hanja] = (unsigned char *) strdup (utf_hanja);
  
  free (hhentry->hanja_list);
  hhentry->hanja_list = tmp;
  hhentry->n_hanja += 1;
  return hhentry;
}

/* src, dst is a pointer to the existing buffer */
void
hhitem_copy (HHItem *dst, HHItem *src)
{
  int n_hanja;
  int utf_len;
  assert (dst != NULL);
  assert (src != NULL);

  if (dst == NULL || src == NULL)
    return ;
  utf_len = strlen (src->hangul);
  if (dst->hangul)
    free (dst->hangul);
  
  dst->hangul = (unsigned char *) calloc (utf_len +1, sizeof (unsigned char));
  strcpy (dst->hangul, src->hangul);
  
  dst->n_hanja = src->n_hanja;

  if (dst->hanja_list)
    free (dst->hanja_list);
  
  dst->hanja_list =
    (unsigned char **) calloc (src->n_hanja, sizeof (unsigned char *));
  for (n_hanja = 0; n_hanja < dst->n_hanja; n_hanja++){
    utf_len = strlen (src->hanja_list[n_hanja]);
    dst->hanja_list[n_hanja] =
      (unsigned char *) calloc (utf_len +1, sizeof (unsigned char));
    strcpy (dst->hanja_list[n_hanja], src->hanja_list[n_hanja]);
  }
}


int
hhitem_serialize
(HHItem *hhitem, int buffer_size, unsigned char *buffer_return)
{
  int i, i_len = 0;
  int total = 0;
  unsigned char *pchar;
  int buffer_overflow = 0;
    
  assert (buffer_return != NULL);
  if (buffer_return == NULL){
    fprintf (stderr, "fp can't be null\n");
    return 0;
  }
  pchar = buffer_return;
  /* length infomaon */
  /* nothing is written at this time */
  /* just placeholder will be written to file */
  put_int24_to_buffer (i_len, pchar);
  pchar += 3;
  total += 3;
  
  /* hangul length */
  i_len = strlen (hhitem->hangul);
  put_int24_to_buffer (i_len, pchar);
  pchar += 3;
  total += 3;
  
  /* hangul string with terminating null */
  memcpy (pchar, hhitem->hangul, strlen (hhitem->hangul) + 1);
  total += strlen (hhitem->hangul) + 1;
  pchar += strlen (hhitem->hangul) + 1;

  /* dump info of 'number of hanjas' */
  put_int24_to_buffer (hhitem->n_hanja, pchar);
  total += 3;
  pchar += 3;

  /* dump hanja strings */
  for (i = 0 ; i < hhitem->n_hanja; i++){
    i_len = strlen (hhitem->hanja_list[i]);

    /* dump info of 'lenth of hanja' */
    put_int24_to_buffer (i_len, pchar);
    total += 3;
    if (total >= buffer_size){
      buffer_overflow = 1;
      break;
    }
      
    pchar += 3;

    memcpy (pchar, hhitem->hanja_list[i], i_len + 1);
    total += i_len + 1;
    pchar += i_len + 1;
  }

  if (buffer_overflow)
    return 0;
  
  pchar = buffer_return;
  put_int24_to_buffer (total, pchar);

  return total;
}

void
hhlist_dump_content (HHList *hhlist, FILE *fp)
{
  int i;
  int i_total;
  HHItem *p_cursor;
  unsigned char buffer_return[1024];
  unsigned char *tmp = NULL;
  int dump_return;

  i_total = hhlist->n_count;
  /* write number of HHItems in the HHList */
  put_int24_to_file (i_total, fp);
  
  for (i = 0 ; i < hhlist->n_count; i++){
    p_cursor = hhlist->list[i];
    dump_return = hhitem_serialize ( p_cursor,
				     sizeof (buffer_return), buffer_return);
    if (dump_return <= 0){
      fprintf (stderr, "hhlist_dump_content error : "
	       "failed to write hhlist[%d]\n",  i);
      continue;
    } else if (dump_return > 0 && dump_return < 1024){
      fwrite (buffer_return, 1, dump_return, fp);
    } else {
      /* try with bigger buffer */
      fprintf
	(stderr, "hhlist_dump_content, buffer was not big enough\n"
	 "trying with bigger buffer....\n");

      tmp = (unsigned char *) calloc (dump_return, sizeof (unsigned char));
      if (tmp){
	dump_return = hhitem_serialize (p_cursor, dump_return, tmp);
	fwrite (tmp, 1, dump_return, fp);
	free (tmp);
      } else {
	/* failed to write this list, thus skipping... */
	fprintf (stderr, "hhlist_dump_content error : "
		 "failed to write hhlist[%d]\n",  i);
	continue;
      }
    }
  }
}

#if 0
void
hash_table_read_content (FILE *fp, HHList **table, int *size) 
{
  int i;
  char dict_name[100];
  int version_signature;
  int major_version;
  int minor_version;
  int table_size;

  fread (dict_name, strlen(IIIM_KO_LE_DIC) + 1, 1,  fp);
  if (strcmp (dict_name, IIIM_KO_LE_DIC)){
    /* this is not ko-le dictionary, cancelling... */
    exit (-1);
  }
  /* read version signature from the file.
     but, I don't care about version for now.. */  
  get_int24_from_file (&version_signature, fp);

  /* read info for number of tables */
  get_int24_from_file (&table_size, fp);
  *size = table_size;

  if (table_size > 0){
    *table = (HHList *) calloc (table_size, sizeof (HHList));
    for (i = 0 ; i < table_size; i++)
      hhlist_init ((*table) + i);
  }
  
  for (i = 0 ; i < table_size; i++){
    hhlist_read_from_file (fp, (*table) + i);
  }
}
#endif

void
hash_table_dump_content (HHList table[], int size, FILE *fp)
{
  int i;
  char *dict_name = IIIM_KO_LE_DIC;
  int major_version = 1;
  int minor_version = 5;
  int version_signature;

  int table_size  = size;

  version_signature = 0;
  
  version_signature = (major_version & 0x0f);
  version_signature = (version_signature << 8) | (minor_version & 0x0000000f);

  /* write dictionary name to file*/
  fwrite (dict_name, strlen(dict_name) + 1, 1, fp);

  /* write version signature */
  put_int24_to_file (version_signature, fp);

  /* write info for number of tables */
  put_int24_to_file (table_size, fp);
  
  for (i = 0  ; i < table_size; i++){
    hhlist_dump_content (&table[i], fp);
  }
}

#if 0
/* read  each bucket */
void
hhlist_read_from_file (FILE *fp, HHList *hhlist)
{
  int i_total;
  int i;
  HHItem *hhentry;
  hhlist_init (hhlist);

  /* number of hangul-hanja pairs in this bucket */
  get_int24_from_file (&i_total, fp);

  for (i = 0 ; i < i_total; i++){
    hhentry = hhitem_new ();
    hhitem_read_from_file (fp, hhentry);
    hhlist_add_hhitem (hhlist, hhentry);
    hhitem_free (hhentry);
    hhentry = NULL;
  }
}
#endif

/* entry_return is a pointer to existing buffer */
void
hhitem_read_from_file (FILE *fp, HHItem *entry_return)
{
  int n_hanja;
  int total_length;
  int hangul_length;
  
  int i;
  int j;

  assert (entry_return != NULL);
  /* read length info of this HHItem
     this is not used for now
  */
  get_int24_from_file (&total_length, fp);

  /* read length info of hangul */
  get_int24_from_file (&hangul_length, fp);

  /* read hangul */
  entry_return->hangul =
    (unsigned char *)calloc (hangul_length + 1, sizeof (unsigned char));
  fread (entry_return->hangul, sizeof(unsigned char), hangul_length + 1, fp);

  /* read info for number of hanja */
  get_int24_from_file (&n_hanja, fp);
  entry_return->n_hanja = n_hanja;
  
  entry_return->hanja_list =
    (unsigned char **) calloc (n_hanja, sizeof (unsigned char *));
  for (i = 0 ; i < n_hanja; i++){
    /* now read length info of each hanja string */
    get_int24_from_file (&j, fp);
    entry_return->hanja_list[i] =
      (unsigned char *) calloc (j + 1, sizeof (unsigned char));
    fread (entry_return->hanja_list[i], sizeof (unsigned char), j + 1, fp);
  }
  return;
}

#if 0
void
print_utfchar_value (unsigned char *str)
{
  unsigned char *p = str;
  while(*p){
    printf ("0xhhx ", *p);
    p++;
  }
}
#endif

HHItem *
hhlist_search_hhitem (HHList *hhlist, HH *hh)
{
  int i;
  assert (hhlist != NULL);
  assert (hh != NULL);

  if (hhlist == NULL || hh == NULL){
    return NULL;
  }

  for (i = 0 ; i < hhlist->n_count; i++){
    if ( strcmp (hhlist->list[i]->hangul, hh->utf_hangul) == 0)
      return hhlist->list[i];
  }
  return NULL;
}

void
hhlist_add_hhitem (HHList *hhlist, HHItem *hhitem)
{
  
  int n_count;
  HHItem **tmp;
  assert (hhlist != NULL);
  assert (hhitem != NULL);

  n_count = hhlist->n_count;
  tmp = (HHItem **) calloc (n_count + 1, sizeof (HHItem *));
  memcpy (tmp, hhlist->list, n_count * sizeof (HHItem *));

  tmp [n_count] =  hhitem_new ();
  hhitem_copy (tmp [n_count], hhitem);
  hhlist->n_count += 1;
  hhlist->list = tmp;
}

HHItem *
hhlist_add_hh (HHList *hhlist, HH *item)
{
  HHItem *hhitem;
  HHItem **tmp;
  int n_count;

  assert (hhlist != NULL);
  assert (item != NULL);

  if (hhlist == NULL || item == NULL)
    return NULL;

  n_count = hhlist->n_count;
  
  hhitem = hhlist_search_hhitem (hhlist, item);

  if (hhitem == NULL){
    /* need to add new item */
    hhitem = hhitem_new_with_data (item);
    tmp = (HHItem **) calloc (n_count + 1, sizeof (HHItem *));
    memcpy (tmp, hhlist->list, n_count * sizeof (HHItem *));
    tmp[n_count] = hhitem;
    free (hhlist->list);
    hhlist->list = tmp;
    hhlist->n_count = n_count +1;
    
  } else {
    /* need to update existing item */
    hhitem_add_hanja (hhitem, item->utf_hanja);
  }

  return hhitem;
}

void
hhlist_init (HHList *hhlist)
{
  assert (hhlist != NULL);
  hhlist->n_count = 0;
  hhlist = NULL;
}

void
hhlist_print_content (HHList *hhlist, FILE *fp)
{
  int n_hangul;
  int i, j;
  HHItem *p_cursor;
  
  if (fp == NULL)
    fp = stdout;

  n_hangul = hhlist->n_count;
  for (i = 0 ; i < n_hangul; i++){
    p_cursor = hhlist->list[i];
    fprintf (fp, "[ ");    
    print_utfchar_hex_value (p_cursor->hangul, fp);
    fprintf (fp, " ]");
    fprintf (fp, "\n\t");

    for (j = 0 ; j < p_cursor->n_hanja; j++){
      fprintf (fp, "[ ");    
      print_utfchar_hex_value (p_cursor->hanja_list[j], fp);
      fprintf (fp, " ] ");

    }
    fprintf (fp, "\n");

  }
}

void
hhitem_print_string (HHItem *hhitem, FILE *fp)
{
  int n;
  assert (hhitem != NULL);
  if (fp == NULL)
    fp = stdout;
  
  fprintf (fp, "HANGUL[");
  fprintf (fp, hhitem->hangul);
#if 0
  _utfchar_print (hhitem->hangul);
#endif
  fprintf (fp, "]: HANJA[ ");
  
  for (n = 0 ; n < hhitem->n_hanja; n++){
#if 0
    _utfchar_print (hhitem->hanja_list[n]);
#endif
    fprintf (fp, hhitem->hanja_list[n]);
    fprintf (fp, " ");
  }
  fprintf (fp, "]");
}

void
hhlist_print_string (HHList *hhlist, FILE *fp)
{
  int n_count;
  if (fp == NULL)
    fp = stdout;
  fprintf (fp, "item count: %d\n", hhlist->n_count);
  for (n_count = 0; n_count < hhlist->n_count; n_count++){
    hhitem_print_string (hhlist->list[n_count], fp);
    fprintf (fp, "\n");
  }
}


void
print_utfchar_hex_value (unsigned char *str, FILE *fp)
{
  unsigned char *p = str;

  while (*p){
    fprintf (fp, "0x%hhx, ", *p);
    p++;
  }
}


int
hash (UTFCHAR *string)
{
  UTFCHAR *p;
  int hv = 0;
  
  for (p = string; *p; p++){
    hv += *p;
  }
  hv = (hv >> 2) & 0x00ff;
  return hv;
}

void
put_int24_to_buffer (int i, char *buffer)
{
  char *ptr = buffer;
  if (buffer == NULL){
    fprintf (stderr, "put_int24_to_buffer error: buffer is null\n");
    return;
  }
  *ptr++ = (i >> 16)  & 0xff;
  *ptr++ = (i >> 8)  & 0xff;
  *ptr   = (i)  & 0xff;
  
  return;
}

void
put_int24_to_file (int i, FILE *fp)
{
  int a, b, c;
  if (fp == NULL){
    fprintf (stderr, "put_int24_to_buffer error: fp is null\n");
    return;
  }
  a  = (i >> 16)  & 0x000000ff;
  b  = (i >> 8)  & 0x000000ff;
  c  = (i)  & 0x000000ff;
  
  fputc (a, fp);
  fputc (b, fp);
  fputc (c, fp);
  
  return;
}

void
get_int24_from_buffer (int *val, char *buffer)
{
  int i = 0;
  char *ptr = buffer;
  if ((val == NULL) || (buffer == NULL)){
    fprintf (stderr, "get_int24_from_buffer error: val or buffer is null\n");
    return;
  }
  i = *ptr++;
  i = (i << 8) | *ptr++;
  i = ( i << 8) | *ptr;

  *val = i;
  return;
}


void
get_int24_from_file (int *val, FILE *fp)
{
  int i = 0;
  if ((val == NULL) || (fp == NULL)){
    fprintf (stderr, "get_int24_from_buffer error: val or fp is null\n");
    return;
  }
  i = fgetc (fp);
  i = (i << 8) | fgetc (fp);
  i = ( i << 8) | fgetc (fp);

  *val = i;
  return;
}

void
get_int8_from_file (int *val, FILE *fp)
{
  int i = 0;
  if ((val == NULL) || (fp == NULL)){
    fprintf (stderr, "get_int24_from_buffer error: val or fp is null\n");
    return;
  }
  i = fgetc (fp);
  *val = i;
  return;
}

