/* @@@HEAD@@@
// Function operators acting on strings.
*/

#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include "y.tab.h"
#include "operators.h"
#include "execute.h"
#include "cdc_types.h"
#include "match.h"
#include "util.h"
#include "memory.h"

void op_strlen(void) {
    data_t *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_t *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_t *args, d;
    list_t *exploded;
    char *sep, *s, *p, *q;
    string_t *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_t *args;
    char *search, *replace, *s, *p, *q;
    string_t *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);

    if (*s == NULL || *search == NULL) {
        subbed = string_dup(args[0].u.str);
    } else {
        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_t *args;
    char *filler;
    string_t *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_t *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_t *args;
    list_t *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_t *args;
    list_t *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_t * args,
             d;
    regexp * reg;
    list_t * fields = (list_t *)0;
    list_t * 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;

    /* note: this regexp is free'd by string_discard() */
    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;
            /* BUG: backwards logic broke regexp matching
               found by Miroslav Silovic (Jenner) */
	    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_t *args;
    char *s, *encrypted;
    string_t *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_t * 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_t *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_t *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);
}

/*
// -------------------------------------------------------------
//  %<args><type>
// 
// types: d or D       (literal data),
//        l or L       (string -- left aligned)
//        r or R       (string -- right)
//        c or C       (string -- centered)
//        e or E       (string, breaks with an elipse after pad width)
// 
// args are integers, plus an optional colon seperator, which specifies
// the fill character.  If the fill character is any of the special
// characters, prefix it with a slash.  A period may eventually specify
// precision, with 'f'.  Capitalized versions of each string will
// cut the string, when it reaches the end of the specified padding.
// 
//    "%r", "test"      => "test"
//    "%l", "test"      => "test"
//    "%c", "test"      => "test"
//    "%d", "test"      => "\"test\""
// 
//    "%10r", "test"    => "      test"
//    "%10l", "test"    => "test      "
//    "%10c", "test"    => "   test   "
//    "%10:|r", "test"  => "||||||test"
//    "%10:|l", "test"  => "test||||||"
//    "%10:|c", "test"  => "|||test|||"
// 
//    "%5e", "testing"  => "te..."
// -------------------------------------------------------------
*/

void op_strfmt(void) {
    data_t   * argv,
             * arg;
    string_t * str,
             * value;
    list_t   * args;
    char     * fmt,
             * s,
             * tmp,
               pchar;
    int        len, pad;

    /* accept two arguments, second is a list for the format */
    if (!func_init_2(&argv, STRING, LIST))
	return;

    fmt = string_chars(argv[0].u.str);
    len = argv[0].u.str->len;
    args = argv[1].u.list;
    str = string_new(0);
    arg = list_first(argv[1].u.list);

    for (;;) {
        s = strchr(fmt, '%');
        if (s == NULL || s[1] == NULL) {
            str = string_add_chars(str, fmt, strlen(fmt));
            break;
        }

        str = string_add_chars(str, fmt, s - fmt);
        s++;
        len -= (s - fmt);

        if (*s == '%') {
            str = string_addc(str, '%');
            continue;
        }

        pad = 0;
        pchar = ' ';

        /* get the pad width */
        if (isdigit(*s)) {
            pad = (int) atol(s);
            while (len && isdigit(*s)) {
                s++;
                len--;
            }

            /* arbitrary restriction on pad length,
               dont want them making it too big and chomping memory */
            if (pad > 64)
                pad = 64;
        }

        /* get the pad char */
        if (*s == ':' && len) {
            s++;
            pchar = *s++;
            len -= 2;
        }

        /* invalid format, just abort, they need to know when it is wrong */
        if (len <= 0) {
            string_discard(str);
            cthrow(type_id, "Invalid format, ends in control sequence.");
            return;
        }

        if (!arg) {
            s++;
            continue;
        }

        if (*s != 'd' && arg->type == STRING)
            value = string_dup(arg->u.str);
        else if (*s != 'd' && arg->type == SYMBOL) {
            tmp = ident_name(arg->u.symbol);
            value = string_from_chars(tmp, strlen(tmp));
        } else
            value = data_to_literal(arg);

        switch (*s) {
            case 'L':
            case 'S':
                if (pad == 0)
                    break;
                if (value->len > pad) {
                    value = string_truncate(value, pad);
                } else {
                    pad -= value->len;
                    while (pad-- > 0)
                        value = string_addc(value, pchar);
                }
                str = string_add(str, value);
                break;
            case 'd':
            case 's':
            case 'l':
                pad -= value->len;
                while (pad-- > 0)
                    value = string_addc(value, pchar);
                str = string_add(str, value);
                break;
            case 'R':
                if (pad == 0)
                    break;
                if (value->len > pad) {
                    value = string_truncate(value, pad);
                } else {
                    pad -= value->len;
                    while (pad-- > 0)
                        str = string_addc(str, pchar);
                }
                str = string_add(str, value);
                break;
            case 'r':
                pad -= value->len;
                while (pad-- > 0)
                    str = string_addc(str, pchar);
                str = string_add(str, value);
                break;
            case 'C':
                if (pad == 0)
                    break;
                if (value->len > pad) {
                    value = string_truncate(value, pad);
                } else {
                    pad -= value->len;
                    pad = pad / 2;
                    while (pad-- > 0) {
                        str = string_addc(str, pchar);
                        value = string_addc(value, pchar);
                    }
                }
                str = string_add(str, value);
                break;
            case 'c':
                if (pad >= value->len) {
                    pad -= value->len;
                    pad = pad / 2;
                    while (pad-- > 0) {
                        str = string_addc(str, pchar);
                        value = string_addc(value, pchar);
                    }
                }
                str = string_add(str, value);
                break;
            case 'E':
            case 'e':
                if (pad == 0)
                    break;
                if (pad <= 3) {
                    string_discard(str);
                    string_discard(value);
                    cthrow(type_id,
                           "Elipse pad length must be at least 4 or more.");
                    return;
                }
                if (value->len > pad) {
                    if (value->len - (pad - 3) > 0)
                        pad = pad - 3;
                    value = string_truncate(value, pad);
                    value = string_add_chars(value, "...", 3);
                } else {
                    pad -= value->len;
                    while (pad-- > 0)
                        value = string_addc(value, pchar);
                }
                str = string_add(str, value);
                break;
            default:
                /* invalid format, abort */
                string_discard(str);
                string_discard(value);
                cthrow(type_id, "Invalid format, unknown control sequence.");
                return;
        }
        string_discard(value);
        arg = list_next(argv[1].u.list, arg);
        fmt = ++s;
        len--;
    }

    pop(2);
    push_string(str);
    string_discard(str);
}