/*
 * File: fstr.c
 *  Contents: center, detab, entab, left, map, repl, reverse, right, trim
 */

#include "..\h\config.h"
#include "..\h\rt.h"
#include "rproto.h"
#include <ctype.h>

/*
 * Prototype.
 */

hidden	int	nxttab	Params((int col));


/*
 * center(s1,n,s2) - pad s1 on left and right with s2 to length n.
 */

FncDcl(center,3)
   {
   register char *s, *st;
   word cnt, slen, hcnt;
   char *sbuf, *s3;
   char sbuf1[MaxCvtLen], sbuf2[MaxCvtLen];

   /*
    * Arg1 must be a string.  Arg2 must be a non-negative integer and defaults
    *  to 1.  Arg3 must be a string and defaults to a blank.
    */
   if (cvstr(&Arg1, sbuf1) == CvtFail) 
      RunErr(103, &Arg1);
   if (defshort(&Arg2, 1) == Error) 
      RunErr(0, NULL);
   if ((cnt = IntVal(Arg2)) < 0) 
      RunErr(205, &Arg2);
   if (defstr(&Arg3, sbuf2, &blank) == Error) 
      RunErr(0, NULL);

   if (strreq(cnt) == Error) 
      RunErr(0, NULL);

   if (StrLen(Arg3) == 0) {
      /*
       * The padding string is null; make it a blank.
       */
      slen = 1;
      s3 = " ";
      }
   else {
      slen = StrLen(Arg3);
      s3 = StrLoc(Arg3);
      }

   /*
    * Get space for the new string.  Start at the right
    *  of the new string and copy Arg3 into it from right to left as
    *  many times as will fit in the right half of the new string.
    */
   sbuf = alcstr(NULL, cnt);
   hcnt = cnt / 2;
   s = sbuf + cnt;
   while (s > sbuf + hcnt) {
      st = s3 + slen;
      while (st > s3 && s > sbuf + hcnt)
         *--s = *--st;
      }

   /*
    * Start at the left end of the new string and copy Arg1 into it from
    *  left to right as many time as will fit in the left half of the
    *  new string.
    */
   s = sbuf;
   while (s < sbuf + hcnt) {
      st = s3;
      while (st < s3 + slen && s < sbuf + hcnt)
         *s++ = *st++;
      }

   slen = StrLen(Arg1);
   if (cnt < slen) {
      /*  
       * Arg1 is larger than the field to center it in.  The source for the
       *  copy starts at the appropriate point in Arg1 and the destination
       *  starts at the left end of of the new string.
       */
      s = sbuf;
      st = StrLoc(Arg1) + slen/2 - hcnt + (~cnt&slen&1);
      }
   else {
      /*
       * Arg1 is smaller than the field to center it in.  The source for the
       *  copy starts at the left end of Arg1 and the destination starts at
       *  the appropriate point in the new string.
       */
      s = sbuf + hcnt - slen/2 - (~cnt&slen&1);
      st = StrLoc(Arg1);
      }
   /*
    * Perform the copy, moving min(*Arg1,Arg2) bytes from st to s.
    */
   if (slen > cnt)
      slen = cnt;
   while (slen-- > 0)
      *s++ = *st++;

   /*
    * Return the new string.
    */
   StrLen(Arg0) = cnt;
   StrLoc(Arg0) = sbuf;
   Return;
   }


/*
 * detab(s,i,...) - replace tabs with spaces, with stops at columns indicated.
 */

FncDclV(detab)
   {
   int i, last, interval, cnt, col, target; 
   char *in, *out, *iend, c, sbuf1[MaxCvtLen];
   float expan, etmp;

   /*
    * Arg1 is required and must be a string.
    * Additional args must be strictly increasing positive integers.
    * Calculate maximum expansion factor while checking. 
    */
   if (nargs < 1)
      RunErr(103, &nulldesc);
   if (cvstr(&Arg(1), sbuf1) == CvtFail)
      RunErr(103, &Arg(1));
   last = 1;
   if (nargs < 2) {
      interval = 8;
      expan = 8.0;
   }
   else {
      expan = 1.0;
      for (i = 2; i <= nargs; i++) {
         if (ArgType(i) != D_Integer) {
            if (cvint(&Arg(i)) != T_Integer) {
               RunErr(101, &Arg(i));
               }
            }
         interval = ArgVal(i) - last;
         if (interval <= 0)
            RunErr(210, &Arg(i));
         etmp = (float) (ArgVal(i) - 1) / (float) (i - 1);
         if (etmp > expan)
            expan = etmp;
         last = (int)ArgVal(i);
         }
      last -= interval;
      if (interval > expan)
         expan = interval;
   }

   /*
    * Get memory for worst case expansion.  This would be a string of all tabs,
    *  or repeated newlines after tabbing past a large tab interval.
    */
   cnt = expan * StrLen(Arg1) + 1;
   if (strreq((word)cnt) == Error)
      RunErr(0, NULL);
   if (strfree + cnt > strend)
      syserr("detab allocation botch");

   /*
    * Copy the string, expanding tabs.
    */
   col = 1;
   target = 0;
   iend = StrLoc(Arg(1)) + StrLen(Arg(1));
   for (in = StrLoc(Arg(1)), out = strfree; in < iend; )
      switch (c = *out++ = *in++) {
         case '\b':
            col--;
            break;
         case LineFeed:
         case CarriageReturn:
            col = 1;
            break;
         case '\t':
            out--;
            if (col >= last)
               target = col + interval - (col - last) % interval;
            else {
               for (i = 2; col >= ArgVal(i); i++)
                  ;
               target = (int)ArgVal(i);
            }
            while (col < target) {
               *out++ = ' ';
               col++;
               }
            break;
         default:
#if SASC
            if (isascii(c) && !iscntrl(c))      /* if "printable ASCII" */
#else					/* SASC */
            if (isprint(c))
#endif					/* SASC */
               col++;
         }

   /*
    * Return new string if indeed there were tabs; otherwise return original
    *  string to conserve memory.
    */
   i = DiffPtrs(out, strfree);
   if (i > cnt)
      syserr("overenthusiastic tab expansion");
   if (target > 0) {
      StrLen(Arg0) = i;			/* set string length */
      StrLoc(Arg0) = alcstr(NULL, (word)i);	/* allocate the space we just filled */
      }
   else	
      Arg0 = Arg1;			/* don't allocate, reuse old string */
   Return;
   }


/*
 * entab(s,i,...) - replace spaces with tabs, with stops at columns indicated.
 */

/* temps for communication with nxttab(), following entab() */
static dptr tablist;	/* explicit tab stops (descriptors of ints) */
static int last, interval;	/* last explicit stop, and repeat interval */

FncDclV(entab)
   {
   int i, target; 
   char *in, *out, *iend, c, sbuf1[MaxCvtLen];
   long col, cnt;

   /*
    * Arg1 is required and must be a string.
    * Additional args must be strictly increasing positive integers.
    */
   if (nargs < 1)
      RunErr(103, &nulldesc);
   if (cvstr(&Arg(1), sbuf1) == CvtFail)
      RunErr(103, &Arg(1));
   last = 1;
   interval = 8;
   for (i = 2; i <= nargs; i++) {
      if (ArgType(i) != D_Integer) {
         if (cvint(&Arg(i)) != T_Integer) {
            RunErr(101, &Arg(i));
            }
         }
      interval = ArgVal(i) - last;
      if (interval <= 0)
         RunErr(210, &Arg(i));
      last = (int)ArgVal(i);
      }
   if (last > 1)
      last -= interval;
   tablist = &Arg(2);	/* if there is no arg 2, this won't be used, so ok */

   /*
    * Get memory for result at end of string space.  We may give some back
    *  if not all needed, or all of it if no tabs can be inserted.
    */
   cnt = StrLen(Arg1);
   if (strreq((word)cnt) == Error)
      RunErr(0, NULL);
   if (strfree + cnt > strend)
      syserr("entab allocation botch");

   /*
    * Copy the string, looking for runs of spaces.
    */
   col = 1;
   target = 0;
   iend = StrLoc(Arg(1)) + StrLen(Arg(1));
   for (in = StrLoc(Arg(1)), out = strfree; in < iend; )
      switch (c = *out++ = *in++) {
         case '\b':
            col--;
            break;
         case LineFeed:
         case CarriageReturn:
            col = 1;
            break;
         case '\t':
            if (col >= last)
               col += interval - (col - last) % interval;
            else {
               for (i = 2; col >= ArgVal(i); i++)
                  ;
               col = ArgVal(i);
            }
            break;
         case ' ':
            target = col + 1;
            while (in < iend && *in == ' ')
               target++, in++;
            cnt = target - col; 
            if (cnt > 1) {	/* never tab just 1; already copied space */
               if (nxttab(col) == col+1 && nxttab(col+1) > target)
                  col++;	/* keep space to avoid 1-col tab then spaces */
               else
                  out--;	/* back up to begin tabbing */
               while ((i = nxttab(col)) <= target)  {
                  *out++ = '\t';	/* put tabs to tab positions */
                  col = i;
                  }
               while (col++ < target)
                  *out++ = ' ';		/* complete gap with spaces */
               }
            col = target;
            break;
         default:
#if SASC
            if (isascii(c) && !iscntrl(c))      /* if "printable ASCII" */
#else					/* SASC */
            if (isprint(c))
#endif					/* SASC */
               col++;
         }

   /*
    * Return new string if indeed there were tabs; otherwise return original
    *  string to conserve memory.
    */
   if (out > strend)
      syserr("entab allocation botch");
   if (target) {			/* if we did indeed insert tabs */
      cnt = DiffPtrs(out, strfree);
      StrLen(Arg0) = cnt;		/* set string length */
      StrLoc(Arg0) = alcstr(NULL, cnt);	/* allocate the space we just filled */
      }
   else
      Arg0 = Arg1;			/* don't allocate, return old string */
   Return;
   }

/*  nxttab(col) -- helper routine for entab, returns next tab beyond col  */

static int nxttab(col)
int col;
{
   dptr dp;
   long n;

   if (col >= last)
      return col + interval - (col - last) % interval;
   dp = tablist;
   while ((n = IntVal(*dp)) <= col)
      dp++;
   return n;
}

/*
 * left(s1,n,s2) - pad s1 on right with s2 to length n.
 */

FncDcl(left,3)
   {
   register char *s, *st;
   word cnt, slen;
   char *sbuf, *s3, sbuf1[MaxCvtLen], sbuf2[MaxCvtLen];

   /*
    * Arg1 must be a string.  Arg2 must be a non-negative integer and defaults
    *  to 1.  Arg3 must be a string and defaults to a blank.
    */
   if (cvstr(&Arg1, sbuf1) == CvtFail) 
      RunErr(103, &Arg1);
   if (defshort(&Arg2, 1) == Error) 
      RunErr(0, NULL);
   if ((cnt = IntVal(Arg2)) < 0) 
      RunErr(205, &Arg2);
   if (defstr(&Arg3, sbuf2, &blank) == Error) 
      RunErr(0, NULL);

   if (strreq(cnt) == Error) 
      RunErr(0, NULL);
   if (StrLen(Arg3) == 0) {
      /*
       * The padding string is null; make it a blank.
       */
      slen = 1;
      s3 = " ";
      }
   else {
      slen = StrLen(Arg3);
      s3 = StrLoc(Arg3);
      }

   /*
    * Get Arg2 bytes of string space.  Start at the right end of the new
    *  string and copy Arg3 into the new string as many times as it fits.
    *  Note that Arg3 is copied from right to left.
    */
   sbuf = alcstr(NULL, cnt);
   s = sbuf + cnt;
   while (s > sbuf) {
      st = s3 + slen;
      while (st > s3 && s > sbuf)
         *--s = *--st;
      }

   /*
    * Copy Arg1 into the new string, starting at the left end.
    *  If *Arg1 > Arg2, only copy Arg2 bytes.
    */
   s = sbuf;
   slen = StrLen(Arg1);
   st = StrLoc(Arg1);
   if (slen > cnt)
      slen = cnt;
   while (slen-- > 0)
      *s++ = *st++;

   /*
    * Return the new string.
    */
   StrLen(Arg0) = cnt;
   StrLoc(Arg0) = sbuf;
   Return;
   }

/*
 * map(s1,s2,s3) - map s1, using s2 and s3.
 */

FncDcl(map,3)
   {
   register int i;
   register word slen;
   register char *s1, *s2, *s3;
   char sbuf1[MaxCvtLen], sbuf2[MaxCvtLen], sbuf3[MaxCvtLen];
   static char maptab[256];

   /*
    * Arg1 must be a string; Arg2 and Arg3 default to &ucase and &lcase,
    *  respectively.
    */
   if (cvstr(&Arg1, sbuf1) == CvtFail) 
      RunErr(103, &Arg1);
   if (ChkNull(Arg2))
      Arg2 = ucase;
   if (ChkNull(Arg3))
      Arg3 = lcase;

   /*
    * If Arg2 and Arg3 are the same as for the last call of map,
    *  the current values in maptab can be used. Otherwise, the
    *  mapping information must be recomputed.
    */
   if (!EqlDesc(maps2,Arg2) || !EqlDesc(maps3,Arg3)) {
      maps2 = Arg2;
      maps3 = Arg3;

      /*
       * Convert Arg2 and Arg3 to strings.  They must be of the
       *  same length.
       */
      if (cvstr(&Arg2, sbuf2) == CvtFail) 
         RunErr(103, &Arg2);
      if (cvstr(&Arg3, sbuf3) == CvtFail) 
         RunErr(103, &Arg3);
      if (StrLen(Arg2) != StrLen(Arg3)) 
         RunErr(-208, NULL);

      /*
       * The array maptab is used to perform the mapping.  First,
       *  maptab[i] is initialized with i for i from 0 to 255.
       *  Then, for each character in Arg2, the position in maptab
       *  corresponding to the value of the character is assigned
       *  the value of the character in Arg3 that is in the same 
       *  position as the character from Arg2.
       */
      s2 = StrLoc(Arg2);
      s3 = StrLoc(Arg3);
      for (i = 0; i <= 255; i++)
         maptab[i] = i;
      for (slen = 0; slen < StrLen(Arg2); slen++)
         maptab[s2[slen]&0377] = s3[slen];
      }

   if (StrLen(Arg1) == 0) {
      Arg0 = emptystr;
      Return;
      }

   /*
    * The result is a string the size of Arg1; ensure that much space.
    */
   slen = StrLen(Arg1);
   if (strreq(slen) == Error) 
      RunErr(0, NULL);
   s1 = StrLoc(Arg1);

   /*
    * Create the result string, but specify no value for it.
    */
   StrLen(Arg0) = slen;
   StrLoc(Arg0) = alcstr(NULL, slen);
   s2 = StrLoc(Arg0);

   /*
    * Run through the string, using values in maptab to do the
    *  mapping.
    */
   while (slen-- > 0)
      *s2++ = maptab[(*s1++)&0377];
   Return;
   }

/*
 * repl(s,n) - concatenate n copies of string s.
 */

FncDcl(repl,2)
   {
   register char *sloc;
   register int cnt;
   char sbuf[MaxCvtLen];

   /*
    * Make sure that Arg1 is a string.
    */
   if (cvstr(&Arg1, sbuf) == CvtFail) 
      RunErr(103, &Arg1);

   /*
    * Make sure that Arg2 is an integer.
    */
   switch (cvint(&Arg2)) {

      /*
       * Make sure count is not negative.
       */
      case T_Integer:
         if ((cnt = (int)IntVal(Arg2)) >= 0)
            break;
         RunErr(205, &Arg2);

      default:
         RunErr(101, &Arg2);
      }

   /*
    * Make sure the resulting string will not be too long.
    */
   if ((IntVal(Arg2) * StrLen(Arg1)) > MaxStrLen) 
      RunErr(-205, NULL);

   /*
    * Return an empty string if Arg2 is 0.
    */
   if (cnt == 0)
      Arg0 = emptystr;

   else {
      /*
       * Ensure enough space for the replicated string and allocate
       *  a copy of s.  Then allocate and copy s n - 1 times.
       */
      if (strreq(cnt * StrLen(Arg1)) == Error) 
         RunErr(0, NULL);
      sloc = alcstr(StrLoc(Arg1), StrLen(Arg1));
      cnt--;
      while (cnt--)
         alcstr(StrLoc(Arg1), StrLen(Arg1));

      /*
       * Make Arg0 a descriptor for the replicated string.
       */
      StrLen(Arg0) = (int)IntVal(Arg2) * StrLen(Arg1);
      StrLoc(Arg0) = sloc;
      }
   Return;
   }

/*
 * reverse(s) - reverse string s.
 */

FncDcl(reverse,1)
   {
   register char c, *floc, *lloc;
   register word slen;
   char sbuf[MaxCvtLen];

   /*
    * Make sure that Arg1 is a string.
    */
   if (cvstr(&Arg1, sbuf) == CvtFail) 
      RunErr(103, &Arg1);

   /*
    * Ensure that there is enough room and allocate a copy of Arg1.
    */
   slen = StrLen(Arg1);
   if (strreq(slen) == Error) 
      RunErr(0, NULL);
   StrLen(Arg0) = slen;
   StrLoc(Arg0) = alcstr(StrLoc(Arg1), slen);

   /*
    * Point floc at the start of Arg0 and lloc at the end of Arg0.  Work floc
    *  and lloc along Arg0 in opposite directions, swapping the characters
    *  at floc and lloc.
    */
   floc = StrLoc(Arg0);
   lloc = floc + --slen;
   while (floc < lloc) {
      c = *floc;
      *floc++ = *lloc;
      *lloc-- = c;
      }
   Return;
   }


/*
 * right(s1,n,s2) - pad s1 on left with s2 to length n.
 */

FncDcl(right,3)
   {
   register char *s, *st;
   word cnt, slen;
   char *sbuf, *s3, sbuf1[MaxCvtLen], sbuf2[MaxCvtLen];

   /*
    * Arg1 must be a string.  Arg2 must be a non-negative integer and defaults
    *  to 1.  eArg3 must be a string and defaults to a blank.
    */
   if (cvstr(&Arg1, sbuf1) == CvtFail) 
      RunErr(103, &Arg1);
   if (defshort(&Arg2, 1) == Error) 
      RunErr(0, NULL);
   if ((cnt = IntVal(Arg2)) < 0) 
      RunErr(205, &Arg2);
   if (defstr(&Arg3, sbuf2, &blank) == Error) 
      RunErr(0, NULL);

   if (strreq(cnt) == Error) 
      RunErr(0, NULL);

   if (StrLen(Arg3) == 0) {
      /*
       * The padding string is null; make it a blank.
       */
      slen = 1;
      s3 = " ";
      }
   else {
      slen = StrLen(Arg3);
      s3 = StrLoc(Arg3);
      }

   /*
    * Get Arg2 bytes of string space.  Start at the left end of the new
    *  string and copy Arg3 into the new string as many times as it fits.
    */
   sbuf = alcstr(NULL, cnt);
   s = sbuf;
   while (s < sbuf + cnt) {
      st = s3;
      while (st < s3 + slen && s < sbuf + cnt)
         *s++ = *st++;
      }

   /*
    * Copy Arg1 into the new string, starting at the right end and copying
    *  Arg3 from right to left.  If *Arg1 > Arg2, only copy Arg2 bytes.
    */
   s = sbuf + cnt;
   slen = StrLen(Arg1);
   st = StrLoc(Arg1) + slen;
   if (slen > cnt)
      slen = cnt;
   while (slen-- > 0)
      *--s = *--st;

   /*
    * Return the new string.
    */
   StrLen(Arg0) = cnt;
   StrLoc(Arg0) = sbuf;
   Return;
   }

/*
 * trim(s,c) - trim trailing characters in c from s.
 */

FncDcl(trim,2)
   {
   char *sloc;
   char sbuf[MaxCvtLen];
   int *cs, csbuf[CsetSize], cvted;
   static int spcset[CsetSize] = /* ' ' */

#if EBCDIC != 1
      cset_display(0, 0, 01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
#else					/* EBCDIC != 1 */
      cset_display(0, 0, 0, 0, 01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
#endif					/* EBCDIC != 1 */

   /*
    * Arg1 must be a string.
    */
   if ((cvted = cvstr(&Arg1, sbuf)) == CvtFail)
      RunErr(103, &Arg1);

   /*
    * Arg2 defaults to a cset containing a blank.
    */
   if (defcset(&Arg2, &cs, csbuf, spcset) == Error) 
      RunErr(0, NULL);

   /*
    * Start at the end of Arg1 and then back up until a character that is
    *  not in Arg2 is found.  The actual trimming is done by having a
    *  descriptor that points at a substring of Arg1, but with the length
    *  reduced.
    */
   Arg0 = Arg1;
   sloc = StrLoc(Arg1) + StrLen(Arg1) - 1;
   while (sloc >= StrLoc(Arg1) && Testb(ToAscii(*sloc), cs)) {
      sloc--;
      StrLen(Arg0)--;
      }

   /*
    * Save the temporary string in the string region if conversion was done.
    */
   if (cvted == Cvt) {
      if (strreq(StrLen(Arg0)) == Error) 
         RunErr(0, NULL);
      StrLoc(Arg0) = alcstr(StrLoc(Arg0), StrLen(Arg0));
      }
   Return;
   }
