/***************************************************************************
 *   copyright           : (C) 2002 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 "smscoding.h"
#include "smsudh.h"

#include <charsets.h>
#include <helper.h>
#include <intincl.h>
#include <timeincl.h>
#include <gtincl.h>
#include <w32compat.h>

#include <string.h>
#include <stdlib.h>
#include <unistd.h>

static
uint16_t sms_new_sequence_id () {
  /* this does not have to be good random (linear would be ok)
   * just not the same on every call
   */
  srand(time(NULL));
  return rand()&UINT16_MAX;    
}

static
uint8_t sms_header_size (struct sms_pdu_ud_header** header) {
  uint8_t headersize = 1;
  unsigned int i = 0;

  if (header == NULL) {
    return 0;
  } else {
    for (; header[i] != NULL; ++i) {
      headersize += header[i]->len + 2;
    }
  }
  return headersize;
}

static
void sms_data_insert_header (struct sms_pdu_ud_header** header,
			     struct sms_pdu_raw* userdata)
{
  uint8_t size = sms_header_size(header);
  unsigned int i = 0;

  if (userdata == NULL || size == 0) return;
  --size;

  sms_pdu_raw_append(userdata,&size,1);
  for (; header[i] != NULL; ++i) {
    sms_pdu_raw_append(userdata,&header[i]->type,1);
    sms_pdu_raw_append(userdata,&header[i]->len,1);
    sms_pdu_raw_append(userdata,header[i]->data,header[i]->len);
  }
}

static
struct sms_pdu_raw* sms_data_gsm_encode (struct sms_pdu_ud_header** header,
					 ucs4char_t* input)
{
  struct sms_pdu_raw* retval;
  gsmchar_t* temp;
  unsigned char first, end;

  unsigned int headersize = sms_header_size(header);
  unsigned int enc_len = 0;
  unsigned int inc = 0;
  uint8_t state = (headersize*8)/7;
  uint8_t value = 0;
  
  if (headersize > 140) return NULL;

  temp = convert_to_gsm(input);
  enc_len = strlen((char*)temp);
  retval = mem_alloc(sizeof(*retval),0);
  sms_pdu_raw_init(retval);
  retval->size = 1;
  sms_data_insert_header(header,retval);

  if (ucs4len(input) > 0) {
    /* 7bit-GSM octet encoding
     */
    if (state%8 != 0) { //state%8 == 0 equals headersize%7 == 0
      // do the septet padding
      value = ((temp[inc] & 0x7F) << (7-(state%8))) & 0xFF;
      sms_pdu_raw_append(retval,&value,1);
      ++state;
    }
    for (; inc < enc_len && state < (140*8/7); ++inc, ++state) {
      if (state%8 != 7) {
	//the character are 7bit
	//shifting dependent on state (0-7)
	first = ((temp[inc+1] & 0x7F) << (7-(state%8))) & 0xFF;
	end = (temp[inc] & 0x7F) >> (state%8);
	value = first|end;
	sms_pdu_raw_append(retval,&value,1);
      }
    }

    //Return NULL to indicate error during encoding.
    //In this case, input string was too large to fit.
    if (inc != enc_len) {
      mem_realloc(retval,0);
      return NULL;
    }
  }

  /* the user data length must contain the septet count
   * of all userdata (including header)
   */
  retval->data[0] = state;

  return retval;
}

static
struct sms_pdu_raw* sms_data_ucs2_encode (struct sms_pdu_ud_header** header,
			    ucs4char_t* input)
{
  struct sms_pdu_raw* retval;
  ucs2char_t* tmp;
  uint8_t enc_len = 0;
  uint8_t headersize = sms_header_size(header);
  
  if (headersize > 140) return NULL;

  tmp = convert_to_ucs2(input);
  /* convert to big-endian */
  if (tmp) for (; tmp[enc_len] != 0; ++enc_len)
    tmp[enc_len] = htobes(tmp[enc_len]);
  enc_len *= 2;
  retval = mem_alloc(sizeof(*retval),1);
  sms_pdu_raw_init(retval);
  retval->size = 1;

  if (headersize) sms_data_insert_header(header,retval);
  sms_pdu_raw_append(retval,(uint8_t*)tmp,(size_t)enc_len);
  enc_len = retval->size-1;
  retval->data[0] = enc_len;

  return retval;
}

#define SMS_PARTS_SUGGESTED_MAX 10
struct sms_pdu_raw** sms_data_encode (enum sms_encoding charset,
				      struct sms_pdu_ud_header** header,
				      ucs4char_t* input)
{
  struct sms_pdu_raw** retval = NULL;

  struct sms_pdu_ud_header** h = NULL;
  unsigned int hcount = 0;
  unsigned int count = 0;
  unsigned int parts = 0;
  unsigned int i = 0;
  unsigned int k;

  char* delayMessage = _("Delaying for %2d seconds, press Ctrl+C to abort.");
  ucs2char_t* input2 = NULL;
  ucs4char_t* input_tmp;
  uint16_t sequence = sms_new_sequence_id();

  /* get rid of compiler warning */
  header = header;

  switch (charset) {
  case SMS_CHARSET_GSM:
    /* If more than 160 septets are needed:
     * Text data must be aligned at septet:
     * if (H % 7) != 0: minus 1 septet
     *
     * H = 1+6
     * (140-H)*8/7 - ((H%7)?1:0) = 152 Septets
     */
    count = gsm_count(input,0);
    if (count <= 160) parts = 1;
    else while (i < ucs4len(input)) {
      i += gsm_count(input+i,152);
      parts += 1;
    }
    break;
  case SMS_CHARSET_UCS2:
    /* If more than 70 characters are needed (H=1+6):
     * 140-(H/2 + H%2)*2 = 140-(3+1)*2 = 132 Oktets
     *                                 = 66 UCS-2 characters
     */
    input2 = convert_to_ucs2(input);
    count = ucs2len(input2);
    if (count <= 70) parts = 1;
    else {
      parts = count/66;
      if (count%66) ++parts;
    }
    break;
  case SMS_CHARSET_8BIT:
    //no break
  default:
    return NULL; //not supported
  }
  fprintf(stderr,"%s: ",_("Notice"));
  fprintf(stderr,
	  ngettext("This message has %lu character",
		   "This message has %lu characters",
		   ucs4len(input)),
	  (unsigned long)ucs4len(input));
  fprintf(stderr,"%s"," ");
  fprintf(stderr,
	  ngettext("and will use %u part.\n",
		   "and will use %u parts.\n",
		   parts),
	  parts);
  if (parts > SMS_PARTS_SUGGESTED_MAX) {
    fprintf(stderr, _("%s: The suggested maximum parts count is %d.\n"),
	    _("Warning"),SMS_PARTS_SUGGESTED_MAX);
    for (i=10; i>0; --i) {
      fprintf(stderr, delayMessage, i);
      fprintf(stderr,"\r");
      sleep(1);
    }
   for (i=strlen(delayMessage)-2; i>0; --i)
      fprintf(stderr," ");
    fprintf(stderr,"\r");
  }
  if (parts > 255) {
    fprintf(stderr,_("%s: message part count exceeds maximum of 255\n"),
	    _("Error"));
    fprintf(stderr,_("%s: SMS text is too long (max. %d characters).\n"),
	    _("Error"),
	    (charset==SMS_CHARSET_GSM) ? 255*152 : 255*66);
    if (charset == SMS_CHARSET_GSM) {
      fprintf(stderr,"%s\n",
	      _("Please be aware that some characters "
	        "are encoded as 14bit (instead of 7bit), "
	        "e.g. the euro character."));
    }
    return NULL;
  }

  retval = mem_alloc(sizeof(*retval)*(parts+1),0);
  for (i=0; i<=parts; ++i) retval[i] = NULL;
  if (parts > 1) {
    hcount += 1;
    h = mem_alloc((hcount+1)*sizeof(*h),0);
    for (i=0; i<=hcount; ++i) h[i] = NULL;

    count = 0;
    switch (charset) {
    case SMS_CHARSET_GSM:
      for (i=0; i<parts; ++i) {
	h[0] = sms_udh_multipart16_get(sequence,i+1,parts);
	k = gsm_count(input+count,152);
	input_tmp = ucs4ndup(input+count,k);
	retval[i] = sms_data_gsm_encode(h,input_tmp);
	input_tmp = mem_realloc(input_tmp,0);
	if (retval[i] == NULL) { //encoding failed
	  if (i != 0)
	    do { mem_realloc(retval[--i],0); } while (i != 0);
	  return mem_realloc(retval,0);
	}
	count += k;
      }
      break;
    case SMS_CHARSET_UCS2:
      k = 66;
      input = convert_from_ucs2(input2);
      for (i=0; i<parts; ++i) {
	h[0] = sms_udh_multipart16_get(sequence,i+1,parts);
	input_tmp = ucs4ndup(input+count,k);
	retval[i] = sms_data_ucs2_encode(h,input_tmp);
	input_tmp = mem_realloc(input_tmp,0);
	count += k;
      }
      mem_realloc(input,0);
    break;
    case SMS_CHARSET_8BIT:
      //no break
    default:
    return NULL; //not supported
    }
  } else {
    switch (charset) {
    case SMS_CHARSET_GSM:
      retval[0] = sms_data_gsm_encode(NULL,input);
      if (retval[0] == NULL) //encoding failed
	return mem_realloc(retval,0);
      break;
    case SMS_CHARSET_UCS2:
      retval[0] = sms_data_ucs2_encode(NULL,input);
      break;
    case SMS_CHARSET_8BIT:
      //no break
    default:
      return NULL; //not supported
    }
  }
  
  return retval;
}
