/* User text formatting functions
   Copyright (C) 1992 Joseph H. Allen

This file is part of JOE (Joe's Own Editor)

JOE is free software; you can redistribute it and/or modify it under the 
terms of the GNU General Public License as published by the Free Software 
Foundation; either version 1, or (at your option) any later version.  

JOE is distributed in the hope that it will be useful, but WITHOUT ANY 
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
details.  

You should have received a copy of the GNU General Public License along with 
JOE; see the file COPYING.  If not, write to the Free Software Foundation, 
675 Mass Ave, Cambridge, MA 02139, USA.  */ 

#include "config.h"
#include "b.h"
#include "bw.h"
#include "zstr.h"
#include "ublock.h"
#include "uformat.h"

/* Center line cursor is on and move cursor to beginning of next line */

int ucenter(bw)
BW *bw;
 {
 P *p=bw->cursor, *q;
 long endcol, begcol, x;
 int c;
 
 peol(p);
 while(cwhite(c=prgetc(p)));
 if(c=='\n') { pgetc(p); goto done; }
 if(c==MAXINT) goto done;
 pgetc(p); endcol=piscol(p);
 
 pbol(p);
 while(cwhite(c=pgetc(p)));
 if(c=='\n') { prgetc(p); goto done; }
 if(c==MAXINT) goto done;
 prgetc(p); begcol=piscol(p);
 
 if(endcol-begcol>bw->o.rmargin+bw->o.lmargin) goto done;
 
 q=pdup(p); pbol(q); bdel(q,p); prm(q);
 
 for(x=0;x!=(bw->o.lmargin+bw->o.rmargin)/2-(endcol-begcol)/2;++x) binsc(p,' ');
 
 done:
 if(!pnextl(p))
  {
  binsc(p,'\n');
  pgetc(p);
  return -1;
  }
 else return 0;
 }

/* Return true if c is a character which can indent a paragraph */

int cpara(c)
 {
 if(c==' ' || c=='\t' || c=='\\' ||
    c=='>' || c=='|' || c==':' || c=='*' || c=='/' || c==',' || c=='.' ||
    c=='?' || c==';' || c==']' || c=='}' || c=='=' || c=='+' || c=='-' ||
    c=='_' || c==')' || c=='&' || c=='^' || c=='%' || c=='$' || c=='#' ||
    c=='@' || c=='!' || c=='~')
  return 1;
 else
  return 0;
 }

/* Return true if line is definitly not a paragraph line.
 * Lines which arn't paragraph lines:
 *  1) Blank lines
 *  2) Lines which begin with '.'
 */

int pisnpara(p)
P *p;
 {
 P *q;
 int c;
 q=pdup(p);
 pbol(q);
 while(cpara(c=pgetc(q)));
 prm(q);
 if(c=='.' || c=='\r' || c=='\n') return 1;
 else return 0;
 }

/* Determine amount of indentation on current line */

long nindent(p)
P *p;
 {
 P *q=pdup(p);
 long col;
 pbol(q);
 do col=q->col; while(cpara(pgetc(q)));
 prm(q);
 return col;
 }

/* Get indentation prefix column */

long prefix(p)
P *p;
 {
 long len;
 P *q=pdup(p);
 pbol(q);
 while(cpara(brc(q))) pgetc(q);
 while(!pisbol(q))
  if(!cwhite(prgetc(q)))
   {
   pgetc(q);
   break;
   }
 len=q->col;
 prm(q);
 return len;
 }

/* Move pointer to beginning of paragraph
 *
 * This function simply moves backwards until it sees:
 *  0) The beginning of the file
 *  1) A blank line
 *  2) A line with a different indentation prefix
 *  3) A line with indentation greater than that of the line we started with
 *  4) A line with indentation less than that of the starting line, but with
 *     a blank line (or beginning of file) preceeding it.
 */

int within=0;

P *pbop(p)
P *p;
 {
 long indent;
 long prelen;
 pbol(p); indent=nindent(p); prelen=prefix(p);
 while(!pisbof(p) && (!within || !markb || p->byte>markb->byte))
  {
  long ind;
  long len;
  pprevl(p); pbol(p); ind=nindent(p); len=prefix(p);
  if(pisnpara(p) || len!=prelen) { pnextl(p); break; }
  if(ind>indent) break;
  if(ind<indent)
   {
   if(pisbof(p)) break;
   pprevl(p); pbol(p);
   if(pisnpara(p)) { pnextl(p); break; }
   else { pnextl(p); pnextl(p); break; }
   }
  }
 return p;
 }

/* Move pointer to end of paragraph.  Pointer must already be on first
 * line of paragraph for this to work correctly.
 *
 * This function moves forwards until it sees:
 *  0) The end of the file.
 *  1) A blank line
 *  2) A line with indentation different from the second line of the paragraph
 *  3) A line with prefix column different from first line
 */

P *peop(p)
P *p;
 {
 long indent;
 long prelen;
 if(!pnextl(p) || pisnpara(p) ||
    (within && markk && p->byte>=markk->byte)) return p;
 indent=nindent(p);
 prelen=prefix(p);
 while(pnextl(p) && (!within || !markk || p->byte<markk->byte))
  {
  long ind=nindent(p);
  long len=prefix(p);
  if(ind!=indent || len!=prelen || pisnpara(p)) break;
  }
 return p;
 }

/* Motion commands */

int ubop(bw)
BW *bw;
 {
 P *q=pdup(bw->cursor);
 up: while(pisnpara(q) && !pisbof(q) &&
           (!within || !markb || q->byte>markb->byte)) pprevl(q);
 pbop(q);
 if(q->byte!=bw->cursor->byte)
  {
  pset(bw->cursor,q);
  prm(q);
  return 0;
  }
 else if(!pisbof(q))
  {
  prgetc(q);
  goto up;
  }
 else
  {
  prm(q);
  return -1;
  }
 }

int ueop(bw)
BW *bw;
 {
 P *q=pdup(bw->cursor);
 up: while(pisnpara(q) && !piseof(q)) pnextl(q);
 pbop(q); peop(q);
 if(q->byte!=bw->cursor->byte)
  {
  pset(bw->cursor,q);
  prm(q);
  return 0;
  }
 else if(!piseof(q))
  {
  pnextl(q);
  goto up;
  }
 else
  {
  prm(q);
  return -1;
  }
 }

/* Wrap word.  If 'french' is set, only one space will be placed
 * after . ? or !
 */

void wrapword(p,indent,french,indents)
P *p;
long indent;
char *indents;
 {
 P *q;
 int rmf=0;
 int c;
 long to=p->byte;

 /* Get indentation prefix from beginning of line */
/*
 if(!indents)
  {
  int f=0;
  P *r=pdup(p);
  pbol(r);
  q=pdup(r);
  while(cpara(c=brc(q)))
   {
   if(!cwhite(c)) f=1;
   pgetc(q);
   }
  if(f)
   {
   indents=brs(r,q->byte-r->byte); rmf=1;
   if(indents[0]=='/' && indents[1]=='*') indents[0]=' ';
   }
  prm(r); prm(q);
  }
*/

 /* Get to beginning of word */
 while(!pisbol(p) && piscol(p)>indent && !cwhite(prgetc(p)));

 /* If we found the beginning of a word... */
 if(!pisbol(p) && piscol(p)>indent)
  {
  /* Move q to two (or one if 'french' is set) spaces after end of previous
     word */
  q=pdup(p);
  while(!pisbol(q))
   if(!cwhite(c=prgetc(q)))
    {
    pgetc(q);
    if((c=='.'||c=='?'||c=='!') && q->byte!=p->byte && !french) pgetc(q);
    break;
    }
  pgetc(p);

  /* Delete space between start of word and end of previous word */
  to-=p->byte-q->byte;
  bdel(q,p);
  prm(q);

  /* Move word to beginning of next line */
  binsc(p,'\n'), ++to;
  if(p->b->o.crlf) ++to;
  pgetc(p);

  /* Indent to left margin */
  if(indents) binss(p,indents), to+=zlen(indents);
  else while(indent--) binsc(p,' '), ++to;

  if(rmf) free(indents);
  }

 /* Move cursor back to original position */
 pfwrd(p,to-p->byte);
 }

/* Reformat paragraph */

int uformat(bw)
BW *bw;
 {
 long indent;
 char *indents;
 char *buf, *b;
 int len;
 long curoff;
 int c;
 P *p, *q;
 p=pdup(bw->cursor); pbol(p);
 
 /* Do nothing if we're not on a paragraph line */
 if(pisnpara(p))
  {
  prm(p);
  return 0;
  }
 
 /* Move p to beginning of paragraph, bw->cursor to end of paragraph and
  * set curoff to original cursor offset within the paragraph */
 pbop(p);
 curoff=bw->cursor->byte-p->byte;
 pset(bw->cursor,p); peop(bw->cursor);
 
 /* Insure that paragraph ends on a beginning of a line */
 if(!pisbol(bw->cursor)) binsc(bw->cursor,'\n'), pgetc(bw->cursor);
 
 /* Record indentation of second line of paragraph, of first line if there
  * is only one line */
 q=pdup(p); pnextl(q);
 if(q->line!=bw->cursor->line)
  {
  P *r=pdup(q);
  indent=nindent(q);
  pcol(r,indent);
  indents=brs(q,r->byte-q->byte);
  prm(r);
  }
 else
  {
  P *r=pdup(p);
  indent=nindent(p);
  pcol(r,indent);
  indents=brs(p,r->byte-p->byte);
  prm(r);
  }
 prm(q);

 /* But if the left margin is greater, we use that instead */
 if(bw->o.lmargin>indent) indent=bw->o.lmargin;
 
 /* Cut paragraph into memory buffer */
 buf=(char *)malloc(len=(bw->cursor->byte-p->byte));
 brmem(p,buf,len);
 bdel(p,bw->cursor);
 
 /* text is in buffer.  insert it at cursor */
 
 /* Do first line */
 b=buf;
 
 while(len--)
  {
  /* Set cursor position if we're at original offset */
  if(b-buf==curoff) pset(bw->cursor,p);

  /* Get character from buffer */
  c= *b++;

  /* Stop if we found end of line */
  if(c=='\n' ||
     c=='\r' && len && *b=='\n') { ++len; --b; break; }

  /* Stop if we found white-space followed by end of line */
  if(cwhite(c))
   {
   char *r=b;
   int rlen=len;
   int z;
   while(rlen--)
    {
    z= *r++;
    if(z=='\n') break;
    if(!cwhite(z)) goto ok;
    }
   ++len; --b; break;
   ok:;
   }

  /* Insert character, advance pointer */
  binsc(p,c); pgetc(p);

  /* Do word wrap if we reach right margin */
  if(piscol(p)>bw->o.rmargin && !cwhite(c))
   {
   wrapword(p,indent,bw->o.french,indents);
   break;
   }
  }
 
 /* Do rest */
 
 while(len>0)
  if(cwhitel(*b) || *b=='\r')
   {
   int f=0;
   /* Set f if there are two spaces after . ? or ! instead of one */
   if((b[-1]=='.' || b[-1]=='?' || b[-1]=='!') && cwhitel(b[1])) f=1;

   /* Skip past the whitespace.  Skip over indentations */
   loop:

   if(*b=='\r' && len)
    {
    if(b-buf==curoff) pset(bw->cursor,p);
    ++b, --len;
    }

   if(*b=='\n' && len)
    {
    if(b-buf==curoff) pset(bw->cursor,p);
    ++b, --len;
    while(cpara(*b) && len)
     {
     if(b-buf==curoff) pset(bw->cursor,p);
     ++b, --len;
     }
    }

   if(len && cwhite(*b))
    {
    if(b-buf==curoff) pset(bw->cursor,p);
    ++b, --len;
    goto loop;
    }

   /* Insert proper amount of whitespace */
   if(len)
    {
    if(f && !bw->o.french) binsc(p,' '), pgetc(p);
    binsc(p,' '); pgetc(p);
    }
   }
  else
   {
   /* Insert characters of word and wrap if necessary */
   if(b-buf==curoff) pset(bw->cursor,p);
   binsc(p,*b++); --len; pgetc(p);
   if(piscol(p)>bw->o.rmargin) wrapword(p,indent,bw->o.french,indents);
   }
 
 binsc(p,'\n');
 prm(p);
 free(buf);
 free(indents);
 return 0;
 }

/* Format entire block */

extern int lightoff;

int ufmtblk(bw)
BW *bw;
 {
 if(markv(1) && bw->cursor->byte>=markb->byte && bw->cursor->byte<=markk->byte)
  {
  markk->end=1;
  utomarkk(bw);
  within=1;
  do
   ubop(bw), uformat(bw);
   while(bw->cursor->byte>markb->byte);
  within=0;
  markk->end=0;
  if(lightoff) unmark(bw);
  return 0;
  }
 else return uformat(bw);
 }
