/*
// Full copyright information is available in the file ../doc/CREDITS
*/

#define NATIVE_MODULE "$dictionary"

#include "cdc.h"

NATIVE_METHOD(dict_values)
{
    cList *list;

    INIT_1_ARG(DICT);

    list = dict_values(DICT1);

    CLEAN_RETURN_LIST(list);
}

NATIVE_METHOD(dict_keys)
{
    cList *list;

    INIT_1_ARG(DICT);

    list = dict_keys(DICT1);

    CLEAN_RETURN_LIST(list);
}

/* ugh; what we do to keep from copying */
NATIVE_METHOD(dict_add)
{
    cDict *dict;
    cData arg1, arg2;
    DEF_args;

    INIT_ARGC(ARG_COUNT, 3, "three");
    INIT_ARG1(DICT);

    dict = dict_dup(DICT1);
    data_dup(&arg1, &args[1]);
    data_dup(&arg2, &args[2]);

    CLEAN_STACK();
    anticipate_assignment();

    dict = dict_add(dict, &arg1, &arg2);
    data_discard(&arg1);
    data_discard(&arg2);

    RETURN_DICT(dict);
}

NATIVE_METHOD(dict_del)
{
    cData arg1;
    cDict *dict;
    DEF_args;

    INIT_ARGC(ARG_COUNT, 2, "two");
    INIT_ARG1(DICT);

    if (!dict_contains(DICT1, &args[1]))
        THROW((keynf_id, "Key (%D) is not in the dictionary.", &args[1]));

    dict = dict_dup(DICT1);
    data_dup(&arg1, &args[1]);

    CLEAN_STACK();
    anticipate_assignment();

    dict = dict_del(dict, &arg1);
    data_discard(&arg1);

    RETURN_DICT(dict);
}

NATIVE_METHOD(dict_contains)
{
    Int val;
    DEF_args;

    INIT_ARGC(ARG_COUNT, 2, "two");
    INIT_ARG1(DICT);

    val = dict_contains(DICT1, &args[1]);

    CLEAN_RETURN_INTEGER(val);
}

NATIVE_METHOD(dict_add_elem)
{
    cDict *dict;
    cData listd, d, key, value;
    DEF_args;

    INIT_ARGC(ARG_COUNT, 3, "three");
    INIT_ARG1(DICT);

    if (dict_find(DICT1, &args[1], &listd) == keynf_id) {
        listd.type = LIST;
        listd.u.list = list_new(0);
    } else if (listd.type != LIST) {
        cthrow(type_id, "Value for %D (%D) is not a list.", &args[0], &listd);
        data_discard(&listd);
        RETURN_FALSE;
    }

    /*
       // set the list to zero to remove the reference to the list, then
       // dup the dictionary so we can clear the stack and anticipate the
       // assignmenent, all to keep references at their most minimal so we
       // do not copy if we do not need to.
       //
       // also dup the key and the value being inserted.
     */

    d.type = INTEGER;
    d.u.val = 0;
    dict = dict_add(DICT1, &args[1], &d);
    dict = dict_dup(dict);
    data_dup(&key, &args[1]);
    data_dup(&value, &args[2]);

    /* clean the stack and anticipate the assignment */
    CLEAN_STACK();
    anticipate_assignment();

    /* Add the element to the list. */
    listd.u.list = list_add(listd.u.list, &value);
    dict = dict_add(dict, &key, &listd);

    data_discard(&key);
    data_discard(&value);
    data_discard(&listd);

    RETURN_DICT(dict);
}

NATIVE_METHOD(dict_del_elem)
{
    cDict *dict;
    cData dlist, d, elem, key;
    cList *list;
    Int pos;
    DEF_args;

    INIT_ARGC(ARG_COUNT, 3, "three");
    INIT_ARG1(DICT);

    if (dict_find(DICT1, &args[1], &dlist) == keynf_id) {
        THROW((keynf_id, "Key (%D) is not in the dictionary.", &args[1]));
    } else if (dlist.type != LIST) {
        cthrow(type_id, "Value for %D (%D) is not a list.", &args[0], &dlist);
        data_discard(&dlist);
        RETURN_FALSE;
    }

    dict = dict_dup(DICT1);
    data_dup(&key, &args[1]);
    data_dup(&elem, &args[2]);

    list = dlist.u.list;

    /* clean the stack and anticipate the assignment */
    CLEAN_STACK();
    anticipate_assignment();

    /* find the element in the list */
    pos = list_search(list, &elem);

    /* we are finished with 'elem' */
    data_discard(&elem);

    /* the element is not in the list, simply return the dictionary */
    if (pos == -1) {
        data_discard(&dlist);
        data_discard(&key);
        RETURN_DICT(dict);
    }

    /* if the list length is one, then simply delete the dict element */
    if (list_length(list) == 1) {
        dict = dict_del(dict, &key);
        data_discard(&dlist);
        data_discard(&key);
        RETURN_DICT(dict);
    }

    /*
       // Temorarily set the dictionary's value for the key to zero, to
       // remove the reference to the list.
     */
    d.type = INTEGER;
    d.u.val = 0;
    dict = dict_add(dict, &key, &d);

    /* Remove elem from the list */
    dlist.u.list = list_delete(list, pos);

    /* Set the new list as the value for the key in the dictionary */
    dict = dict_add(dict, &key, &dlist);

    /* clean up and return */
    data_discard(&dlist);
    data_discard(&key);

    RETURN_DICT(dict);
}

NATIVE_METHOD(dict_union)
{
    cDict *dict1, *dict2;

    INIT_2_ARGS(DICT, DICT) dict1 = dict_dup(DICT1);
    dict2 = dict_dup(DICT2);

    CLEAN_STACK();
    anticipate_assignment();

    RETURN_DICT(dict_union(dict1, dict2));
}
