/*******************************************************************************
*
*       This file is part of the General Hidden Markov Model Library,
*       GHMM version __VERSION__, see http://ghmm.org
*
*       Filename: ghmm/ghmm/mprintf.c
*       Authors:  Frank Nübel
*
*       Copyright (C) 1998-2004 Alexander Schliep
*       Copyright (C) 1998-2001 ZAIK/ZPR, Universitaet zu Koeln
*       Copyright (C) 2002-2004 Max-Planck-Institut fuer Molekulare Genetik,
*                               Berlin
*
*       Contact: schliep@ghmm.org
*
*       This library is free software; you can redistribute it and/or
*       modify it under the terms of the GNU Library General Public
*       License as published by the Free Software Foundation; either
*       version 2 of the License, or (at your option) any later version.
*
*       This library 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
*       Library General Public License for more details.
*
*       You should have received a copy of the GNU Library General Public
*       License along with this library; if not, write to the Free
*       Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
*       This file is version $Revision: 1449 $
*                       from $Date: 2005-10-15 08:58:44 -0400 (Sat, 15 Oct 2005) $
*             last change by $Author: grunau $.
*
*******************************************************************************/

#ifdef HAVE_CONFIG_H
#  include "../config.h"
#endif

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#ifdef WIN32
#include <windows.h>
#include <io.h>
#endif

#include "mprintf.h"

#define MPRINTF_FLAG_LEFT   1
#define MPRINTF_FLAG_ZERO   2
#define MPRINTF_FLAG_PLUS   4
#define MPRINTF_FLAG_SPACE  8
#define MPRINTF_FLAG_FLAT  16

#define MPRINTF_TMP_LEN    1024
#define MPRINTF_FRM_LEN    64


/*----------------------------------------------------------------------------*/
static int mprintf_int (char *dst, int dlen, int x)
{
  int i;
  if (!x) {
    *dst++ = '0';
    *dst++ = 0;
    return (1);
  }

  for (i = dlen; x && i > 0;) {
    dst[--i] = x % 10 + '0';
    x /= 10;
  }
  memcpy (dst, dst + i, dlen - i);
  if (i > 0)
    i = dlen - i;
  else
    i = dlen - 1;
  dst[i] = 0;
  return (i);
}                               /* mprintf_int */

/*----------------------------------------------------------------------------*/
static void mprintf_conv_float (char *dst, int flags, int minwidth,
                                int precision, char sizemodif, char convchar,
                                long double x)
{
  char tmp[MPRINTF_FRM_LEN];
  int i = 0;;

  tmp[i++] = '%';
  if (flags & MPRINTF_FLAG_LEFT)
    tmp[i++] = '-';
  if (flags & MPRINTF_FLAG_PLUS)
    tmp[i++] = '+';
  if (flags & MPRINTF_FLAG_SPACE)
    tmp[i++] = ' ';
  if (flags & MPRINTF_FLAG_FLAT)
    tmp[i++] = '#';
  if (flags & MPRINTF_FLAG_ZERO)
    tmp[i++] = '0';

  if (minwidth > 0)
    i += mprintf_int (tmp + i, MPRINTF_FRM_LEN - i, minwidth);

  if (precision >= 0) {
    tmp[i++] = '.';
    i += mprintf_int (tmp + i, MPRINTF_FRM_LEN - i, precision);
  }
  if (sizemodif)
    tmp[i++] = sizemodif;
  tmp[i++] = convchar;
  tmp[i] = 0;

  if (sizemodif == 'L')
    sprintf (dst, tmp, x);
  else
    sprintf (dst, tmp, (double) x);

  return;
}                               /* mprintf_conv_float */

/*----------------------------------------------------------------------------*/
static void mprintf_conv_str (char *dst, int flags, int minwidth,
                              int precision, char *src)
{
  char *p;
  int zeros;

  dst[0] = 0;
  if (!src)
    return;
  p = memchr (src, 0, precision);
  if (p)
    precision = p - src;

  if (!(flags & MPRINTF_FLAG_ZERO) || minwidth < precision)
    zeros = 0;
  else
    zeros = minwidth - precision;
  minwidth -= precision + zeros;
  if (minwidth < 0)
    minwidth = 0;

  if (flags & MPRINTF_FLAG_LEFT) {
    if (zeros)
      memset (dst, '0', zeros);
    memcpy (dst + zeros, src, precision);
    if (minwidth)
      memset (dst + zeros + precision, ' ', minwidth);
  }
  else {
    if (minwidth)
      memset (dst, ' ', minwidth);
    if (zeros)
      memset (dst + minwidth, '0', zeros);
    memcpy (dst + minwidth + zeros, src, precision);
  }
  dst[zeros + precision + minwidth] = 0;
  return;
}                               /* mprintf_conv_str */

/*----------------------------------------------------------------------------*/
static void mprintf_conv_int (char *dst, int flags, int minwidth,
                              int precision, char convchar, unsigned int x)
{
  int i = x;
  char neg = 0;
  int k;
  int fillchars;
  char aux;

  if (precision < 1)
    precision = 1;

  if (i < 0 && (convchar == 'i' || convchar == 'd')) {
    x = -x;
    neg = 1;
  }

  if (strchr ("uoxXbc", convchar)) {
    flags &= ~MPRINTF_FLAG_PLUS;
    flags &= ~MPRINTF_FLAG_SPACE;
  }
  if (strchr ("duci", convchar)) {
    flags &= ~MPRINTF_FLAG_FLAT;
  }

  k = MPRINTF_TMP_LEN;
  switch (convchar) {
  case 'u':
  case 'd':
  case 'i':
    for (; x && k > 0;) {
      dst[--k] = x % 10 + '0';
      x /= 10;
    }
    break;
  case 'o':
    for (; x && k > 0;) {
      dst[--k] = (x & 7) + '0';
      x >>= 3;
    }
    break;
  case 'X':
  case 'x':
    aux = convchar - 'X' + 'A' - '0' - 10;
    for (; x && k > 0;) {
      dst[--k] = (x & 0xF) + '0';
      x >>= 4;
      if (dst[k] > '9')
        dst[k] += aux;
    }
    break;
  case 'b':
    for (; x && k > 0;) {
      dst[--k] = (x & 1) + '0';
      x >>= 1;
    }
    break;
  case 'c':
    if (x)
      dst[--k] = x;
    else
      dst[--k] = -1;
    break;
  }

  while (MPRINTF_TMP_LEN - k < precision)
    dst[--k] = '0';

  if (minwidth > 0 && flags & MPRINTF_FLAG_ZERO) {
    fillchars = minwidth - (MPRINTF_TMP_LEN - k);
    if (neg)
      fillchars--;
    else if (flags & (MPRINTF_FLAG_SPACE | MPRINTF_FLAG_PLUS))
      fillchars--;
    if (flags & MPRINTF_FLAG_FLAT) {
      if (convchar == 'o')
        fillchars--;
      else if (strchr ("Xxb", convchar))
        fillchars -= 2;
    }
    while (fillchars-- > 0)
      dst[--k] = '0';
  }


  if (neg)
    dst[--k] = '-';
  else if (flags & MPRINTF_FLAG_SPACE)
    dst[--k] = ' ';
  else if (flags & MPRINTF_FLAG_PLUS)
    dst[--k] = '+';
  if (flags & MPRINTF_FLAG_FLAT) {
    switch (convchar) {
    case 'o':
      dst[--k] = '0';
      break;
    case 'X':
      dst[--k] = 'X';
      dst[--k] = '0';
      break;
    case 'x':
      dst[--k] = 'x';
      dst[--k] = '0';
      break;
    case 'b':
      dst[--k] = '_';
      dst[--k] = '0';
      break;
    }
  }

  if (flags & MPRINTF_FLAG_LEFT) {
    memcpy (dst, dst + k, MPRINTF_TMP_LEN - k);
    k = MPRINTF_TMP_LEN - k;
    while (k < minwidth)
      dst[k++] = ' ';
    dst[k] = 0;
  }

  else {
    if (minwidth > 0)
      fillchars = minwidth - (MPRINTF_TMP_LEN - k);
    else
      fillchars = 0;
    if (fillchars < 0)
      fillchars = 0;
    for (i = fillchars; i-- > 0;)
      dst[i] = ' ';
    memcpy (dst + fillchars, dst + k, MPRINTF_TMP_LEN - k);
    dst[fillchars + MPRINTF_TMP_LEN - k] = 0;
  }

  return;
}                               /* mprintf_conv_int */



/* workaround for an ABI-change on PPC and X86-64 */
#if defined(__GNUC__) && (defined (__powerpc__) || defined(__amd64__))
#define AP_POINTER ap
#else
#define AP_POINTER *ap
#endif
/*----------------------------------------------------------------------------*/
static int mprintf_scan_int (char **src, va_list AP_POINTER)
{
  char *p = *src;
  int res = 0;

  if (*p == '*') {
    res = va_arg (AP_POINTER, int);
    p++;
  }
  else
    while (*p <= '9' && *p >= '0') {
      res = res * 10 + (*p - '0');
      p++;
    }
  if (res < 0)
    res = -res;
  if (p == *src)
    res = -1;
  *src = p;
  return (res);
}                               /* mprintf_scan_int */

/*----------------------------------------------------------------------------*/
static char *mprintf_get_next (char **format, int *flen, int *dlen,
                               va_list AP_POINTER)
{
  static char tmp[MPRINTF_TMP_LEN];
  char *res = NULL;
  int minwidth = -1;
  int maxwidth = MPRINTF_TMP_LEN - 1;
  int precision = -1;
  char sizemodif = 0;
  char convchar = 0;
  int flags = 0;
  char *src;
  int leave;
  long double aux_float;
  clock_t clock_diff;

  tmp[0] = 0;


  /* skip '%' : */
  src = *format + 1;
  *dlen = 0;

  while (1) {

    /* scan flags : */
    leave = 0;
    while (*src) {
      switch (*src) {
      case '-':
        flags |= MPRINTF_FLAG_LEFT;
        break;
      case '0':
        flags |= MPRINTF_FLAG_ZERO;
        break;
      case '+':
        flags |= MPRINTF_FLAG_PLUS;
        break;
      case ' ':
        flags |= MPRINTF_FLAG_SPACE;
        break;
      case '#':
        flags |= MPRINTF_FLAG_FLAT;
        break;
      default:
        leave = 1;
        break;
      }
      if (leave)
        break;
      src++;
    }
    if (!*src)
      break;

    /* scan minwidth : */
    minwidth = mprintf_scan_int (&src, ap);
    if (!*src)
      break;
    if (minwidth >= MPRINTF_TMP_LEN)
      minwidth = MPRINTF_TMP_LEN - 1;


    /* scan precision : */
    if (*src == '.') {
      src++;
      precision = mprintf_scan_int (&src, ap);
      if (!*src)
        break;
    }

    /* scan maxwidth : */
    if (*src == ':') {
      src++;
      maxwidth = mprintf_scan_int (&src, ap);
      if (!*src)
        break;
      if (!maxwidth) {
        res = tmp;
        break;
      }
    }

    /* scan size modifier : */
    if (strchr ("hlL", *src))
      sizemodif = *src++;
    if (!*src)
      break;

    /* scan conversion letter */
    if (!strchr ("diuoxXfeEgGcspntTb", *src))
      break;
    convchar = *src++;

    if (maxwidth < 0 || maxwidth >= MPRINTF_TMP_LEN)
      maxwidth = MPRINTF_TMP_LEN - 1;
    if (minwidth > maxwidth)
      minwidth = maxwidth;
    leave = 0;
    switch (convchar) {
    case 'c':
    case 'd':
    case 'i':
    case 'u':
    case 'o':
    case 'x':
    case 'X':
    case 'b':
      mprintf_conv_int (tmp, flags, minwidth, precision, convchar,
                        va_arg (AP_POINTER, int));
      break;
#if 0                           /* makes only trouble */
    case 'p':
      mprintf_conv_int (tmp, flags, minwidth, precision, 'X',
                        (int) (va_arg (AP_POINTER, char *)));
      break;
#endif /* 0 */
    case 's':
      if (precision < 0 || precision > maxwidth)
        precision = maxwidth;
      mprintf_conv_str (tmp, flags, minwidth, precision,
                        va_arg (AP_POINTER, char *));
      maxwidth = MPRINTF_TMP_LEN - 1;
      break;
    case 'f':
    case 'e':
    case 'E':
    case 'g':
    case 'G':
      if (sizemodif == 'L')
        aux_float = va_arg (AP_POINTER, long double);
      else
        aux_float = va_arg (AP_POINTER, double);
      mprintf_conv_float (tmp, flags, minwidth, precision, sizemodif,
                          convchar, aux_float);
      break;
    case 't':                  /* rel. time   */
    case 'T':                  /* abs. time   */
      {
        clock_diff = clock ();
        if (minwidth < 0)
          minwidth = 6;
        if (precision < 0)
          precision = 2;
        if (convchar == 't') {
          clock_diff = clock () - va_arg (AP_POINTER, clock_t);
          minwidth--;
          tmp[0] = '+';
          res = tmp + 1;
        }
        else {
          res = tmp;
        }
        mprintf_conv_float (res, flags, minwidth, precision, sizemodif, 'f',
                            clock_diff / (double) CLOCKS_PER_SEC);
        strcat (tmp, " sec");
      }
      break;
    case 'n':
      flags = 0;
      minwidth = 0;
      break;
    default:
      leave = 1;
      break;
    }
    if (leave)
      break;

    leave = strlen (tmp);
    if (leave > maxwidth) {
      memset (tmp, '~', maxwidth);
      tmp[maxwidth] = 0;
      leave = maxwidth;
    }
    *dlen = leave;
    res = tmp;
    break;

  }                             /* while( 1 ) */
  /* "mprintf.c", line 350: warning: end-of-loop code not reached   */
  /* SUN cc complains, but I am not Frank Nübel.... (achim 010422 ) */

  /* update format and flen: */
  *flen -= src - *format;
  *format = src;
  return (res);
}                               /* mprintf_get_next */
#undef AP_POINTER

/*----------------------------------------------------------------------------*/
static int mprintf_memcpy (char **dst, int *dpos, int *dlen,
                           char *src, int slen, int dyn)
{
  char *newdst;
  int newlen;

  if (*dpos + slen >= *dlen) {
    newdst = NULL;
    if (dyn) {
      newlen = *dlen + 0x100;
      while (newlen <= *dpos + slen)
        newlen += 0x100;
      newdst = realloc (*dst, newlen);
    }
    else
      newlen = 0;
    if (!dyn || !newdst) {
      if (*dlen < 1)
        return (0);
      memset (*dst, '~', *dlen - 1);
      (*dst)[*dlen - 1] = 0;
      *dpos = *dlen;
      return (-1);
    }

    *dst = newdst;
    *dlen = newlen;
  }
  memcpy (*dst + *dpos, src, slen);
  (*dst)[*dpos + slen] = 0;
  *dpos += slen;
  return (0);
}                               /* mprintf_memcpy */


/* workaround for an ABI-change on PPC and X86-64 */
#if defined(__GNUC__) && (defined (__powerpc__) || defined(__amd64__))
#define VA_ADDRESS(VAL) (VAL)
#else
#define VA_ADDRESS(VAL) (&(VAL))
#endif
/*============================================================================*/
char *ighmm_mprintf_va (char *dst, int dlen, char *format, va_list args)
{
  va_list ap;

  int dyn;
  int dpos;
  char *next;
  int nlen;
  int flen;

  if (dst && dlen)
    dst[0] = 0;
  if (!format || dlen == 1)
    return (dst);


  /*****************************************************/

  memcpy ((char *) VA_ADDRESS(ap), (char *) VA_ADDRESS(args), sizeof (va_list));

  /*****************************************************/


  dyn = (!dst || dlen <= 0);
  if (!dst)
    dlen = 0;
  else if (dlen < 0)
    dlen = -dlen;
  if (!dlen) {
    dlen = 0x400;
    if (dst)
      free (dst);
    dst = malloc (dlen);        /* no messsage here  ! */
    if (!dst)
      return (NULL);
  }

  flen = strlen (format);
  dpos = 0;
  while (flen) {
    if (*format == '%') {
      if (format[1] == '%') {
        next = format;
        nlen = 1;
        format += 2;
        flen -= 2;
      }
      else {
        next = mprintf_get_next (&format, &flen, &nlen, VA_ADDRESS(ap));
        if (!next)
          return (dst);
      }
    }
    else {
      next = memchr (format, '%', flen);
      if (!next)
        next = format + flen;
      nlen = next - format;
      next = format;
      format += nlen;
      flen -= nlen;
    }
    if (mprintf_memcpy (&dst, &dpos, &dlen, next, nlen, dyn))
      return (dst);
  }
  va_end (ap);

  return (dst);
}                               /* ighmm_mprintf_va */
#undef VA_ADDRESS

/*============================================================================*/
char *ighmm_mprintf (char *dst, int maxlen, char *format, ...)
{
  va_list args;
  char *res;

  va_start (args, format);
  res = ighmm_mprintf_va (dst, maxlen, format, args);
  va_end (args);

  return (res);
}                               /* ighmm_mprintf */

/*============================================================================*/
char *ighmm_mprintf_va_dyn (char *dst, int maxlen, char *format, va_list args)
{
  char *res = NULL;

  res = ighmm_mprintf_va (dst, maxlen, format, args);
  if (!res || !dst || maxlen <= 0)
    return (res);
  if (strlen (res) < maxlen - 1)
    return (res);
  res = ighmm_mprintf_va (NULL, 0, format, args);
  return (res);
}                               /* ighmm_mprintf_va_dyn */


/*============================================================================*/
char *ighmm_mprintf_dyn (char *dst, int maxlen, char *format, ...)
{
  va_list args;
  char *res;

  va_start (args, format);
  res = ighmm_mprintf_va_dyn (dst, maxlen, format, args);
  va_end (args);

  return (res);
}                               /* ighmm_mprintf_dyn */



/******************************************************************************/
#if defined( TEST )

#include "mes.h"
/*============================================================================*/
int mprintf_tst (void)
{
  char dst[13];
  char *dyn = malloc (10);

  int i;
  char *format_i[] = {
    "%12d",
    "%012d",
    "% 012d",
    "%+12d",
    "%+012d",
    "%-12d",
    "%- 12d",
    "%-+12d",
    "%12.4d",
    "%-12.4d",
    "\n",

    "%14u",
    "%014u",
    "%#14u",
    "%#014u",
    "%-14u",
    "%-#14u",
    "%14.4u",
    "%-14.4u",
    "\n",

    "%14o",
    "%014o",
    "%#14o",
    "%#014o",
    "%-14o",
    "%-#14o",
    "%14.4o",
    "%-#14.4o",
    "%-14.4o",
    "\n",

    "%12x",
    "%012x",
    "%#12X",
    "%#012X",
    "%-12x",
    "%-#12x",
    "%12.4x",
    "%-#12.4x",
    "%-12.4X",
    "\n",

    "\n"
  };

  char *format_c[] = {
    "%12c",
    "%012c",
    "%-12c",
    "\n"
  };

  char *format_s[] = {
    "%12s",
    "%12.5s",
    "%012s",
    "%-12s",
    "\n"
  };

  char *format_f[] = {
    "%10.2f",
    "%010.2f",
    "% 010.2f",
    "%+10.2f",
    "%+010.2f",
    "%-10.2f",
    "%- 10.2f",
    "%-+10.4f",
    "\n",

    "%10.2e",
    "%010.2e",
    "% 010.2e",
    "%+10.2E",
    "%+010.2E",
    "%-10.2e",
    "%- 10.2e",
    "%-+10.4e",
    "\n",

  };

  for (i = 0; i < sizeof (format_i) / sizeof (format_i[0]); i++) {
    mes_win (ighmm_mprintf (dst, sizeof (dst), "%12s", format_i[i]));
    mes_win ("\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_i[i], 45));
    mes_win ("\t\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_i[i], -45));
    mes_win ("\n");
  }

  for (i = 0; i < sizeof (format_c) / sizeof (format_c[0]); i++) {
    mes_win (ighmm_mprintf (dst, sizeof (dst), "%12s", format_c[i]));
    mes_win ("\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_c[i], '*'));
    mes_win ("\n");
  }

  for (i = 0; i < sizeof (format_s) / sizeof (format_s[0]); i++) {
    mes_win (ighmm_mprintf (dst, sizeof (dst), "%12s", format_s[i]));
    mes_win ("\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_s[i], "zap"));
    mes_win ("\t\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_s[i], "longish"));
    mes_win ("\n");
  }

  for (i = 0; i < sizeof (format_f) / sizeof (format_f[0]); i++) {
    mes_win (ighmm_mprintf (dst, sizeof (dst), "%12s", format_f[i]));
    mes_win ("\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_f[i], 12.678));
    mes_win ("\t\t");
    mes_win (ighmm_mprintf (dst, sizeof (dst), format_f[i], -12.678));
    mes_win ("\n");
  }

  dyn = ighmm_mprintf_dyn (dst, sizeof (dst), " %s %s (%2T)\n", "Uhrzeit", ":");
  mes_win (dyn);

  if (dyn && dyn - dst)
    free (dyn);
  return (0);
}                               /* mprintf_tst */

#endif /* defined( TEST ) */
