/* listop.c: Function operators for list manipulation. */

#define _POSIX_SOURCE

#include "x.tab.h"
#include "operator.h"
#include "execute.h"
#include "data.h"
#include "ident.h"
#include "memory.h"

void op_listlen(void)
{
    Data *args;
    int len;

    /* Accept a list to take the length of. */
    if (!func_init_1(&args, LIST))
	return;

    /* Replace the argument with its length. */
    len = args[0].u.sublist.span;
    pop(1);
    push_int(len);
}

void op_sublist(void)
{
    int num_args, start, span;
    Data *args;

    /* Accept a list, an integer, and an optional integer. */
    if (!func_init_2_or_3(&args, &num_args, LIST, INTEGER, INTEGER))
	return;

    /* Make sure range is in bounds. */
    start = args[1].u.val - 1;
    span = (num_args == 3) ? args[2].u.val : args[0].u.sublist.span - start;
    if (start < 0) {
	throw(range_id, "Start (%d) less than one", start + 1);
    } else if (span < 0) {
	throw(range_id, "Sublist length (%d) less than zero", span);
    } else if (start + span > args[0].u.sublist.span) {
	throw(range_id, "Sublist extends to %d, past end of list (length %d)",
	      start + span, args[0].u.sublist.span);
    } else {
	/* Replace first argument with sublist, and pop other arguments. */
	args[0].u.sublist.start += start;
	args[0].u.sublist.span = span;
	pop(num_args - 1);
    }
}

void op_insert(void)
{
    int pos;
    Data *args;
    Sublist *sublist;

    /* Accept a list, an integer offset, and a data value of any type. */
    if (!func_init_3(&args, LIST, INTEGER, 0))
	return;

    pos = args[1].u.val - 1;
    sublist = &args[0].u.sublist;

    if (pos < 0) {
	throw(range_id, "Position (%d) less than one", pos + 1);
    } else if (pos > sublist->span) {
	throw(range_id, "Position (%d) beyond end of list (length %d)",
	      pos + 1, sublist->span);
    } else {
	/* Modify the list and discard the offset and data. */
	anticipate_assignment();
	sublist->list = list_insert(sublist->list, sublist->start + pos,
				    &args[2]);
	sublist->span++;
	pop(2);
    }
}

void op_replace(void)
{
    int pos;
    Data *args;
    Sublist *sublist;
    List *list;

    /* Accept a list, an integer offset, and a data value of any type. */
    if (!func_init_3(&args, LIST, INTEGER, 0))
	return;

    pos = args[1].u.val - 1;
    sublist = &args[0].u.sublist;

    if (pos < 0) {
	throw(range_id, "Position (%d) less than one", pos + 1);
    } else if (pos > sublist->span - 1) {
	throw(range_id, "Position (%d) greater than length of list (%d)",
	      pos + 1, sublist->span);
    } else {
	anticipate_assignment();

	/* If we don't own the list, make a copy of the sublist. */
	if (sublist->list->refs != 1) {
	    list = sublist->list;
	    sublist->list = list_from_data(list->el + sublist->start,
					   sublist->span);
	    list_discard(list);
	    sublist->start = 0;
	}

	/* Replace the list element. */
	data_discard(&sublist->list->el[sublist->start + pos]);
	data_dup(&sublist->list->el[sublist->start + pos], &args[2]);

	/* Discard the second and third argument. */
	pop(2);
    }
}

void op_delete(void)
{
    int pos;
    Data *args, *dptr;
    Sublist *sublist;

    /* Accept a list and an integer offset. */
    if (!func_init_2(&args, LIST, INTEGER))
	return;

    pos = args[1].u.val - 1;
    sublist = &args[0].u.sublist;

    if (pos < 0) {
	throw(range_id, "Position (%d) less than one", pos + 1);
    } else if (pos > sublist->span - 1) {
	throw(range_id, "Position (%d) greater than length of list (%d)",
	      pos + 1, sublist->span);
    } else {
	anticipate_assignment();

	/* Remove the list element. */
	sublist_truncate(sublist);
	dptr = &sublist->list->el[sublist->start + pos];
	data_discard(dptr);
	MEMMOVE(dptr, dptr + 1, Data,
		sublist->span - sublist->start - pos - 1);
	sublist->span--;
	sublist->list->len--;

	/* Pop the second argument. */
	pop(1);
    }
}

void op_setadd(void)
{
    int i;
    Data *args;
    Sublist *sublist;

    /* Accept a list and a data value of any type. */
    if (!func_init_2(&args, LIST, 0))
	return;

    /* If args[1] is in args[0], then just pop args[1] and return. */
    for (i = 0; i < args[0].u.sublist.span; i++) {
	if (data_cmp(data_dptr(&args[0]) + i, &args[1]) == 0) {
	    pop(1);
	    return;
	}
    }

    /* Add args[1] to args[0] and pop args[1]. */
    anticipate_assignment();
    sublist = &args[0].u.sublist;
    sublist_truncate(sublist);
    sublist->list = list_add(sublist->list, &args[1]);
    sublist->span++;
    pop(1);
}

void op_setremove(void)
{
    int i;
    Data *args, *d;
    Sublist *sublist;

    /* Accept a list and a data value of any type. */
    if (!func_init_2(&args, LIST, 0))
	return;

    /* Look for args[1] in args[0]. */
    for (i = 0; i < args[0].u.sublist.span; i++) {
	if (data_cmp(data_dptr(&args[0]) + i, &args[1]) == 0)
	    break;
    }

    /* Nothing to do if args[1] wasn't there. */
    if (i == args[0].u.sublist.span) {
	pop(1);
	return;
    }

    /* Remove the list element. */
    anticipate_assignment();
    sublist = &args[0].u.sublist;
    sublist_truncate(sublist);
    d = &sublist->list->el[sublist->start + i];
    data_discard(d);
    MEMCPY(d, d + 1, Data, sublist->span - sublist->start - i - 1);
    sublist->list->len--;
    sublist->span--;

    /* Pop the second argument. */
    pop(1);
}

void op_union(void)
{
    int i, j;
    Data *args, *base1, *base2;
    Sublist *sublist;

    /* Accept two lists. */
    if (!func_init_2(&args, LIST, LIST))
	return;

    anticipate_assignment();

    /* Prepare to add elements to left list. */
    sublist = &args[0].u.sublist;
    sublist_truncate(sublist);
    base1 = sublist->list->el + sublist->start;
    base2 = data_dptr(&args[1]);

    /* Iterate over elements in second list. */
    for (i = 0; i < args[1].u.sublist.span; i++) {
	/* Look for data in first sublist. */
	for (j = 0; j < sublist->span; j++) {
	    if (data_cmp(&base1[j], &base2[i]) == 0)
		break;
	}
	if (j == sublist->span) {
	    /* Data wasn't in first sublist; add it. */
	    sublist->list = list_add(sublist->list, &base2[i]);
	    sublist->span++;
	}
    }

    /* Pop the second list. */
    pop(1);
}