#include "config.h"

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

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "nlltypes.h"
#include "value.h"
#include "libprintf.h"

static char *_utostr(unsigned long val)
{
  static char s[32];
  char *p;

  p = s + sizeof(s);
  *(--p) = '\0';
  while (val) {
    *(--p) = "0123456789"[val % 10];
    val /= 10;
  }
  if (*p == '\0')
    *(--p) = '0';

  return p;
}

static char *_itostr(long val)
{
  char *p;

  p = _utostr((val < 0) ? -val : val);
  if (val < 0)
    *(--p) = '-';

  return p;
}

static char *_btostr(unsigned long val)
{
  static char s[128];
  char *p;

  p = s + sizeof(s);
  *(--p) = '\0';
  while (val) {
    *(--p) = "01"[val & 0x1];
    val >>= 1;
  }
  if (*p == '\0')
    *(--p) = '0';

  return p;
}

static char *_otostr(unsigned long val)
{
  static char s[32];
  char *p;

  p = s + sizeof(s);
  *(--p) = '\0';
  while (val) {
    *(--p) = "01234567"[val & 0x7];
    val >>= 3;
  }
  if (*p == '\0')
    *(--p) = '0';

  return p;
}

static char *_xtostr(unsigned long val, int upper, int suffix)
{
  static char s[32];
  char *p;
  char *x;

  x = upper ? "0123456789ABCDEF" : "0123456789abcdef";

  p = s + sizeof(s);
  *(--p) = '\0';
  while (val) {
    *(--p) = x[val & 0xf];
    val >>= 4;
  }
  if (*p == '\0')
    *(--p) = '0';

  if (suffix) {
    *(--p) = 'x';
    *(--p) = '0';
  }

  return p;
}

#ifdef NLL_FLOATING_POINT
static char *_ftostr(double f)
{
  static char s[64];
  sprintf(s, "%f", f);
  return s;
}
#endif

static char *outc(char *p, int *size, int c)
{
  if (p && (*size > 0)) {
    *(p++) = c;
    (*size)--;
  }

  return p;
}

static char *outs(char *p, int *size, char *s, int col, int pad)
{
  int len;
  char ch;

  if (s == NULL)
    s = "(null)";
  len = strlen(s);

  ch = pad;
  for (; col > len; col--) {
    if (p && (*size > 0)) {
      *(p++) = ch;
      (*size)--;
    }
  }

  if (p) {
    while (*s && (*size > 0)) {
      *(p++) = *(s++);
      (*size)--;
    }
  }

  return p;
}

static integer_t arg_integer(value_t arg)
{
  int r;
  integer_t integer = 0;

  if (arg) {
    if ((r = value_get_integer(arg, &integer)) < 0)
      return 0;
  }

  return integer;
}

static char *arg_string(value_t arg)
{
  int r;
  char *string = "";

  if (arg) {
    if ((r = value_get_string(arg, &string, NULL)) < 0)
      return "";
    if (!string)
      string = "(null)";
  }

  return string;
}

#ifdef NLL_FLOATING_POINT
static double arg_float(value_t arg)
{
  int r;
  double f = 0.0;

  if (arg) {
    if ((r = value_get_float(arg, &f)) < 0)
      return 0.0;
  }

  return f;
}
#endif

int nll_sprintf(char *str, int size, const char *format, value_t args)
{
  char *p = str, *sv;
  const char *f;
  int col = 0, pad = ' ';
  enum {
    MODE_NORMAL = 0,
    MODE_INT = 1,
    MODE_LONG = 2,
    MODE_QUAD = 3,
  } mode;

  mode = MODE_NORMAL;

  if (p)
    *p = '\0';

  size--;

  for (f = format; *f; f++) {
    if (mode != MODE_NORMAL) {
      switch (*f) {
      case '0':
	if (!col) {
	  pad = '0';
	  continue;
	}
	/* fall through */
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6':
      case '7':
      case '8':
      case '9':
	col = col * 10 + (*f - '0');
	continue;
      case 'c':
	p = outc(p, &size, arg_integer(args));
	if (args) args = args->next;
	break;
      case 's':
	p = outs(p, &size, arg_string(args), col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
      case 'l':
	switch (mode) {
	case MODE_INT:  mode = MODE_LONG; break;
	case MODE_LONG: mode = MODE_QUAD; break;
	default: break;
	}
	continue;
      case 'q':
	switch (mode) {
	case MODE_INT:
	case MODE_LONG: mode = MODE_QUAD; break;
	default: break;
	}
	continue;
      case 'd':
	switch (mode) {
	case MODE_LONG:
	case MODE_QUAD: sv = _itostr((long)arg_integer(args)); break;
	case MODE_INT:
	default:        sv = _itostr((long)arg_integer(args)); break;
	}
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
      case 'u':
	switch (mode) {
	case MODE_LONG:
	case MODE_QUAD: sv = _utostr((unsigned long)arg_integer(args)); break;
	case MODE_INT:
	default:        sv = _utostr((unsigned long)arg_integer(args)); break;
	}
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
      case 'b':
	switch (mode) {
	case MODE_LONG:
	case MODE_QUAD: sv = _btostr((unsigned long)arg_integer(args)); break;
	case MODE_INT:
	default:        sv = _btostr((unsigned long)arg_integer(args)); break;
	}
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
      case 'o':
	switch (mode) {
	case MODE_LONG:
	case MODE_QUAD: sv = _otostr((unsigned long)arg_integer(args)); break;
	case MODE_INT:
	default:        sv = _otostr((unsigned long)arg_integer(args)); break;
	}
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
      case 'x':
	switch (mode) {
	case MODE_LONG:
	case MODE_QUAD: sv = _xtostr((unsigned long)arg_integer(args), 0, 0); break;
	case MODE_INT:
	default:        sv = _xtostr((unsigned long)arg_integer(args), 0, 0); break;
	}
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
      case 'X':
	switch (mode) {
	case MODE_LONG:
	case MODE_QUAD: sv = _xtostr((unsigned long)arg_integer(args), 1, 0); break;
	case MODE_INT:
	default:        sv = _xtostr((unsigned long)arg_integer(args), 1, 0); break;
	}
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
#ifdef NLL_FLOATING_POINT
      case 'f':
	sv = _ftostr(arg_float(args));
	p = outs(p, &size, sv, col, pad);
	if (args) args = args->next;
	col = 0; pad = ' ';
	break;
#endif
      case '%':
	p = outc(p, &size, '%');
	break;
      default:
	break;
      }
      mode = MODE_NORMAL;
      continue;
    }

    if (*f == '%') {
      mode = MODE_INT;
      continue;
    }

    p = outc(p, &size, *f);
  }

  *p = '\0';

  return p - str;
}
