/* vi:set ts=8 sts=4 sw=4 noet:
 *
 * MNV - MNV is not Vim	by Bram Moolenaar
 *
 * Do ":help uganda"  in MNV to read copying and usage conditions.
 * Do ":help credits" in MNV to see a list of people who contributed.
 * See README.txt for an overview of the MNV source code.
 */

/*
 * tuple.c: Tuple support functions.
 */

#include "mnv.h"

#if defined(FEAT_EVAL)

// Tuple heads for garbage collection.
static tuple_T		*first_tuple = NULL;	// list of all tuples

    static void
tuple_init(tuple_T *tuple)
{
    // Prepend the tuple to the list of tuples for garbage collection.
    if (first_tuple != NULL)
	first_tuple->tv_used_prev = tuple;
    tuple->tv_used_prev = NULL;
    tuple->tv_used_next = first_tuple;
    first_tuple = tuple;

    ga_init2(&tuple->tv_items, sizeof(typval_T), 20);
}

/*
 * Allocate an empty header for a tuple.
 * Caller should take care of the reference count.
 */
    tuple_T *
tuple_alloc(void)
{
    tuple_T  *tuple;

    tuple = ALLOC_CLEAR_ONE(tuple_T);
    if (tuple != NULL)
	tuple_init(tuple);
    return tuple;
}

/*
 * Allocate space for a tuple with "count" items.
 * This uses one allocation for efficiency.
 * The reference count is not set.
 * Next tuple_set_item() must be called for each item.
 */
    tuple_T *
tuple_alloc_with_items(int count)
{
    tuple_T	*tuple;

    tuple = tuple_alloc();
    if (tuple == NULL)
	return NULL;

    if (count <= 0)
	return tuple;

    if (ga_grow(&tuple->tv_items, count) == FAIL)
    {
	tuple_free(tuple);
	return NULL;
    }

    return tuple;
}

/*
 * Set item "idx" for a tuple previously allocated with
 * tuple_alloc_with_items().
 * The contents of "tv" is copied into the tuple item.
 * Each item must be set exactly once.
 */
    void
tuple_set_item(tuple_T *tuple, int idx, typval_T *tv)
{
    *TUPLE_ITEM(tuple, idx) = *tv;
    tuple->tv_items.ga_len++;
}

/*
 * Allocate an empty tuple for a return value, with reference count set.
 * Returns OK or FAIL.
 */
    int
rettv_tuple_alloc(typval_T *rettv)
{
    tuple_T	*tuple = tuple_alloc();

    if (tuple == NULL)
	return FAIL;

    rettv->v_lock = 0;
    rettv_tuple_set(rettv, tuple);
    return OK;
}

/*
 * Set a tuple as the return value.  Increments the reference count.
 */
    void
rettv_tuple_set(typval_T *rettv, tuple_T *tuple)
{
    rettv->v_type = VAR_TUPLE;
    rettv->vval.v_tuple = tuple;
    if (tuple != NULL)
	++tuple->tv_refcount;
}

/*
 * Set a new tuple with "count" items as the return value.
 * Returns OK on success and FAIL on allocation failure.
 */
    int
rettv_tuple_set_with_items(typval_T *rettv, int count)
{
    tuple_T *new_tuple;

    new_tuple = tuple_alloc_with_items(count);
    if (new_tuple == NULL)
	return FAIL;

    rettv_tuple_set(rettv, new_tuple);

    return OK;
}

/*
 * Unreference a tuple: decrement the reference count and free it when it
 * becomes zero.
 */
    void
tuple_unref(tuple_T *tuple)
{
    if (tuple != NULL && --tuple->tv_refcount <= 0)
	tuple_free(tuple);
}

/*
 * Free a tuple, including all non-container items it points to.
 * Ignores the reference count.
 */
    static void
tuple_free_contents(tuple_T *tuple)
{
    for (int i = 0; i < TUPLE_LEN(tuple); i++)
	clear_tv(TUPLE_ITEM(tuple, i));

    ga_clear(&tuple->tv_items);
}

/*
 * Go through the list of tuples and free items without the copyID.
 * But don't free a tuple that has a watcher (used in a for loop), these
 * are not referenced anywhere.
 */
    int
tuple_free_nonref(int copyID)
{
    tuple_T	*tt;
    int		did_free = FALSE;

    for (tt = first_tuple; tt != NULL; tt = tt->tv_used_next)
	if ((tt->tv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
	{
	    // Free the Tuple and ordinary items it contains, but don't recurse
	    // into Lists and Dictionaries, they will be in the list of dicts
	    // or list of lists.
	    tuple_free_contents(tt);
	    did_free = TRUE;
	}
    return did_free;
}

    static void
tuple_free_list(tuple_T  *tuple)
{
    // Remove the tuple from the list of tuples for garbage collection.
    if (tuple->tv_used_prev == NULL)
	first_tuple = tuple->tv_used_next;
    else
	tuple->tv_used_prev->tv_used_next = tuple->tv_used_next;
    if (tuple->tv_used_next != NULL)
	tuple->tv_used_next->tv_used_prev = tuple->tv_used_prev;

    free_type(tuple->tv_type);
    mnv_free(tuple);
}

    void
tuple_free_items(int copyID)
{
    tuple_T	*tt, *tt_next;

    for (tt = first_tuple; tt != NULL; tt = tt_next)
    {
	tt_next = tt->tv_used_next;
	if ((tt->tv_copyID & COPYID_MASK) != (copyID & COPYID_MASK))
	{
	    // Free the tuple and ordinary items it contains, but don't recurse
	    // into Lists and Dictionaries, they will be in the list of dicts
	    // or list of lists.
	    tuple_free_list(tt);
	}
    }
}

    void
tuple_free(tuple_T *tuple)
{
    if (in_free_unref_items)
	return;

    tuple_free_contents(tuple);
    tuple_free_list(tuple);
}

/*
 * Get the number of items in a tuple.
 */
    long
tuple_len(tuple_T *tuple)
{
    if (tuple == NULL)
	return 0L;
    return tuple->tv_items.ga_len;
}

/*
 * Return TRUE when two tuples have exactly the same values.
 */
    int
tuple_equal(
    tuple_T	*t1,
    tuple_T	*t2,
    int		ic)	// ignore case for strings
{
    if (t1 == t2)
	return TRUE;

    int t1_len = tuple_len(t1);
    int t2_len = tuple_len(t2);

    if (t1_len != t2_len)
	return FALSE;

    if (t1_len == 0)
	// empty and NULL tuples are considered equal
	return TRUE;

    // If the tuples "t1" or "t2" is NULL, then it is handled by the length
    // checks above.

    for (int i = 0, j = 0; i < t1_len && j < t2_len; i++, j++)
	if (!tv_equal(TUPLE_ITEM(t1, i), TUPLE_ITEM(t2, j), ic))
	    return FALSE;

    return TRUE;
}

/*
 * Locate item with index "n" in tuple "tuple" and return it.
 * A negative index is counted from the end; -1 is the last item.
 * Returns NULL when "n" is out of range.
 */
    typval_T *
tuple_find(tuple_T *tuple, long n)
{
    if (tuple == NULL)
	return NULL;

    // Negative index is relative to the end.
    if (n < 0)
	n = TUPLE_LEN(tuple) + n;

    // Check for index out of range.
    if (n < 0 || n >= TUPLE_LEN(tuple))
	return NULL;

    return TUPLE_ITEM(tuple, n);
}

    int
tuple_append_tv(tuple_T *tuple, typval_T *tv)
{
    if (ga_grow(&tuple->tv_items, 1) == FAIL)
	return FAIL;

    tuple_set_item(tuple, TUPLE_LEN(tuple), tv);

    return OK;
}

/*
 * Concatenate tuples "t1" and "t2" into a new tuple, stored in "tv".
 * Return FAIL when out of memory.
 */
    int
tuple_concat(tuple_T *t1, tuple_T *t2, typval_T *tv)
{
    tuple_T	*tuple;

    // make a copy of the first tuple.
    if (t1 == NULL)
	tuple = tuple_alloc();
    else
	tuple = tuple_copy(t1, FALSE, TRUE, 0);
    if (tuple == NULL)
	return FAIL;

    tv->v_type = VAR_TUPLE;
    tv->v_lock = 0;
    tv->vval.v_tuple = tuple;
    if (t1 == NULL)
	++tuple->tv_refcount;

    // append all the items from the second tuple
    for (int i = 0; i < tuple_len(t2); i++)
    {
	typval_T    new_tv;

	copy_tv(TUPLE_ITEM(t2, i), &new_tv);

	if (tuple_append_tv(tuple, &new_tv) == FAIL)
	{
	    tuple_free(tuple);
	    return FAIL;
	}
    }

    return OK;
}

/*
 * Return a slice of tuple starting at index n1 and ending at index n2,
 * inclusive (tuple[n1 : n2])
 */
    tuple_T *
tuple_slice(tuple_T *tuple, long n1, long n2)
{
    tuple_T	*new_tuple;

    new_tuple = tuple_alloc_with_items(n2 - n1 + 1);
    if (new_tuple == NULL)
	return NULL;

    for (int i = n1; i <= n2; i++)
    {
	typval_T    new_tv;

	copy_tv(TUPLE_ITEM(tuple, i), &new_tv);

	if (tuple_append_tv(new_tuple, &new_tv) == FAIL)
	{
	    tuple_free(new_tuple);
	    return NULL;
	}
    }

    return new_tuple;
}

    int
tuple_slice_or_index(
    tuple_T	*tuple,
    int		range,
    varnumber_T	n1_arg,
    varnumber_T	n2_arg,
    int		exclusive,
    typval_T	*rettv,
    int		verbose)
{
    long	len = tuple_len(tuple);
    varnumber_T	n1 = n1_arg;
    varnumber_T	n2 = n2_arg;
    typval_T	var1;

    if (n1 < 0)
	n1 = len + n1;
    if (n1 < 0 || n1 >= len)
    {
	// For a range we allow invalid values and for legacy script return an
	// empty tuple, for MNV9 script start at the first item.
	// A tuple index out of range is an error.
	if (!range)
	{
	    if (verbose)
		semsg(_(e_tuple_index_out_of_range_nr), (long)n1_arg);
	    return FAIL;
	}
	if (in_mnv9script())
	    n1 = n1 < 0 ? 0 : len;
	else
	    n1 = len;
    }
    if (range)
    {
	tuple_T	*new_tuple;

	if (n2 < 0)
	    n2 = len + n2;
	else if (n2 >= len)
	    n2 = len - (exclusive ? 0 : 1);
	if (exclusive)
	    --n2;
	if (n2 < 0 || n2 + 1 < n1)
	    n2 = -1;
	new_tuple = tuple_slice(tuple, n1, n2);
	if (new_tuple == NULL)
	    return FAIL;
	clear_tv(rettv);
	rettv_tuple_set(rettv, new_tuple);
    }
    else
    {
	// copy the item to "var1" to avoid that freeing the tuple makes it
	// invalid.
	copy_tv(tuple_find(tuple, n1), &var1);
	clear_tv(rettv);
	*rettv = var1;
    }
    return OK;
}

/*
 * Make a copy of tuple "orig".  Shallow if "deep" is FALSE.
 * The refcount of the new tuple is set to 1.
 * See item_copy() for "top" and "copyID".
 * Returns NULL when out of memory.
 */
    tuple_T *
tuple_copy(tuple_T *orig, int deep, int top, int copyID)
{
    tuple_T	*copy;
    int		idx;

    if (orig == NULL)
	return NULL;

    copy = tuple_alloc_with_items(TUPLE_LEN(orig));
    if (copy == NULL)
	return NULL;

    if (orig->tv_type == NULL || top || deep)
	copy->tv_type = NULL;
    else
	copy->tv_type = alloc_type(orig->tv_type);
    if (copyID != 0)
    {
	// Do this before adding the items, because one of the items may
	// refer back to this tuple.
	orig->tv_copyID = copyID;
	orig->tv_copytuple = copy;
    }

    for (idx = 0; idx < TUPLE_LEN(orig) && !got_int; idx++)
    {
	copy->tv_items.ga_len++;
	if (deep)
	{
	    if (item_copy(TUPLE_ITEM(orig, idx), TUPLE_ITEM(copy, idx),
						deep, FALSE, copyID) == FAIL)
		break;
	}
	else
	    copy_tv(TUPLE_ITEM(orig, idx), TUPLE_ITEM(copy, idx));
    }

    ++copy->tv_refcount;
    if (idx != TUPLE_LEN(orig))
    {
	tuple_unref(copy);
	copy = NULL;
    }

    return copy;
}

/*
 * Allocate a variable for a tuple and fill it from "*arg".
 * "*arg" points to the "," after the first element.
 * "rettv" contains the first element.
 * Returns OK or FAIL.
 */
    int
eval_tuple(char_u **arg, typval_T *rettv, evalarg_T *evalarg, int do_error)
{
    int		evaluate = evalarg == NULL ? FALSE
					 : evalarg->eval_flags & EVAL_EVALUATE;
    tuple_T	*tuple = NULL;
    typval_T	tv;
    int		mnv9script = in_mnv9script();
    int		had_comma;

    if (check_typval_is_value(rettv) == FAIL)
    {
	// the first item is not a valid value type
	clear_tv(rettv);
	return FAIL;
    }

    if (evaluate)
    {
	tuple = tuple_alloc();
	if (tuple == NULL)
	    return FAIL;

	if (rettv->v_type != VAR_UNKNOWN)
	{
	    // Add the first item to the tuple from "rettv"
	    if (tuple_append_tv(tuple, rettv) == FAIL)
		goto failret;
	    // The first item in "rettv" is added to the tuple.  Set the rettv
	    // type to unknown, so that the caller doesn't free it.
	    rettv->v_type = VAR_UNKNOWN;
	}
    }

    if (**arg == ')')
	// empty tuple
	goto done;

    if (mnv9script && !IS_WHITE_NL_OR_NUL((*arg)[1]) && (*arg)[1] != ')')
    {
	semsg(_(e_white_space_required_after_str_str), ",", *arg);
	goto failret;
    }

    *arg = skipwhite_and_linebreak(*arg + 1, evalarg);
    while (**arg != ')' && **arg != NUL)
    {
	if (eval1(arg, &tv, evalarg) == FAIL)	// recursive!
	    goto failret;
	if (check_typval_is_value(&tv) == FAIL)
	{
	    if (evaluate)
		clear_tv(&tv);
	    goto failret;
	}

	if (evaluate)
	{
	    if (tuple_append_tv(tuple, &tv) == FAIL)
	    {
		clear_tv(&tv);
		goto failret;
	    }
	}

	if (!mnv9script)
	    *arg = skipwhite(*arg);

	// the comma must come after the value
	had_comma = **arg == ',';
	if (had_comma)
	{
	    if (mnv9script && !IS_WHITE_NL_OR_NUL((*arg)[1]) && (*arg)[1] != ')')
	    {
		semsg(_(e_white_space_required_after_str_str), ",", *arg);
		goto failret;
	    }
	    *arg = skipwhite(*arg + 1);
	}

	// The ")" can be on the next line.  But a double quoted string may
	// follow, not a comment.
	*arg = skipwhite_and_linebreak(*arg, evalarg);
	if (**arg == ')')
	    break;

	if (!had_comma)
	{
	    if (do_error)
	    {
		if (**arg == ',')
		    semsg(_(e_no_white_space_allowed_before_str_str),
								    ",", *arg);
		else
		    semsg(_(e_missing_comma_in_tuple_str), *arg);
	    }
	    goto failret;
	}
    }

    if (**arg != ')')
    {
	if (do_error)
	    semsg(_(e_missing_end_of_tuple_rsp_str), *arg);
failret:
	if (evaluate)
	    tuple_free(tuple);
	return FAIL;
    }

done:
    *arg += 1;
    if (evaluate)
	rettv_tuple_set(rettv, tuple);

    return OK;
}

/*
 * Lock or unlock a tuple.  "deep" is number of levels to go.
 * When "check_refcount" is TRUE do not lock a tuple with a reference
 * count larger than 1.
 */
    void
tuple_lock(tuple_T *tuple, int deep, int lock, int check_refcount)
{
    if (tuple == NULL || (check_refcount && tuple->tv_refcount > 1))
	return;

    if (lock)
	tuple->tv_lock |= VAR_LOCKED;
    else
	tuple->tv_lock &= ~VAR_LOCKED;

    if (deep < 0 || deep > 1)
    {
	// recursive: lock/unlock the items the Tuple contains
	for (int i = 0; i < TUPLE_LEN(tuple); i++)
	    item_lock(TUPLE_ITEM(tuple, i), deep - 1, lock, check_refcount);
    }
}

typedef struct join_S {
    string_T	s;
    char_u	*tofree;
} join_T;

    static int
tuple_join_inner(
    garray_T	*gap,		// to store the result in
    tuple_T	*tuple,
    char_u	*sep,
    int		echo_style,
    int		restore_copyID,
    int		copyID,
    garray_T	*join_gap)	// to keep each tuple item string
{
    int		i;
    join_T	*p;
    int		sumlen = 0;
    int		first = TRUE;
    char_u	*tofree;
    char_u	numbuf[NUMBUFLEN];
    string_T	s;
    typval_T	*tv;
    size_t	seplen = 0;

    // Stringify each item in the tuple.
    for (i = 0; i < TUPLE_LEN(tuple) && !got_int; i++)
    {
	tv = TUPLE_ITEM(tuple, i);
	s.string = echo_string_core(tv, &tofree, numbuf, copyID,
				      echo_style, restore_copyID, !echo_style);
	if (s.string == NULL)
	    return FAIL;

	s.length = STRLEN(s.string);
	sumlen += (int)s.length;

	(void)ga_grow(join_gap, 1);
	p = ((join_T *)join_gap->ga_data) + (join_gap->ga_len++);
	if (tofree != NULL || s.string != numbuf)
	{
	    p->s.string = s.string;
	    p->s.length = s.length;
	    p->tofree = tofree;
	}
	else
	{
	    p->s.string = mnv_strnsave(s.string, s.length);
	    p->s.length = s.length;
	    p->tofree = p->s.string;
	}

	line_breakcheck();
	if (did_echo_string_emsg)  // recursion error, bail out
	    break;
    }

    // Allocate result buffer with its total size, avoid re-allocation and
    // multiple copy operations.  Add 2 for a tailing ')' and NUL.
    if (join_gap->ga_len > 0)
    {
	seplen = STRLEN(sep);
	if (join_gap->ga_len >= 2)
	    sumlen += (int)seplen * (join_gap->ga_len - 1);
    }
    if (ga_grow(gap, sumlen + 2) == FAIL)
	return FAIL;

    for (i = 0; i < join_gap->ga_len && !got_int; ++i)
    {
	if (first)
	    first = FALSE;
	else
	    ga_concat_len(gap, sep, seplen);
	p = ((join_T *)join_gap->ga_data) + i;

	if (p->s.string != NULL)
	    ga_concat_len(gap, p->s.string, p->s.length);
	line_breakcheck();
    }

    // If there is only one item in the tuple, then add the separator after
    // that.
    if (join_gap->ga_len == 1)
	ga_concat_len(gap, sep, seplen);

    return OK;
}

/*
 * Join tuple "tuple" into a string in "*gap", using separator "sep".
 * When "echo_style" is TRUE use String as echoed, otherwise as inside a Tuple.
 * Return FAIL or OK.
 */
    int
tuple_join(
    garray_T	*gap,
    tuple_T	*tuple,
    char_u	*sep,
    int		echo_style,
    int		restore_copyID,
    int		copyID)
{
    garray_T	join_ga;
    int		retval;
    join_T	*p;
    int		i;

    if (TUPLE_LEN(tuple) < 1)
	return OK; // nothing to do
    ga_init2(&join_ga, sizeof(join_T), TUPLE_LEN(tuple));
    retval = tuple_join_inner(gap, tuple, sep, echo_style, restore_copyID,
							    copyID, &join_ga);

    if (join_ga.ga_data == NULL)
	return retval;

    // Dispose each item in join_ga.
    p = (join_T *)join_ga.ga_data;
    for (i = 0; i < join_ga.ga_len; ++i)
    {
	mnv_free(p->tofree);
	++p;
    }
    ga_clear(&join_ga);

    return retval;
}

/*
 * Return an allocated string with the string representation of a tuple.
 * May return NULL.
 */
    char_u *
tuple2string(typval_T *tv, int copyID, int restore_copyID)
{
    garray_T	ga;

    if (tv->vval.v_tuple == NULL)
	return NULL;
    ga_init2(&ga, sizeof(char), 80);
    ga_append(&ga, '(');
    if (tuple_join(&ga, tv->vval.v_tuple, (char_u *)", ",
				       FALSE, restore_copyID, copyID) == FAIL)
    {
	mnv_free(ga.ga_data);
	return NULL;
    }
    ga_append(&ga, ')');
    ga_append(&ga, NUL);
    return (char_u *)ga.ga_data;
}

/*
 * Implementation of foreach() for a Tuple.  Apply "expr" to
 * every item in Tuple "tuple" and return the result in "rettv".
 */
    void
tuple_foreach(
    tuple_T	*tuple,
    filtermap_T	filtermap,
    typval_T	*expr)
{
    int		len = tuple_len(tuple);
    int		rem;
    typval_T	newtv;
    funccall_T	*fc;

    // set_mnv_var_nr() doesn't set the type
    set_mnv_var_type(VV_KEY, VAR_NUMBER);

    // Create one funccall_T for all eval_expr_typval() calls.
    fc = eval_expr_get_funccal(expr, &newtv);

    for (int idx = 0; idx < len; idx++)
    {
	set_mnv_var_nr(VV_KEY, idx);
	if (filter_map_one(TUPLE_ITEM(tuple, idx), expr, filtermap, fc,
						     &newtv, &rem) == FAIL)
	    break;
    }

    if (fc != NULL)
	remove_funccal();
}

/*
 * Count the number of times item "needle" occurs in Tuple "l" starting at index
 * "idx". Case is ignored if "ic" is TRUE.
 */
    long
tuple_count(tuple_T *tuple, typval_T *needle, long idx, int ic)
{
    long	n = 0;

    if (tuple == NULL)
	return 0;

    int	len = TUPLE_LEN(tuple);
    if (len == 0)
	return 0;

    if (idx < 0 || idx >= len)
    {
	semsg(_(e_tuple_index_out_of_range_nr), idx);
	return 0;
    }

    for (int i = idx; i < len; i++)
    {
	if (tv_equal(TUPLE_ITEM(tuple, i), needle, ic))
	    ++n;
    }

    return n;
}

/*
 * "items(tuple)" function
 * Caller must have already checked that argvars[0] is a tuple.
 */
    void
tuple2items(typval_T *argvars, typval_T *rettv)
{
    tuple_T	*tuple = argvars[0].vval.v_tuple;
    varnumber_T	idx;

    if (rettv_list_alloc(rettv) == FAIL)
	return;

    if (tuple == NULL)
	return;  // null tuple behaves like an empty list

    for (idx = 0; idx < TUPLE_LEN(tuple); idx++)
    {
	list_T	*l = list_alloc();

	if (l == NULL)
	    break;

	if (list_append_list(rettv->vval.v_list, l) == FAIL)
	{
	    mnv_free(l);
	    break;
	}
	if (list_append_number(l, idx) == FAIL
		|| list_append_tv(l, TUPLE_ITEM(tuple, idx)) == FAIL)
	    break;
    }
}

/*
 * Search for item "tv" in tuple "tuple" starting from index "start_idx".
 * If "ic" is set to TRUE, then case is ignored.
 *
 * Returns the index where "tv" is present or -1 if it is not found.
 */
    int
index_tuple(tuple_T *tuple, typval_T *tv, int start_idx, int ic)
{
    if (start_idx < 0)
    {
	start_idx = TUPLE_LEN(tuple) + start_idx;
	if (start_idx < 0)
	    start_idx = 0;
    }

    for (int idx = start_idx; idx < TUPLE_LEN(tuple); idx++)
    {
	if (tv_equal(TUPLE_ITEM(tuple, idx), tv, ic))
	    return idx;
    }

    return -1;		// "tv" not found
}

/*
 * Evaluate 'expr' for each item in the Tuple 'tuple' starting with the item at
 * 'startidx' and return the index of the item where 'expr' is TRUE.  Returns
 * -1 if 'expr' doesn't evaluate to TRUE for any of the items.
 */
    int
indexof_tuple(tuple_T *tuple, long startidx, typval_T *expr)
{
    long	idx = 0;
    int		len;
    int		found;

    if (tuple == NULL)
	return -1;

    len = TUPLE_LEN(tuple);

    if (startidx < 0)
    {
	// negative index: index from the end
	startidx = len + startidx;
	if (startidx < 0)
	    startidx = 0;
    }

    set_mnv_var_type(VV_KEY, VAR_NUMBER);

    int		called_emsg_start = called_emsg;

    for (idx = startidx; idx < len; idx++)
    {
	set_mnv_var_nr(VV_KEY, idx);
	copy_tv(TUPLE_ITEM(tuple, idx), get_mnv_var_tv(VV_VAL));

	found = indexof_eval_expr(expr);
	clear_tv(get_mnv_var_tv(VV_VAL));

	if (found)
	    return idx;

	if (called_emsg != called_emsg_start)
	    return -1;
    }

    return -1;
}

/*
 * Return the max or min of the items in tuple "tuple".
 * If an error occurs NULL is returned instead.
 */
    typval_T *
tuple_max_min(tuple_T *tuple, int domax)
{
    typval_T	*tv = NULL;
    int		res;

    if (tuple == NULL || TUPLE_LEN(tuple) == 0)
	return NULL;

    for (int idx = 0; idx < TUPLE_LEN(tuple); idx++)
    {
	if (tv == NULL)
	    tv = TUPLE_ITEM(tuple, idx);
	else
	{
	    if (typval_compare2(TUPLE_ITEM(tuple, idx), tv,
		    domax ? EXPR_GREATER : EXPR_SMALLER, FALSE, &res) == FAIL)
		return NULL;
	    if (res == OK)
		tv = TUPLE_ITEM(tuple, idx);
	}
    }

    return tv;
}

/*
 * Repeat the tuple "tuple" "n" times and set "rettv" to the new tuple.
 */
    void
tuple_repeat(tuple_T *tuple, int n, typval_T *rettv)
{
    rettv->v_type = VAR_TUPLE;
    rettv->vval.v_tuple = NULL;

    if (tuple == NULL || TUPLE_LEN(tuple) == 0 || n <= 0)
	return;

    if (rettv_tuple_set_with_items(rettv, TUPLE_LEN(tuple) * n) == FAIL)
	return;

    tuple_T	*new_tuple = rettv->vval.v_tuple;
    for (int count = 0; count < n; count++)
    {
	for (int idx = 0; idx < TUPLE_LEN(tuple); idx++)
	{
	    copy_tv(TUPLE_ITEM(tuple, idx),
		    TUPLE_ITEM(new_tuple, TUPLE_LEN(new_tuple)));
	    new_tuple->tv_items.ga_len++;
	}
    }
}

/*
 * Reverse "tuple" and return the new tuple in "rettv"
 */
    void
tuple_reverse(tuple_T *tuple, typval_T *rettv)
{
    rettv->v_type = VAR_TUPLE;
    rettv->vval.v_tuple = NULL;

    int	len = tuple_len(tuple);

    if (len == 0)
	return;

    if (rettv_tuple_set_with_items(rettv, len) == FAIL)
	return;

    tuple_T	*new_tuple = rettv->vval.v_tuple;
    for (int i = 0; i < len; i++)
	copy_tv(TUPLE_ITEM(tuple, i), TUPLE_ITEM(new_tuple, len - i - 1));
    new_tuple->tv_items.ga_len = tuple->tv_items.ga_len;
}

/*
 * Tuple reduce() function
 */
    void
tuple_reduce(typval_T *argvars, typval_T *expr, typval_T *rettv)
{
    tuple_T	*tuple = argvars[0].vval.v_tuple;
    int		called_emsg_start = called_emsg;
    typval_T	initial;
    int		idx = 0;
    funccall_T	*fc;
    typval_T	argv[3];
    int		r;

    if (argvars[2].v_type == VAR_UNKNOWN)
    {
	if (tuple == NULL || TUPLE_LEN(tuple) == 0)
	{
	    semsg(_(e_reduce_of_an_empty_str_with_no_initial_value), "Tuple");
	    return;
	}
	initial = *TUPLE_ITEM(tuple, 0);
	idx = 1;
    }
    else
    {
	initial = argvars[2];
	idx = 0;
    }

    copy_tv(&initial, rettv);

    if (tuple == NULL)
	return;

    // Create one funccall_T for all eval_expr_typval() calls.
    fc = eval_expr_get_funccal(expr, rettv);

    for ( ; idx < TUPLE_LEN(tuple); idx++)
    {
	argv[0] = *rettv;
	rettv->v_type = VAR_UNKNOWN;
	argv[1] = *TUPLE_ITEM(tuple, idx);

	r = eval_expr_typval(expr, TRUE, argv, 2, fc, rettv);

	clear_tv(&argv[0]);

	if (r == FAIL || called_emsg != called_emsg_start)
	    break;
    }

    if (fc != NULL)
	remove_funccal();
}

/*
 * Returns TRUE if two tuples with types "type1" and "type2" are addable.
 * Otherwise returns FALSE.
 */
    int
check_tuples_addable(type_T *type1, type_T *type2)
{
    int	addable = TRUE;

    // If the first operand is a variadic tuple and the second argument is
    // non-variadic, then concatenation is not possible.
    if ((type1->tt_flags & TTFLAG_VARARGS)
	    && !(type2->tt_flags & TTFLAG_VARARGS)
	    && (type2->tt_argcount > 0))
	addable = FALSE;

    if ((type1->tt_flags & TTFLAG_VARARGS)
	    && (type2->tt_flags & TTFLAG_VARARGS))
    {
	// two variadic tuples
	if (type1->tt_argcount > 1 || type2->tt_argcount > 1)
	    // one of the variadic tuple has fixed number of items
	    addable = FALSE;
	else if ((type1->tt_argcount == 1 && type2->tt_argcount == 1)
		&& !equal_type(type1->tt_args[0], type2->tt_args[0], 0))
	    // the tuples have different item types
	    addable = FALSE;
    }

    if (!addable)
    {
	emsg(_(e_cannot_use_variadic_tuple_in_concatenation));
	return FAIL;
    }

    return OK;
}

#endif // defined(FEAT_EVAL)
