/*      Copyright (C) 2001, 2002, 2003, 2004 Stijn van Dongen
 *
 * This file is part of Zoem. You can redistribute and/or modify Zoem under the
 * terms of the GNU General Public License;  either version 2 of the License or
 * (at your option) any later  version.  You should have received a copy of the
 * GPL along with Zoem, in the file COPYING.
*/

#include "op-format.h"

#include <ctype.h>
#include <stdlib.h>

#include "util.h"
#include "digest.h"
#include "key.h"
#include "curly.h"
#include "segment.h"
#include "read.h"
#include "parse.h"
#include "iface.h"

#include "util/ting.h"
#include "util/ding.h"
#include "util/err.h"


/* TODO
 *
 * sth to specify no padding at right side for centered
 * and substring alignment.  (to remove trailing spaces)
 *
 * for padding, might also need to apply length key if it is given,
 * same for delimiters.
 *
 * support for both left and right delimiter.
*/


long includein
(  long num
,  long lft
,  long rgt
)
   {  if (num < lft)
      return lft
   ;  if (num > rgt)
      return rgt
   ;  return num
;  }


int writeScopes
(  const mcxTing* fmt
,  int offset
,  mcxTing* sc1
,  mcxTing* sc2
)
   {  char* q = fmt->str + offset      /* should contain '{' */
   ;  int l

   ;  if (!yamBlock(fmt, offset, &l, sc1))
      return -1

   ;  q += l+1    /* should now be at start of final scope */

   ;  if (!yamBlock(fmt, q-fmt->str, &l, sc2))
      return -1

   ;  return ((q-fmt->str)-offset+l)
    /* should point just beyond final scope */
;  }


char* parsepart
(  mcxTing* fmt
,  char*    p
,  int*     justify
,  int*     width
,  mcxTing* scr
,  mcxTing* padpat
,  mcxTing* padstop
,  mcxTing* atpivot
,  mcxTing* atnum
,  mcxTing* lenkey
,  mcxTing* virarg
)  ;


yamSeg* yamFormat2
(  yamSeg*  seg
)
   {  mcxTing* fmt      =  mcxTingNew(arg1_g->str)
   ;  mcxTing* args     =  mcxTingNew(arg2_g->str)
   ;  mcxTing* scr      =  mcxTingEmpty(NULL, 2*args->len)
   ;  mcxTing* scr2     =  mcxTingEmpty(NULL, args->len)
   ;  mcxTing* scr3     =  mcxTingEmpty(NULL, args->len)
   ;  mcxTing* arg      =  mcxTingEmpty(NULL, args->len)
   ;  mcxTing* padpat   =  mcxTingEmpty(NULL, 10)
   ;  mcxTing* lenkey   =  mcxTingEmpty(NULL, 10)
   ;  mcxTing* atpivot  =  mcxTingEmpty(NULL, 10)
   ;  mcxTing* atnum    =  mcxTingEmpty(NULL, 10)
   ;  mcxTing* padstop  =  mcxTingEmpty(NULL, 10)
   ;  mcxTing* virarg   =  mcxTingEmpty(NULL, 10)
   ;  yamSeg* tmpseg    =  NULL
   ;  int x             =  0
   ;  char* p = NULL, *z = NULL
   ;  mcxbool ok = TRUE             /* somewhat convoluted ok logic */

   ;  if (yamDigest(args, args, seg) || yamDigest(fmt, fmt, seg))
      ok = FALSE

   ;  if (ok)
      {  p = fmt->str, z = p + fmt->len
      ;  tmpseg = yamStackPushTmp(args)
   ;  }

      if (ok)
      while ((x = yamParseScopes(tmpseg, 1, 0)) == 1)
      {  int justify = 1, width = 0, atoffset = 0
      ;  int arglen, virlen

      ;  ok = FALSE

      ;  mcxTingWrite(padpat, " ")
      ;  mcxTingEmpty(padstop, 0)
      ;  mcxTingEmpty(atpivot, 0)
      ;  mcxTingEmpty(atnum, 0)
      ;  mcxTingEmpty(lenkey, 0)
      ;  mcxTingEmpty(virarg, 0)
      ;  mcxTingEmpty(scr2, 0)
      ;  mcxTingEmpty(scr3, 0)

      ;  mcxTingWrite(arg, arg1_g->str)
      ;  arglen = arg->len
      ;  virlen = arglen

      ;  p  = 
         parsepart
         (  fmt
         ,  p
         ,  &justify
         ,  &width
         ,  scr
         ,  padpat
         ,  padstop
         ,  atpivot
         ,  atnum
         ,  lenkey
         ,  virarg
         )

      ;  if (!p)
         break

      ;  if (atnum->len)
         atoffset = atoi(atnum->str)

      ;  width = includein(width, 0, 4096)
      ;  atoffset = includein(atoffset, 0, 4096)

      ;  if (lenkey->len)
         {  mcxTing* tmp = mcxTingEmpty(NULL, 80)
         ;  mcxTingPrint(tmp, "\\%s{%s}", lenkey->str, virarg->str)

         ;  if (yamDigest(tmp, tmp, seg))

         ;  else
            virlen = atoi(tmp->str)

         ;  mcxTingFree(&tmp)
         ;  if (seg->flags & SEGMENT_INTERRUPT)
            break
      ;  }

         if (atoffset)
         {  const mcxTing* usearg = virarg->len ? virarg : arg
         ;  char* at  = strstr(usearg->str, atpivot->str)

         ;  int padsize = width - virlen - 2 * padstop->len
         ;  int myoffset = 0
         ;  int prefixlen = 0

         ;  if (at)
            {  mcxTing* prefix
               =  mcxTingNWrite(NULL, usearg->str, at-usearg->str+atpivot->len)
            ;  if (lenkey->len)
               {  mcxTing* tmp
                  =  mcxTingPrint(NULL, "\\%s{%s}", lenkey->str, prefix->str)

               ;  if (yamDigest(tmp, tmp, seg))

               ;  else
                  prefixlen = atoi(tmp->str)

               ;  mcxTingFree(&tmp)
            ;  }
               else
               prefixlen = prefix->len

            ;  mcxTingFree(&prefix)
            ;  if (seg->flags & SEGMENT_ERROR)
               break
         ;  }

            if (padsize > 0)     /* left justify the padding */
            {  mcxTingKAppend(scr2, padpat->str, ((width-1) / padpat->len)+1)
            ;  mcxTingShrink(scr2, width)

           /* the lines below provide right adjustment at the right side */
            ;  if (width % padpat->len)
               mcxTingAppend(scr3,padpat->str+(padpat->len-width%padpat->len))
            ;  mcxTingKAppend(scr3, padpat->str, width / padpat->len)
            ;  mcxTingSplice
               (scr2, scr3->str+(width+1)/2, (width+1)/2, width/2,width/2)

            ;  if (at)
               myoffset = atoffset - prefixlen - atpivot->len

            ;  myoffset = includein(myoffset, 0, width)
         ;  }
            mcxTingPrintSplice
            (  scr2
            ,  myoffset
            ,  virlen+2*padstop->len
            ,  "%s%s%s"
            ,  padstop->str
            ,  arg->str
            ,  padstop->str
            )
         ;  mcxTingNAppend(scr, scr2->str, scr2->len)
      ;  }
         else if (justify < 0)
         {  int padsize = width - virlen - padstop->len
         ;  if (padsize > 0)                    /* right justify the padding */
            {  if (width % padpat->len)
               mcxTingAppend(scr2, padpat->str + (padpat->len - width % padpat->len))
            ;  mcxTingKAppend(scr2, padpat->str, width / padpat->len)
            ;  mcxTingShrink(scr2, width)
         ;  }
            mcxTingPrintSplice
            (  scr2
            ,  0
            ,  virlen+padstop->len
            ,  "%s%s"
            ,  arg->str
            ,  padstop->str
            )
         ;  mcxTingNAppend(scr, scr2->str, scr2->len)
      ;  }
         else if (justify > 0)
         {  int padsize = width - virlen - padstop->len
         ;  int myoffset = 0
         ;  if (padsize > 0)     /* left justify the padding */
            {  mcxTingKAppend(scr2, padpat->str, ((width-1) / padpat->len)+1)
            ;  mcxTingShrink(scr2, width)
            ;  myoffset = padsize
         ;  }
            mcxTingPrintSplice
            (  scr2
            ,  padsize
            ,  virlen+padstop->len
            ,  "%s%s"
            ,  padstop->str
            ,  arg->str
            )
         ;  mcxTingNAppend(scr, scr2->str, scr2->len)
      ;  }
         else if (justify == 0)
         {  int padsize = width - virlen - 2 * padstop->len
         ;  int myoffset = 0
         ;  if (padsize > 0)     /* left justify the padding */
            {  mcxTingKAppend(scr2, padpat->str, ((width-1) / padpat->len)+1)
            ;  mcxTingShrink(scr2, width)

           /* the lines below provide right adjustment at the right side */
            ;  if (width % padpat->len)
               mcxTingAppend(scr3, padpat->str + (padpat->len - width % padpat->len))
            ;  mcxTingKAppend(scr3, padpat->str, width / padpat->len)
            ;  mcxTingSplice
               (scr2, scr3->str+(width+1)/2, (width+1)/2, width/2,width/2)

            ;  myoffset = padsize / 2
         ;  }
            mcxTingPrintSplice
            (  scr2
            ,  myoffset
            ,  virlen+2*padstop->len
            ,  "%s%s%s"
            ,  padstop->str
            ,  arg->str
            ,  padstop->str
            )
         ;  mcxTingNAppend(scr, scr2->str, scr2->len)
      ;  }
         ok = TRUE
   ;  }
      if (x != 0)
      ok = FALSE

   ;  if (ok && p != z)
      mcxTingNAppend(scr, p, z-p)

   ;  if (!ok && !(seg->flags & SEGMENT_INTERRUPT))
      seg->flags |= SEGMENT_ERROR

   ;  yamStackFreeTmp(&tmpseg)
   ;  mcxTingFree(&scr2)
   ;  mcxTingFree(&scr3)
   ;  mcxTingFree(&arg)
   ;  mcxTingFree(&padpat)
   ;  mcxTingFree(&lenkey)
   ;  mcxTingFree(&atpivot)
   ;  mcxTingFree(&atnum)
   ;  mcxTingFree(&padstop)
   ;  mcxTingFree(&virarg)
   ;  mcxTingFree(&args)
   ;  mcxTingFree(&fmt)
   ;  return yamSegPush(seg, scr)
;  }



char* parsepart
(  mcxTing* fmt
,  char*    p
,  int*     justify
,  int*     width
,  mcxTing* scr
,  mcxTing* padpat
,  mcxTing* padstop
,  mcxTing* atpivot
,  mcxTing* atnum
,  mcxTing* lenkey
,  mcxTing* virarg
)
   {  char* q         /* find first % occurrence which is not %% */
   ;  mcxbool  fmtend = FALSE
   ;  mcxbool  fmtok = TRUE
   ;  mcxbool  ok = FALSE
   ;  mcxTing* msg = mcxTingNew("format spec error")

   ;  while((q = strchr(p, '%')))
      {  if (q[1] == '%')
         {  p = q+2
         ;  continue
      ;  }

         if (q-p)
         mcxTingNAppend(scr, p, q-p)
      ;  break
   ;  }
      if (!q)
      mcxTingWrite(msg, "missing format specifier, spurious args")

                  /* do a loop to find the consecutive parts */
   ;  while (q)
      {  int c = (++q)[0]
      ;  int l

      ;  switch(c)
         {
      case '.' :
            fmtend = TRUE
         ;  break
         ;
      case '<' :
      case '>' :
      case '=' :
            *justify = (unsigned char) c - '='  /* fixme: ascii hack */
         ;  break
         ;
      case '~' :  
            q++
         ;  if ((l = writeScopes(fmt, q-fmt->str, padpat, padstop)) < 0)
               fmtok = FALSE
            ,  mcxTingWrite(msg, "format error in ~ part")
         ;  else
            q += l      /* at '}' */
         ;  break
         ;
      case '@' :  
            q++
         ;  if ((l = writeScopes(fmt, q-fmt->str, atpivot, atnum)) < 0)
               fmtok = FALSE
            ,  mcxTingWrite(msg, "format error in @ part")
         ;  else
            q += l    /* at '}' */
         ;  break
         ;
      case '*' :  
            q++
         ;  if ((l = writeScopes(fmt, q-fmt->str, lenkey, virarg)) < 0)
               fmtok = FALSE
            ,  mcxTingWrite(msg, "format error in * part")
         ;  else
            q += l      /* at '}' */
         ;  break
         ;
      case '1' : case '2' : case '3' :
      case '4' : case '5' : case '6' :
      case '7' : case '8' : case '9' :
            while (isdigit(q[0]))
            *width = 10 * *width + ((unsigned char) (q++)[0] - '0')
         ;  q--
         ;  break
         ;
      default  :
            fmtok = FALSE
         ;  if (c)
            mcxTingPrint(msg, "unexpected token [%c]", c)
         ;  else
            mcxTingPrint(msg, "unexpected end of format sequence (no dot)")
         ;  break
         ;
         }

         if (fmtend)
         {  ok = TRUE
         ;  break
      ;  }
         else if (!fmtok)
         break
   ;  }
      if (!ok)
      {  mcxErr("\\format#2", msg->str)
      ;  q = 0
   ;  }

      mcxTingFree(&msg)
   ;  return q ? q+1 : NULL
;  }



