/* $NetBSD: tparm.c,v 1.18 2020/03/27 15:11:57 christos Exp $ */

/*
 * Copyright (c) 2009, 2011, 2013 The NetBSD Foundation, Inc.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Roy Marples.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/cdefs.h>
#include <sys/param.h>

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <term_private.h>
#include <term.h>

#define LONG_STR_MAX ((CHAR_BIT * sizeof(long)) / 3)
#define BUFINC 128 /* Size to increament the terminal buffer by */

#define VA_LONG_LONG 1
#define VA_CHAR_INT 2
//#define VA_CHAR_LONG	3	/* No need for this yet */

static TERMINAL *dumbterm; /* For non thread safe functions */

typedef struct {
  long nums[20];
  char *strings[20];
  size_t offset;
} TPSTACK;

typedef struct {
  long num;
  char *string;
} TPVAR;

static int push(long num, char *string, TPSTACK *stack) {
  if (stack->offset >= sizeof(stack->nums)) {
    errno = E2BIG;
    return -1;
  }
  stack->nums[stack->offset] = num;
  stack->strings[stack->offset] = string;
  stack->offset++;
  return 0;
}

static int pop(long *num, char **string, TPSTACK *stack) {
  if (stack->offset == 0) {
    if (num)
      *num = 0;
    if (string)
      *string = NULL;
    errno = E2BIG;
    return -1;
  }
  stack->offset--;
  if (num)
    *num = stack->nums[stack->offset];
  if (string)
    *string = stack->strings[stack->offset];
  return 0;
}

static char *checkbuf(TERMINAL *term, size_t len) {
  char *buf;

  if (term->_bufpos + len >= term->_buflen) {
    len = term->_buflen + MAX(len, BUFINC);
    buf = realloc(term->_buf, len);
    if (buf == NULL)
      return NULL;
    term->_buf = buf;
    term->_buflen = len;
  }
  return term->_buf;
}

static size_t ochar(TERMINAL *term, int c) {
  if (c == 0)
    c = 0200;
  /* Check we have space and a terminator */
  if (checkbuf(term, 2) == NULL)
    return 0;
  term->_buf[term->_bufpos++] = (char)c;
  return 1;
}

static size_t onum(TERMINAL *term, const char *fmt, int num, size_t len) {
  int l;
  size_t r;

  if (len < LONG_STR_MAX)
    len = LONG_STR_MAX;
  if (checkbuf(term, len + 2) == NULL)
    return 0;
  l = snprintf(term->_buf + term->_bufpos, len + 2, fmt, num);
  if (l == -1)
    return 0;
  r = (size_t)l;
  term->_bufpos += r;
  return r;
}

/*
  Make a pass through the string so we can work out
  which parameters are ints and which are char *.
  Basically we only use char * if %p[1-9] is followed by %l or %s.
*/
int _ti_parm_analyse(const char *str, int *piss, int piss_len) {
  int nparm, lpop;
  char c;

  nparm = 0;
  lpop = -1;
  while ((c = *str++) != '\0') {
    if (c != '%')
      continue;
    c = *str++;
    switch (c) {
      case 'l': /* FALLTHROUGH */
      case 's':
        if (lpop > 0) {
          if (lpop <= piss_len)
            piss[lpop - 1] = 1;
          else if (piss)
            errno = E2BIG;
        }
        break;
      case 'p':
        c = *str++;
        if (c < '1' || c > '9') {
          errno = EINVAL;
          continue;
        } else {
          lpop = c - '0';
          if (lpop > nparm)
            nparm = lpop;
        }
        break;
      default:
        lpop = -1;
    }
  }

  return nparm;
}

static char *_ti_tiparm(TERMINAL *term, const char *str, int va_type,
                        va_list parms) {
  char c, fmt[64], *fp, *ostr;
  long val, val2;
  long dnums[26]; /* dynamic variables a-z, not preserved */
  size_t l, max, width, precision, olen;
  TPSTACK stack;
  TPVAR params[TPARM_MAX];
  unsigned int done, dot, minus;
  int piss[TPARM_MAX]; /* Parameter IS String - piss ;) */

  if (str == NULL)
    return NULL;

  /*
    If not passed a terminal, malloc a dummy one.
    This means we can preserve buffers and variables per terminal and
    still work with non thread safe functions (which sadly are still the
    norm and standard).
  */
  if (term == NULL) {
    if (dumbterm == NULL) {
      dumbterm = malloc(sizeof(*dumbterm));
      if (dumbterm == NULL)
        return NULL;
      dumbterm->_buflen = 0;
    }
    term = dumbterm;
  }

  term->_bufpos = 0;
  /* Ensure we have an initial buffer */
  if (term->_buflen == 0) {
    term->_buf = malloc(BUFINC);
    if (term->_buf == NULL)
      return NULL;
    term->_buflen = BUFINC;
  }

  memset(&piss, 0, sizeof(piss));
  max = (size_t)_ti_parm_analyse(str, piss, TPARM_MAX);

  /* Put our parameters into variables */
  memset(&params, 0, sizeof(params));
  for (l = 0; l < max; l++) {
    if (piss[l]) {
      if (va_type == VA_LONG_LONG) {
        /* This only works if char * fits into a long
         * on this platform. */
        if (sizeof(char *) <= sizeof(long) /*CONSTCOND*/)
          params[l].string = (char *)va_arg(parms, long);
        else {
          errno = ENOTSUP;
          return NULL;
        }
      } else
        params[l].string = va_arg(parms, char *);
    } else {
      if (va_type == VA_CHAR_INT)
        params[l].num = (long)va_arg(parms, int);
      else
        params[l].num = va_arg(parms, long);
    }
  }

  memset(&stack, 0, sizeof(stack));
  while ((c = *str++) != '\0') {
    if (c != '%' || (c = *str++) == '%') {
      if (c == '\0')
        break;
      if (ochar(term, c) == 0)
        return NULL;
      continue;
    }

    /* Handle formatting. */
    fp = fmt;
    *fp++ = '%';
    done = dot = minus = 0;
    width = precision = 0;
    val = 0;
    while (done == 0 && (size_t)(fp - fmt) < sizeof(fmt)) {
      switch (c) {
        case 'c': /* FALLTHROUGH */
        case 's':
          *fp++ = c;
          done = 1;
          break;
        case 'd': /* FALLTHROUGH */
        case 'o': /* FALLTHROUGH */
        case 'x': /* FALLTHROUGH */
        case 'X': /* FALLTHROUGH */
          *fp++ = 'l';
          *fp++ = c;
          done = 1;
          break;
        case '#': /* FALLTHROUGH */
        case ' ':
          *fp++ = c;
          break;
        case '.':
          *fp++ = c;
          if (dot == 0) {
            dot = 1;
            width = (size_t)val;
          } else
            done = 2;
          val = 0;
          break;
        case ':':
          minus = 1;
          break;
        case '-':
          if (minus)
            *fp++ = c;
          else
            done = 1;
          break;
        default:
          if (isdigit((unsigned char)c)) {
            val = (val * 10) + (c - '0');
            if (val > 10000)
              done = 2;
            else
              *fp++ = c;
          } else
            done = 1;
      }
      if (done == 0)
        c = *str++;
    }
    if (done == 2) {
      /* Found an error in the format */
      fp = fmt + 1;
      *fp = *str;
      olen = 0;
    } else {
      if (dot == 0)
        width = (size_t)val;
      else
        precision = (size_t)val;
      olen = MAX(width, precision);
    }
    *fp++ = '\0';

    /* Handle commands */
    switch (c) {
      case 'c':
        pop(&val, NULL, &stack);
        if (ochar(term, (unsigned char)val) == 0)
          return NULL;
        break;
      case 's':
        pop(NULL, &ostr, &stack);
        if (ostr != NULL) {
          int r;

          l = strlen(ostr);
          if (l < (size_t)olen)
            l = olen;
          if (checkbuf(term, (size_t)(l + 1)) == NULL)
            return NULL;
          r = snprintf(term->_buf + term->_bufpos, l + 1, fmt, ostr);
          if (r != -1)
            term->_bufpos += (size_t)r;
        }
        break;
      case 'l':
        pop(NULL, &ostr, &stack);
        if (ostr == NULL)
          l = 0;
        else
          l = strlen(ostr);
#ifdef NCURSES_COMPAT_57
        if (onum(term, "%ld", (long)l, 0) == 0)
          return NULL;
#else
        push((long)l, NULL, &stack);
#endif
        break;
      case 'd': /* FALLTHROUGH */
      case 'o': /* FALLTHROUGH */
      case 'x': /* FALLTHROUGH */
      case 'X':
        pop(&val, NULL, &stack);
        if (onum(term, fmt, (int)val, olen) == 0)
          return NULL;
        break;
      case 'p':
        if (*str < '1' || *str > '9')
          break;
        l = (size_t)(*str++ - '1');
        if (push(params[l].num, params[l].string, &stack))
          return NULL;
        break;
      case 'P':
        pop(&val, NULL, &stack);
        if (*str >= 'a' && *str <= 'z')
          dnums[*str - 'a'] = val;
        else if (*str >= 'A' && *str <= 'Z')
          term->_snums[*str - 'A'] = val;
        break;
      case 'g':
        if (*str >= 'a' && *str <= 'z') {
          if (push(dnums[*str - 'a'], NULL, &stack))
            return NULL;
        } else if (*str >= 'A' && *str <= 'Z') {
          if (push(term->_snums[*str - 'A'], NULL, &stack))
            return NULL;
        }
        break;
      case 'i':
        if (piss[0] == 0)
          params[0].num++;
        if (piss[1] == 0)
          params[1].num++;
        break;
      case '\'':
        if (push((long)(unsigned char)*str++, NULL, &stack))
          return NULL;
        while (*str != '\0' && *str != '\'')
          str++;
        if (*str == '\'')
          str++;
        break;
      case '{':
        val = 0;
        for (; isdigit((unsigned char)*str); str++)
          val = (val * 10) + (*str - '0');
        if (push(val, NULL, &stack))
          return NULL;
        while (*str != '\0' && *str != '}')
          str++;
        if (*str == '}')
          str++;
        break;
      case '+': /* FALLTHROUGH */
      case '-': /* FALLTHROUGH */
      case '*': /* FALLTHROUGH */
      case '/': /* FALLTHROUGH */
      case 'm': /* FALLTHROUGH */
      case 'A': /* FALLTHROUGH */
      case 'O': /* FALLTHROUGH */
      case '&': /* FALLTHROUGH */
      case '|': /* FALLTHROUGH */
      case '^': /* FALLTHROUGH */
      case '=': /* FALLTHROUGH */
      case '<': /* FALLTHROUGH */
      case '>':
        pop(&val, NULL, &stack);
        pop(&val2, NULL, &stack);
        switch (c) {
          case '+':
            val = val + val2;
            break;
          case '-':
            val = val2 - val;
            break;
          case '*':
            val = val * val2;
            break;
          case '/':
            val = val ? val2 / val : 0;
            break;
          case 'm':
            val = val ? val2 % val : 0;
            break;
          case 'A':
            val = val && val2;
            break;
          case 'O':
            val = val || val2;
            break;
          case '&':
            val = val & val2;
            break;
          case '|':
            val = val | val2;
            break;
          case '^':
            val = val ^ val2;
            break;
          case '=':
            val = val == val2;
            break;
          case '<':
            val = val2 < val;
            break;
          case '>':
            val = val2 > val;
            break;
        }
        if (push(val, NULL, &stack))
          return NULL;
        break;
      case '!':
      case '~':
        pop(&val, NULL, &stack);
        switch (c) {
          case '!':
            val = !val;
            break;
          case '~':
            val = ~val;
            break;
        }
        if (push(val, NULL, &stack))
          return NULL;
        break;
      case '?': /* if */
        break;
      case 't': /* then */
        pop(&val, NULL, &stack);
        if (val == 0) {
          l = 0;
          for (; *str != '\0'; str++) {
            if (*str != '%')
              continue;
            str++;
            if (*str == '?')
              l++;
            else if (*str == ';') {
              if (l > 0)
                l--;
              else {
                str++;
                break;
              }
            } else if (*str == 'e' && l == 0) {
              str++;
              break;
            }
          }
        }
        break;
      case 'e': /* else */
        l = 0;
        for (; *str != '\0'; str++) {
          if (*str != '%')
            continue;
          str++;
          if (*str == '?')
            l++;
          else if (*str == ';') {
            if (l > 0)
              l--;
            else {
              str++;
              break;
            }
          }
        }
        break;
      case ';': /* fi */
        break;
    }
  }
  term->_buf[term->_bufpos] = '\0';
  return term->_buf;
}

char *ti_tiparm(TERMINAL *term, const char *str, ...) {
  va_list va;
  char *ret;

  _DIAGASSERT(term != NULL);
  _DIAGASSERT(str != NULL);

  va_start(va, str);
  ret = _ti_tiparm(term, str, VA_CHAR_INT, va);
  va_end(va);
  return ret;
}

char *tiparm(const char *str, ...) {
  va_list va;
  char *ret;

  _DIAGASSERT(str != NULL);

  va_start(va, str);
  ret = _ti_tiparm(NULL, str, VA_CHAR_INT, va);
  va_end(va);
  return ret;
}

#ifdef VA_CHAR_LONG
char *ti_tlparm(TERMINAL *term, const char *str, ...) {
  va_list va;
  char *ret;

  _DIAGASSERT(term != NULL);
  _DIAGASSERT(str != NULL);

  va_start(va, str);
  ret = _ti_tiparm(term, str, VA_CHAR_LONG, va);
  va_end(va);
  return ret;
}

char *tlparm(const char *str, ...) {
  va_list va;
  char *ret;

  _DIAGASSERT(str != NULL);

  va_start(va, str);
  ret = _ti_tiparm(NULL, str, VA_CHAR_LONG, va);
  va_end(va);
  return ret;
}
#endif

static char *_tparm(const char *str, ...) {
  va_list va;
  char *ret;

  _DIAGASSERT(str != NULL);

  va_start(va, str);
  ret = _ti_tiparm(NULL, str, VA_LONG_LONG, va);
  va_end(va);
  return ret;
}

char *tparm(const char *str, long p1, long p2, long p3, long p4, long p5,
            long p6, long p7, long p8, long p9) {

  return _tparm(str, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
