/* xlstr - xlisp string and character built-in functions */
/*	Copyright (c) 1985, by David Michael Betz
        All Rights Reserved
        Permission is granted for unrestricted non-commercial use	*/

/* CHANGE LOG
 * --------------------------------------------------------------------
 * 28Apr03  dm  eliminate some compiler warnings
 */

#include "string.h"
#include "xlisp.h"

/* local definitions */
#define fix(n)	cvfixnum((FIXTYPE)(n))
#define TLEFT	1
#define TRIGHT	2

/* external variables */
extern LVAL k_start,k_end,k_1start,k_1end,k_2start,k_2end;
extern LVAL s_true;
extern char buf[];

/* forward declarations */
FORWARD LOCAL LVAL strcompare(int fcn, int icase);
FORWARD LOCAL LVAL chrcompare(int fcn, int icase);
FORWARD LOCAL LVAL changecase(int fcn, int destructive);
FORWARD LOCAL LVAL trim(int fcn);
FORWARD LOCAL void getbounds(LVAL str, LVAL skey, LVAL ekey, int *pstart, int *pend);
FORWARD LOCAL int inbag(int ch, LVAL bag);

/* string comparision functions */
LVAL xstrlss(void) { return (strcompare('<',FALSE)); } /* string< */
LVAL xstrleq(void) { return (strcompare('L',FALSE)); } /* string<= */
LVAL xstreql(void) { return (strcompare('=',FALSE)); } /* string= */
LVAL xstrneq(void) { return (strcompare('#',FALSE)); } /* string/= */
LVAL xstrgeq(void) { return (strcompare('G',FALSE)); } /* string>= */
LVAL xstrgtr(void) { return (strcompare('>',FALSE)); } /* string> */

/* string comparison functions (not case sensitive) */
LVAL xstrilss(void) { return (strcompare('<',TRUE)); } /* string-lessp */
LVAL xstrileq(void) { return (strcompare('L',TRUE)); } /* string-not-greaterp */
LVAL xstrieql(void) { return (strcompare('=',TRUE)); } /* string-equal */
LVAL xstrineq(void) { return (strcompare('#',TRUE)); } /* string-not-equal */
LVAL xstrigeq(void) { return (strcompare('G',TRUE)); } /* string-not-lessp */
LVAL xstrigtr(void) { return (strcompare('>',TRUE)); } /* string-greaterp */

/* strcompare - compare strings */
LOCAL LVAL strcompare(int fcn, int icase)
{
    int start1,end1,start2,end2,ch1,ch2;
    unsigned char *p1,*p2;
    LVAL str1,str2;

    /* get the strings */
    str1 = xlgastring();
    str2 = xlgastring();

    /* get the substring specifiers */
    getbounds(str1,k_1start,k_1end,&start1,&end1);
    getbounds(str2,k_2start,k_2end,&start2,&end2);

    /* setup the string pointers */
    p1 = &getstring(str1)[start1];
    p2 = &getstring(str2)[start2];

    /* compare the strings */
    for (; start1 < end1 && start2 < end2; ++start1,++start2) {
        ch1 = *p1++;
        ch2 = *p2++;
        if (icase) {
            if (isupper(ch1)) ch1 = tolower(ch1);
            if (isupper(ch2)) ch2 = tolower(ch2);
        }
        if (ch1 != ch2)
            switch (fcn) {
            case '<':	return (ch1 < ch2 ? fix(start1) : NIL);
            case 'L':	return (ch1 <= ch2 ? fix(start1) : NIL);
            case '=':	return (NIL);
            case '#':	return (fix(start1));
            case 'G':	return (ch1 >= ch2 ? fix(start1) : NIL);
            case '>':	return (ch1 > ch2 ? fix(start1) : NIL);
            }
    }

    /* check the termination condition */
    switch (fcn) {
    case '<':	return (start1 >= end1 && start2 < end2 ? fix(start1) : NIL);
    case 'L':	return (start1 >= end1 ? fix(start1) : NIL);
    case '=':	return (start1 >= end1 && start2 >= end2 ? s_true : NIL);
    case '#':	return (start1 >= end1 && start2 >= end2 ? NIL : fix(start1));
    case 'G':	return (start2 >= end2 ? fix(start1) : NIL);
    case '>':	return (start2 >= end2 && start1 < end1 ? fix(start1) : NIL);
    }

    return NIL; /* Normally shouldn't happen */
}

/* case conversion functions */
LVAL xupcase(void)   { return (changecase('U',FALSE)); }
LVAL xdowncase(void) { return (changecase('D',FALSE)); }

/* destructive case conversion functions */
LVAL xnupcase(void)   { return (changecase('U',TRUE)); }
LVAL xndowncase(void) { return (changecase('D',TRUE)); }

/* changecase - change case */
LOCAL LVAL changecase(int fcn, int destructive)
{
    unsigned char *srcp,*dstp;
    int start,end,len,ch,i;
    LVAL src,dst;

    /* get the string */
    src = xlgastring();

    /* get the substring specifiers */
    getbounds(src,k_start,k_end,&start,&end);
    len = getslength(src) - 1;

    /* make a destination string */
    dst = (destructive ? src : new_string(len+1));

    /* setup the string pointers */
    srcp = getstring(src);
    dstp = getstring(dst);

    /* copy the source to the destination */
    for (i = 0; i < len; ++i) {
        ch = *srcp++;
        if (i >= start && i < end)
            switch (fcn) {
            case 'U':	if (islower(ch)) ch = toupper(ch); break;
            case 'D':	if (isupper(ch)) ch = tolower(ch); break;
            }
        *dstp++ = ch;
    }
    *dstp = '\0';

    /* return the new string */
    return (dst);
}

/* search for string within a string */
LVAL xstrsearch(void)
{
    int start,end,pat_len,str_len;
    unsigned char *pat,*str,*patptr,*strptr,*patend;
    LVAL str1,str2;

    /* get the strings */
    str1 = xlgastring(); /* the pat */
    str2 = xlgastring(); /* the string */

    /* get the substring specifiers */
    getbounds(str2, k_start, k_end, &start, &end);    

    /* setup the string pointers */
    pat = getstring(str1);
    str = &getstring(str2)[start];

    pat_len = getslength(str1) - 1;
    str_len = end - start;
    patend = pat + pat_len;
    for (; pat_len <= str_len; str_len--) {
        patptr = pat;
        strptr = str;
        /* two outcomes: (1) no match, goto step (2) match, return */
        while (patptr < patend) {
            if (*patptr++ != *strptr++) goto step;
        }
        /* compute match index */
        return cvfixnum(str - getstring(str2));
    step:
        str++;
    }
    /* no match */
    return NIL;
}
    

/* trim functions */
LVAL xtrim(void)      { return (trim(TLEFT|TRIGHT)); }
LVAL xlefttrim(void)  { return (trim(TLEFT)); }
LVAL xrighttrim(void) { return (trim(TRIGHT)); }

/* trim - trim character from a string */
LOCAL LVAL trim(int fcn)
{
    unsigned char *leftp,*rightp,*dstp;
    LVAL bag,src,dst;

    /* get the bag and the string */
    bag = xlgastring();
    src = xlgastring();
    xllastarg();

    /* setup the string pointers */
    leftp = getstring(src);
    rightp = leftp + getslength(src) - 2;

    /* trim leading characters */
    if (fcn & TLEFT)
        while (leftp <= rightp && inbag(*leftp,bag))
            ++leftp;

    /* trim character from the right */
    if (fcn & TRIGHT)
        while (rightp >= leftp && inbag(*rightp,bag))
            --rightp;

    /* make a destination string and setup the pointer */
    dst = new_string((int)(rightp-leftp+2));
    dstp = getstring(dst);

    /* copy the source to the destination */
    while (leftp <= rightp)
        *dstp++ = *leftp++;
    *dstp = '\0';

    /* return the new string */
    return (dst);
}

/* getbounds - get the start and end bounds of a string */
LOCAL void getbounds(LVAL str, LVAL skey, LVAL ekey, int *pstart, int *pend)
{
    LVAL arg;
    int len;

    /* get the length of the string */
    len = getslength(str) - 1;

    /* get the starting index */
    if (xlgkfixnum(skey,&arg)) {
        *pstart = (int)getfixnum(arg);
        if (*pstart < 0 || *pstart > len)
            xlerror("string index out of bounds",arg);
    }
    else
        *pstart = 0;

    /* get the ending index */
    if (xlgkfixnum(ekey,&arg)) {
        *pend = (int)getfixnum(arg);
        if (*pend < 0 || *pend > len)
            xlerror("string index out of bounds",arg);
    }
    else
        *pend = len;

    /* make sure the start is less than or equal to the end */
    if (*pstart > *pend)
        xlerror("starting index error",cvfixnum((FIXTYPE)*pstart));
}

/* inbag - test if a character is in a bag */
LOCAL int inbag(int ch, LVAL bag)
{
    unsigned char *p;
    for (p = getstring(bag); *p != '\0'; ++p)
        if (*p == ch)
            return (TRUE);
    return (FALSE);
}

/* xstrcat - concatenate a bunch of strings */
LVAL xstrcat(void)
{
    LVAL *saveargv,tmp,val;
    unsigned char *str;
    int saveargc,len;

    /* save the argument list */
    saveargv = xlargv;
    saveargc = xlargc;

    /* find the length of the new string */
    for (len = 0; moreargs(); ) {
        tmp = xlgastring();
        len += (int)getslength(tmp) - 1;
    }

    /* create the result string */
    val = new_string(len+1);
    str = getstring(val);

    /* restore the argument list */
    xlargv = saveargv;
    xlargc = saveargc;
    
    /* combine the strings */
    for (*str = '\0'; moreargs(); ) {
        tmp = nextarg();
        strcat((char *) str, (char *) getstring(tmp));
    }

    /* return the new string */
    return (val);
}

/* xsubseq - return a subsequence */
LVAL xsubseq(void)
{
    unsigned char *srcp,*dstp;
    int start,end,len;
    LVAL src,dst;

    /* get string and starting and ending positions */
    src = xlgastring();

    /* get the starting position */
    dst = xlgafixnum(); start = (int)getfixnum(dst);
    if (start < 0 || start > getslength(src) - 1)
        xlerror("string index out of bounds",dst);

    /* get the ending position */
    if (moreargs()) {
        dst = xlgafixnum(); end = (int)getfixnum(dst);
        if (end < 0 || end > getslength(src) - 1)
            xlerror("string index out of bounds",dst);
    }
    else
        end = getslength(src) - 1;
    xllastarg();

    /* setup the source pointer */
    srcp = getstring(src) + start;
    len = end - start;

    /* make a destination string and setup the pointer */
    dst = new_string(len+1);
    dstp = getstring(dst);

    /* copy the source to the destination */
    while (--len >= 0)
        *dstp++ = *srcp++;
    *dstp = '\0';

    /* return the substring */
    return (dst);
}

/* xstring - return a string consisting of a single character */
LVAL xstring(void)
{
    LVAL arg;

    /* get the argument */
    arg = xlgetarg();
    xllastarg();

    /* make sure its not NIL */
    if (null(arg))
        xlbadtype(arg);

    /* check the argument type */
    switch (ntype(arg)) {
    case STRING:
        return (arg);
    case SYMBOL:
        return (getpname(arg));
    case CHAR:
        buf[0] = (int)getchcode(arg);
        buf[1] = '\0';
        return (cvstring(buf));
    case FIXNUM:
        buf[0] = getfixnum(arg);
        buf[1] = '\0';
        return (cvstring(buf));
    default:
        xlbadtype(arg);
        return NIL; /* never happens */
    }
}

/* xchar - extract a character from a string */
LVAL xchar(void)
{
    LVAL str,num;
    int n;

    /* get the string and the index */
    str = xlgastring();
    num = xlgafixnum();
    xllastarg();

    /* range check the index */
    if ((n = (int)getfixnum(num)) < 0 || n >= getslength(str) - 1)
        xlerror("index out of range",num);

    /* return the character */
    return (cvchar(getstring(str)[n]));
}

/* xcharint - convert an integer to a character */
LVAL xcharint(void)
{
    LVAL arg;
    arg = xlgachar();
    xllastarg();
    return (cvfixnum((FIXTYPE)getchcode(arg)));
}

/* xintchar - convert a character to an integer */
LVAL xintchar(void)
{
    LVAL arg;
    arg = xlgafixnum();
    xllastarg();
    return (cvchar((int)getfixnum(arg)));
}

/* xuppercasep - built-in function 'upper-case-p' */
LVAL xuppercasep(void)
{
    int ch;
    ch = getchcode(xlgachar());
    xllastarg();
    return (isupper(ch) ? s_true : NIL);
}

/* xlowercasep - built-in function 'lower-case-p' */
LVAL xlowercasep(void)
{
    int ch;
    ch = getchcode(xlgachar());
    xllastarg();
    return (islower(ch) ? s_true : NIL);
}

/* xbothcasep - built-in function 'both-case-p' */
LVAL xbothcasep(void)
{
    int ch;
    ch = getchcode(xlgachar());
    xllastarg();
    return (isupper(ch) || islower(ch) ? s_true : NIL);
}

/* xdigitp - built-in function 'digit-char-p' */
LVAL xdigitp(void)
{
    int ch;
    ch = getchcode(xlgachar());
    xllastarg();
    return (isdigit(ch) ? cvfixnum((FIXTYPE)(ch - '0')) : NIL);
}

/* xcharcode - built-in function 'char-code' */
LVAL xcharcode(void)
{
    int ch;
    ch = getchcode(xlgachar());
    xllastarg();
    return (cvfixnum((FIXTYPE)ch));
}

/* xcodechar - built-in function 'code-char' */
LVAL xcodechar(void)
{
    LVAL arg;
    int ch;
    arg = xlgafixnum(); ch = getfixnum(arg);
    xllastarg();
    return (ch >= 0 && ch <= 127 ? cvchar(ch) : NIL);
}

/* xchupcase - built-in function 'char-upcase' */
LVAL xchupcase(void)
{
    LVAL arg;
    int ch;
    arg = xlgachar(); ch = getchcode(arg);
    xllastarg();
    return (islower(ch) ? cvchar(toupper(ch)) : arg);
}

/* xchdowncase - built-in function 'char-downcase' */
LVAL xchdowncase(void)
{
    LVAL arg;
    int ch;
    arg = xlgachar(); ch = getchcode(arg);
    xllastarg();
    return (isupper(ch) ? cvchar(tolower(ch)) : arg);
}

/* xdigitchar - built-in function 'digit-char' */
LVAL xdigitchar(void)
{
    LVAL arg;
    int n;
    arg = xlgafixnum(); n = getfixnum(arg);
    xllastarg();
    return (n >= 0 && n <= 9 ? cvchar(n + '0') : NIL);
}

/* xalphanumericp - built-in function 'alphanumericp' */
LVAL xalphanumericp(void)
{
    int ch;
    ch = getchcode(xlgachar());
    xllastarg();
    return (isupper(ch) || islower(ch) || isdigit(ch) ? s_true : NIL);
}

/* character comparision functions */
LVAL xchrlss(void) { return (chrcompare('<',FALSE)); } /* char< */
LVAL xchrleq(void) { return (chrcompare('L',FALSE)); } /* char<= */
LVAL xchreql(void) { return (chrcompare('=',FALSE)); } /* char= */
LVAL xchrneq(void) { return (chrcompare('#',FALSE)); } /* char/= */
LVAL xchrgeq(void) { return (chrcompare('G',FALSE)); } /* char>= */
LVAL xchrgtr(void) { return (chrcompare('>',FALSE)); } /* char> */

/* character comparision functions (case insensitive) */
LVAL xchrilss(void) { return (chrcompare('<',TRUE)); } /* char-lessp */
LVAL xchrileq(void) { return (chrcompare('L',TRUE)); } /* char-not-greaterp */
LVAL xchrieql(void) { return (chrcompare('=',TRUE)); } /* char-equal */
LVAL xchrineq(void) { return (chrcompare('#',TRUE)); } /* char-not-equal */
LVAL xchrigeq(void) { return (chrcompare('G',TRUE)); } /* char-not-lessp */
LVAL xchrigtr(void) { return (chrcompare('>',TRUE)); } /* char-greaterp */

/* chrcompare - compare characters */
LOCAL LVAL chrcompare(int fcn, int icase)
{
    int ch1,ch2,icmp;
    LVAL arg;
    
    /* get the characters */
    arg = xlgachar(); ch1 = getchcode(arg);

    /* convert to lowercase if case insensitive */
    if (icase && isupper(ch1))
        ch1 = tolower(ch1);

    /* handle each remaining argument */
    for (icmp = TRUE; icmp && moreargs(); ch1 = ch2) {

        /* get the next argument */
        arg = xlgachar(); ch2 = getchcode(arg);

        /* convert to lowercase if case insensitive */
        if (icase && isupper(ch2))
            ch2 = tolower(ch2);

        /* compare the characters */
        switch (fcn) {
        case '<':	icmp = (ch1 < ch2); break;
        case 'L':	icmp = (ch1 <= ch2); break;
        case '=':	icmp = (ch1 == ch2); break;
        case '#':	icmp = (ch1 != ch2); break;
        case 'G':	icmp = (ch1 >= ch2); break;
        case '>':	icmp = (ch1 > ch2); break;
        }
    }

    /* return the result */
    return (icmp ? s_true : NIL);
}