/* stringop.c: Function operators acting on strings. */
#define _POSIX_SOURCE

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

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

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

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

void op_substr(void)
{
    int num_args, start, len, string_len;
    Data *args;

    /* Accept a string for the initial string, an integer specifying the start
     * of the substring, and an optional integer specifying the length of the
     * substring. */
    if (!func_init_2_or_3(&args, &num_args, STRING, INTEGER, INTEGER))
	return;

    string_len = string_length(args[0].u.str);
    start = args[1].u.val - 1;
    len = (num_args == 3) ? args[2].u.val : string_len - start;

    /* Make sure range is in bounds. */
    if (start < 0) {
	cthrow(range_id, "Start (%d) is less than one.", start + 1);
    } else if (len < 0) {
	cthrow(range_id, "Length (%d) is less than zero.", len);
    } else if (start + len > string_len) {
	cthrow(range_id,
	      "The substring extends to %d, past the end of the string (%d).",
	      start + len, string_len);
    } else {
	/* Replace first argument with substring, and pop other arguments. */
	anticipate_assignment();
	args[0].u.str = string_substring(args[0].u.str, start, len);
	pop(num_args - 1);
    }
}

void op_explode(void)
{
    int num_args, sep_len, len, want_blanks;
    Data *args, d;
    List *exploded;
    char *sep, *s, *p, *q;
    String *word;

    /* Accept a string to explode and an optional string for the word
     * separator. */
    if (!func_init_1_to_3(&args, &num_args, STRING, STRING, 0))
	return;

    want_blanks = (num_args == 3) ? data_true(&args[2]) : 0;
    if (num_args >= 2) {
	sep = string_chars(args[1].u.str);
	sep_len = string_length(args[1].u.str);
    } else {
	sep = " ";
	sep_len = 1;
    }

    if (!*sep) {
      cthrow(range_id, "Null string as separator.");
      return;
    }

    s = string_chars(args[0].u.str);
    len = string_length(args[0].u.str);

    exploded = list_new(0);
    p = s;
    for (q = strcstr(p, sep); q; q = strcstr(p, sep)) {
	if (want_blanks || q > p) {
	    /* Add the word. */
	    word = string_from_chars(p, q - p);
	    d.type = STRING;
	    d.u.str = word;
	    exploded = list_add(exploded, &d);
	    string_discard(word);
	}
	p = q + sep_len;
    }

    if (*p || want_blanks) {
	/* Add the last word. */
	word = string_from_chars(p, len - (p - s));
	d.type = STRING;
	d.u.str = word;
	exploded = list_add(exploded, &d);
	string_discard(word);
    }

    /* Pop the arguments and push the list onto the stack. */
    pop(num_args);
    push_list(exploded);
    list_discard(exploded);
}

void op_strsub(void)
{
    int len, search_len, replace_len;
    Data *args;
    char *search, *replace, *s, *p, *q;
    String *subbed;

    /* Accept a base string, a search string, and a replacement string. */
    if (!func_init_3(&args, STRING, STRING, STRING))
	return;

    s = string_chars(args[0].u.str);
    len = string_length(args[0].u.str);
    search = string_chars(args[1].u.str);
    search_len = string_length(args[1].u.str);
    replace = string_chars(args[2].u.str);
    replace_len = string_length(args[2].u.str);

    subbed = string_new(search_len);
    p = s;
    for (q = strcstr(p, search); q; q = strcstr(p, search)) {
	subbed = string_add_chars(subbed, p, q - p);
	subbed = string_add_chars(subbed, replace, replace_len);
	p = q + search_len;
    }

    subbed = string_add_chars(subbed, p, len - (p - s));

    /* Pop the arguments and push the new string onto the stack. */
    pop(3);
    push_string(subbed);
    string_discard(subbed);
}

/* Pad a string on the left (positive length) or on the right (negative
 * length).  The optional third argument gives the fill character. */
void op_pad(void)
{
    int num_args, len, padding, filler_len;
    Data *args;
    char *filler;
    String *padded;

    if (!func_init_2_or_3(&args, &num_args, STRING, INTEGER, STRING))
	return;

    if (num_args == 3) {
	filler = string_chars(args[2].u.str);
	filler_len = string_length(args[2].u.str);
    } else {
	filler = " ";
	filler_len = 1;
    }

    len = (args[1].u.val > 0) ? args[1].u.val : -args[1].u.val;
    padding = len - string_length(args[0].u.str);

    /* Construct the padded string. */
    anticipate_assignment();
    padded = args[0].u.str;
    if (padding == 0) {
	/* Do nothing.  Easiest case. */
    } else if (padding < 0) {
	/* We're shortening the string.  Almost as easy. */
	padded = string_truncate(padded, len);
    } else if (args[1].u.val > 0) {
	/* We're lengthening the string on the right. */
	padded = string_add_padding(padded, filler, filler_len, padding);
    } else {
	/* We're lengthening the string on the left. */
	padded = string_new(padding + args[0].u.str->len);
	padded = string_add_padding(padded, filler, filler_len, padding);
	padded = string_add(padded, args[0].u.str);
	string_discard(args[0].u.str);
    }
    args[0].u.str = padded;

    /* Discard all but the first argument. */
    pop(num_args - 1);
}

void op_match_begin(void)
{
    Data *args;
    int sep_len, search_len;
    char *sep, *search, *s, *p;
    int num_args;

    /* Accept a base string, a search string, and an optional separator. */
    if (!func_init_2_or_3(&args, &num_args, STRING, STRING, STRING))
	return;

    s = string_chars(args[0].u.str);

    search = string_chars(args[1].u.str);
    search_len = string_length(args[1].u.str);

    if (num_args > 2) {
      sep = string_chars(args[2].u.str);
      sep_len = string_length(args[2].u.str);
    } else {
      sep = " ";
      sep_len = 1;
    }

    for (p = s - sep_len; p; p = strcstr(p + 1, sep)) {
	/* We found a separator; see if it's followed by search. */
	if (strnccmp(p + sep_len, search, search_len) == 0) {
	    pop(num_args);
	    push_int(1);
	    return;
	}
    }

    pop(num_args);
    push_int(0);
}

/* Match against a command template. */
void op_match_template(void)
{
    Data *args;
    List *fields;
    char *ctemplate, *str;

    /* Accept a string for the template and a string to match against. */
    if (!func_init_2(&args, STRING, STRING))
	return;

    ctemplate = string_chars(args[0].u.str);
    str = string_chars(args[1].u.str);

    fields = match_template(ctemplate, str);

    pop(2);
    if (fields) {
	push_list(fields);
	list_discard(fields);
    } else {
	push_int(0);
    }
}

/* Match against a command template. */
void op_match_pattern(void)
{
    Data *args;
    List *fields;
    char *pattern, *str;

    /* Accept a string for the pattern and a string to match against. */
    if (!func_init_2(&args, STRING, STRING))
	return;

    pattern = string_chars(args[0].u.str);
    str = string_chars(args[1].u.str);

    fields = match_pattern(pattern, str);

    pop(2);
    if (!fields) {
	push_int(0);
	return;
    }

    /* fields is backwards.  Reverse it. */
    fields = list_reverse(fields);

    push_list(fields);
    list_discard(fields);
}

void op_match_regexp(void)
{
    Data *args, d;
    regexp *reg;
    List *fields = (List *)0;
    List *elemlist;
    int num_args, case_flag, i;
    char *s;

    if (!func_init_2_or_3(&args, &num_args, STRING, STRING, 0))
	return;

    case_flag = (num_args == 3) ? data_true(&args[2]) : 0;

    reg = string_regexp(args[0].u.str);
    if (!reg) {
	cthrow(regexp_id, "%s", regerror(NULL));
	return;
    }

    /* Execute the regexp. */
    s = string_chars(args[1].u.str);
    if (regexec(reg, s, case_flag)) {
	/* Build the list of fields. */
	fields = list_new(NSUBEXP);
	for (i = 0; i < NSUBEXP; i++) {
	    elemlist = list_new(2);

	    d.type = INTEGER;
	    if (reg->startp[i]) {
		d.u.val = 0;
		elemlist = list_add(elemlist, &d);
		elemlist = list_add(elemlist, &d);
	    } else {
		d.u.val = reg->startp[i] - s + 1;
		elemlist = list_add(elemlist, &d);
		d.u.val = reg->endp[i] - reg->startp[i];
		elemlist = list_add(elemlist, &d);
	    }

	    d.type = LIST;
	    d.u.list = elemlist;
	    fields = list_add(fields, &d);
	}
    }

    pop(num_args);
    if (fields) {
	push_list(fields);
	list_discard(fields);
    } else {
	push_int(0);
    }
}

/* Encrypt a string. */
void op_crypt(void)
{
    int num_args;
    Data *args;
    char *s, *encrypted;
    String *str;

    /* Accept a string to encrypt and an optional salt. */
    if (!func_init_1_or_2(&args, &num_args, STRING, STRING))
	return;
    if (num_args == 2 && string_length(args[1].u.str) != 2) {
	cthrow(salt_id, "Salt (%S) is not two characters.", args[1].u.str);
	return;
    }

    s = string_chars(args[0].u.str);

    if (num_args == 2) {
	encrypted = crypt_string(s, string_chars(args[1].u.str));
    } else {
	encrypted = crypt_string(s, NULL);
    }

    pop(num_args);
    str = string_from_chars(encrypted, strlen(encrypted));
    push_string(str);
    string_discard(str);
}

void op_uppercase(void)
{
    Data *args;

    /* Accept a string to uppercase. */
    if (!func_init_1(&args, STRING))
	return;

    args[0].u.str = string_uppercase(args[0].u.str);
}

void op_lowercase(void)
{
    Data *args;

    /* Accept a string to uppercase. */
    if (!func_init_1(&args, STRING))
	return;

    args[0].u.str = string_lowercase(args[0].u.str);
}

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

    /* Accept two strings to compare. */
    if (!func_init_2(&args, STRING, STRING))
	return;

    /* Compare the strings case-sensitively. */
    val = strcmp(string_chars(args[0].u.str), string_chars(args[1].u.str));
    pop(2);
    push_int(val);
}