/* dictop.c: Function operators for dictionary 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_dict_keys(void)
{
    Data *args;
    List *keys;

    if (!func_init_1(&args, DICT))
	return;

    keys = dict_keys(args[0].u.dict);
    pop(1);
    push_list(keys);
    list_discard(keys);
}

void op_dict_add(void)
{
    Data *args;

    if (!func_init_3(&args, DICT, 0, 0))
	return;

    anticipate_assignment();
    args[0].u.dict = dict_add(args[0].u.dict, &args[1], &args[2]);
    pop(2);
}

void op_dict_del(void)
{
    Data *args;

    if (!func_init_2(&args, DICT, 0))
	return;

    if (!dict_contains(args[0].u.dict, &args[1])) {
	throw(keynf_id, "Key (%D) is not in the dictionary.", &args[1]);
    } else {
	anticipate_assignment();
	args[0].u.dict = dict_del(args[0].u.dict, &args[1]);
	pop(1);
    }
}

void op_dict_add_elem(void)
{
    Data *args, oldval, d;
    Sublist *sublist;

    if (!func_init_3(&args, DICT, 0, 0))
	return;

    if (dict_find(args[0].u.dict, &args[1], &oldval) == keynf_id) {
	oldval.type = LIST;
	sublist_set_to_full_list(&oldval.u.sublist, list_new(0));
    } else if (oldval.type != LIST) {
	throw(type_id, "Value for %D (%D) is not a list.", &args[0], &oldval);
	data_discard(&oldval);
	return;
    }

    /* Anticipate assignment, and also temporarily replace the value for the
     * key args[1] with 0 to get rid of the dictionary's reference count on
     * the list. */
    anticipate_assignment();
    d.type = INTEGER;
    d.u.val = 0;
    args[0].u.dict = dict_add(args[0].u.dict, &args[1], &d);

    /* Add the element to the list. */
    sublist = &oldval.u.sublist;
    sublist_truncate(sublist);
    sublist->list = list_add(sublist->list, &args[2]);
    sublist->span++;

    /* Replace the dictionary's value for args[1] with the updated list. */
    args[0].u.dict = dict_add(args[0].u.dict, &args[1], &oldval);
    data_discard(&oldval);

    pop(2);
}

void op_dict_del_elem(void)
{
    Data *args, *elem, oldval, d;
    Sublist *sublist;
    int pos;

    if (!func_init_3(&args, DICT, 0, 0))
	return;

    if (dict_find(args[0].u.dict, &args[1], &oldval) == keynf_id) {
	throw(keynf_id, "Key (%D) is not in the dictionary.", &args[1]);
	return;
    }
    if (oldval.type != LIST) {
	throw(type_id, "Value for %D (%D) is not a list.", &args[1], &oldval);
	data_discard(&oldval);
	return;
    }

    sublist = &oldval.u.sublist;
    pos = sublist_search(sublist, &args[2]);
    if (pos == -1) {
	/* The key's not there; make no modifications. */
	data_discard(&oldval);
	pop(2);
	return;
    }

    anticipate_assignment();

    if (sublist->span == 1) {
	/* args[2] is the only element in the list; delete the key from the
	 * dictionary. */
	args[0].u.dict = dict_del(args[0].u.dict, &args[1]);
	data_discard(&oldval);
	pop(2);
	return;
    }

    /* Temporarily replace the dictionary's value for the key args[1] with 0
     * to get rid of the dictionary's reference count the list. */
    d.type = INTEGER;
    d.u.val = 0;
    args[0].u.dict = dict_add(args[0].u.dict, &args[1], &d);

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

    /* Reassign the key in the dictionary. */
    args[0].u.dict = dict_add(args[0].u.dict, &args[1], &oldval);
    data_discard(&oldval);

    pop(2);
}

void op_dict_contains(void)
{
    Data *args;
    int val;

    if (!func_init_2(&args, DICT, 0))
	return;

    val = dict_contains(args[0].u.dict, &args[1]);
    pop(2);
    push_int(val);
}