/* Copyright 2009-2024
 * Kaz Kylheku <kaz@kylheku.com>
 * Vancouver, Canada
 * All rights reserved.
 *
 * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 */

struct args {
  cnum argc;
  cnum fill;
  val list;
  val arg[1]; /* deliberate disuse of FLEX_ARRAY */
};

typedef int arg_index;

#define ARGS_MAX 32
#define ARGS_MIN 4

#define ARGS_ABS_MIN 1

struct args_bool_key {
  val key;
  val arg_p;
  val *store;
};

INLINE varg args_init_list(varg args, cnum argc, val list)
{
  args->argc = argc;
  args->fill = 0;
  args->list = list;
  return args;
}

INLINE void args_set_fill(varg args, cnum fill)
{
  args->fill = fill;
}

#define args_decl_list(NAME, N, L)                                      \
  struct { struct args args; val arg[(N) - 1]; } _ac;                   \
  varg NAME = args_init_list(&_ac.args, N, L)

#define args_decl_constsize(NAME, N) args_decl_list(NAME, N, nil)

#define args_decl_list_dyn(NAME, N, L)                                  \
  mem_t *NAME ## _mem =                                                 \
    coerce(mem_t *,                                                     \
           alloca(offsetof(struct args, arg) + (N)*sizeof (val)));      \
  varg NAME = args_init_list(coerce(varg ,              \
                                            NAME ## _mem), N, L)

#define args_decl(NAME, N) args_decl_list_dyn(NAME, N, nil)

INLINE val args_add(varg args, val arg)
{
  return args->arg[args->fill++] = arg;
}

INLINE void args_add2(varg args, val arg1, val arg2)
{
  val *arg = args->arg + args->fill;
  args->fill += 2;
  *arg++ = arg1;
  *arg++ = arg2;
}

INLINE void args_add3(varg args, val arg1, val arg2, val arg3)
{
  val *arg = args->arg + args->fill;
  args->fill += 3;
  *arg++ = arg1;
  *arg++ = arg2;
  *arg++ = arg3;
}

INLINE void args_add4(varg args, val arg1, val arg2, val arg3, val arg4)
{
  val *arg = args->arg + args->fill;
  args->fill += 4;
  *arg++ = arg1;
  *arg++ = arg2;
  *arg++ = arg3;
  *arg++ = arg4;
}

INLINE void args_add5(varg args, val arg1, val arg2, val arg3,
                      val arg4, val arg5)
{
  val *arg = args->arg + args->fill;
  args->fill += 5;
  *arg++ = arg1;
  *arg++ = arg2;
  *arg++ = arg3;
  *arg++ = arg4;
  *arg++ = arg5;
}

val args_add_checked(val name, varg args, val arg);

INLINE void args_add_list(varg args, val list)
{
  args->list = list;
}

INLINE int args_more(varg args, cnum index)
{
  return index < args->fill || args->list;
}

INLINE int args_two_more(varg args, cnum index)
{
  return
    index + 1 < args->fill ||
    (index + 1 == args->fill && args->list) ||
    cdr(args->list);
}

INLINE int args_more_nozap(varg args, cnum index, val list)
{
  return list || index < args->fill;
}

void args_normalize_exact(varg args, cnum fill);
void args_normalize_least(varg args, cnum fill);
void args_normalize_fill(varg args, cnum minfill, cnum maxfill);

INLINE val args_get_list(varg args)
{
  if (args->fill == 0)
    return z(args->list);
  args_normalize_exact(args, 0);
  return z(args->list);
}

INLINE val args_get_rest(varg args, cnum index)
{
  if (args->fill == index)
    return z(args->list);
  args_normalize_exact(args, index);
  return z(args->list);
}

INLINE val args_at(varg args, cnum arg_index)
{
  if (arg_index < args->fill)
    return args->arg[arg_index];
  return car(args->list);
}

INLINE val args_atz(varg args, cnum arg_index)
{
  if (arg_index < args->fill) {
    return z(args->arg[arg_index]);
  } else {
    return car(z(args->list));
  }
}

INLINE val args_get(varg args, cnum *arg_index)
{
  if (*arg_index < args->fill)
    return z(args->arg[(*arg_index)++]);
  return pop(&args->list);
}

INLINE val args_get_nozap(varg args, cnum *arg_index, val *list)
{
  if (*arg_index < args->fill)
    return args->arg[(*arg_index)++];
  return pop(list);
}

INLINE cnum args_count(varg args, val self)
{
  return args->fill + c_num(length_list(args->list), self);
}

val args_get_checked(val name, varg args, cnum *arg_index);
varg args_copy(varg to, varg from);
varg args_copy_zap(varg to, varg from);
varg args_cat(varg to, varg from);
varg args_cat_from(varg to, varg from, cnum index);
varg args_cat_zap(varg to, varg from);
varg args_cat_zap_from(varg to, varg from, cnum index);
varg args_copy_reverse(varg to, varg from, cnum nargs);
val args_copy_to_list(varg args);
void args_for_each(varg args,
                   int (*fn)(val arg, int ix, mem_t *ctx),
                   mem_t *ctx);
void args_keys_extract(varg args, struct args_bool_key *, int n);
val dyn_args(varg args, val car, val cdr);
