/* listg.c  version 2.4; B D McKay, Aug 2022 */

#define USAGE \
 "listg [-fp#:#l#o#Ftq] [-a|-A|-c|-d|-e|-H|-M|-W|-L|S|-s|-b|-G|-y|-Yxxx]\n" \
       "           [infile [outfile]]"

#define HELPTEXT \
" Write graphs in human-readable format.\n\
\n\
    -f  : assume inputs have same size (only used from a file\n\
           and only if -p is given)\n\
    -p#, -p#:#, -p#-# : only display one graph or a sequence of\n\
           graphs.  The first graph is number 1.  A second number\n\
           which is empty or zero means infinity.\n\
           This option won't work for incremental input.\n\
    -a  : write as adjacency matrix, not as list of adjacencies\n\
    -A  : same as -a with a space between entries\n\
    -l# : specify screen width limit (default 78, 0 means no limit)\n\
          This is not currently implemented with -a or -A.\n\
    -o# : specify number of first vertex (default is 0).\n\
    -d  : write output to satisfy dreadnaut \n\
    -c  : write ascii form with minimal line-breaks\n\
    -e  : write a list of edges, preceded by the order and the\n\
          number of edges\n\
    -H  : write in HCP operations research format\n\
    -M  : write in Magma format\n\
    -W  : write matrix in Maple format\n\
    -L  : (only with -M or -W) write Laplacian rather than adjacency matrix\n\
    -S  : (only with -M or -W) write signless Laplacian not adjacency matrix\n\
    -b  : write in Bliss format\n\
    -G  : write in GRAPE format\n\
    -y  : write in dot file format\n\
    -Yxxx : extra dotty commands for dot files (arg continues to end of param)\n\
    -t  : write upper triangle only (affects -a, -A, -d and default)\n\
    -s  : write only the numbers of vertices and edges\n\
    -F  : write a form-feed after each graph except the last\n\
    -q  : suppress auxiliary output\n\
\n\
    -a, -A, -c, -d, -M, -W, -H and -e are incompatible.\n"

#define MAPLE_MATRIX 1  /* 1 for Matrix(..), 0 for array(..) */

/*************************************************************************

    June 26, 2007 : Fix error in putve() reported by Evan Heidtmann
    March 3, 2013 : Allow incremental input
    June 18, 2015 : Add digraph code

*************************************************************************/

#include "gtools.h"
#define LABELORG 0   /* number of first vertex (any integer >= 0) */
#define LINELEN CONSOLWIDTH   /* max characters per line (0 = no limit) */

static FILE *infile,*outfile;
static unsigned long nin;
extern int labelorg;

/*****************************************************************************
*  putsetx(f,set1,curlenp,linelength,m,compress,start)   writes the set      *
*  set1 to file f using at most linelength characters per line (excluding    *
*  '\n').   Set elements less than or equal to start are ignored.            *
*  *curlenp is the number of characters on the line so far; it is updated.   *
*  A range j1,j1+1,...,j2 for j2-j1>=2 is written as "j1:j2" if compress     *
*  is nonzero (eg. TRUE); otherwise each element is written separately.      *
*  No final '\n' is written.  labelorg is used.                              *
*                                                                            *
*  FUNCTIONS CALLED: nextelement(),itos()                                    *
*                                                                            *
*****************************************************************************/

static void
putsetx(FILE *f, set *set1, int *curlenp, int linelength, int m,
        boolean compress, int start)
{
    int slen,j1,j2;
    char s[40];
    boolean first;

    first = TRUE;
    j1 = start;
    while ((j1 = nextelement(set1,m,j1)) >= 0)
    {
        j2 = j1;
        if (compress)
        {
            while (nextelement(set1,m,j2) == j2 + 1)
                ++j2;
            if (j2 == j1+1)
                j2 = j1;
        }
        slen = itos(j1 + labelorg,s);
        if (j2 >= j1 + 2)
        {
            s[slen] = ':';
            slen += 1 + itos(j2 + labelorg,&s[slen+1]);
        }

        if (*curlenp + slen + 1 >= linelength && linelength > 0)
        {
            fprintf(f,"\n ");
            *curlenp = 1;
        }
        if (first)
        {
            fprintf(f,"%s",s);
            *curlenp += slen;
            first = FALSE;
        }
        else
        {    
            fprintf(f," %s",s);
            *curlenp += slen + 1;
        }
        j1 = j2;
    }
}

/*****************************************************************************
*                                                                            *
*  STOLEN FROM naututil.c                                                    *
*  putgraphx(f,g,linelength,m,n) writes a list of the edges of g to f        *
*  using at most linelength characters per line (excluding '\n').            *
*  If triang, only write the upper triangle.                                 *
*  labelorg is used.                                                         *
*                                                                            *
*****************************************************************************/

static void
putgraphx(FILE *f, graph *g, int linelength, boolean triang, int m, int n)
{
    int i,curlen;
    set *pg;

    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        fprintf(f,"%3d : ",i + labelorg);
        curlen = 7;
        putsetx(f,pg,&curlen,linelength,m,FALSE,triang ? i-1 : -1);
        fprintf(f,";\n");
    }
}

/***************************************************************************/

static void
putedges(FILE *f, graph *g, boolean ptn, int linelength, 
         boolean digraph, int m, int n)
/* Write list of edges, preceded by the numbers of vertices and
   edges and optionally by "1" if "ptn" is TRUE.  Use labelorg */
{
    int i,j,curlen,ne;
    char s[20];
    set *pg;

    ne = 0;
    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        for (j = (digraph?-1:i-1); (j = nextelement(pg,m,j)) >= 0;)
            ++ne;
    }

    if (ptn) fprintf(f,"%d %d 1\n",n,ne);
    else     fprintf(f,"%d %d\n",n,ne);

    curlen = 0;
    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        for (j = (digraph ? -1 : i-1); (j = nextelement(pg,m,j)) >= 0;)
        { 
            if (curlen > 0 && curlen > linelength - 10 && linelength > 0)
            {
                fprintf(f,"\n");
                curlen = 0;
            }
            if (curlen > 0)
            {
                fprintf(f,"  ");
                curlen += 2;
            }
            curlen += itos(i+labelorg,s);
            fprintf(f,"%s",s);
            fprintf(f," ");
            curlen += 1 + itos(j+labelorg,s);
            fprintf(f,"%s",s);
        }
    }
    fprintf(f,"\n");
}

/***************************************************************************/

static void
putHCP(FILE *f, graph *g, int m, int n)
/* Write list of edges in HCP format.  labelorg is ignored */
{
    int i,j;
    set *pg;

    fprintf(f,"NAME : G%lu\n",nin);
    fprintf(f,"TYPE : HCP\n");
    fprintf(f,"DIMENSION : %d\n",n);
    fprintf(f,"EDGE_DATA_FORMAT : EDGE_LIST\n");
    fprintf(f,"EDGE_DATA_SECTION\n");

    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        for (j = -1; (j = nextelement(pg,m,j)) >= 0;)
            fprintf(f,"%d %d\n",i+1,j+1);
    }
    fprintf(f,"-1\nEOF\n");
}

/***************************************************************************/

static void
putcgraph(FILE *f, graph *g, int linelength, boolean digraph, int m, int n)
/* write compressed form, using labelorg */
{
    int i,curlen,j0;
    int semicolons;
    char s[20];
    set *pg;

    curlen = itos(n,s)+2;
    fprintf(f,";n%s%s",s,(digraph?"dg":"g"));

    semicolons = 0;
    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        j0 = digraph ? -1: i-1;
        if (nextelement(pg,m,j0) >= 0)
        {
            while (semicolons > 0)
            {
                if (curlen >= linelength-1 && linelength > 0)
                {
                    fprintf(f,"\n ");
                    curlen = 1;
                }
                fprintf(f,";");
                ++curlen;
                --semicolons;
            }
            putsetx(f,pg,&curlen,linelength,m,FALSE,j0);
            semicolons = 1;
        }
        else
            ++semicolons;
    }
    fprintf(f,".\n");
}

/**************************************************************************/

static void
putve(FILE *f, unsigned long id, graph *g, boolean digraph, int m, int n)
/* Write the numbers of vertices and edges */
{
    unsigned long ne;
    setword x,*pg;

    ne = 0;
    for (pg = g + m*(size_t)n; --pg >= g;)
        if ((x = *pg) != 0) ne += POPCOUNT(x);

    fprintf(f,"Graph %lu has %d vertices and %lu edges.\n",id,n,
            (digraph?ne:ne/2));
}

/**************************************************************************/

static void
putGRAPE(FILE *f, graph *g, int m, int n)
/* Write the graph in GRAPE format */
{
    int i,j;
    setword *pg;
    boolean first;

    fprintf(f,
       "rec( isGraph:=true, order:=%d, group:=Group([],()),\n",n);
    fprintf(f,
       "  representatives := Immutable([1..%d]),\n",n);
    fprintf(f,"  adjacencies := [\n");
    
    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        first = TRUE;
        fprintf(f,"   [");
        for (j = nextelement(pg,m,-1); j >= 0; j = nextelement(pg,m,j))
        {
            if (!first) fprintf(f,",");
            fprintf(f,"%d",j+1);
            first = FALSE;
        }
        if (i < n-1) fprintf(f,"],\n");
        else         fprintf(f,"]],\n");
    }

    fprintf(f,"  schreierVector := Immutable([-1,-2..-%d]) )",n);
}

/**************************************************************************/

static void
putdotty(FILE *f, graph *g, unsigned long id, char *extras, int m, int n)
/* Write the graph in dotty format */
{
    int i,j;
    setword *pg;

    fprintf(f,"graph G%lu {\n",id);
    if (extras) fprintf(f,"%s\n",extras);

    for (i = 0, pg = g; i < n; ++i, pg += m)
    {
        for (j = nextelement(pg,m,i); j >= 0; j = nextelement(pg,m,j))
        {
            fprintf(f,"%d--%d;\n",labelorg+i,labelorg+j);
        }
    }

    fprintf(f,"}\n");
}

/**************************************************************************/

static void
putbliss(FILE *f, unsigned long id, boolean qswitch, graph *g, int m, int n)
/* Write the graph in Bliss format, according to
 *      http://www.tcs.hut.fi/Software/bliss/fileformat.shtml */
{
    unsigned long ne;
    setword x,*pg;
    int i,j;

    ne = 0;
    for (pg = g + m*(size_t)n; --pg >= g;)
        if ((x = *pg) != 0) ne += POPCOUNT(x);
    ne /= 2;

    if (!qswitch) fprintf(f,"c Graph %lu\n",id);
    else          fprintf(f,"\n");
    fprintf(f,"p edge %d %lu\n",n,ne);

    for (i = 0, pg = g; i < n; ++i, pg += m)
        for (j = nextelement(pg,m,i); j >= 0; j = nextelement(pg,m,j))
            fprintf(f,"e %d %d\n",i+1,j+1);
}

/**************************************************************************/

static void
putam(FILE *f, graph *g, int linelength, boolean space,
    boolean triang, int m, int n)
/* write adjacency matrix */
{
    set *gi;
    int i,j;
    boolean first;

    for (i = 0, gi = (set*)g; i < n - (triang!=0); ++i, gi += m)
    {
        first = TRUE;
        for (j = triang ? i+1 : 0; j < n; ++j)
        {
            if (!first && space) putc(' ',f);
            else                 first = FALSE;
            if (ISELEMENT(gi,j)) putc('1',f);
            else                 putc('0',f);
        }
        putc('\n',f);
    }
}

/**************************************************************************/

static void
putMagma(FILE *outfile, graph *g, int linelength, boolean digraph,
         int m, int n, long index)
{
    int i,j,j0;
    set *gi;
    boolean first;

    fprintf(outfile,"g%ld := %s<%d|[\n",
                 index,(digraph?"Digraph":"Graph"),n);

    for (i = 0, gi = (set*)g; i < n; ++i, gi += m)
    {
        fprintf(outfile,"{");
        first = TRUE;
        j0 = digraph ? -1 : i;
        for (j = j0; (j = nextelement(gi,m,j)) >= 0; )
        {
            if (!first) fprintf(outfile,",");
            first = FALSE;
            fprintf(outfile,"%d",j+1);
        }
        fprintf(outfile,"}");
        if (i != n-1) fprintf(outfile,",\n");
    }
    fprintf(outfile,"]>;\n");
}

/**************************************************************************/

static void
putMaple(FILE *outfile, graph *g, int linelength, int m, int n, long index)
{
    int i,j;
    set *gi;
    boolean first;

#if MAPLE_MATRIX
    fprintf(outfile,"f%ld := Matrix(%d,%d,[\n",index,n,n);
#else
    fprintf(outfile,"f%ld := array(1..%d,1..%d,[\n",index,n,n);
#endif

    for (i = 0, gi = (set*)g; i < n; ++i, gi += m)
    {
        fprintf(outfile,"[");
        first = TRUE;
        for (j = 0; j < n; ++j)
        {
            if (!first) fprintf(outfile,",");
            first = FALSE;
            fprintf(outfile,"%d",(ISELEMENT(gi,j)?1:0));
        }
        fprintf(outfile,"]");
        if (i != n-1) fprintf(outfile,",\n");
    }
    fprintf(outfile,"]);\n");
}

/**************************************************************************/

static void
putLaplacianMagma(FILE *outfile, graph *g, int linelength, boolean signless,
         int m, int n, long index)
{
    int i,j,d,e;
    set *gi;

    if (signless) e = 1; else e = -1;

    fprintf(outfile,"g%ld := Matrix(%d,[\n",index,n);

    for (i = 0, gi = (set*)g; i < n; ++i, gi += m)
    {
        d = 0;
        for (j = 0; j < m; ++j) d += POPCOUNT(gi[j]);

        for (j = 0; j < n; ++j)
        {
            if (j == i)
                if (ISELEMENT(gi,j)) fprintf(outfile,"%d",d-1);
                else                 fprintf(outfile,"%d",d);
            else
                if (ISELEMENT(gi,j)) fprintf(outfile,"%d",e);
                else                 fprintf(outfile,"%d",0);
            if (j < n-1) fprintf(outfile,",");
        }
        if (i < n-1) fprintf(outfile,",\n");
        else         fprintf(outfile,"]);\n");
    }
}

/**************************************************************************/

static void
putLaplacianMaple(FILE *outfile, graph *g, int linelength, boolean signless,
                                              int m, int n, long index)
{
    int i,j,d,e;
    set *gi;
    boolean first;

    if (signless) e = 1; else e = -1;

#if MAPLE_MATRIX
    fprintf(outfile,"f%ld := Matrix(%d,%d,[\n",index,n,n);
#else
    fprintf(outfile,"f%ld := array(1..%d,1..%d,[\n",index,n,n);
#endif

    for (i = 0, gi = (set*)g; i < n; ++i, gi += m)
    {
        d = 0;
        for (j = 0; j < m; ++j) d += POPCOUNT(gi[j]);

        fprintf(outfile,"[");
        first = TRUE;
        for (j = 0; j < n; ++j)
        {
            if (!first) fprintf(outfile,",");
            first = FALSE;
            if (j == i)
                if (ISELEMENT(gi,j)) fprintf(outfile,"%d",d-1);
                else                 fprintf(outfile,"%d",d);
            else
                if (ISELEMENT(gi,j)) fprintf(outfile,"%d",e);
                else                 fprintf(outfile,"%d",0);
        }
        fprintf(outfile,"]");
        if (i != n-1) fprintf(outfile,",\n");
    }
    fprintf(outfile,"]);\n");
}

/**************************************************************************/
/**************************************************************************/

int
main(int argc, char *argv[])
{
    graph *g,*gprev;
    int m,n,codetype;
    int argnum,j,nprev,mprev;
    char *arg,sw;
    boolean badargs,first,digraph;
    unsigned long maxin;
    long pval1,pval2;
    boolean fswitch,pswitch,cswitch,dswitch;
    boolean aswitch,lswitch,oswitch,Fswitch,Sswitch;
    boolean Aswitch,eswitch,tswitch,qswitch;
    boolean sswitch,Mswitch,Wswitch,Lswitch,Eswitch;
    boolean bswitch,Gswitch,yswitch,Yswitch,Hswitch;
    int linelength;
    char *infilename,*outfilename,*yarg;

    HELP; PUTVERSION;

    fswitch = pswitch = cswitch = dswitch = FALSE;
    aswitch = lswitch = oswitch = Fswitch = FALSE;
    Aswitch = eswitch = tswitch = qswitch = Sswitch = FALSE;
    sswitch = Mswitch = Wswitch = Lswitch = Eswitch = FALSE;
    bswitch = Gswitch = yswitch = Yswitch = Hswitch = FALSE;
    infilename = outfilename = NULL;
    linelength = LINELEN;
    labelorg = 0;

    argnum = 0;
    badargs = FALSE;
    for (j = 1; !badargs && j < argc; ++j)
    {
        arg = argv[j];
        if (arg[0] == '-' && arg[1] != '\0')
        {
            ++arg;
            while (*arg != '\0')
            {
                sw = *arg++;
                     SWBOOLEAN('a',aswitch)
                else SWBOOLEAN('A',Aswitch)
                else SWBOOLEAN('c',cswitch)
                else SWBOOLEAN('d',dswitch)
                else SWBOOLEAN('e',eswitch)
                else SWBOOLEAN('H',Hswitch)
                else SWBOOLEAN('E',Eswitch)
                else SWBOOLEAN('f',fswitch)
                else SWBOOLEAN('F',Fswitch)
                else SWBOOLEAN('t',tswitch)
                else SWBOOLEAN('b',bswitch)
                else SWBOOLEAN('G',Gswitch)
                else SWBOOLEAN('q',qswitch)
                else SWBOOLEAN('M',Mswitch)
                else SWBOOLEAN('W',Wswitch)
                else SWBOOLEAN('L',Lswitch)
                else SWBOOLEAN('S',Sswitch)
                else SWBOOLEAN('s',sswitch)
                else SWBOOLEAN('y',yswitch)
                else SWRANGE('p',":-",pswitch,pval1,pval2,"listg -p")
                else SWINT('l',lswitch,linelength,"listg -l")
                else SWINT('o',oswitch,labelorg,"listg -o")
                else if (sw == 'Y')
                {
                    Yswitch = TRUE;
                    yarg = arg;
                    break;
                }
                else badargs = TRUE;
            }
        }
        else
        {
            ++argnum;
            if      (argnum == 1) infilename = arg;
            else if (argnum == 2) outfilename = arg;
            else                  badargs = TRUE;
        }
    }

    if (Yswitch) yswitch = TRUE;

    if (labelorg < 0) gt_abort(">E listg: negative origin forbidden.\n");

    if ((aswitch!=0) + (Aswitch!=0) + (eswitch!=0) + (Mswitch!=0) +
        (Wswitch!=0) + (sswitch!=0) + (dswitch!=0) + (cswitch!=0) +
        (Eswitch!=0) + (bswitch!=0) + (Gswitch!=0) + (yswitch!=0) +
        (Hswitch!=0) > 1)
        gt_abort(">E listg: -aAbMWeEHcdsGy are incompatible\n");

    if (Sswitch) Lswitch = TRUE;

    if (Lswitch && !Mswitch && !Wswitch)
        gt_abort(">E listg: -L is only allowed with -M or -W\n");

    if (badargs)
    {
        fprintf(stderr,">E Usage: %s\n",USAGE);
        fprintf(stderr,"  Try listg -help for more detailed help.\n");
        exit(1);
    }

    if (!pswitch || pval1 < 1) pval1 = 1;

    if (infilename && infilename[0] == '-') infilename = NULL;
    infile = opengraphfile(infilename,&codetype,fswitch,
                           pswitch ? pval1 : 1);
    if (!infile) exit(1);
    if (!infilename) infilename = "stdin";

    if (!outfilename || outfilename[0] == '-')
    {
        outfilename = "stdout";
        outfile = stdout;
    }
    else if ((outfile = fopen(outfilename,"w")) == NULL)
        gt_abort_1(">E Can't open output file %s\n",outfilename);

    nin = 0;
    if (!pswitch || pval2 == NOLIMIT)
        maxin = NOLIMIT;
    else if (pval1 < 1) maxin = pval2;
    else                maxin = pval2 - pval1 + 1;
    first = TRUE;
    gprev = NULL;
    nprev = mprev = 1;

    while (nin < maxin || maxin == NOLIMIT)
    {
        if ((g = readgg_inc(infile,NULL,0,&m,&n,
                  gprev,mprev,nprev,&digraph)) == NULL) break;
        ++nin;

        if (Gswitch)
        {
            if (first) fprintf(outfile,"graphs := [\n");
            else       fprintf(outfile,",\n");
        }

        first = FALSE;

        if (Fswitch && nin > 1) fprintf(outfile,"\f");

        if (cswitch)
            putcgraph(outfile,g,linelength,digraph,m,n);
        else if (dswitch)
        {
            if (qswitch)
                fprintf(outfile,"%d\n",n);
            else
            {
                fprintf(outfile,"\n!Graph %lu.\n",pval1+nin-1);
                fprintf(outfile,"n=%d $=%d %sg\n",
                                n,labelorg,(digraph?"d":""));
            }
            putgraphx(outfile,g,linelength,tswitch,m,n);
            if (!qswitch) fprintf(outfile,"$$\n");
        }
        else if (Mswitch)
        {
            if (Lswitch)
                putLaplacianMagma(outfile,g,linelength,Sswitch,m,n,pval1+nin-1);
            else
                putMagma(outfile,g,linelength,digraph,m,n,pval1+nin-1);
        }
        else if (Wswitch)
        {
            if (Lswitch)
                putLaplacianMaple(outfile,g,linelength,Sswitch,m,n,pval1+nin-1);
            else
                putMaple(outfile,g,linelength,m,n,pval1+nin-1);
        }
        else if (sswitch)
            putve(outfile,pval1+nin-1,g,digraph,m,n);
        else if (bswitch)
            putbliss(outfile,pval1+nin-1,qswitch,g,m,n);
        else if (Gswitch)
            putGRAPE(outfile,g,m,n);
        else if (yswitch)
            putdotty(outfile,g,pval1+nin-1,(Yswitch?yarg:NULL),m,n);
        else if (Hswitch)
            putHCP(outfile,g,m,n);
        else
        {
            if (qswitch)
            {
                if (!eswitch && !Eswitch) fprintf(outfile,"%d\n",n);
            }
            else fprintf(outfile,"\nGraph %lu, order %d.\n",
                                 pval1+nin-1,n);
            if (aswitch|Aswitch)
                putam(outfile,g,linelength,Aswitch,tswitch,m,n);
            else if (eswitch || Eswitch)
                putedges(outfile,g,Eswitch,linelength,digraph,m,n);
            else
                putgraphx(outfile,g,linelength,tswitch,m,n);
        }
        if (gprev) FREES(gprev);
        gprev = g;
        nprev = n;
        mprev = m;

        if (ferror(outfile)) gt_abort(">E listg output error\n");
    }

    if (Gswitch && !first) fprintf(outfile,"\n];\n");

    exit(0);
}
