#############################################################################
##
#W  brgrids.g             GAP 4 package `browse'                Thomas Breuer
##
##  This file contains some <C>SpecialGrid</C> functions.
##

#T TB, May 2007:
#T extended version of `NCurses.Grid',
#T supports ``indicating continuation'' and attributes for lines;
#T the optional components `tend', `bend', `lend', `rend' are `true'
#T if the shown grid is not continued at the top, bottom, left, and right,
#T respectively (this is also the default), and `false' otherwise;
#T the argument `attrs', if given, must be an integer
#T describing the attributes for the lines.

NCurses.GridExt:= function( win, args, attrs )
  local size, trow, brow, lcol, rcol, rowinds, colinds,
        tend, bend, lend, rend,
        tvis, bvis, ht, lvis, rvis, wdth, ld, lmr, i, j;

  size := NCurses.getmaxyx(win);
  if size = false then return false; fi;

  trow:= args.trow;
  brow:= args.brow;
  lcol:= args.lcol;
  rcol:= args.rcol;
  rowinds:= args.rowinds;
  colinds:= args.colinds;
  tend:= not IsBound( args.tend ) or args.tend = true;
  bend:= not IsBound( args.bend ) or args.bend = true;
  lend:= not IsBound( args.lend ) or args.lend = true;
  rend:= not IsBound( args.rend ) or args.rend = true;

  if not ForAll([trow, brow, lcol, rcol], IsInt) then return false; fi;
  if not ForAll(rowinds, IsInt) then return false; fi;
  if not ForAll(colinds, IsInt) then return false; fi;
  # find viewable rows and cols
  rowinds := Filtered(rowinds, i-> i >= 0 and i >= trow and
                                   i <= size[1]-1 and i <= brow);
  colinds := Filtered(colinds, i-> i >= 0 and i >= lcol and
                                   i <= size[2]-1 and i <= rcol);
  if IsEmpty( rowinds ) and IsEmpty( colinds ) then
    return false;
  fi;
  tvis := Maximum(trow, 0);
  bvis := Minimum(brow, size[1]);
  ht := bvis - tvis + 1;
  lvis := Maximum(lcol, 0);
  rvis := Minimum(rcol, size[2]);
  wdth := rvis - lvis + 1;
  # Set attributes for the lines.
  NCurses.wattrset( win, attrs );
  # draw vlines
  ld := NCurses.lineDraw;
  for i in colinds do
    NCurses.wmove(win, tvis, i);
    NCurses.wvline(win, ld.VLINE, ht);
  od;
  # draw hlines and handle crossings
  for i in rowinds do
    NCurses.wmove(win, i, lvis);
    NCurses.whline(win, ld.HLINE, wdth);
    if i = trow and tend then
      lmr := [ld.ULCORNER, ld.TTEE, ld.URCORNER];
    elif i = brow and bend then
      lmr := [ld.LLCORNER, ld.BTEE, ld.LRCORNER];
    else
      lmr := [ld.LTEE, ld.PLUS, ld.RTEE];
    fi;
    for j in colinds do
      NCurses.wmove(win, i, j);
      if j = lcol and lend then
        NCurses.waddch(win, lmr[1]);
      elif j = rcol and rend then
        NCurses.waddch(win, lmr[3]);
      else
        NCurses.waddch(win, lmr[2]);
      fi;
    od;
  od;
  # Reset the attributes.
  NCurses.wattrset( win, NCurses.attrs.NORMAL );
  return true;
end;


#############################################################################
##
#F  BrowseData.SpecialGridLineDraw( <t>, <data> )
##
##  When this special grid is used in a browse table,
##  nonempty row separators are overwritten with horizontal rows
##  that consist of the special character <C>NCurses.lineDraw.HLINE</C>,
##  non-blank characters in column separators are overwritten with vertical
##  rows that consist of the special character <C>NCurses.lineDraw.VLINE</C>,
##  and <Q>crossings</Q> of horizontal and vertical lines are handled as in
##  <Ref Func="NCurses.Grid"/>.
##  <P/>
##  In categorized browse tables, each set of table rows shown under
##  a category row gets a grid of its own,
##  and the separators below category rows are regarded as the top rows of
##  these grids.
##  So this special grid requires nonempty category separators if nonempty
##  row separators occur.
##  (Note that below a row separator, first the category rows and their
##  separators appear, and then the data rows and their separators.
##  If there is a nonempty row separator above the first data row
##  then it is overwritten by whitespace.)
##
BrowseData.SpecialGridLineDraw:= function( t, data )
    local win, entry, top, i;

    win:= t.dynamic.window;

    # Clear category separators, since they may exceed the last column.
    for entry in data.catSeparators do
      NCurses.wmove( win, entry[1], entry[2] );
      NCurses.waddstr( win, ListWithIdenticalEntries( entry[3], ' ' ) );
    od;

    # Replace the row and column separators in all four tables by the lines.
    for entry in data.gridsInfo do
      NCurses.GridExt( win, entry, NCurses.attrs.NORMAL );
    od;

    # Overwrite a row separator above the first data row
    # if the table has a category for the first data row.
    top:= t.dynamic.topleft[1];
    if IsOddInt( top )
       and t.dynamic.topleft[3] = 1
       and BrowseData.LengthCell( t, top, "vert" ) <> 0 then
      for i in [ top+1, top+3 .. Length( t.dynamic.indexRow ) - 1 ] do
        if BrowseData.LengthCell( t, i, "vert" ) <> 0 then
          if i in t.dynamic.categories[1] then
            NCurses.wmove( win, data.topmargin + data.headerLength
                                + BrowseData.HeightLabelsColTable( t ),
                           data.leftmargin );
            NCurses.waddstr( win, ListWithIdenticalEntries(
                NCurses.getmaxyx( win )[2] - data.leftmargin, ' ' ) );
          fi;
          break;
        fi;
      od;
    fi;

    # Print those category separators that do not overwrite grid top lines.
#T force printing of category separators involving attributes!
    for entry in data.catSeparators do
      if ForAll( data.gridsInfo, x -> entry[1] <> x.trow ) then
        NCurses.wmove( win, entry[1], entry[2] );
        NCurses.whline( win, NCurses.lineDraw.HLINE, entry[3] );
      fi;
    od;
end;


#############################################################################
##
#F  BrowseData.SpecialGridLineDrawPlus( <t>, <data> )
##
##  This function is used for example in the `Browse' method for matrices.
##
##  It draws the crossing of the two lines that separate the row and column
##  labels from the main table.
##  Note that the grids drawn by `BrowseData.SpecialGridLineDraw' belong to
##  one of the four subtables of a browse table, and it is not supported
##  that lines separate these tables.
##
BrowseData.SpecialGridLineDrawPlus:= function( t, data )
    local win;

    BrowseData.SpecialGridLineDraw( t, data );
    win:= t.dynamic.window;
    NCurses.wmove( win, data.gridsInfo[1].rowinds[1],
                        data.gridsInfo[1].colinds[1] );
    NCurses.waddch( win, NCurses.lineDraw.PLUS );
end;


#############################################################################
##
#F  BrowseData.SpecialGridTreeStyle( <t>, <data> )
##
##  *NOTE*:
##  This function does not yet support category separators
##  and cells of height larger than 1.
##  (And there is the general problem that when one expands all categories at
##  the end of the table, the programmatically hidden rows are taken into
##  account and then lead to unmotivated empty rows.)
##  (Scrolling by cells seems to be not appropriate for this application,
##  scrolling by characters would be better.)
##
BrowseData.SpecialGridTreeStyle:= function( t, data )
    local categories, catpos, win, i, fromrow, maxdepth, previousrow, scr,
          ymin, ymax, y, prevdatarow, pos, entry, level, currlevel, pos2,
          kmin, j, k, lastcategorypos, height, l;

    categories:= t.dynamic.categories;
    catpos:= categories[1];
    if IsEmpty( catpos ) or 2 < catpos[1] then
      # `BrowseData.SpecialGridTreeStyle' expects category rows,
      # starting in the first row.
      return;
    fi;
    win:= t.dynamic.window;
    i:= t.dynamic.topleft[1];
    fromrow:= t.dynamic.topleft[3];
    maxdepth:= Length( t.work.sepCol[1] );

    # The `i'-th entry in `previousrow' is the position of the previous
    # level `i' category if there is one on the screen, and zero otherwise.
    previousrow:= 0 * [ 0 .. maxdepth ];

    # Process the current line.
    scr:= BrowseData.HeightWidthWindow( t );
    ymin:= data.topmargin + BrowseData.HeightLabelsColTable( t )
                          + data.headerLength + 1;
    ymax:= scr[1] - data.bottommargin - data.footerLength;
    y:= ymin;
    prevdatarow:= fail;
    while y <= ymax+1 and IsBound( t.dynamic.indexRow[i] ) do
      # We consider row `ymax+1' but we do not print there.
      if i mod 2 = 0 then

        # Deal with categories, omitting the first `fromrow - 1' rows.
        pos:= PositionSorted( catpos, i );
#T Why is there no variant with a <from> argument?
        while pos <= Length( catpos ) and catpos[ pos ] = i do
          entry:= categories[2][ pos ];
          if entry.isUnderCollapsedCategory or entry.isRejectedCategory then
            # This category and all of higher level are hidden.
            break;
          fi;
          level:= entry.level;

          # Deal with the category line.
          if 1 < fromrow then
            # The beginning of the cell is above the screen.
            fromrow:= fromrow - 1;
          else
            # (We print no path to level 1 rows.)
            if 1 < level then
              if not IsBound( currlevel ) then
                # The current level is that of the previous category row.
                pos2:= pos;
                while 1 < pos2 and catpos[ pos2 ] = i do
                  pos2:= pos2 - 1;
                od;
                currlevel:= categories[2][ pos2 ].level;
              fi;

              # Print the grid part for this level above the current line.
              if   currlevel < level then
                # Fill the level `currlevel' column above the current line.
                if ymin < y and previousrow[ currlevel ] + 1 < y then
                  NCurses.wmove( win, y-2, 0 );
                  NCurses.waddstr( win, ListWithIdenticalEntries(
                                            2*currlevel-2, ' ' ) );
                  NCurses.wmove( win, y-2, 2*currlevel-2 );
                  NCurses.waddch( win, NCurses.lineDraw.LTEE );
                fi;
              elif currlevel = level then
                # Fill the level `currlevel' column above the current line.
                if previousrow[ currlevel ] = 0 then
                  kmin:= ymin;
                  NCurses.wmove( win, kmin-1, 0 );
                  NCurses.waddstr( win, ListWithIdenticalEntries(
                                            2*currlevel-4, ' ' ) );
                  NCurses.wmove( win, kmin - 1, 2*currlevel-4 );
                  NCurses.waddch( win, NCurses.lineDraw.VLINE );
                else
                  kmin:= previousrow[ currlevel ];
                  NCurses.wmove( win, kmin-1, 0 );
                  NCurses.waddstr( win, ListWithIdenticalEntries(
                                            2*currlevel-4, ' ' ) );
                  NCurses.wmove( win, kmin - 1, 2*currlevel-4 );
                  NCurses.waddch( win, NCurses.lineDraw.LTEE );
                fi;
                NCurses.wmove( win, kmin, 2 * ( currlevel-2 ) );
                NCurses.wvline( win, NCurses.lineDraw.VLINE, y - kmin - 1 );
              else
                # Fill the column for level `level' above the current line.
                if previousrow[ level ] = 0 then
                  kmin:= ymin;
                else
                  kmin:= previousrow[ level ];
                fi;
                NCurses.wmove( win, kmin-1, 0 );
                NCurses.waddstr( win, ListWithIdenticalEntries(
                                          2*level-4, ' ' ) );
                NCurses.wmove( win, kmin - 1, 2*level-4 );
                if previousrow[ level ] = 0 then
                  NCurses.waddch( win, NCurses.lineDraw.VLINE );
                else
                  NCurses.waddch( win, NCurses.lineDraw.LTEE );
                fi;
                NCurses.wmove( win, kmin, 2 * ( level-2 ) );
                NCurses.wvline( win, NCurses.lineDraw.VLINE, y - kmin - 1 );
              fi;
              if y <= ymax then
                NCurses.wmove( win, y-1, 0 );
                NCurses.waddstr( win, ListWithIdenticalEntries(
                                          2*level-4, ' ' ) );
                NCurses.wmove( win, y-1, 2*level-4 );
                NCurses.waddch( win, NCurses.lineDraw.LLCORNER );
              fi;
            fi;
            previousrow[ level ]:= y;
            currlevel:= level;
            lastcategorypos:= pos;

            y:= y + 1;
          fi;

          pos:= pos + 1;
        od;
      fi;

      # Deal with the data rows.
#T what about fromrow?
      height:= BrowseData.HeightRow( t, i );
      if 0 < height then
        if i mod 2 = 0 then
          if not IsBound( currlevel ) then
            # The first shown row is a data row.
            # Determine the level of the category row above the first line.
            pos:= PositionSorted( catpos, i );
            if IsBound( catpos[ pos ] ) and catpos[ pos ] = i then
              if BrowseData.HeightCategories( t, i ) < fromrow then
                # Take the last category row for `i'.
                while IsBound( catpos[ pos ] ) and catpos[ pos ] = i do
                  pos:= pos + 1;
                od;
              else
                # Take the last category row for `i' above the first line,
                # together with its category separator below.
                while IsBound( catpos[ pos ] )
                      and BrowseData.HeightCategories( t, i,
                              categories[2][ pos ].level ) < fromrow do
                  pos:= pos + 1;
                od;
              fi;
            fi;
            if 1 < pos then
              pos:= pos-1;
            fi;
            currlevel:= categories[2][ pos ].level;
            lastcategorypos:= pos;
          fi;

          # Fill the column for level `currlevel' above the current line.
          if prevdatarow = y-1 then
            NCurses.wmove( win, y-2, 0 );
            NCurses.waddstr( win, ListWithIdenticalEntries(
                                      2*currlevel-2, ' ' ) );
            NCurses.wmove( win, y-2, 2*currlevel-2 );
            NCurses.waddch( win, NCurses.lineDraw.LTEE );
          fi;
          if y <= ymax then
            NCurses.wmove( win, y-1, 0 );
            NCurses.waddstr( win, ListWithIdenticalEntries(
                                      2*currlevel-2, ' ' ) );
            NCurses.wmove( win, y-1, 2*currlevel-2 );
            NCurses.waddch( win, NCurses.lineDraw.LLCORNER );
          fi;
          prevdatarow:= y;
#T fill with LTEE only in the first line of a cell, otherwise with VLINE
          # Draw a horizontal line in row `y'.
          if y <= ymax then
            NCurses.wmove( win, y-1, 2 * currlevel - 1 );
            NCurses.whline( win, NCurses.lineDraw.HLINE,
                            2 * ( maxdepth - currlevel ) );
          fi;
        fi;
        y:= y + height - fromrow + 1;
      fi;
      i:= i + 1;
      fromrow:= 1;
    od;

    # Draw the missing vertical lines from and to outside categories:
    # If there is an unhidden category of level $k$ below and above
    # the next category of level less than $k$
    # then either replace the `LLCORNER' in column $k-1$ by an `LTEE' and
    # draw a `VLINE' below until the end of the window,
    # or in the case that there is no `LLCORNER' in column $k-1$
    # draw a `VLINE' through the whole window.
    for k in [ 2 .. Length( previousrow ) ] do
      if previousrow[k] >= previousrow[ k-1 ] then
        for pos2 in [ lastcategorypos + 1 .. Length( catpos ) ] do
          if   categories[2][ pos2 ].level = k
               and not categories[2][ pos2 ].isRejectedCategory then
            if previousrow[k] = 0 then
              NCurses.wmove( win, ymin - 1, 2 * ( k-2 ) );
              NCurses.wvline( win, NCurses.lineDraw.VLINE,
                              ymax - ymin + 1 );
            elif previousrow[k] <= ymax then
              NCurses.wmove( win, previousrow[k]-1, 2 * ( k-2 ) );
              NCurses.waddch( win, NCurses.lineDraw.LTEE );
              NCurses.wmove( win, previousrow[k], 2 * ( k-2 ) );
              NCurses.wvline( win, NCurses.lineDraw.VLINE,
                              ymax - previousrow[k] );
            fi;
            break;
          elif categories[2][ pos2 ].level < k
               and not categories[2][ pos2 ].isRejectedCategory then
            break;
          fi;
        od;
      fi;
    od;
end;


#############################################################################
##
#E

