/* planarg : test for planarity and find embedding or obstruction */
/* Version 2.0: B D McKay, 16 April 2020 */

#define USAGE "planarg [-v] [-nVq] [-p|-u] [infile [outfile]]"

#define HELPTEXT \
" For each input, write to output if planar.\n\
\n\
    The output file has a header if and only if the input file does.\n\
\n\
    -v  Write non-planar graphs instead of planar graphs\n\
    -V  Write report on every input\n\
    -u  Don't write anything, just count\n\
    -p  Write in planar_code if planar (without -p, same format as input)\n\
    -k  Follow each non-planar output with an obstruction in sparse6\n\
        format (implies -v, incompatible with -p)\n\
    -n  Suppress checking of the result\n\
    -q  Suppress auxiliary information\n\
\n\
    This program permits multiple edges and loops\n"

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

#include "gtools.h" 
#include "planarity.h"

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

static void
write_planarcode(FILE *f, t_ver_sparse_rep *VR, t_adjl_sparse_rep *A,
                 t_embed_sparse_rep *ER, int n, int ne)
/* Write the embedding to f in planar_code */
{
    int bytes;
    size_t i,j,len,k,k0;
    unsigned int w;
    DYNALLSTAT(unsigned char,buff,buff_sz);
#define PUT1(x) buff[j++]=(x);
#define PUT2(x) w=(x); buff[j++]=(w>>8)&0xFF; buff[j++]=w&0xff;
#define PUT4(x) w=(x); buff[j++]=(w>>24)&0xFF; buff[j++]=(w>>16)&0xff; \
             buff[j++]=(w>>8)&0xFF; buff[j++]=w&0xff;

    if (n <= 255)        bytes = 1;
    else if (n <= 65535) bytes = 2;
    else                 bytes = 4;

    len = bytes * (1 + n + 2*(size_t)ne);
    if (bytes == 2)      len += 1;
    else if (bytes == 4) len += 3;

    DYNALLOC1(unsigned char,buff,buff_sz,len,"planarg");

    if (bytes == 1)
    {
        j = 0;
        PUT1(n);
        for (i = 0; i < n; ++i)
        {
            k = k0 = VR[i].first_edge;
            if (k != NIL)
                do
                {
                    PUT1(A[ER[k].in_adjl].end_vertex+1);
                    k = ER[k].next;
                } while (k != k0);
            PUT1(0);
        }
    }
    else if (bytes == 2)
    {
        j = 0;
        PUT1(0);
        PUT2(n);
        for (i = 0; i < n; ++i)
        {
            k = k0 = VR[i].first_edge;
            if (k != NIL)
                do
                {
                    PUT2(A[ER[k].in_adjl].end_vertex+1);
                    k = ER[k].next;
                } while (k != k0);
            PUT2(0);
        }
    }
    else
    {
        j = 0;
        PUT1(0);
        PUT2(0);
        PUT4(n);
        for (i = 0; i < n; ++i)
        {
            k = k0 = VR[i].first_edge;
            if (k != NIL)
                do
                {
                    PUT4(A[ER[k].in_adjl].end_vertex+1);
                    k = ER[k].next;
                } while (k != k0);
            PUT4(0);
        }
    }

    if (fwrite((void*)buff,1,len,f) != len)
    {
        fprintf(stderr,">E write_planarcode : error on writing\n");
        ABORT(">E write_planarcode");
    }
}

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

static boolean
isplanar(t_ver_sparse_rep *V, int n, t_adjl_sparse_rep *A, int e,
         int *c, t_ver_sparse_rep **VR, t_adjl_sparse_rep **AR,
         t_embed_sparse_rep  **ER, int *nbr_e_obs,
         boolean planarcheck, boolean nonplanarcheck)
/*
  The input graph is given as an adjacency list:
      V: array of vertices 
      n: size of graph
      A: adjacency list
      e: number of edges
      
  If the graph is planar the embedding is stored  in VR and ER;
  the embedding contains e edges (nbr_e_obs not used)
      
  If the graph is non planar the obstruction is returned in
  VR and AR together with the number of edges in nbr_e_obs.

  In all cases the number of components is return in c.

  planarcheck and nonplanarcheck determine if checking is done.
  The embedding and obstruction outputs are only made if the
  appropriate checking is done.
*/
{
    t_dlcl           **dfs_tree, **back_edges, **mult_edges;
    int              edge_pos, v, w;
    boolean          ans;
    t_ver_edge       *embed_graph;
 
    ans = sparseg_adjl_is_planar(V, n, A, c,
                                 &dfs_tree, &back_edges, &mult_edges,
                                 &embed_graph, &edge_pos, &v, &w);
    
    if (!ans && nonplanarcheck)
    {
        embedg_obstruction(V, A, dfs_tree, back_edges,
                           embed_graph, n, &edge_pos,
                           v, w, VR, AR, nbr_e_obs);
    }
    else if (planarcheck)
    {
        embedg_embedding(V, A, embed_graph, n, e, *c, edge_pos, mult_edges,
                         VR, ER);
    }
    
    sparseg_dlcl_delete(dfs_tree, n);
    sparseg_dlcl_delete(back_edges, n);
    sparseg_dlcl_delete(mult_edges, n);
    embedg_VES_delete(embed_graph, n);
 
    return ans;
}

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

int
main(int argc, char *argv[])
{
    char *infilename,*outfilename;
    FILE *infile,*outfile;
    sparsegraph sg;
    boolean badargs;
    boolean verbose,nonplanar,quiet;
    boolean planarcode,nowrite,nocheck;
    int i,j,k,n,argnum,ne,loops;
    int codetype,outcode;
    t_ver_sparse_rep *VR;
    t_adjl_sparse_rep *AR;
    t_embed_sparse_rep *ER;
    int nbr_c,nbr_e_obs;
    nauty_counter nin,nout,nplan;
    char *arg,sw;
    double t0,tm0,tp,tnp,netotalp,netotalnp;
    DYNALLSTAT(t_ver_sparse_rep,V,V_sz);
    DYNALLSTAT(t_adjl_sparse_rep,A,A_sz);

    HELP; PUTVERSION;

    infilename = outfilename = NULL;
    quiet = nowrite = planarcode = FALSE;
    verbose = nonplanar = nocheck = FALSE;

    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('v',nonplanar)
                else SWBOOLEAN('q',quiet)
                else SWBOOLEAN('V',verbose)
                else SWBOOLEAN('p',planarcode)
                else SWBOOLEAN('u',nowrite)
                else SWBOOLEAN('n',nocheck)
                else badargs = TRUE;
            }
        }
        else
        {
            ++argnum;
            if      (argnum == 1) infilename = arg;
            else if (argnum == 2) outfilename = arg;
            else                  badargs = TRUE;
        }
    }

    if (badargs)
    {
        fprintf(stderr,">E Usage: %s\n",USAGE);
        exit(1);
    }

    if (planarcode && nonplanar)
    {
        fprintf(stderr,">E planarg: -p and -v are incompatible\n");
        exit(1);
    }

    if (!quiet)
    {
        fprintf(stderr,">A planarg");
        if (nonplanar||planarcode||nowrite||nocheck)
            fprintf(stderr," -");
        if (nonplanar) fprintf(stderr,"v");
        if (nowrite) fprintf(stderr,"u");
        if (planarcode) fprintf(stderr,"p");
        if (nocheck) fprintf(stderr,"n");
        if (argnum > 0) fprintf(stderr," %s",infilename);
        if (argnum > 1) fprintf(stderr," %s",outfilename);
        fprintf(stderr,"\n");
        fflush(stderr);
    }

    if (infilename && infilename[0] == '-') infilename = NULL;
    infile = opengraphfile(infilename,&codetype,FALSE,1);
    if (!infile) exit(1);
    if (!infilename) infilename = "stdin";

    NODIGRAPHSYET(codetype);

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

        if (planarcode)            outcode = PLANARCODE;
        else if (codetype&SPARSE6) outcode = SPARSE6;
        else                       outcode = GRAPH6;

        if (outcode == PLANARCODE)
            writeline(outfile,PLANARCODE_HEADER);
        else if (codetype&HAS_HEADER)
        {
            if (outcode == SPARSE6) writeline(outfile,SPARSE6_HEADER);
            else                    writeline(outfile,GRAPH6_HEADER);
        }
    }

    nin = nout = nplan = 0;
    netotalp = netotalnp = 0.0;
    SG_INIT(sg);

    tm0 = CPUTIME;

    tp = tnp = 0.0;
    while (TRUE)
    {
        if (read_sg_loops(infile,&sg,&loops) == NULL) break;
        n = sg.nv;
        ne = (sg.nde+loops)/2;
        ++nin;

        DYNALLOC1(t_ver_sparse_rep,V,V_sz,n,"planarg");
        DYNALLOC1(t_adjl_sparse_rep,A,A_sz,2*ne+1,"planarg");
        k = 0;
        for (i = 0; i < n; ++i)
            if (sg.d[i] == 0)
                V[i].first_edge = NIL;
            else
            {
                V[i].first_edge = k;
                for (j = sg.v[i]; j < sg.v[i]+sg.d[i]; ++j)
                {
                    A[k].end_vertex = sg.e[j];
                    A[k].next = k+1;
                    ++k;
                    if (A[k-1].end_vertex == i)  /* loops go in twice */
                    {
                        A[k].end_vertex = i;
                        A[k].next = k+1;
                        k++;
                    }
                }
                A[k-1].next = NIL;
            }

        if (k != 2*ne)
        {
            fprintf(stderr,
               ">E planarg: decoding error; nin=" COUNTER_FMT "\n",nin);
            fprintf(stderr,"n=%d nde=%lu ne=%d loops=%d\n",
                    n,(unsigned long)sg.nde,ne,loops);
            exit(1);
        }

        VR = NULL;
        AR = NULL;
        ER = NULL;
        t0 = CPUTIME;
        if (isplanar(V,n,A,ne,&nbr_c,&VR,&AR,&ER,&nbr_e_obs,
                     !nocheck||planarcode,!nocheck))
        {
            ++nplan;
            tp += CPUTIME - t0;
            netotalp += ne;
            if (!nowrite && !nonplanar)
            {
                if (planarcode)
                    write_planarcode(outfile,VR,A,ER,n,ne);
                else
                    writelast(outfile);
                ++nout;
            }
            if (verbose)
                fprintf(stderr,"graph " COUNTER_FMT ": n=%d ne=%d planar\n",
                        nin,n,ne);
        }
        else
        {
            tnp += CPUTIME - t0;
            netotalnp += ne;
            if (!nowrite && nonplanar)
            {
                writelast(outfile);
                ++nout;
            }
            if (verbose)
                fprintf(stderr,"graph " COUNTER_FMT ": n=%d ne=%d non-planar\n",
                        nin,n,ne);
        }
        FREES(VR);
        FREES(AR);
        FREES(ER);
    }


    if (!nowrite)
    {
        if (!quiet)
            fprintf(stderr,
            ">Z  " COUNTER_FMT " graphs read from %s, "
                  COUNTER_FMT " written to %s; %3.2f sec.\n",
                nin,infilename,nout,outfilename,CPUTIME-tm0);
    }
    else
    {
        fprintf(stderr,
                " " COUNTER_FMT " graphs input\n "
                    COUNTER_FMT " graphs planar\n",nin,nplan);
        fprintf(stderr," cpu = %3.2f sec. ",CPUTIME-tm0);
        if (netotalp)
            fprintf(stderr," planar:%.3f",1e6*tp/netotalp);
        if (netotalnp)
            fprintf(stderr," nonplanar:%.3f",1e6*tnp/netotalnp);
        fprintf(stderr,"\n");
    }

    return 0;
}
