/***************************************************************************
 *   copyright           : (C) 2004 by Hendrik Sattler                     *
 *   mail                : post@hendrik-sattler.de                         *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "adr2vcf.h"
#include "opt.h"
#include <charsets.h>
#include <helper.h>
#include <smspdu.h>
#include <options.h>
#include <versit.h>

#include <errno.h>
#include <iconv.h>
#include "intincl.h"
#include <locale.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

#define PROGRAM_NAME "adr2vcf"
#define PROGRAM_VERSION  "1.0rc1"
#include <disclaimer.h>

int qsort_int32_callback (const void* a, const void* b) {
  return (int)((*(uint32_t*)a)-(*(uint32_t*)b));
}

/* return entrysize if entry seems to be valid
 * or 0 if entry is invalid
 */
uint32_t verify_offset (void* ptr,
			uint16_t fields,
			struct field_spec* specs)
{
  uint32_t retval = 0;
  uint16_t k = 0;
  uint16_t fieldsize;

  for (; k < fields; ++k) {
    fieldsize = letohs(((uint16_t*)ptr)[k]);
    if (fieldsize > specs[k].size ||
	fieldsize == 0) {
      //detected a broken entry
      return 0;
    }
    retval += fieldsize;
  }
  return retval;
}

/* return a list of valid offsets or return NULL on error
 * returned value must be free()d
 */
uint32_t* get_verify_offsets (uint16_t fields,
			      struct field_spec* specs,
			      struct mmapped_file* file5,
			      char* file)
{
  struct mmapped_file file7;
  uint32_t* ptrlist;
  uint8_t* i;
  unsigned int counter = 0;
  uint32_t entrysize;

  /* map the offset file here and put all values into a list
   */
  if (mem_map(file,&file7) < 0) {
    fprintf(stderr,"%s: %s\n",file,strerror(errno));
    return NULL;
  }
  ptrlist = mem_alloc(file7.size + sizeof(uint32_t),0);
  if (ptrlist == NULL) {
    mem_unmap(&file7);
    return NULL;
  }
  for (i = file7.ptr; i < ((uint8_t*)file7.ptr)+file7.size; i += sizeof(uint32_t)) {
    ptrlist[counter] = letohl(*(uint32_t*)i);
    //do not include deleted entries (MSB is 1)
#ifdef DEBUG
      fprintf(DEBUG,"Checking verification offset 0x%08lx\n",
	      (unsigned long)ptrlist[counter]);
#endif
      
    if ((ptrlist[counter]&0x80000000) == 0 &&
	ptrlist[counter]+(fields*sizeof(uint16_t)) < (uint32_t)file5->size) {
      entrysize = verify_offset(((uint8_t*)file5->ptr)+ptrlist[counter],fields,specs);
      if (entrysize > 0 &&
	  ptrlist[counter]+(fields*sizeof(uint16_t))+entrysize <= (uint32_t)file5->size) {
#if DEBUG
	fprintf(DEBUG,"offset 0x%08x added to pointer list\n",ptrlist[counter]);
#endif
	++counter;
      } else {
	fprintf(stderr,"Warning: "
		"Detected broken verification entry at offset 0x%08lx: "
		"field size out of range.\n",
		(unsigned long)ptrlist[counter]);
      }
    }
  }
  if (counter == 0) {
    fprintf(stderr,"Warning: "
	      "Verification list empty, probably the wrong file\n");
    mem_realloc(ptrlist,0);
    return NULL;
  }
  
  //sorting the list
  qsort(ptrlist,counter,sizeof(uint32_t),&qsort_int32_callback);
  ptrlist[counter] = 0;

  mem_unmap(&file7);
  return ptrlist;
}

/* return a list of valid offsets or return NULL on error
 * returned value must be free()d
 */
uint32_t* get_offsets (uint16_t fields,
		       struct field_spec* specs,
		       uint16_t entrycount,
		       struct mmapped_file* file)
{
  uint8_t* i;
  uint32_t* ptrlist = NULL;
  unsigned int counter = 0;
  uint32_t entrysize;

  if (file == NULL) {
    return NULL;
  }
  ptrlist = (uint32_t*)mem_alloc((entrycount+1)*sizeof(uint32_t),0);

  //jump over file header
  i = (uint8_t*)(((uint16_t*)file->ptr) + 5 + fields);

  //do not go beyond entrycount or mmap'd area
  while (counter < entrycount && i < ((uint8_t*)file->ptr)+file->size) {
    //ignore all leading 0xdd
    while (*i == 0xdd) ++i;
    if (*i == 0xee) {
      ++i; //just ignore it, 0xdd case takes care of the rest
    } else {
      //found one
      if (i+(fields*sizeof(uint16_t)) < ((uint8_t*)file->ptr)+file->size) {
	entrysize = verify_offset(i,fields,specs);
      } else {
	entrysize = 0;
      }
      if (entrysize == 0 ||
	  i+(fields*sizeof(uint16_t))+entrysize > ((uint8_t*)file->ptr)+file->size) {
	//what to do on broken entry?
	fprintf(stderr,"Warning: "
		"Detected broken entry at offset 0x%08lx: "
		"field size out of range.\n",
		((unsigned long)i-(unsigned long)file->ptr));
	fprintf(stderr,"Warning: Aborting offset scan\n");
	break;
      } else {
	ptrlist[counter++] = (uint32_t)((unsigned long)i-(unsigned long)file->ptr);
      }
      i += (fields*sizeof(uint16_t))+entrysize;
    } 
  }
  ptrlist[counter] = 0;

  return ptrlist;
}

struct gsm_number* decode_telnum (uint8_t* field,
				  uint8_t type)
{
  struct gsm_number* s;
  size_t len = 0;

  s = mem_alloc(sizeof(*s),0);
  gsm_number_init(s);
  while (field[len] < 0xfe) ++len;
  gsm_number_set_semioctets(s,type,field,len);
  return s;
}

void print_telnum_field (void* h,
			 uint8_t ptype,
			 void* address1,
			 uint32_t ttype)
{
  struct gsm_number* num = decode_telnum((uint8_t*)address1,ptype);
  versit_out_number(h,num,ttype);
  mem_realloc(num,0);
}

void print_name_field (void* h,
		       uint16_t size1, void* address1,
		       uint16_t size2, void* address2)
{
  ucs4char_t* first = convert_to_internal("UCS-2LE",(char*)address1,size1);
  ucs4char_t* last = convert_to_internal("UCS-2LE",(char*)address2,size2);
  
  versit_out_name(h,first,last);
  mem_realloc(first,0);
  mem_realloc(last,0);
}

void print_organization_field (void*h, uint16_t size1, void* address1) {
  ucs4char_t* org = convert_to_internal("UCS-2LE",(char*)address1,size1);

  versit_out_org(h,org);
  mem_realloc(org,0);
}

void print_address_field (void* h,
			  uint16_t street_s, void* street,
			  uint16_t town_s, void* town,
			  uint16_t country_s, void* country,
			  uint16_t postal_s, void* postal)
{
  ucs4char_t* s = convert_to_internal("UCS-2LE",(char*)street,street_s);
  ucs4char_t* t = convert_to_internal("UCS-2LE",(char*)town,town_s);
  ucs4char_t* c = convert_to_internal("UCS-2LE",(char*)country,country_s);
  ucs4char_t* p = convert_to_internal("ISO-8859-1",(char*)postal,postal_s);

  versit_out_address(h,s,t,c,p);
  mem_realloc(s,0);
  mem_realloc(t,0);
  mem_realloc(c,0);
  mem_realloc(p,0);
}

void print_email_field (void* h, uint16_t size1, void* address1) {
  ucs4char_t* p = convert_to_internal("ISO-8859-1",(char*)address1,size1);
  versit_out_email(h,p);
  mem_realloc(p,0);
}

void print_url_field (void* h, uint16_t size1, void* address1) {
  ucs4char_t* p = convert_to_internal("ISO-8859-1",(char*)address1,size1);
  versit_out_url(h,p);
  mem_realloc(p,0);
}

void print_revision_field (void* h, uint16_t size1, void* address1) {
  ucs4char_t* p = convert_to_internal("ISO-8859-1",(char*)address1,size1);
  versit_out_revision(h,p);
  mem_realloc(p,0);
}

void print_birthday_field (void* h, uint16_t size1, void* address1) {
  uint16_t day;
  uint16_t month;
  uint16_t year;

  if (size1 < 6) return;
  day = letohs(((uint16_t*)address1)[0]);
  month = letohs(((uint16_t*)address1)[1]);
  year = letohs(((uint16_t*)address1)[2]);
  versit_out_birthday(h,year,month,day);
}

void process_data (FILE* fp,
		   const uint16_t fields,
		   const void* address,
		   struct args_t* args)
{
  unsigned int i = 0;
  uint16_t* sizes = (uint16_t*)address;
  void** adr;
  void* h;

  adr = mem_alloc(fields*sizeof(*adr),1);
  adr[0] = ((uint16_t*)address) + fields;
  for (i = 1; i < fields; ++i) {
    adr[i] = ((uint8_t*)adr[i-1]) + letohs(sizes[i-1]);
  }

  h = versit_out_open(fp,args->system_charset,args->encoding,args->vcard_version);

  //modification time / revision
  switch (fields) {
  case FILE_VERSION_2:
  case FILE_VERSION_3:
    print_revision_field(h,letohs(sizes[18]),adr[18]);
    break;
  case FILE_VERSION_7:
  case FILE_VERSION_8:
    print_revision_field(h,letohs(sizes[22]),adr[22]);
    break;
  }

  //both parts of the name
  print_name_field(h,letohs(sizes[0]),adr[0],letohs(sizes[1]),adr[1]);

  //organization/company
  print_organization_field(h,letohs(sizes[2]),adr[2]);

  //address
  switch (fields) {
  case FILE_VERSION_2:
  case FILE_VERSION_3:
    print_address_field(h,letohs(sizes[3]),adr[3],letohs(sizes[4]),adr[4],
			letohs(sizes[5]),adr[5],letohs(sizes[9]),adr[9]);
    break;
  case FILE_VERSION_7:
  case FILE_VERSION_8:
    print_address_field(h,letohs(sizes[3]),adr[3],letohs(sizes[4]),adr[4],
			letohs(sizes[5]),adr[5],letohs(sizes[11]),adr[11]);
    break;
  }

  //electronic mail
  switch (fields) {
  case FILE_VERSION_2:
  case FILE_VERSION_3:
    print_email_field(h,letohs(sizes[7]),adr[7]);
    break;
  case FILE_VERSION_7:
  case FILE_VERSION_8:
    print_email_field(h,letohs(sizes[8]),adr[8]);
    print_email_field(h,letohs(sizes[9]),adr[9]);
    break;
  }

  //home page
  switch (fields) {
  case FILE_VERSION_2:
  case FILE_VERSION_3:
    print_url_field(h,letohs(sizes[8]),adr[8]);
    break;
  case FILE_VERSION_7:
  case FILE_VERSION_8:
    print_url_field(h,letohs(sizes[10]),adr[10]);
    break;
  }

  //telephone numbers
  switch (fields) {
  case FILE_VERSION_2:
  case FILE_VERSION_3:
    print_telnum_field(h,*((uint8_t*)adr[14]),adr[10],VERSIT_NUMBER_HOME);
    print_telnum_field(h,*((uint8_t*)adr[15]),adr[11],VERSIT_NUMBER_WORK);
    print_telnum_field(h,*((uint8_t*)adr[16]),adr[12],VERSIT_NUMBER_CELL);
    print_telnum_field(h,*((uint8_t*)adr[17]),adr[13],VERSIT_NUMBER_FAX);
    break;
  case FILE_VERSION_7:
  case FILE_VERSION_8:
    print_telnum_field(h,*((uint8_t*)adr[17]),adr[12],VERSIT_NUMBER_HOME);
    print_telnum_field(h,*((uint8_t*)adr[18]),adr[13],VERSIT_NUMBER_WORK);
    print_telnum_field(h,*((uint8_t*)adr[19]),adr[14],VERSIT_NUMBER_CELL);
    print_telnum_field(h,*((uint8_t*)adr[20]),adr[15],VERSIT_NUMBER_FAX);
    print_telnum_field(h,*((uint8_t*)adr[21]),adr[16],VERSIT_NUMBER_FAX);
    break;
  }

  //birthday
  switch(fields) {
  case FILE_VERSION_7:
  case FILE_VERSION_8:
    print_birthday_field(h,letohs(sizes[25]),adr[25]);
    break;
  }

#ifdef DEBUG
  //unknown fields
  switch (fields) {
  case FILE_VERSION_3:
    fprintf(DEBUG,"Field 20 has unknown meaning: 0x%02x 0x%02x 0x%02x 0x%02x\n",
	    ((uint8_t*)adr[19])[0],((uint8_t*)adr[19])[1],((uint8_t*)adr[19])[2],((uint8_t*)adr[19])[3]);
  case FILE_VERSION_2:
    break;
  case FILE_VERSION_8:
  case FILE_VERSION_7:
    fprintf(DEBUG,"Field 28 has unknown meaning: 0x%02x\n",
	    ((uint8_t*)adr[27])[0]);
    fprintf(DEBUG,"Field 25 has unknown meaning: 0x%02x 0x%02x 0x%02x 0x%02x\n",
	    ((uint8_t*)adr[24])[0],((uint8_t*)adr[24])[1],((uint8_t*)adr[24])[2],((uint8_t*)adr[24])[3]);
    fprintf(DEBUG,"Field 24 has unknown meaning: 0x%02x 0x%02x\n",
	    ((uint8_t*)adr[23])[0],((uint8_t*)adr[23])[1]);
    break;
  }
#endif

  versit_out_close(h);
}

void process_all_data (const uint16_t fields,
		       const void* base,
		       uint32_t* off,
		       struct args_t* args)
{
  unsigned int i = 0;

  while (known_fsizes[i] != fields && known_fsizes[i] != 0) ++i;
  if (known_fsizes[i] == 0) {
    fprintf(stderr,"Error: "
	    "Number of fields (%d) is not registered. "
	    "Unknown format, aborting.\n", fields);
    exit(EXIT_FAILURE);
  }
  
  for (i = 0; off[i] != 0; ++i) {
    if (i) fprintf(stdout,"\r\n");
#ifdef DEBUG
    fprintf(DEBUG,"Processing offset 0x%08lx\n",(unsigned long)off[i]);
#endif
    process_data(stdout,fields,((uint8_t*)base)+off[i],args);
  }
}

uint16_t adr_data_get_fieldcount (void* i) {
  return letohs(PTR16(i)[0]);
}

struct field_spec* adr_data_get_fieldspecs (void* i, int count) {
  struct field_spec* specs = mem_alloc(count*sizeof(*specs),1);
  uint16_t k = 0;

  for (; k < count; ++k) {
    specs[k].size = letohs(PTR16(i)[k+5]);
    specs[k].type = specs[k].size&0x7;
    specs[k].size = (specs[k].size>>3)&0x1FFF;
#ifdef DEBUG
    fprintf(DEBUG,"Field %2d: type=0x%x, maxsize=%d bytes\n",
	    k+1,specs[k].type,specs[k].size);
#endif
  }
  return specs;
}

uint16_t adr_data_get_entries (void* i) {
  uint16_t entries = letohs(PTR16(i)[1]);
#ifdef DEBUG
  fprintf(DEBUG,"Number of entries: %d\n",entries);
#endif
  return entries;
}

uint32_t* offset_list_merge (uint32_t* ptrlist5, uint16_t counter5,
			     uint32_t* ptrlist7, uint16_t counter7)
{
  /* merging both lists
   */
  if (ptrlist7 != NULL) {
    //resize
    ptrlist7 = mem_realloc(ptrlist7,(counter5+counter7+1)*sizeof(*ptrlist7));
    
    //merge
    for (counter5 = 0; ptrlist5[counter5] != 0; ++counter5)
      ptrlist7[counter7++] = ptrlist5[counter5];
    ptrlist7[counter7] = 0;
    
    //sort
    qsort(ptrlist7,counter7,sizeof(*ptrlist7),&qsort_int32_callback);

    //copy back and omit duplicates
    ptrlist5 = mem_realloc(ptrlist5,(counter7+1)*sizeof(*ptrlist5));
    counter5 = 0;
    for (counter7 = 0; ptrlist7[counter7] != 0; ++counter7) {
      if (ptrlist7[counter7+1] == 0) break;
      if (ptrlist7[counter7] != ptrlist7[counter7+1]) {
	ptrlist5[counter5++] = ptrlist7[counter7];
      }
    }
    ptrlist5[counter5] = 0;
    mem_realloc(ptrlist7,0);
  }
  return ptrlist5;
}

int main (int argc, char** argv) {
  struct mmapped_file file5;
  uint32_t* ptrlist5 = NULL;
  uint32_t* ptrlist7 = NULL;

  uint16_t fields; //amount of fields
  struct field_spec* specs;

  uint16_t entries; /* what the data file headers says */
  uint16_t counter5 = 0; /* what we count in the data file */
  uint16_t counter7 = 0; /* what we count in the offset file */

  char** files = NULL;
  struct args_t* args;
  int exit_code = EXIT_SUCCESS;

  //set program locale
  setlocale(LC_ALL,"");

  args_init();
  files = adr2vcf_args_parse(argc,argv);
  args = adr2vcf_get_args();

  if (args->version) {
    print_proginfo(PROGRAM_NAME,PROGRAM_VERSION);
    exit(exit_code);
  } else if (files == NULL || files[0] == NULL ||
	     (files[1] != NULL && files[2] != NULL)) {
    exit_code = EXIT_FAILURE;
    args->help = 1;
  }

  //sets the output charset
  charset_init(args->system_charset,1);

  if (args->help) {
    help(argv[0],args_type_list,args_list,"files...");
    fprintf(stderr,
	    "\n"
	    "Known values for ?? are 02, 03, 07 and 08.\n"
	    "The .adr files can be found in the \"/Address book/\" subdir of the flexmem memory.\n");
    exit(exit_code);
  }

  print_disclaimer();
    
  /* Now we have the offset list of probably valid entries
   * and can mmap the data file
   */
  if (mem_map(files[0],&file5) < 0) {
    fprintf(stderr,"%s: %s\n",files[0],strerror(errno));
    exit(EXIT_FAILURE);
  }

  /* extract the number of fields and their sizes */
  fields = adr_data_get_fieldcount(file5.ptr);
  specs = adr_data_get_fieldspecs(file5.ptr,fields);
  entries = adr_data_get_entries(file5.ptr);

  /* get offset list from data file */
  ptrlist5 = get_offsets(fields,specs,entries,&file5);
  while (ptrlist5[counter5] != 0) ++counter5;
  if (entries != counter5) {
    fprintf(stderr,"%s: Offset count (%d) does not match entry count field (%d)!\n",
	    files[0],counter5,entries);  
  }


  /* get verification offsets if two file names are present
   */
  if (files[1] != NULL) {
    ptrlist7 = get_verify_offsets(fields,specs,&file5,files[1]);
    if (ptrlist7 == NULL) exit(EXIT_FAILURE);

    /* compare the number of entries of both files */
    while (ptrlist7[counter7] != 0) ++counter7;
    if (entries != counter7)
      fprintf(stderr,"%s: Offset count (%d) does not match entry count field (%d) from file %s!\n",
	      files[1],counter7,entries,files[0]);
  } else {
    if (entries != counter5)
      fprintf(stderr,"Hint: Try to use the 7F??.adr file for offset verification.\n");
  }

  ptrlist5 = offset_list_merge(ptrlist5,counter5,ptrlist7,counter7);

  process_all_data(fields,file5.ptr,ptrlist5,args);
  exit(EXIT_SUCCESS);
}
