#############################################################################
##
#W  atlasbrowse.g         GAP 4 package `browse'                Thomas Breuer
##


#############################################################################
##
#V  BrowseData.AtlasInfoActionShowOverview
##
##  is used in the tables constructed by `BrowseData.AtlasInfoGroupTable' and
##  `BrowseData.AtlasInfoOverview'.
##
BrowseData.AtlasInfoActionShowOverview:= [
    [ [ "Y" ], rec( helplines := [
         "show an overview of selected entries" ],
      action := function( t )
        local str, grps, grp, line;

        str:= "";
        grps:= Set( List( t.dynamic.viewReturn, x -> x[1] ) );
        for grp in grps do
          Append( str, "G = " );
          Append( str, grp );
          Append( str, ":\n" );
          for line in Filtered( t.dynamic.viewReturn,
                                x -> x[1] = grp ) do
            Append( str, "  " );
            Append( str, line[2] );
            Append( str, "\n" );
          od;
          Append( str, "\n" );
        od;
        if IsEmpty( str ) then
          str:= "(nothing was chosen yet)";
        fi;

        if BrowseData.IsDoneReplay( t.dynamic.replay ) then
          NCurses.Pager( str );
          NCurses.update_panels();
          NCurses.doupdate();
          NCurses.curs_set( 0 );
        fi;
        return t.dynamic.changed;
      end ) ],
  ];


#############################################################################
##
#F  BrowseData.AtlasInfoGroupTable( <conditions>, <log>, <replay>, <t> )
##
##  This function is called by `BrowseData.AtlasInfoOverview' when a row in
##  the overview table is clicked.
##  It returns a browse table with the details for the chosen group,
##  which is shown via a second level `Browse' call.
##
BrowseData.AtlasInfoGroupTable:= function( conditions, log, replay, t )
    local gapname, cats, labelsRow, info, inforeps, name, infoprgs, prgslist,
          i, entry, sel_action, header, footer, showTables, modes, mode,
          table;

    gapname:= AGR.GAPName( conditions[1] );
    cats:= [];
    labelsRow:= [];
    info:= [];

    # Construct the information about representations for `gapname'.
    inforeps:= AGR.InfoReps( conditions );
    if 0 < Length( inforeps.list ) then
      Add( cats, rec( pos:= 2,
                      level:= 1,
                      value:= Concatenation( inforeps.header ),
                      separator:= "",
                      isUnderCollapsedCategory:= false,
                      isRejectedCategory:= false ) );
      Append( labelsRow,
              List( inforeps.list, x -> [ Concatenation( x[1] ) ] ) );
      Append( info,
              List( inforeps.list,
                    x -> [ rec( rows:= [ BrowseData.SimplifiedString(
                                             Concatenation( x[2] ) ) ],
                                align:= "l" ),
                           rec( rows:= [ BrowseData.SimplifiedString(
                                             Concatenation( x[3] ) ) ],
                                align:= "l" ) ] ) );
    fi;

    # Construct the information about scripts for `gapname'.
    infoprgs:= AGR.InfoPrgs( conditions );
    if ForAny( infoprgs.list, x -> not IsEmpty( x ) ) then
      Add( cats, rec( pos:= 2 * Length( info ) + 2,
                      level:= 1,
                      value:= Concatenation( infoprgs.header ),
                      separator:= "",
                      isUnderCollapsedCategory:= false,
                      isRejectedCategory:= false ) );

      prgslist:= [];

      for i in [ 1 .. Length( infoprgs.list ) ] do
        entry:= infoprgs.list[i];
        if not IsEmpty( entry ) then
          if   IsString( entry[2] ) then
            Add( info,
                 List( entry{ [ 1, 2 ] },
                       x -> rec( rows:= [ BrowseData.SimplifiedString( x ) ],
                                 align:= "l" ) ) );
            Add( labelsRow, [ "" ] );
            Add( prgslist, [ i, entry ] );
          else
            Add( cats, rec( pos:= 2 * Length( info ) + 2,
                            level:= 2,
                            value:= entry[1],
                            separator:= "",
                            isUnderCollapsedCategory:= false,
                            isRejectedCategory:= false ) );
            Append( info,
                List( entry{ [ 2 .. Length( entry ) ] },
                      x -> List( x{ [ 1, 2 ] },
                                 y -> rec( rows:= [ BrowseData.SimplifiedString( y ) ],
                                           align:= "l" ) ) ) );
            Append( labelsRow,
                List( [ 2 .. Length( entry ) ], x -> [ "" ] ) );
            Append( prgslist,
                List( entry{ [ 2 .. Length( entry ) ] }, x -> [ i, x ] ) );
          fi;
        fi;
      od;
    fi;

    # Provide functionality for the `Click' function, the header, the footer,
    # and a modified `showTables' function.
    sel_action:= rec(
      helplines:= [ "add the selected entry to the result" ],
      action:= function( t )
        local info, pos, line, i, data, choice;

        if t.dynamic.selectedEntry <> [ 0, 0 ] then
          # Set the return value and close the window.
          pos:= t.dynamic.indexRow[ t.dynamic.selectedEntry[1] ] / 2;
          if pos <= Length( inforeps.list ) then
            info:= inforeps.list[ pos ];
            pos:= t.work.labelsRow[ pos ][1];
            pos:= pos{ [ 1 .. Length( pos ) - 1 ] };
            line:= BrowseData.SimplifiedString( Concatenation( info[2] ) );
            if not IsEmpty( info[3] ) then
              Append( line, " (" );
              Append( line, BrowseData.SimplifiedString(
                                Concatenation( info[3] ) ) );
              Append( line, ")" );
            fi;
            choice:= [ gapname, Int( pos ) ];
          else
            pos:= pos - Length( inforeps.list );
            i:= prgslist[ pos ][1];
            data:= prgslist[ pos ][2];
            choice:= data[3];
            if ForAll( infoprgs.list[i], IsString ) then
              line:= JoinStringsWithSeparator(
                         Filtered( data{ [ 1, 2 ] }, x -> x <> "" ),
                         "  " );
            elif infoprgs.nams[i] = "maxes" then
              line:= NormalizedWhitespace( Concatenation( "max. no. ",
                         data[1] ) );
            else
              line:= data[1];
            fi;
          fi;
          if choice in t.dynamic.Return then
            t.dynamic.currentFooterRow:= Concatenation( line,
                " was already chosen" );
          else
            Add( t.dynamic.Return, choice );
            Add( t.dynamic.viewReturn, [ gapname, line ] );
            t.dynamic.currentFooterRow:= Concatenation( line,
                " added to the result" );
          fi;
        fi;
      end );

    header:= Concatenation( "AtlasRep info for ", gapname );
    if 1 < Length( conditions ) then
      Append( header, " (selected entries)" );
    fi;

    footer:= t -> [ [ NCurses.attrs.UNDERLINE, true,
                      RepeatedString( " ",
                          BrowseData.HeightWidthWindow( t )[2] ) ],
                    t.dynamic.currentFooterRow ];

    showTables:= function( t )
      BrowseData.ShowTables( t );
      t.dynamic.currentFooterRow:= "";
      end;

    # Construct the modified modes if necessary.
    if not IsBound( BrowseData.defaults.work.customizedModes.atlasbrowse2 )
       then
      # Create a shallow copy of each default mode for `Browse',
      # modify the `showTables' function,
      # and add a new action to all available modes (except the help mode):
      # - Y: Show a pager with the entries that have been selected.
      modes:= List( BrowseData.defaults.work.availableModes,
                    BrowseData.ShallowCopyMode );
      for mode in modes do
        if mode.name in [ "select_entry", "select_row",
             "select_row_and_entry", "select_column_and_entry" ] then
          mode.ShowTables:= showTables;
        fi;
        if mode.name <> "help" then
          BrowseData.SetActions( mode,
              BrowseData.AtlasInfoActionShowOverview );
        fi;
      od;
      BrowseData.defaults.work.customizedModes.atlasbrowse2:= modes;
    else
      modes:= BrowseData.defaults.work.customizedModes.atlasbrowse2;
    fi;

    # Construct the browse table.
    table:= rec(
      work:= rec(
        align:= "tl",
        header:= [ "", [ NCurses.attrs.UNDERLINE, true, header ], "" ],
        footer:= rec(
          select_entry:= footer,
          select_row:= footer,
          select_row_and_entry:= footer,
          select_column_and_entry:= footer,
        ),
        availableModes:= modes,
        main:= info,
        labelsRow:= labelsRow,
        sepLabelsRow:= [ " ", "" ],
        sepCol:= [ " ", " ", "" ],
        Click:= rec(
          select_entry:= sel_action,
          select_row:= sel_action,
          select_row_and_entry:= sel_action,
          select_column_and_entry:= sel_action,
        ),
      ),
      dynamic:= rec(
        categories:= [ List( cats, x -> x.pos ), cats, [ 1 ] ],
        initialSteps:= [ 115, 114 ],
        Return:= [],
        currentFooterRow:= "",
        viewReturn:= [],
      ),
    );
    if log <> fail then
      table.dynamic.log:= log;
      table.dynamic.replay:= replay;
    fi;
    if t <> fail then
      table.dynamic.Return:= t.dynamic.Return;
      table.dynamic.viewReturn:= t.dynamic.viewReturn;
    fi;

    return table;
    end;


#############################################################################
##
#F  BrowseData.AtlasInfoOverview( <gapnames>, <conditions>, <log>, <replay> )
##
##  Part of the code is analogous to `AGR.StringAtlasInfoOverview'.
##
BrowseData.AtlasInfoOverview:= function( gapnames, conditions, log, replay )
    local tocs, rep_rest_funs, only_if_rep, columns, len, type, choice,
          mark, i, sel_action, header, modes, mode, table;

    tocs:= AGR.TablesOfContents( conditions );

    # Consider only those names for which actually information is available.
    # (The ordering shall be the same as in the input.)
    if gapnames = "all" then
      gapnames:= AtlasOfGroupRepresentationsInfo.GAPnamesSortDisp;
    else
      gapnames:= Filtered( List( gapnames, AGR.InfoForName ),
                           x -> x <> fail );
    fi;
    gapnames:= Filtered( gapnames,
                   x -> ForAny( tocs, toc -> IsBound( toc.( x[2] ) ) ) );
    if IsEmpty( gapnames ) then
      return [];
    fi;

    # If 'conditions' restricts the representations then omit rows
    # with empty representations part.
    rep_rest_funs:= [ Characteristic, Dimension, Identifier, IsMatrixGroup,
                      IsPermGroup, IsPrimitive, IsTransitive, NrMovedPoints,
                      RankAction, Ring, Transitivity ];
    only_if_rep:= ForAny( conditions, x -> x in rep_rest_funs );

    # Compute the data of the columns.
    columns:= [ [ "G", "l", List( gapnames, x -> [ x[1], false ] ) ] ];
    for type in AGR.DataTypes( "rep", "prg" ) do
      if type[2].DisplayOverviewInfo <> fail then
        Add( columns, [
             type[2].DisplayOverviewInfo[1],
             type[2].DisplayOverviewInfo[2],
             List( gapnames,
                   n -> type[2].DisplayOverviewInfo[3](
                            Concatenation( [ n ], conditions ) ) ) ] );
        if only_if_rep then
          if type[3] = "rep" then
            len:= Length( columns );
          fi;
        else
          len:= Length( columns );
        fi;
      fi;
    od;

    # Restrict the lists to the nonempty rows.
    choice:= [];
    mark:= UserPreference( "AtlasRep", "AtlasRepMarkNonCoreData" );
    if mark = fail then
      if IsBound( AtlasOfGroupRepresentationsInfo.markprivate ) then
        mark:= AtlasOfGroupRepresentationsInfo.markprivate;
      else
        mark:= "*";
      fi;
    fi;
    for i in [ 1 .. Length( gapnames ) ] do
      if ForAny( [ 2 .. len ],
                 c -> columns[c][3][i][1] <> "" ) then
        Add( choice, i );

        # Evaluate the privacy flag.
        if ForAny( columns, x -> x[3][i][2] ) then
          columns[1][3][i][1]:= Concatenation( columns[1][3][i][1], mark );
        fi;
      fi;
    od;

    if IsEmpty( choice ) then
      return [];
    fi;

    sel_action:= function( t )
      local name, tt;

      if t.dynamic.selectedEntry <> [ 0, 0 ] then
        name:= gapnames[ choice[ t.dynamic.indexRow[
                             t.dynamic.selectedEntry[1] ] / 2 ] ][1];
        tt:= BrowseData.AtlasInfoGroupTable(
               Concatenation( [ name ], conditions ),
               t.dynamic.log, t.dynamic.replay, t );
        if IsEmpty( tt.work.main ) then
          BrowseData.AlertWithReplay( t,
            Concatenation( "There are no data for the group", name, "." ),
            NCurses.attrs.BOLD );
        else
          NCurses.BrowseGeneric( tt );
          if tt.dynamic.interrupt then
            BrowseData.actions.QuitTable.action( t );
          else
            t.dynamic.Return:= tt.dynamic.Return;
            t.dynamic.viewReturn:= tt.dynamic.viewReturn;
          fi;
        fi;
      fi;
    end;

    header:= "Atlas Of Group Representations: Overview";
    if gapnames <> "all" or not IsEmpty( conditions ) then
      Append( header, " (selected entries)" );
    fi;

    # Construct the modified modes if necessary.
    if not IsBound( BrowseData.defaults.work.customizedModes.atlasbrowse1 )
       then
      # Create a shallow copy of each default mode for `Browse'
      # and add a new action to all available modes (except the help mode):
      # - Y: Show a pager with the entries that have been selected.
      modes:= List( BrowseData.defaults.work.availableModes,
                    BrowseData.ShallowCopyMode );
      for mode in modes do
        if mode.name <> "help" then
          BrowseData.SetActions( mode,
              BrowseData.AtlasInfoActionShowOverview );
        fi;
      od;
      BrowseData.defaults.work.customizedModes.atlasbrowse1:= modes;
    else
      modes:= BrowseData.defaults.work.customizedModes.atlasbrowse1;
    fi;

    # Construct and show the browse table.
    table:= rec(
      work:= rec(
        align:= "tl",
        header:= t -> BrowseData.HeaderWithRowCounter( t, header,
                          Length( gapnames ) ),
        availableModes:= modes,
        main:= List( choice, i ->  List( columns,
                    col -> rec( rows:= [ col[3][i][1] ], align:= col[2] ) ) ),
        labelsCol:= [ List( columns,
                        col -> rec( rows:= [ col[1] ], align:= col[2] ) ) ],
        sepLabelsCol:= [ "-" ],
        sepCol:= Concatenation( [ "| " ],
                     List( [ 1 .. Length( columns ) - 1 ], i -> " | " ),
                     [ " |" ] ),
        SpecialGrid:= BrowseData.SpecialGridLineDraw,
        Click:= rec(
          select_entry:= rec(
            helplines:= [ "open an overview for the selected entry" ],
            action:= sel_action ),
          select_row:= rec(
            helplines:= [ "open an overview for the selected row" ],
            action:= sel_action ),
          select_row_and_entry:= rec(
            helplines:= [ "open an overview for the selected entry" ],
            action:= sel_action ),
          select_column_and_entry:= rec(
            helplines:= [ "open an overview for the selected entry" ],
            action:= sel_action ),
        ),
      ),
      dynamic:= rec(
        Return:= [],
        viewReturn:= [],
      ),
    );

    if log <> fail then
      table.dynamic.log:= log;
      table.dynamic.replay:= replay;
    fi;

    return NCurses.BrowseGeneric( table );
    end;


#############################################################################
##
#F  BrowseData.AtlasInfoGroup( <conditions>, <log>, <replay>, <t> )
##
BrowseData.AtlasInfoGroup:= function( conditions, log, replay, t )
    local table;

    table:= BrowseData.AtlasInfoGroupTable( conditions, log, replay, t );
    if IsEmpty( table.work.main ) then
      BrowseData.AlertWithReplay( table,
          Concatenation( "There are no data for the group ", conditions[1],
                         "." ),
          NCurses.attrs.BOLD );
      return [];
    fi;
    return NCurses.BrowseGeneric( table );
    end;


#############################################################################
##
#F  BrowseAtlasInfo( [...] )
##
##  <#GAPDoc Label="AtlasRep_section">
##  <Section Label="sec:atlasdisp">
##  <Heading>Table of Contents of <Package>AtlasRep</Package></Heading>
##
##  The &GAP; package <Package>AtlasRep</Package>
##  (see&nbsp;<Cite Key="AtlasRep"/>) is an interface to a database
##  of representations and related data.
##  The table of contents of this database can be displayed via the function
##  <Ref Func="DisplayAtlasInfo" BookName="atlasrep"/> of this package.
##  The &Browse; package provides an alternative based on the function
##  <Ref Func="NCurses.BrowseGeneric"/>;
##  one can scroll, search, and fetch data for later use.
##
##  <ManSection>
##  <Heading>BrowseAtlasInfo</Heading>
##  <Func Name="BrowseAtlasInfo"
##   Arg='[listofnames, ]["contents", sources][, ...]'
##   Label="overview of groups"/>
##  <Func Name="BrowseAtlasInfo" Arg="gapname[, std][, ...]"
##   Label="overview for one group"/>
##
##  <Returns>
##  the list of <Q>clicked</Q> info records.
##  </Returns>
##
##  <Description>
##  This function shows the information available via the &GAP; package
##  <Package>AtlasRep</Package> in a browse table,
##  cf. Section&nbsp;<Ref Sect="Accessing Data via AtlasRep"
##  BookName="atlasrep"/> in the <Package>AtlasRep</Package> manual.
##  <P/>
##  The optional arguments can be used to restrict the table to core data
##  or data extensions, or to show an overview for one particular group.
##  The arguments are the same as for
##  <Ref Func="DisplayAtlasInfo" BookName="AtlasRep"/>,
##  see the documentation of this function for details.
##  (Note that additional conditions such as
##  <Ref Func="IsPermGroup" BookName="ref"/> can be entered also in the case
##  that no <A>gapname</A> is given.
##  In this situation, the additional conditions are evaluated for the
##  <Q>second level tables</Q> that are opened by <Q>clicking</Q> on a
##  table row or entry.)
##  <P/>
##  When one <Q>clicks</Q> on one of the table rows or entries then a
##  browse table with an overview of the information available for this
##  group is shown, and <Q>clicking</Q> on one of the rows in these tables
##  adds the corresponding info record
##  (see <Ref Func="OneAtlasGeneratingSetInfo" BookName="AtlasRep"/>)
##  to the list of return values of
##  <Ref Func="BrowseAtlasInfo" Label="overview of groups"/>.
##  <P/>
##  The full functionality of the function
##  <Ref Func="NCurses.BrowseGeneric"/> is available.
##  <P/>
##  The following example shows how
##  <Ref Func="BrowseAtlasInfo" Label="overview of groups"/> can be
##  used to fetch info records about permutation representations of the
##  alternating groups <M>A_5</M> and <M>A_6</M>:
##  We search for the group name <C>"A5"</C> in the overview table, and the
##  first cell in the table row for <M>A_5</M> becomes selected;
##  hitting the <B>Enter</B> key causes a new window to be opened, with an
##  overview of the data available for <M>A_5</M>;
##  moving down two rows and hitting the <B>Enter</B> key again causes the
##  second representation to be added to the result list;
##  hitting <B>Q</B> closes the second window, and we are back in the
##  overview table;
##  we move the selection down twice (to the row for the group <M>A_6</M>),
##  and choose the first representation for this group;
##  finally we leave the table, and the return value is the list with the
##  data for the two representations.
##  <P/>
##  <Example><![CDATA[
##  gap> d:= [ NCurses.keys.DOWN ];;  r:= [ NCurses.keys.RIGHT ];;
##  gap> c:= [ NCurses.keys.ENTER ];;
##  gap> BrowseData.SetReplay( Concatenation(
##  >        "/A5",         # Find the string A5 ...
##  >        d, d, r,       # ... such that just the word matches,
##  >        c,             # start the search,
##  >        c,             # click the table entry A5,
##  >        d, d,          # move down two rows,
##  >        c,             # click the row for this representation,
##  >        "Q",           # quit the second level table,
##  >        d, d,          # move down two rows,
##  >        c,             # click the table entry A6,
##  >        d,             # move down one row,
##  >        c,             # click the first row,
##  >        "Q",           # quit the second level table,
##  >        "Q" ) );       # and quit the application.
##  gap> if IsBound( BrowseAtlasInfo ) and IsBound( AtlasProgramInfo ) then
##  >      SetUserPreference( "AtlasRep", "AtlasRepMarkNonCoreData", "" );
##  >      tworeps:= BrowseAtlasInfo();
##  >    else
##  >      tworeps:= [ fail ];
##  >    fi;
##  gap> BrowseData.SetReplay( false );
##  gap> if fail in tworeps then
##  >      Print( "no access to the Web ATLAS\n" );
##  >    else
##  >      Print( List( tworeps, x -> x.identifier[1] ), "\n" );
##  >    fi;
##  [ "A5", "A6" ]
##  ]]></Example>
##  <P/>
##  <E>Implementation remarks</E>:
##  The first browse table shown has a static header,
##  no footer and row labels,
##  one row of column labels describing the type of data summarized in the
##  columns.
##  <P/>
##  Row and column separators are drawn as grids
##  (cf.&nbsp;<Ref Func="NCurses.Grid"/>) composed from the special characters
##  described in Section&nbsp;<Ref Subsect="ssec:ncursesLines"/>,
##  using the component <C>work.SpecialGrid</C> of the browse table,
##  see <Ref Var="BrowseData"/>.
##  <P/>
##  When a row is selected, the <Q>click</Q> functionality opens a new window
##  (via a second level call to <Ref Func="NCurses.BrowseGeneric"/>),
##  in which a browse table with the list of available data for
##  the given group is shown;
##  in this table, <Q>click</Q> results in adding the info for the selected
##  row to the result list, and a message about this addition is shown in the
##  footer row.
##  One can choose further data, return to the first browse table,
##  and perhaps iterate the process for other groups.
##  When the first level table is left, the list of info records for the
##  chosen data is returned.
##  <P/>
##  For the two kinds of browse tables,
##  the standard modes in <Ref Var="BrowseData"/> (except the <C>help</C>
##  mode) have been extended by a new action that opens a pager giving an
##  overview of all data that have been chosen in the current call.
##  The corresponding user input is the <B>Y</B> key.
##  <P/>
##  This function is available only if the &GAP; package
##  <Package>AtlasRep</Package> is available.
##  <P/>
##  The code can be found in the file <F>app/atlasbrowse.g</F> of the package.
##  </Description>
##  </ManSection>
##  </Section>
##  <#/GAPDoc>
##
BindGlobal( "BrowseAtlasInfo", function( arg )
    local log, replay, result, i, pos, pos2;

    # A record at the first position may prescribe the replay with history.
    log:= fail;
    replay:= fail;
    if Length( arg ) <> 0 and IsRecord( arg[1] )
                          and IsBound( arg[1].log )
                          and IsBound( arg[1].replay ) then
      log:= arg[1].log;
      replay:= arg[1].replay;
      arg:= arg{ [ 2 .. Length( arg ) ] };
    fi;

    # Distinguish the summary overview for at least one group
    # from the detailed overview for exactly one group.
    if   Length( arg ) = 0 then
      result:= BrowseData.AtlasInfoOverview( "all", arg, log, replay );
    elif IsList( arg[1] ) and ForAll( arg[1], IsString ) then
      result:= BrowseData.AtlasInfoOverview( arg[1],
                   arg{ [ 2 .. Length( arg ) ] }, log, replay );
    elif not IsString( arg[1] ) or arg[1] = "contents" then
      result:= BrowseData.AtlasInfoOverview( "all", arg, log, replay );
    else
      result:= BrowseData.AtlasInfoGroup( arg, log, replay, fail );
    fi;

    # Evaluate the return value.
    for i in [ 1 .. Length( result ) ] do
      if IsInt( result[i][2] ) then
        # a representation, identified by its number
        result[i]:= OneAtlasGeneratingSetInfo( result[i][1], Position,
                        result[i][2] );
      else
        # a program, identified by its identifier list
        result[i]:= AtlasProgramInfo( result[i] );
      fi;
    od;

    return result;
    end );


#############################################################################
##
##  Add the Browse application to the list shown by `BrowseGapData'.
##
BrowseGapDataAdd( "AtlasRep Overview", BrowseAtlasInfo, "\
an overview of the information provided by the \
Atlas of Group Representations, \
the return value is the list of records \
encoding the clicked entries" );


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

