/***************************************************************************
 *   copyright           : (C) 2005 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 "apoheader.h"
#include "helper.h"
#include "charsets.h"
#include "smspdu.h"

#include <stdio.h>
#include <time.h>

static enum apo_version version;

void apo_header_init(enum apo_version ver) {
  version = ver;
}  

struct apo_header_def {
  uint16_t type;
  uint8_t apotype;
  char* descr;
  enum apo_entry_type format;
};
static struct apo_header_def apo_header[] = {
  { 0x00, APO_REM, "alarm time", APO_DATE },
  { 0x01, APO_REM, "start time", APO_DATE },
  { 0x02, APO_REM, "end time", APO_DATE },
  { 0x0a, APO_NOTE, "creation time", APO_DATE },
  { 0x0b, APO_NOTE, "modification time", APO_DATE },
  { 0x0c, APO_NOTE, "unknown", APO_2BYTE },
  { 0x0d, APO_NOTE, "text", APO_STRING },

  { 0x10, APO_APP|APO_TASK, "appointment type", APO_APPTYPE },
  { 0x11, APO_APP, "description", APO_STRING },
  { 0x12, APO_TASK, "priority", APO_PRIO },
  { 0x13, APO_TASK, "status", APO_STATUS },
  { 0x14, APO_TASK, "due time", APO_DATE },
  { 0x15, APO_APP|APO_TASK, "alarm time", APO_DATE },
  { 0x16, APO_APP, "start time", APO_DATE },
  { 0x17, APO_TASK, "closed at", APO_DATE },
  { 0x18, APO_APP, "voice memo filename", APO_STRING },
  { 0x19, APO_APP, "location", APO_STRING },
  { 0x1a, APO_APP, "end time", APO_DATE },
  { 0x1b, APO_APP, "reoccurrence", APO_STRING },
  { 0x1d, APO_APP, "call number", APO_TELNUM },
  { 0x1e, APO_APP, "address book reference", APO_LINK },

  { 0x20, APO_REM,  "appointment reference", APO_LINK },
  { 0x21, APO_APP|APO_TASK, "alarm", APO_ALARM },
  { 0x23, APO_ADDR, "family name", APO_STRING },
  { 0x24, APO_ADDR, "given name", APO_STRING },
  { 0x25, APO_ADDR, "street", APO_STRING },
  { 0x26, APO_ADDR, "postal code", APO_STRING },
  { 0x27, APO_ADDR, "city", APO_STRING },
  { 0x28, APO_ADDR, "country", APO_STRING },
  { 0x29, APO_ADDR, "company", APO_STRING },
  { 0x2a, APO_ADDR, "number office", APO_TELNUM },
  { 0x2b, APO_ADDR, "number fax 1", APO_TELNUM },
  { 0x2c, APO_ADDR, "number mobile", APO_TELNUM },
  { 0x2d, APO_ADDR, "number home", APO_TELNUM },
  { 0x2e, APO_ADDR, "e-mail 1", APO_STRING },
  { 0x2f, APO_ADDR, "URL", APO_STRING },

  { 0x30, APO_ADDR, "birthday", APO_DATE },
  { 0x31, APO_ADDR, "appointment reference", APO_LINK },
  { 0x32, APO_ADDR, "group", APO_GROUP },
  { 0x33, APO_ADDR, "picture file address", APO_STRING },
  { 0x35, APO_ADDR, "title", APO_STRING },
  { 0x3e, APO_ADDR, "wireless village ID", APO_STRING },
  { 0x3f, APO_ADDR, "nickname", APO_STRING },

  { 0x41, APO_ADDR, "ICQ number", APO_STRING },
  { 0x46, APO_ADDR, "AIM buddy name", APO_STRING },
  { 0x4d, APO_APP|APO_TASK, "alarm type", APO_4BYTE }, /* 0=normal 1=silent */
  { 0x4e, APO_APP, "unknown", APO_4BYTE },
  { 0x4f, APO_APP|APO_NOTE|APO_TASK, "unknown", APO_2BYTE },

  { 0x50, APO_CALREC, "call name", APO_STRING },
  { 0x51, APO_CALREC, "call time", APO_DATE },
  { 0x52, APO_CALREC, "unknown", APO_2BYTE },
  { 0x53, APO_CALREC, "unknown", APO_2BYTE },
  { 0x54, APO_CALREC, "unknown", APO_4BYTE },
  { 0x55, APO_CALREC, "unknown", APO_4BYTE },
  { 0x56, APO_CALREC, "unknown", APO_4BYTE },
  { 0x57, APO_CALREC, "unknown", APO_4BYTE },
  { 0x58, APO_CALREC, "unknown", APO_4BYTE },
  { 0x59, APO_CALREC, "unknown", APO_4BYTE },
  { 0x5a, APO_CALREC, "unknown", APO_4BYTE },
  { 0x5d, APO_ADDR, "e-mail 2", APO_STRING },
  { 0x5e, APO_ADDR, "number fax 2", APO_TELNUM },

  { 0x60, APO_ADDR, "display name", APO_STRING },
  { 0x62, APO_ADDR, "number (private)", APO_TELNUM },
  { 0x63, APO_ADDR, "number (office)", APO_TELNUM },
  { 0x64, APO_ADDR, "number (private,mobile)", APO_TELNUM },
  { 0x65, APO_ADDR, "number (office,mobile)", APO_TELNUM },
  { 0x66, APO_ADDR, "ring tone file address", APO_STRING },
  { 0x67, APO_ADDR, "ring video file address", APO_STRING },
  { 0x68, APO_ADDR, "short message tone file address", APO_STRING },
  { 0x69, APO_ADDR, "unknown", APO_4BYTE },
  { 0x6a, APO_ADDR, "number (private,fax)", APO_TELNUM },
  { 0x6b, APO_ADDR, "e-mail (private)", APO_STRING },
  { 0x6c, APO_ADDR, "note (private)", APO_STRING },
  { 0x6d, APO_ADDR, "Push-To-Talk ID", APO_STRING },
  { 0x6e, APO_ADDR, "unknown", APO_2BYTE }, /* CX75 */
  { 0x6f, APO_ADDR, "profession", APO_STRING }, /* SL 75 */
  { 0x6f, APO_APP, "unknown", APO_DATE }, /* S65 */

  { 0x70, APO_APP, "unknown", APO_DATE },
  { 0x71, APO_ADDR, "e-mail (office)", APO_STRING },
  { 0x72, APO_ADDR, "e-mail 2 (office)", APO_STRING },
  { 0x73, APO_ADDR, "URL (office)", APO_STRING },
  { 0x74, APO_ADDR, "street (office)", APO_STRING },
  { 0x75, APO_ADDR, "city (office)", APO_STRING },
  { 0x76, APO_ADDR, "postal code (office)", APO_STRING },
  { 0x77, APO_ADDR, "sub-state (office)", APO_STRING },
  { 0x78, APO_ADDR, "country (office)", APO_STRING },
  { 0x79, APO_ADDR, "note (office)", APO_STRING },
  { 0x7a, APO_ADDR, "wireless village ID", APO_STRING },
  { 0x7e, APO_ADDR, "note (person)", APO_STRING },
  { 0x7f, APO_ADDR, "sub-state (private)", APO_STRING },

  { 0x84, APO_PRES, "unknown", APO_STRING },
  { 0x85, APO_PRES, "unknown", APO_2BYTE },
  { 0x86, APO_PRES, "unknown", APO_2BYTE },
  { 0x87, APO_PRES, "unknown", APO_STRING },
  { 0x88, APO_PRES, "unknown", APO_2BYTE },
  { 0x89, APO_PRES, "unknown", APO_STRING },
  { 0x8a, APO_PRES, "unknown", APO_STRING },
  { 0x8b, APO_PRES, "unknown", APO_2BYTE },
  { 0x8c, APO_PRES, "unknown", APO_2BYTE },
  { 0x8d, APO_PRES, "unknown", APO_STRING },
  { 0x8e, APO_PRES, "unknown", APO_2BYTE },

  { 0x91, APO_ADDR, "number (office,fax)", APO_TELNUM },

  { 0xa7, APO_PRES, "unknown", APO_STRING },

  { 0xffff, 0 , NULL, APO_STRING }
};

static
uint16_t apo_number_size (void* ptr) {
  uint16_t nlen;
  uint16_t tlen;
  uint16_t flen;
  uint16_t retval = 0;

  switch (version) {
  case APO_S55:
  case APO_S65:
    return 5+79;
  case APO_SL75:
    nlen = letohs(PTR16(ptr)[0]);
    retval += 2+(nlen+1)+((nlen+1)%2);
    tlen = letohs(PTR16(PTR8(ptr)+retval)[0]);
    retval += 2+(tlen+1)+((tlen+1)%2);
    flen = letohs(PTR16(PTR8(ptr)+retval)[0]);
    retval += 2+flen+12;
    return retval;
  default:
    return 0xFFFF;
  }
}

static
uint16_t apo_text_size (void* ptr) {
  ptr=ptr;
  return 2+letohs(PTR16(ptr)[0]);
}

static
uint16_t apo_date_size (void* ptr) {
  ptr=ptr;
  switch (version) {
  case APO_S55:
    return 10;
  default:
    return 16;
  }
}

static
uint16_t apo_4byte_size (void* ptr) {
  ptr=ptr;
  return 4;
}

static
uint16_t apo_2byte_size (void* ptr) {
  ptr=ptr;
  return 2;
}

ucs4char_t* apo_text_decode (void* ptr) {
  return convert_to_internal("UCS-2LE",(char*)(ptr)+4,letohs(PTR16(ptr)[1])*2);
}


struct gsm_number* apo_number_decode (void* ptr) {
  struct gsm_number* s = mem_alloc(sizeof(*s),0);
  size_t len = 0;
  
  gsm_number_init(s);
  switch (version) {
  case APO_S55:
  case APO_S65:
    gsm_number_set_semioctets(s,PTR8(ptr)[1],PTR8(ptr)+5,PTR8(ptr)[2]);
    break;
  case APO_SL75:
    len = letohs(PTR16(ptr)[0]);
    gsm_number_set(s,(char*)PTR8(ptr)+2,len);
    break;
  }
  return s;
}

static
ucs4char_t* apo_number_descr_decode (void* ptr) {
  uint8_t tlen = 0;
  uint8_t* temp = ptr;
  gsmchar_t* s = NULL;
  ucs4char_t* retval = NULL;

  switch (version) {
  case APO_S55:
  case APO_S65:
    tlen = PTR8(ptr)[4];
    temp += 5 + 21;
    break;
  case APO_SL75:
    temp += 2+((letohs(PTR16(ptr)[0])+2)/2)*2;
    tlen = letohs(PTR16(temp)[0]);
    temp += 2;
    break;
  default:
    return NULL;
  }
  s = (gsmchar_t*)strn_dup((char*)temp,tlen);
  retval = convert_from_gsm(s);
  mem_realloc(s,0);
  return retval;
}

static
time_t apo_date_decode (void* ptr) {
  struct tm temp = {
    .tm_wday = 0,
    .tm_yday = 0,
    .tm_isdst = -1
  };
  switch (version) {
  case APO_S55:
    temp.tm_year = (letohs(PTR16(ptr)[0])-1900);
    temp.tm_mon = (PTR8(ptr)[2]-1);
    temp.tm_mday = PTR8(ptr)[3];
    temp.tm_hour = PTR8(ptr)[4];
    temp.tm_min = PTR8(ptr)[5];
    temp.tm_sec = PTR8(ptr)[6];
#ifdef DEBUG
    if (PTR8(ptr)[7] || PTR8(ptr)[8] || PTR8(ptr)[9])
      fprintf(DEBUG," (extra bytes not all 0x00) ");
#endif
    break;
  case APO_S65:
  case APO_SL75:
    temp.tm_year = (letohs(PTR16(ptr)[0])-1900);
    temp.tm_mon = (PTR8(ptr)[4]-1);
    temp.tm_mday = PTR8(ptr)[5];
    temp.tm_hour = PTR8(ptr)[8];
    temp.tm_min = PTR8(ptr)[9];
    temp.tm_sec = PTR8(ptr)[10];
#ifdef DEBUG
    if (PTR8(ptr)[2] || PTR8(ptr)[3] ||	PTR8(ptr)[6] || PTR8(ptr)[7] ||
	PTR8(ptr)[11] || PTR8(ptr)[12] || PTR8(ptr)[13] || PTR8(ptr)[14] || PTR8(ptr)[15])
      fprintf(DEBUG," (extra bytes not all 0x00) ");
#endif
    break;
  default:
    return (time_t)0;
  }
  tzset();
  return mktime(&temp);
}

static
void apo_printText_text (void* ptr, FILE* stream) {
  ucs4char_t* s1 = apo_text_decode(ptr);
  char* s2 = convert_to_system(s1,REPMODE_QUESTIONMARK);

  mem_realloc(s1,0);
  if (s2) fprintf(stream,"%s",s2);
  mem_realloc(s2,0);
}

static
void apo_printText_number (void* ptr, FILE* stream) {
  struct gsm_number* s;
  ucs4char_t* descr;
  char* temp;
  
  s = apo_number_decode(ptr);
  temp = gsm_number_get(s);
  if (temp) fprintf(stream,"number=\"%s\"",temp);
  mem_realloc(temp,0);
  mem_realloc(s,0);
  descr = apo_number_descr_decode(ptr);
  if (descr) {
    temp = convert_to_system(descr,REPMODE_QUESTIONMARK);
    mem_realloc(descr,0);
    fprintf(stream,", text=\"%s\"",temp);
    mem_realloc(temp,0);
  }

  switch (version) {
  case APO_S55:
  case APO_S65:
    fprintf(stream," (extra: 0=0x%02x, 3=0x%02x)",PTR8(ptr)[0],PTR8(ptr)[3]);
    break;
  default:
    break;
  }
}

struct apo_value_map {
  uint16_t value;
  char* descr;
};
#define APO_VALUE_MAP_LAST { 0xFFFF, NULL }
static
void apo_printText_2byte (void* ptr, FILE* stream,
			  struct apo_value_map* map)
{
  uint16_t value = letohs(PTR16(ptr)[0]);
  unsigned int i = 0;

  fprintf(stream,"%u",value);
  while (map[i].value != value && map[i].value != 0xFFFF) ++i;
  if (map[i].value == value && map[i].descr != NULL)
    fprintf(stream," (%s)",map[i].descr);
}

static
void apo_printText_group (void* ptr, FILE* stream) {
  struct apo_value_map groups[] = {
    { 0x01  , "VIP" },
    { 0x02  , "office" },
    { 0x03  , "family" },
    { 0x04  , "individual" },
    { 0x05  , "leisure" },
    { 0x06  , "private" },
    { 0x07  , "business" },
    { 0x08  , "received" },
    { 0x09  , "no group" },
    APO_VALUE_MAP_LAST
  };
  apo_printText_2byte(ptr,stream,groups);
}

static
void apo_printText_apptype (void* ptr, FILE* stream) {
  struct apo_value_map types[] = {
    { 0x01  , "memo" },
    { 0x02  , "speech memo" },
    { 0x04  , "meeting" },
    { 0x08  , "holiday" },
    { 0x10  , "birthday" },
    { 0x20  , "call" },
    APO_VALUE_MAP_LAST
  };
  apo_printText_2byte(ptr,stream,types);
}

static
void apo_printText_priority (void* ptr, FILE* stream) {
  struct apo_value_map prios[] = {
    { 0x01  , "highest" },
    { 0x02  , "high" },
    { 0x03  , "normal" },
    { 0x04  , "low" },
    { 0x05  , "lowest" },
    APO_VALUE_MAP_LAST
  };
  apo_printText_2byte(ptr,stream,prios);
}

static
void apo_printText_status (void* ptr, FILE* stream) {
  struct apo_value_map status[] = {
    { 0x00  , "outstanding" },
    { 0x02  , "done" },
    APO_VALUE_MAP_LAST
  };
  apo_printText_2byte(ptr,stream,status);
}

static
void apo_printText_alarm (void* ptr, FILE* stream) {
  struct apo_value_map alarms[] = {
    { 0x00  , "disabled" },
    { 0x10  , "enabled" },
    APO_VALUE_MAP_LAST
  };
  apo_printText_2byte(ptr,stream,alarms);
}

static
void apo_printText_date (void* ptr, FILE* stream) {
  time_t t1 = apo_date_decode(ptr);
  struct tm* t2 = localtime(&t1);
  char s[200];
  char* format;

  if (t2->tm_hour || t2->tm_min || t2->tm_sec) format = "%c";
  else format = "%x";
  strftime(s,sizeof(s),format,t2);
  fprintf(stream,"%s",s);
}

static
void apo_printText_link (void* ptr, FILE* stream) {
  fprintf(stream,"0x%04x (extra: 0x%02x 0x%02x)",
	  letohs(PTR16(ptr)[0]),
	  PTR8(ptr)[2],PTR8(ptr)[3]);
}

static
void apo_debug_2byte (void* ptr, FILE* stream) {
  fprintf(stream,"0x%02x 0x%02x ",PTR8(ptr)[0],PTR8(ptr)[1]);
}

static
void apo_debug_4byte (void* ptr, FILE* stream) {
  apo_debug_2byte(ptr,stream);
  apo_debug_2byte(PTR8(ptr)+2,stream);
}

/* the order MUST match the one in the enum! */
static struct apo_entry_func apo_entry_funcs[] = {
  { APO_STRING, apo_text_size, apo_printText_text },
  { APO_TELNUM, apo_number_size, apo_printText_number },
  { APO_DATE, apo_date_size, apo_printText_date },
  { APO_LINK, apo_4byte_size, apo_printText_link },
  { APO_GROUP, apo_2byte_size, apo_printText_group },
  { APO_APPTYPE, apo_2byte_size, apo_printText_apptype },
  { APO_PRIO, apo_2byte_size, apo_printText_priority },
  { APO_STATUS, apo_2byte_size, apo_printText_status },
  { APO_ALARM, apo_2byte_size, apo_printText_alarm },
  { APO_2BYTE, apo_2byte_size, apo_debug_2byte },
  { APO_4BYTE, apo_4byte_size, apo_debug_4byte },
};

static
long apo_seek (uint16_t type) {
  unsigned int i = 0;
  long retval = -1;

  for (; apo_header[i].type != 0xffff; ++i)
    if (apo_header[i].type == type) break;
  
  if (apo_header[i].type == type) retval = (long)i;

  if (type == 0x6f && version == APO_S65 &&
      apo_header[retval+1].type == type)
    ++retval;

  return retval;
  
}

struct apo_entry_func* apo_get_entry_funcs (uint16_t htype) {
  long i = apo_seek(htype);
  
  if (i < 0) return NULL;
  else return &apo_entry_funcs[apo_header[i].format];
}

char* apo_get_description (uint16_t htype) {
  long i = apo_seek(htype);

  if (i < 0) return NULL;
  return apo_header[i].descr;
}

uint8_t apo_get_type (void* ptr) {
  uint16_t type = letohs(PTR16(ptr)[0]);
  long i = apo_seek(type);

  return apo_header[i].apotype;
}

/* return aligned pointer data after header type */
void* apo_header_align (void* ptr, void* base) {
  if (version == APO_S55) return (void*)(PTR8(ptr)+2);
  else return (void*)(PTR8(ptr)+(4-((PTR8(ptr)-PTR8(base))%4)));
}

void* apo_get_next_header (void* ptr, void* base) {
  uint16_t type = letohs(PTR16(ptr)[0]);
  long i = apo_seek(type);
  
  if (i < 0) return NULL;
  ptr = apo_header_align(ptr,base);
  ptr = (void*)(PTR8(ptr)+apo_entry_funcs[apo_header[i].format].getSize(ptr));

  return ptr;
}

