/*@z44.c:Vertical Hyphenation:VerticalHyphenate()@****************************/
/*                                                                           */
/*  THE LOUT DOCUMENT FORMATTING SYSTEM (VERSION 3.43)                       */
/*  COPYRIGHT (C) 1991, 2008 Jeffrey H. Kingston                             */
/*                                                                           */
/*  Jeffrey H. Kingston (jeff@it.usyd.edu.au)                                */
/*  School of Information Technologies                                       */
/*  The University of Sydney 2006                                            */
/*  AUSTRALIA                                                                */
/*                                                                           */
/*  This program 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 3, or (at your option)      */
/*  any later version.                                                       */
/*                                                                           */
/*  This program 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 this program; if not, write to the Free Software              */
/*  Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA   */
/*                                                                           */
/*  FILE:         z44.c                                                      */
/*  MODULE:       Vertical Hyphenation                                       */
/*  EXTERNS:      VerticalHyphenate(), ConvertGalleyList()                   */
/*                                                                           */
/*****************************************************************************/
#include "externs.h"


/*****************************************************************************/
/*                                                                           */
/*  FirstDefiniteCompressed(x, link, y)                                      */
/*  NextDefiniteWithGapCompressed(x, link, y, g)                             */
/*                                                                           */
/*  Like FirstDefinite() and NextDefiniteWithGap(), except that these        */
/*  versions assume that x is of type VCAT, and they compress any VCAT       */
/*  objects found within x as they go.                                       */
/*                                                                           */
/*****************************************************************************/

#define FirstDefiniteCompressed(x, link, y)				\
{ BOOLEAN jn;								\
  FirstDefinite(x, link, y, jn);					\
  while( link != x && type(y) == VCAT )					\
  { TransferLinks(Down(y), y, link);					\
    DisposeChild(link);							\
    FirstDefinite(x, link, y, jn);					\
  }									\
  assert( link==x || is_definite(type(y)), "FirstDefiniteCompressed!");	\
}

#define NextDefiniteWithGapCompressed(x, link, y, g)			\
{ OBJECT start_link = PrevDown(link), ylink, yg, z;			\
  BOOLEAN jn;								\
  NextDefiniteWithGap(x, link, y, g, jn);				\
  while( link != x && type(y) == VCAT )					\
  { FirstDefinite(y, ylink, z, jn);					\
    if( ylink != y && PrevDown(ylink) != y )				\
    { Child(yg, PrevDown(ylink))					\
        ;								\
      assert( type(yg)==GAP_OBJ && mode(gap(yg)) != NO_MODE, "NDWGC!");	\
      MoveLink(PrevDown(ylink), Up(g), PARENT);				\
      MoveLink(Up(g), ylink, PARENT);					\
    }									\
    TransferLinks(Down(y), y, link);					\
    DisposeChild(link);							\
    link = NextDown(start_link);					\
    NextDefiniteWithGap(x, link, y, g, jn);				\
  }									\
  assert( link==x || is_definite(type(y)), "FirstDefiniteCompressed!");	\
  assert( link==x || mode(gap(g)) != NO_MODE,				\
    "FirstDefiniteWithGapCompressed: mode(gap(g))!" );			\
}


/*@@**************************************************************************/
/*                                                                           */
/*  OBJECT FindTarget(index)                                                 */
/*                                                                           */
/*  Work out what the given index is pointing at, or nilobj if nothing.      */
/*                                                                           */
/*****************************************************************************/

static OBJECT FindTarget(OBJECT index)
{ OBJECT res = nilobj;
  debug1(DVH, DD, "FindTarget(%s)", Image(type(index)));
  switch( type(index) )
  {
    case DEAD:

      res = nilobj;
      break;


    case UNATTACHED:
    case GALL_PREC:
    case GALL_FOLL:
    case GALL_FOLL_OR_PREC:

      res = pinpoint(index);
      break;


    case RECEPTIVE:
    case RECEIVING:
    case RECURSIVE:
    case SCALE_IND:
    case COVER_IND:
    case EXPAND_IND:

      res = actual(index);
      break;


    case PRECEDES:
    case FOLLOWS:
    case CROSS_TARG:
    case CROSS_PREC:
    case CROSS_FOLL:
    case CROSS_FOLL_OR_PREC:
    case PAGE_LABEL_IND:

      res = nilobj;  /* somewhat doubtful */
      break;


    case GALL_TARG:

      res = nilobj;  /* somewhat doubtful */
      break;


    default:

      assert1(FALSE, "FindTarget: unknown index", Image(type(index)));
      break;
  }
  debug1(DVH, DD, "FindTarget returning %s", EchoObject(res));
  return res;
} /* end FindTarget */


/*@@**************************************************************************/
/*                                                                           */
/*  OBJECT WhichComponent(target)                                            */
/*                                                                           */
/*  Return the component of the enclosing galley that contains target,       */
/*  or nilobj if some problem.                                               */
/*                                                                           */
/*****************************************************************************/

static OBJECT WhichComponent(OBJECT target)
{ OBJECT prnt;
  debug1(DVH, DD, "WhichComponent(%s)", EchoObject(target));
  while( Up(target) != target )
  { Parent(prnt, Up(target))
      ;
    if( type(prnt) == HEAD )
    { debug1(DVH, DD, "WhichComponent returning %s", EchoObject(target));
      return target;
    }
    target = prnt;
  }
  debug0(DVH, DD, "WhichComponent returning nilobj");
  return nilobj;
} /* end WhichComponent */


/*****************************************************************************/
/*                                                                           */
/*  OBJECT EncloseInHcat(nxt, y, replace)                                   */
/*                                                                           */
/*  Enclose object nxt in an HCAT, similar to HCAT y, at position replace.  */
/*  The link to nxt will now link to the new HCAT.                          */
/*                                                                           */
/*****************************************************************************/

static OBJECT EncloseInHcat(OBJECT nxt, OBJECT y, OBJECT replace)
{ OBJECT new_y, new_row_thread, s1, new_s1, s2, new_s2, link, sh, new_sh, tmp;
  assert( Up(nxt) != nxt, "EncloseInHCat: Up(nxt) == nxt!" );
  New(new_y, HCAT);
  adjust_cat(new_y) = FALSE;
  MoveLink(Up(nxt), new_y, CHILD);
  assert( Up(nxt) == nxt, "EncloseInHCat: Up(nxt) != nxt!" );
  FposCopy(fpos(new_y), fpos(y));
  back(new_y, COLM) = back(y, COLM);
  fwd(new_y, COLM) = fwd(y, COLM);
  back(new_y, ROWM) = back(nxt, ROWM);
  fwd(new_y, ROWM) = fwd(nxt, ROWM);
  New(new_row_thread, ROW_THR);
  back(new_row_thread, ROWM) = back(new_y, ROWM);
  fwd(new_row_thread, ROWM) = fwd(new_y, ROWM);
  thr_state(new_row_thread) = SIZED;
  for( link = Down(y);  link != y;  link = NextDown(link) )
  { Child(s1, link)
      ;
    if( type(s1) == GAP_OBJ )
    { New(new_s1, GAP_OBJ);
      FposCopy(fpos(new_s1), fpos(s1));
      GapCopy(gap(new_s1), gap(s1));
      hspace(new_s1) = hspace(s1);
      vspace(new_s1) = vspace(s1);
      Link(new_y, new_s1);
      continue;
    }
    if( type(s1) == WIDE || type(s1) == ONE_COL )
      Child(s2, Down(s1))
        ;
    else s2 = s1;
    assert( type(s2) == SPLIT, "EncloseInHcat: type(s2) != SPLIT!" );
    Child(sh, DownDim(s2, COLM))
      ;
    New(new_s2, SPLIT);
    FposCopy(fpos(new_s2), fpos(s2));
    if( s2 != s1 )
    { New(new_s1, type(s1));
      back(new_s1, COLM) = back(s1, COLM);
      fwd(new_s1, COLM) = fwd(s1, COLM);
      back(new_s1, ROWM) = back(new_row_thread, COLM);
      fwd(new_s1, ROWM) = fwd(new_row_thread, COLM);
      Link(new_y, new_s1);
      Link(new_s1, new_s2);
    }
    else Link(new_y, new_s2);
    if( sh == replace )
    { 
      /* replace sh by nxt in the copy */
      new_sh = nxt;
      back(new_sh, COLM) = back(s2, COLM);
      fwd(new_sh, COLM) = fwd(s2, COLM);
    }
    else
    {
      /* replace sh by an empty object of the same width in the copy */
      New(new_sh, WIDE);
      FposCopy(fpos(new_sh), fpos(sh));
      SetConstraint(constraint(new_sh), back(sh,COLM),size(sh,COLM),fwd(sh,COLM));
      back(new_sh, COLM) = back(sh, COLM);
      fwd(new_sh, COLM) = fwd(sh, COLM);
      back(new_sh, ROWM) = fwd(new_sh, ROWM) = 0;
      tmp = MakeWord(WORD, STR_EMPTY, &fpos(sh));
      back(tmp, COLM) = fwd(tmp, COLM) = 0;
      back(tmp, ROWM) = fwd(tmp, ROWM) = 0;
      underline(tmp) = UNDER_OFF;
      Link(new_sh, tmp);
    }
    Link(new_s2, new_sh);
    back(new_s2, COLM) = back(new_sh, COLM);
    fwd(new_s2, COLM) = fwd(new_sh, COLM);
    Link(new_s2, new_row_thread);
    back(new_s2, ROWM) = back(new_row_thread, ROWM);
    fwd(new_s2, ROWM) = fwd(new_row_thread, ROWM);
    Link(new_row_thread, new_sh);
  }
  return new_y;
} /* end EncloseInHcat */


/*@::VerticalHyphenate()@*****************************************************/
/*                                                                           */
/*  BOOLEAN VerticalHyphenate(OBJECT y)                                      */
/*                                                                           */
/*  Attempt to vertically hyphenate galley component y, of type HCAT.        */
/*                                                                           */
/*****************************************************************************/

BOOLEAN VerticalHyphenate(OBJECT y)
{ OBJECT large_comp, index, z, link, g, large_comp_split = nilobj;
  OBJECT row_thread, s1, s2, sh, sv, shp, prev = nilobj, nxt = nilobj;
  FULL_LENGTH rump_fwd;
  debug1(DVH, D, "[ VerticalHyphenate(y: %s), y =", EchoLength(size(y, ROWM)));
  ifdebug(DVH, D, DebugObject(y));
  debug0(DVH, DD, "galley before vertical hyphenation:");
  ifdebug(DVH, DD, Parent(z, Up(y)); DebugGalley(z, y, 2));

  /* find large_comp, the largest VCAT component, or else return FALSE */
  row_thread = large_comp = nilobj;
  rump_fwd = 0;
  assert( type(y) == HCAT, "VerticalHyphenate: type(y) != HCAT!" );
  for( link = Down(y);  link != y;  link = NextDown(link) )
  { Child(s1, link)
      ;
    if( type(s1) == GAP_OBJ )
    { if( !join(gap(s1)) )
      { debug0(DVH, D, "] VerticalHyphenate returning FALSE (not joined)");
	return FALSE;
      }
      continue;
    }

    /* check that s2 is a SPLIT object whose children look right */
    if( type(s1) == WIDE || type(s1) == ONE_COL )
      Child(s2, Down(s1))
        ;
    else s2 = s1;
    if( type(s2) != SPLIT )
    { debug0(DVH, D, "] VerticalHyphenate returning FALSE (child not SPLIT)");
      return FALSE;
    }
    Child(sh, DownDim(s2, COLM))
      ;
    Child(sv, DownDim(s2, ROWM))
      ;
    if( type(sv) != ROW_THR )
    { debug0(DVH, D, "] VerticalHyphenate returning FALSE (no ROW_THR)");
      return FALSE;
    }
    if( row_thread == nilobj )  row_thread = sv;
    if( sv != row_thread )
    { debug0(DVH, D, "] VerticalHyphenate returning FALSE (different ROW_THR)");
      return FALSE;
    }
    Parent(shp, UpDim(sh, ROWM))
      ;
    if( shp != row_thread )
    { debug0(DVH, D, "] VerticalHyphenate returning FALSE (sh parent)");
      return FALSE;
    }

    /* Now sh is one of the HCAT components */
    if( type(sh) != VCAT )
    { rump_fwd = find_max(rump_fwd, fwd(sh, ROWM));
    }
    else if( large_comp != nilobj )
    { debug0(DVH, D, "] VerticalHyphenate returning FALSE (two VCATs)");
      return FALSE;
    }
    else
    { large_comp = sh;
      large_comp_split = s2;
    }
  }

  /* if no large_comp, return */
  if( large_comp == nilobj )
  { debug0(DVH, D, "] VerticalHyphenate returning FALSE (no VCAT)");
    return FALSE;
  }

  /* check that large_comp has at least two components */
  FirstDefiniteCompressed(large_comp, link, prev);
  if( link == large_comp )
  { debug0(DVH,D, "] VerticalHyphenate returning FALSE (VCAT: no components)");
    return FALSE;
  }
  NextDefiniteWithGapCompressed(large_comp, link, nxt, g);
  if( link == large_comp )
  { debug0(DVH,D, "] VerticalHyphenate returning FALSE (VCAT: one component)");
    return FALSE;
  }

  /* make sure that first gap does not change when rearranging */
  rump_fwd = find_max(rump_fwd, fwd(prev, ROWM));
  if( MinGap(rump_fwd, back(nxt, ROWM), fwd(nxt, ROWM), &gap(g)) !=
      MinGap(fwd(prev, ROWM), back(nxt, ROWM), fwd(nxt, ROWM), &gap(g)) )
  { debug0(DVH, D, "] VerticalHyphenate returning FALSE (first gap changes)");
    return FALSE;
  }

  /* check that large_comp has no joins */
  for( link = Down(large_comp);  link != large_comp;  link = NextDown(link) )
  { Child(z, link)
      ;
    if( type(z) == GAP_OBJ && mode(gap(z)) != NO_MODE && join(gap(z)) )
    { debug0(DVH, D, "] VerticalHyphenate returning FALSE (VCAT: joined)");
      return FALSE;
    }
  }

  /* enclose all definite components after the first in HCATs */
  for( link = NextDown(Up(prev));  link != large_comp;  link = NextDown(link) )
  { Child(nxt, link)
      ;
    if( type(nxt) == GAP_OBJ )  continue;
    if( is_definite(type(nxt)) )
      nxt = EncloseInHcat(nxt, y, large_comp);
  }

  /* move all components after the first to the top level */
  TransferLinks(Up(g), large_comp, NextDown(Up(y)));

  /* change the size of y to its new, smaller value */
  fwd(y, ROWM) = fwd(row_thread, ROWM) = fwd(large_comp, ROWM)
	      = fwd(large_comp_split, ROWM) = fwd(prev, ROWM);

  /* set link to the link of the first thing before y which is not an index */
  for( link = PrevDown(Up(y));  type(link) == LINK;  link = PrevDown(link) )
  { Child(index, link)
      ;
    if( !is_index(type(index)) )  break;
  }

  /* for each index, find where it's pointing and possibly move it */
  while( NextDown(link) != Up(y) )
  { Child(index, NextDown(link))
      ;
    assert( is_index(type(index)), "MoveIndexes: is_index!" );
    z = FindTarget(index);
    if( z != nilobj )
    { z = WhichComponent(z);
      if( z != nilobj && z != y )
      { MoveLink(NextDown(link), Up(z), PARENT);
      }
      else link = NextDown(link);
    }
    else link = NextDown(link);
  }

  debug1(DVH, D, "] VerticalHyphenate returning TRUE (y: %s)",
    EchoLength(size(y, ROWM)));
  debug0(DVH, DD, "galley after vertical hyphenation:");
  ifdebug(DVH, DD, Parent(z, Up(y)); DebugGalley(z, y, 2));
  return TRUE;
} /* end VerticalHyphenate */


/*****************************************************************************/
/*                                                                           */
/*  static OBJECT BuildMergeTree(int n, OBJECT x, OBJECT *lenv, *lact)       */
/*                                                                           */
/*  Build a balanced tree of n-1 @Merge symbols, whose parameters are the    */
/*  first n children of x.  Return in lenv the environment of the root       */
/*  @Merge symbol, and in *lact the symbol table entry for the parent of     */
/*  this @Merge symbol.                                                      */
/*                                                                           */
/*****************************************************************************/

static OBJECT BuildMergeTree(int n, OBJECT x, OBJECT *lenv, OBJECT *lact)
{ OBJECT res, merge, link, y = nilobj, l, r, env, act, left_par, right_par;
  debug2(DHY, DD, "BuildMergeTree(%d, %s, -. -)", n, EchoObject(x));

  if( n == 1 )
  { New(res, ENV_OBJ);
    Child(y, Down(x))
      ;
    MoveLink(Down(x), res, PARENT);
    assert(type(y)==CLOSURE && has_merge(actual(y)), "BuildMergeTree: has_m!");
    *lact = actual(y);
    *lenv = DetachEnv(y);
    AttachEnv(*lenv, res);
  }
  else
  {
    /* build the two subtrees */
    l = BuildMergeTree(n/2, x, lenv, lact);
    r = BuildMergeTree( n - n/2, x, &env, &act);

    /* set merge to new @Merge closure */
    for( link = Down(act);  link != act;  link = NextDown(link) )
    { Child(y, link)
        ;
      if( is_merge(y) )  break;
    }
    assert( y != act, "BuildMergeTree: y!" );
    New(merge, CLOSURE);
    actual(merge) = y;

    /* build left parameter of the new @Merge */
    New(left_par, PAR);
    actual(left_par) = ChildSym(y, LPAR);
    Link(merge, left_par);
    Link(left_par, l);

    /* build right parameter of the new @Merge */
    New(right_par, PAR);
    actual(right_par) = ChildSym(y, RPAR);
    Link(merge, right_par);
    Link(right_par, r);

    New(res, ENV_OBJ);
    Link(res, merge);
    Link(res, env);
  }

  debug2(DHY, DD, "BuildMergeTree returning %s (*lact = %s)",
    EchoObject(res), SymName(*lact));
  return res;
} /* end BuildMergeTree */


/*****************************************************************************/
/*                                                                           */
/*  OBJECT ConvertGalleyList(x)                                              */
/*                                                                           */
/*  Convert a set of galleys x into a single galley containing a balanced    */
/*  tree of @Merge symbols.                                                  */
/*                                                                           */
/*****************************************************************************/

OBJECT ConvertGalleyList(OBJECT x)
{ OBJECT res, y, link, junk1, junk2, obj;  int n;
  debug1(DHY, DD, "ConvertGalleyList(%s)", EchoObject(x));
  Child(res, Down(x))
    ;
  Child(y, Down(res))
    ;
  MoveLink(Down(x), y, CHILD);
  DeleteLink(Down(res));
  MoveLink(Up(x), res, CHILD);
  for( link = Down(x), n = 0;  link != x;  link = NextDown(link), n++ );
  y = BuildMergeTree(n, x, &junk1, &junk2);
  assert( Down(x) == x && Up(x) == x, "ConvertGalleyList: x!" );
  Dispose(x);
  Child(obj, Down(y))
    ;
  MoveLink(Down(y), res, PARENT);
  MoveLink(LastDown(y), obj, PARENT);
  assert( Down(y) == y && Up(y) == y, "ConvertGalleyList: y!" );
  Dispose(y);
  debug0(DHY, DD, "ConvertGalleyList returning, res =");
  ifdebug(DHY, DD, DebugObject(res));
  return res;
} /* end ConvertGalleyList */


/*****************************************************************************/
/*                                                                           */
/*  OBJECT BuildEnclose(hd)                                                  */
/*                                                                           */
/*  Build the @Enclose object for galley hd.                                 */
/*                                                                           */
/*****************************************************************************/

OBJECT BuildEnclose(OBJECT hd)
{ OBJECT sym = nilobj, parsym, x, y, link, par, val, env, res;
  debug1(DOM, D, "BuildEnclose(%s)", SymName(actual(hd)));

  /* find @Enclose symbol and check that it has just one parameter */
  for( link = Down(actual(hd));  link != actual(hd);  link = NextDown(link) )
  { Child(sym, link)
      ;
    if( is_enclose(sym) )  break;
  }
  assert( link != actual(hd), "BuildEnclose: no enclose!" );
  parsym = nilobj;
  for( link = Down(sym);  link != sym;  link = NextDown(link) )
  { Child(y, link)
      ;
    switch( type(y) )
    {
	case LPAR:
	case NPAR:

	  Error(44, 1, "%s may not have a left or named parameter", FATAL,
	    &fpos(y), KW_ENCLOSE);
	  break;


	case RPAR:

	  if( has_body(sym) )
	    Error(44, 2, "%s may not have a body parameter", FATAL,
	      &fpos(y), KW_ENCLOSE);
	  parsym = y;
	  break;


	default:

	  break;
    }
  }
  if( parsym == nilobj )
    Error(44, 3, "%s must have a right parameter", FATAL, &fpos(sym),KW_ENCLOSE);

  /* set x to new @Enclose closure with dummy actual right parameter */
  New(x, CLOSURE);
  FposCopy(fpos(x), fpos(hd));
  actual(x) = sym;
  New(par, PAR);
  FposCopy(fpos(par), fpos(hd));
  actual(par) = parsym;
  Link(x, par);
  val = MakeWord(WORD, AsciiToFull("???"), &fpos(hd));
  Link(par, val);

  /* set env to the appropriate environment for this symbol */
  /* strictly speaking y should not be included if sym is a parameter */
  Child(y, Down(hd))
    ;
  assert(type(y) == CLOSURE, "BuildEnclose:  hd child!");
  y = CopyObject(y, &fpos(hd));
  env = SetEnv(y, nilobj);

  /* build res, an ENV_OBJ with x at left and env at right */
  New(res, ENV_OBJ);
  Link(res, x);
  Link(res, env);

  debug1(DOM, D, "BuildEnclose returning %s", EchoObject(res));
  return res;
} /* end BuildEnclose */
