/* arithop.c: Arithmetic and relational operators. */

#define _POSIX_SOURCE

#include <string.h>
#include "x.tab.h"
#include "operator.h"
#include "execute.h"
#include "data.h"
#include "ident.h"
#include "cmstring.h"
#include "util.h"

/* All functions in this file are interpreter opcodes, so they require that the
 * interpreter data (the globals in execute.c) be in a state consistent with
 * interpretation.  They may modify the interpreter data by pushing and popping
 * the data stack or by throwing exceptions. */

/* Effects: Pops the top value on the stack and pushes its logical inverse. */
void op_not(void)
{
    Data *d = &stack[stack_pos - 1];
    int val = !data_true(d);

    /* Replace d with the inverse of its truth value. */
    data_discard(d);
    d->type = INTEGER;
    d->u.val = val;
}

/* Effects: If the top value on the stack is an integer, pops it and pushes its
 *	    its arithmetic inverse. */
void op_negate(void)
{
    Data *d = &stack[stack_pos - 1];

    /* Make sure we're taking the negative of an integer. */
    if (d->type != INTEGER) {
	throw(type_id, "Argument (%D) is not an integer.", d);
    } else {
	/* Replace d with -d. */
	d->u.val *= -1;
    }
}

/* Effects: If the top two values on the stack are integers, pops them and
 *	    pushes their product. */
void op_multiply(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	throw(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	throw(type_id, "Right side (%D) is not an integer.", d2);
    } else {
	/* Replace d1 with d1 * d2, and pop d2. */
	d1->u.val *= d2->u.val;
	pop(1);
    }
}

/* Effects: If the top two values on the stack are integers and the second is
 *	    not zero, pops them, divides the first by the second, and pushes
 *	    the quotient. */
void op_divide(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	throw(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	throw(type_id, "Right side (%D) is not an integer.", d2);
    } else if (d2->u.val == 0) {
	throw(div_id, "Attempt to divide %D by zero.", d1);
    } else {
	/* Replace d1 with d1 / d2, and pop d2. */
	d1->u.val /= d2->u.val;
	pop(1);
    }
}

/* Effects: If the top two values on the stack are integers and the second is
 *	    not zero, pops them, divides the first by the second, and pushes
 *	    the remainder. */
void op_modulo(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	throw(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	throw(type_id, "Right side (%D) is not an integer.", d2);
    } else if (d2->type == 0) {
	throw(div_id, "Attempt to divide %D by zero.", d1);
    } else {
	/* Replace d1 with d1 % d2, and pop d2. */
	d1->u.val %= d2->u.val;
	pop(1);
    }
}

/* Effects: If the top two values on the stack are integers, pops them and
 *	    pushes their sum.  If the top two values are strings, pops them,
 *	    concatenates the second onto the first, and pushes the result. */
void op_add(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    Substring *s1;
    Sublist *l1;

    /* If we're adding two integers or two strings, replace d1 with d1+d2 and
     * discard d2. */
    if (d1->type == INTEGER && d2->type == INTEGER) {
	/* Replace d1 with d1 + d2, and pop d2. */
	d1->u.val += d2->u.val;
    } else if (d1->type == STRING && d2->type == STRING) {
	anticipate_assignment();
	s1 = &d1->u.substr;
	substring_truncate(s1);
	s1->str = string_add(s1->str, data_sptr(d2), d2->u.substr.span);
	s1->span += d2->u.substr.span;
    } else if (d1->type == LIST && d2->type == LIST) {
	anticipate_assignment();
	l1 = &d1->u.sublist;
	sublist_truncate(l1);
	l1->list = list_append(l1->list, data_dptr(d2), d2->u.sublist.span);
	l1->span += d2->u.sublist.span;
    } else {
	throw(type_id, "Cannot add %D and %D.", d1, d2);
	return;
    }
    pop(1);
}

/* Effects: Adds two lists.  (This is used for [@foo, ...];) */
void op_splice_add(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    Sublist *l1;

    /* No need to check if d2 is a list, due to code generation. */
    if (d1->type != LIST) {
	throw(type_id, "%D is not a list.", d1);
	return;
    }

    anticipate_assignment();
    l1 = &d1->u.sublist;
    sublist_truncate(l1);
    l1->list = list_append(l1->list, data_dptr(d2), d2->u.sublist.span);
    l1->span += d2->u.sublist.span;
    pop(1);
}

/* Effects: If the top two values on the stack are integers, pops them and
 *	    pushes their difference. */
void op_subtract(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];

    /* Make sure we're multiplying two integers. */
    if (d1->type != INTEGER) {
	throw(type_id, "Left side (%D) is not an integer.", d1);
    } else if (d2->type != INTEGER) {
	throw(type_id, "Right side (%D) is not an integer.", d2);
    } else {
	/* Replace d1 with d1 - d2, and pop d2. */
	d1->u.val -= d2->u.val;
	pop(1);
    }
}

/* Effects: Pops the top two values on the stack and pushes 1 if they are
 *	    equal, 0 if not. */
void op_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val = (data_cmp(d1, d2) == 0);

    pop(2);
    push_int(val);
}

/* Effects: Pops the top two values on the stack and returns 1 if they are
 *	    unequal, 0 if they are equal. */   
void op_not_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val = (data_cmp(d1, d2) != 0);

    pop(2);
    push_int(val);
}

/* Definition: Two values are comparable if they are of the same type and that
 * 	       type is integer or string. */

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is greater than the second, 0 if not. */
void op_greater(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	throw(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	throw(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) > 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is greater than or equal to the second, 0 if
 *	    not. */
void op_greater_or_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	throw(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	throw(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) >= 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is less than the second, 0 if not. */
void op_less(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	throw(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	throw(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) < 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top two values on the stack are comparable, pops them and
 *	    pushes 1 if the first is greater than or equal to the second, 0 if
 *	    not. */
void op_less_or_equal(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int val, t = d1->type;

    if (d1->type != d2->type) {
	throw(type_id, "%D and %D are not of the same type.", d1, d2);
    } else if (t != INTEGER && t != STRING) {
	throw(type_id, "%D and %D are not integers or strings.", d1, d2);
    } else {
	/* Discard d1 and d2 and push the appropriate truth value. */
	val = (data_cmp(d1, d2) <= 0);
	pop(2);
	push_int(val);
    }
}

/* Effects: If the top value on the stack is a string or a list, pops the top
 *	    two values on the stack and pushes the location of the first value
 *	    in the second (where the first element is 1), or 0 if the first
 *	    value does not exist in the second.  If the second value is a
 *	    string, then the first must be a string of length one. */
void op_in(void)
{
    Data *d1 = &stack[stack_pos - 2];
    Data *d2 = &stack[stack_pos - 1];
    int i, c;
    char *s;

    if (d2->type == LIST) {
	for (i = 0; i < d2->u.sublist.span; i++) {
	    if (data_cmp(data_dptr(d2) + i, d1) == 0)
		break;
	}
	i = (i == d2->u.sublist.span) ? 0 : i + 1;
	pop(2);
	push_int(i);
	return;
    }

    if (d1->type != STRING || d2->type != STRING) {
	throw(type_id, "Cannot search for %D in %D.", d1, d2);
	return;
    }

    /* Return 1 if d1 is an empty string. */
    if (!d1->u.substr.span) {
	pop(2);
	push_int(1);
	return;
    }

    c = LCASE(*d1->u.substr.str->s);
    s = d1->u.substr.str->s + 1;
    i = 0;
    while (1) {
	for (; i <= d2->u.substr.span - d1->u.substr.span; i++) {
	    if (c == LCASE(*(data_sptr(d2) + i)))
		break;
	}
	if (i > d2->u.substr.span - d1->u.substr.span)
	    break;
	if (strnccmp(data_sptr(d2) + i + 1, s, d1->u.substr.span - 1) == 0) {
	    pop(2);
	    push_int(i + 1);
	    return;
	}
	i++;
    }

    pop(2);
    push_int(0);
}