/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  rtfhelper.c defines some helper functions to output RTF 1.7 data
  markus@mhoenicka.de 2008-01-27

  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.
   
  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
   
  You should have received a copy of the GNU General Public License
  along with this program; if not, see <http://www.gnu.org/licenses/>

  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <limits.h> /* need LONG_MAX */
#include <dbi/dbi.h>

/* #include "backend.h" */
#include "strfncs.h"
#include "linklist.h"
#include "connect.h"
#include "refdb.h"
#include "refdbd.h"
#include "rtfhelper.h"

extern int n_log_level;

/* prototypes of static functions */
static char* print_rtf_bm(const char* label, char** ptr_buffer, size_t* ptr_buflen, int n_start);


/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_control_word(): writes a RTF control word with an optional
                         numerical parameter to a string

  char* print_rtf_control_word returns a pointer to the modified string,
                         or NULL in case of an error

  const char* ctrlword name of the control word
                       (without the leading backslash)

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

  long param optional numerical parameter. If a control word does not
             use such a parameter, pass NOPARAM here

  int start_group if nonzero, a group is opened before the control word
             is printed. The calling function is responsible for closing
             this group eventually
 
 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_control_word(const char* ctrlword, char** ptr_buffer, size_t* ptr_buflen, long param, int start_group) {
  char buffer[CTRLWORDLEN+13]; /* leading backslash, word, 10-digit param, space, NULL */
  char *to;
  char *new_ref;

  if (start_group) {
    if (print_rtf_start_group(ptr_buffer, ptr_buflen) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
  }

  if (param == NOPARAM) {
    snprintf(buffer, CTRLWORDLEN+13, "\\%s ", ctrlword);
  }
  else {
    snprintf(buffer, CTRLWORDLEN+13, "\\%s%ld ", ctrlword, param);
  }

  /* we could try to avoid redundant spaces like this, but this may be
     called at the start of a string, causing access violations */
/*   if (*(*ptr_buffer-1) == ' ') { */
/*     to = *ptr_buffer-1; */
/*   } */
/*   else { */
    to = *ptr_buffer;
/*   } */

  if ((new_ref = mstrcat(to, buffer, ptr_buflen, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  /* to keep the text human-readable, insert a linebreak after each \par */
  if (!strcmp(ctrlword, "par")) {
    if ((new_ref = mstrcat(*ptr_buffer, "\n", ptr_buflen, 0)) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
    else {
      *ptr_buffer = new_ref;
    }
  }

  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_control_symbol(): writes a RTF control symbol to a string

  char* print_rtf_control_symbol returns a pointer to the modified string,
                         or NULL in case of an error

  const char symbol the symbol to be printed. This is one character, not
                        a one-character string

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

  int start_group if nonzero, a group is opened before the control word
             is printed. The calling function is responsible for closing
             this group eventually
 
 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_control_symbol(const char symbol, char** ptr_buffer, size_t* ptr_buflen, int start_group) {
  char* new_ref;
  char buffer[16];

  if (start_group) {
    if (print_rtf_start_group(ptr_buffer, ptr_buflen) == NULL) {
      LOG_PRINT(LOG_WARNING, get_status_msg(801));
      return NULL;
    }
  }

  snprintf(buffer, 16, "\\%c", symbol);

  if ((new_ref = mstrcat(*ptr_buffer, buffer, ptr_buflen, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_start_group(): writes a RTF group start character to a string

  char* print_rtf_start_group returns a pointer to the modified string,
                         or NULL in case of an error

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_start_group(char** ptr_buffer, size_t* ptr_buflen) {
  char* new_ref;

  if ((new_ref = mstrcat(*ptr_buffer, "{", ptr_buflen, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_end_group(): writes a RTF group end sequence to a string

  char* print_rtf_end_group returns a pointer to the modified string,
                         or NULL in case of an error

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_end_group(char** ptr_buffer, size_t* ptr_buflen) {
  char* new_ref;

  if ((new_ref = mstrcat(*ptr_buffer, "}", ptr_buflen, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_header(): writes a RTF document header to a string

  char* print_rtf_header returns a pointer to the modified string,
                         or NULL in case of an error

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_header(char** ptr_buffer, size_t* ptr_buflen) {
  char* new_ref;

  /* use fixed string for the time being */
  if ((new_ref = mstrcat(*ptr_buffer, "{\\rtf1\\ansi\\deff1\\adeflang1025\n{\\fonttbl{\\f0\\froman\\fprq2\\fcharset0 Times New Roman;}{\\f1\\fprq2\\fcharset0 Times New Roman;}}\n", ptr_buflen, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_string(): writes a string as escaped RTF text to a string

  char* print_rtf_header returns a pointer to the modified string,
                         or NULL in case of an error

  const char* src ptr to the buffer holding the string to print

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_string(const char* src, char** ptr_buffer, size_t* ptr_buflen) {
  char* new_ref;
  char* escaped_src = NULL;

  if ((escaped_src = rtf_escape_string(src)) == NULL) {
    return NULL;
  }
  
  if ((new_ref = mstrcat(*ptr_buffer, escaped_src, ptr_buflen, 0)) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    free(escaped_src);
    return NULL;
  }
  else {
    *ptr_buffer = new_ref;
  }

  free(escaped_src);
  return *ptr_buffer;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  rtf_escape_string(): escapes special RTF characters in a string

  char* print_rtf_header returns a pointer to the escaped string in a
                         newly allocated buffer, or NULL in case of an
			 error. The calling function is responsible
			 for freeing the buffer

  const char* src ptr to the buffer holding the string to print

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* rtf_escape_string(const char* src) {
  char* escaped_string;
  char* in;
  char* out;

  if (!src) {
    return NULL;
  }

  /* escape backslashes (as \\) and braces (as \{ and \}) */
  escaped_string = malloc(2*strlen(src)+1); /* worst case: every char
					       needs to be escaped,
					       plus terminating NULL*/
  if (!escaped_string) {
    return NULL;
  }

  out = escaped_string;

  for (in = (char*)src; *in; in++) {
    if (*in == '\\' || *in == '{' || *in == '}') {
      *out++ = '\\';
    }
    *out++ = *in;
  }

  /* terminate escaped string */
  *out = '\0';

  return escaped_string;
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_bmstart(): writes a bookmark start to a string

  char* print_rtf_bmstart returns a pointer to the modified string,
                         or NULL in case of an error

  const char* label name of the bookmark

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_bmstart(const char* label, char** ptr_buffer, size_t* ptr_buflen) {
  return print_rtf_bm(label, ptr_buffer, ptr_buflen, 1);
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_bmend(): writes a bookmark end to a string

  char* print_rtf_bmend returns a pointer to the modified string,
                         or NULL in case of an error

  const char* label name of the bookmark

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
char* print_rtf_bmend(const char* label, char** ptr_buffer, size_t* ptr_buflen) {
  return print_rtf_bm(label, ptr_buffer, ptr_buflen, 0);
}

/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
  print_rtf_bm(): writes a bookmark start or end to a string

  char* print_rtf_bm returns a pointer to the modified string,
                         or NULL in case of an error

  const char* label name of the bookmark

  char** ptr_buffer ptr to a ptr to a buffer created with malloc which will
        receive the output. This function will reallocate the
        buffer as needed. *ptr will be updated whenever a realloc is
        necessary. The calling function is responsible for freeing the
        memory again.

  size_t* ptr_buflen pointer to variable holding current length of ref

  int start if nonzero, prints bookmark start. if zero, prints bookmark end

 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
static char* print_rtf_bm(const char* label, char** ptr_buffer, size_t* ptr_buflen, int n_start) {

  if (print_rtf_control_symbol('*', ptr_buffer, ptr_buflen, 1 /* start group */) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }

  if (print_rtf_control_word((n_start) ? "bkmkstart": "bkmkend", ptr_buffer, ptr_buflen, NOPARAM, 0) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }

  if (print_rtf_string(label, ptr_buffer, ptr_buflen) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }

  if (print_rtf_end_group(ptr_buffer, ptr_buflen) == NULL) {
    LOG_PRINT(LOG_WARNING, get_status_msg(801));
    return NULL;
  }

  return *ptr_buffer;
}

