/*************************************************************************
 * Copyright (c) 2011 AT&T Intellectual Property 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: Details at https://graphviz.org
 *************************************************************************/

#include <stdbool.h>
#include <string.h>
#include "tcldot.h"

static int graphcmd_internal(ClientData clientData, Tcl_Interp * interp,
                             int argc, char *argv[]) {
    Agraph_t *g, *sg;
    Agnode_t *n, *tail, *head;
    Agedge_t *e;
    gctx_t *gctx = (gctx_t *)clientData;
    ictx_t *ictx = gctx->ictx;
    Agsym_t *a;
    char buf[12], **argv2;
    int j, argc2;
    GVC_t *gvc = ictx->gvc;

    if (argc < 2) {
	Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " option ?arg arg ...?\"", NULL);
	return TCL_ERROR;
    }
    g = cmd2g(argv[0]);
    if (!g) {
	Tcl_AppendResult(interp, "graph \"", argv[0], "\" not found", NULL);
	return TCL_ERROR;
    }

    if (strcmp("addedge", argv[1]) == 0) {
	if ((argc < 4) || (argc % 2)) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     " addedge tail head ?attributename attributevalue? ?...?\"",
			     NULL);
	    return TCL_ERROR;
	}
        tail = cmd2n(argv[2]);
        if (!tail) {
	    if (!(tail = agfindnode(g, argv[2]))) {
		Tcl_AppendResult(interp, "tail node \"", argv[2], "\" not found.", NULL);
		return TCL_ERROR;
	    }
        }
	if (agroot(g) != agroot(agraphof(tail))) {
	    Tcl_AppendResult(interp, "tail node ", argv[2], " is not in the graph.", NULL);
	    return TCL_ERROR;
	}
        head = cmd2n(argv[3]);
        if (!head) {
	    if (!(head = agfindnode(g, argv[3]))) {
		Tcl_AppendResult(interp, "head node \"", argv[3], "\" not found.", NULL);
		return TCL_ERROR;
	    }
        }
	if (agroot(g) != agroot(agraphof(head))) {
	    Tcl_AppendResult(interp, "head node ", argv[3], " is not in the graph.", NULL);
	    return TCL_ERROR;
	}
	e = agedge(g, tail, head, NULL, 1);
	Tcl_AppendResult(interp, obj2cmd(e), NULL);
	setedgeattributes(agroot(g), e, &argv[4], argc - 4);
	return TCL_OK;

    } else if (strcmp("addnode", argv[1]) == 0) {
	int i;
	if (argc % 2) {
	    /* if odd number of args then argv[2] is name */
	    n = agnode(g, argv[2], 1);
	    i = 3;
	} else {
	    n = agnode(g, NULL, 1);  /* anon node */
	    i = 2;
	}
	Tcl_AppendResult(interp, obj2cmd(n), NULL);
	setnodeattributes(agroot(g), n, &argv[i], argc - i);
	return TCL_OK;

    } else if (strcmp("addsubgraph", argv[1]) == 0) {
	int i;
	if (argc < 2) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
			     "\" addsubgraph ?name? ?attributename attributevalue? ?...?",
			     NULL);
	}
	if (argc % 2) {
	    /* if odd number of args then argv[2] is name */
	    sg = agsubg(g, argv[2], 1);
	    Tcl_AppendResult(interp, obj2cmd(sg), NULL);
	    i = 3;
	} else {
	    sg = agsubg(g, NULL, 1);  /* anon subgraph */
	    i = 2;
	}
	setgraphattributes(sg, &argv[i], argc - i);
	return TCL_OK;

    } else if (strcmp("countnodes", argv[1]) == 0) {
	snprintf(buf, sizeof(buf), "%d", agnnodes(g));
	Tcl_AppendResult(interp, buf, NULL);
	return TCL_OK;

    } else if (strcmp("countedges", argv[1]) == 0) {
	snprintf(buf, sizeof(buf), "%d", agnedges(g));
	Tcl_AppendResult(interp, buf, NULL);
	return TCL_OK;

    } else if (strcmp("delete", argv[1]) == 0) {
	deleteGraph(gctx, g);
	return TCL_OK;

    } else if (strcmp("findedge", argv[1]) == 0) {
	if (argc < 4) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"",
			     argv[0], " findedge tailnodename headnodename\"", NULL);
	    return TCL_ERROR;
	}
	if (!(tail = agfindnode(g, argv[2]))) {
	    Tcl_AppendResult(interp, "tail node \"", argv[2], "\" not found.", NULL);
	    return TCL_ERROR;
	}
	if (!(head = agfindnode(g, argv[3]))) {
	    Tcl_AppendResult(interp, "head node \"", argv[3], "\" not found.", NULL);
	    return TCL_ERROR;
	}
	if (!(e = agfindedge(g, tail, head))) {
	    Tcl_AppendResult(interp, "edge \"", argv[2], " - ", argv[3], "\" not found.", NULL);
	    return TCL_ERROR;
	}
	Tcl_AppendElement(interp, obj2cmd(e));
	return TCL_OK;

    } else if (strcmp("findnode", argv[1]) == 0) {
	if (argc < 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0], " findnode nodename\"", NULL);
	    return TCL_ERROR;
	}
	if (!(n = agfindnode(g, argv[2]))) {
	    Tcl_AppendResult(interp, "node not found.", NULL);
	    return TCL_ERROR;
	}
	Tcl_AppendResult(interp, obj2cmd(n), NULL);
	return TCL_OK;

    } else if (strcmp("layoutedges", argv[1]) == 0) {
	g = agroot(g);
	if (!aggetrec (g, "Agraphinfo_t",0))
	    tcldot_layout(gvc, g, (argc > 2) ? argv[2] : NULL);
	return TCL_OK;

    } else if (strcmp("layoutnodes", argv[1]) == 0) {
	g = agroot(g);
	if (!aggetrec (g, "Agraphinfo_t",0))
	    tcldot_layout(gvc, g, (argc > 2) ? argv[2] : NULL);
	return TCL_OK;

    } else if (strcmp("listattributes", argv[1]) == 0) {
	listGraphAttrs(interp, g);
	return TCL_OK;

    } else if (strcmp("listedgeattributes", argv[1]) == 0) {
	listEdgeAttrs (interp, g);
	return TCL_OK;

    } else if (strcmp("listnodeattributes", argv[1]) == 0) {
	listNodeAttrs (interp, g);
	return TCL_OK;

    } else if (strcmp("listedges", argv[1]) == 0) {
	for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	    for (e = agfstout(g, n); e; e = agnxtout(g, e)) {
		Tcl_AppendElement(interp, obj2cmd(e));
	    }
	}
	return TCL_OK;

    } else if (strcmp("listnodes", argv[1]) == 0) {
	for (n = agfstnode(g); n; n = agnxtnode(g, n)) {
	    Tcl_AppendElement(interp, obj2cmd(n));
	    
	}
	return TCL_OK;

    } else if (strcmp("listnodesrev", argv[1]) == 0) {
	for (n = aglstnode(g); n; n = agprvnode(g, n)) {
	    Tcl_AppendElement(interp, obj2cmd(n));
	}
	return TCL_OK;

    } else if (strcmp("listsubgraphs", argv[1]) == 0) {
	for (sg = agfstsubg(g); sg; sg = agnxtsubg(sg)) {
	    Tcl_AppendElement(interp, obj2cmd(sg));
	}
	return TCL_OK;

    } else if (strcmp("queryattributes", argv[1]) == 0) {
	for (int i = 2; i < argc; i++) {
	    if (Tcl_SplitList
		(interp, argv[i], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    for (j = 0; j < argc2; j++) {
		if ((a = agfindgraphattr(g, argv2[j]))) {
		    Tcl_AppendElement(interp, agxget(g, a));
		} else {
		    Tcl_AppendResult(interp, " No attribute named \"", argv2[j], "\"", NULL);
		    return TCL_ERROR;
		}
	    }
	    Tcl_Free((char *) argv2);
	}
	return TCL_OK;

    } else if (strcmp("queryattributevalues", argv[1]) == 0) {
	for (int i = 2; i < argc; i++) {
	    if (Tcl_SplitList
		(interp, argv[i], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    for (j = 0; j < argc2; j++) {
		if ((a = agfindgraphattr(g, argv2[j]))) {
		    Tcl_AppendElement(interp, argv2[j]);
		    Tcl_AppendElement(interp, agxget(g, a));
		} else {
		    Tcl_AppendResult(interp, " No attribute named \"", argv2[j], "\"", NULL);
		    return TCL_ERROR;
		}
	    }
	    Tcl_Free((char *) argv2);
	}
	return TCL_OK;

    } else if (strcmp("queryedgeattributes", argv[1]) == 0) {
	for (int i = 2; i < argc; i++) {
	    if (Tcl_SplitList
		(interp, argv[i], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    for (j = 0; j < argc2; j++) {
		if ((a = agfindedgeattr(g, argv2[j]))) {
		    Tcl_AppendElement(interp, agxget(g, a));
		} else {
		    Tcl_AppendResult(interp, " No attribute named \"", argv2[j], "\"", NULL);
		    return TCL_ERROR;
		}
	    }
	    Tcl_Free((char *) argv2);
	}
	return TCL_OK;

    } else if (strcmp("queryedgeattributevalues", argv[1]) == 0) {
	for (int i = 2; i < argc; i++) {
	    if (Tcl_SplitList
		(interp, argv[i], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    for (j = 0; j < argc2; j++) {
		if ((a = agfindedgeattr(g, argv2[j]))) {
		    Tcl_AppendElement(interp, argv2[j]);
		    Tcl_AppendElement(interp, agxget(g, a));
		} else {
		    Tcl_AppendResult(interp, " No attribute named \"",
				     argv2[j], "\"", NULL);
		    return TCL_ERROR;
		}
	    }
	    Tcl_Free((char *) argv2);
	}
	return TCL_OK;

    } else if (strcmp("querynodeattributes", argv[1]) == 0) {
	for (int i = 2; i < argc; i++) {
	    if (Tcl_SplitList
		(interp, argv[i], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    for (j = 0; j < argc2; j++) {
		if ((a = agfindnodeattr(g, argv2[j]))) {
		    Tcl_AppendElement(interp, agxget(g, a));
		} else {
		    Tcl_AppendResult(interp, " No attribute named \"",
				     argv2[j], "\"", NULL);
		    return TCL_ERROR;
		}
	    }
	    Tcl_Free((char *) argv2);
	}
	return TCL_OK;

    } else if (strcmp("querynodeattributevalues", argv[1]) == 0) {
	for (int i = 2; i < argc; i++) {
	    if (Tcl_SplitList
		(interp, argv[i], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    for (j = 0; j < argc2; j++) {
		if ((a = agfindnodeattr(g, argv2[j]))) {
		    Tcl_AppendElement(interp, argv2[j]);
		    Tcl_AppendElement(interp, agxget(g, a));
		} else {
		    Tcl_AppendResult(interp, " No attribute named \"", argv2[j], "\"", NULL);
		    return TCL_ERROR;
		}
	    }
	    Tcl_Free((char *) argv2);
	}
	return TCL_OK;

    } else if (strcmp("render", argv[1]) == 0) {
	const char *canvas;

	if (argc < 3) {
	    canvas = "$c";
	} else {
	    canvas = argv[2];
	}

        gvc->write_fn = Tcldot_string_writer;
	tcldot_context_t context = {.canvas = canvas, .interp = interp};

	/* make sure that layout is done */
	g = agroot(g);
	if (!aggetrec (g, "Agraphinfo_t",0) || argc > 3)
	    tcldot_layout (gvc, g, (argc > 3) ? argv[3] : NULL);

	/* render graph TK canvas commands */
	gvc->common.viewNum = 0;
	if (gvRenderContext(gvc, g, "tk", &context) != 0) {
	    return TCL_ERROR;
	}
	fflush(stdout);
	return TCL_OK;

    } else if (strcmp("setattributes", argv[1]) == 0) {
	if (argc == 3) {
	    if (Tcl_SplitList
		(interp, argv[2], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    if ((argc2 == 0) || (argc2 % 2)) {
		Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
				 "\" setattributes attributename attributevalue ?attributename attributevalue? ?...?",
				 NULL);
		Tcl_Free((char *) argv2);
		return TCL_ERROR;
	    }
	    setgraphattributes(g, argv2, argc2);
	    Tcl_Free((char *) argv2);
	}
	if (argc == 4 && strcmp(argv[2], "viewport") == 0) {
	    /* special case to allow viewport to be set without resetting layout */
	    setgraphattributes(g, &argv[2], argc - 2);
	} else {
	    if ((argc < 4) || (argc % 2)) {
		Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
				 "\" setattributes attributename attributevalue ?attributename attributevalue? ?...?",
				 NULL);
		return TCL_ERROR;
	    }
	    setgraphattributes(g, &argv[2], argc - 2);
	}
	return TCL_OK;

    } else if (strcmp("setedgeattributes", argv[1]) == 0) {
	if (argc == 3) {
	    if (Tcl_SplitList
		(interp, argv[2], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    if ((argc2 == 0) || (argc2 % 2)) {
		Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
				 "\" setedgeattributes attributename attributevalue ?attributename attributevalue? ?...?",
				 NULL);
		Tcl_Free((char *) argv2);
		return TCL_ERROR;
	    }
	    setedgeattributes(g, NULL, argv2, argc2);
	    Tcl_Free((char *) argv2);
	} else {
	    if ((argc < 4) || (argc % 2)) {
		Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
				 "\" setedgeattributes attributename attributevalue ?attributename attributevalue? ?...?",
				 NULL);
	    }
	    setedgeattributes(g, NULL, &argv[2], argc - 2);
	}
	return TCL_OK;

    } else if (strcmp("setnodeattributes", argv[1]) == 0) {
	if (argc == 3) {
	    if (Tcl_SplitList
		(interp, argv[2], &argc2,
		 (const char ***) &argv2) != TCL_OK)
		return TCL_ERROR;
	    if ((argc2 == 0) || (argc2 % 2)) {
		Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
				 "\" setnodeattributes attributename attributevalue ?attributename attributevalue? ?...?",
				 NULL);
		Tcl_Free((char *) argv2);
		return TCL_ERROR;
	    }
	    setnodeattributes(g, NULL, argv2, argc2);
	    Tcl_Free((char *) argv2);
	} else {
	    if ((argc < 4) || (argc % 2)) {
		Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
				 "\" setnodeattributes attributename attributevalue ?attributename attributevalue? ?...?",
				 NULL);
	    }
	    setnodeattributes(g, NULL, &argv[2], argc - 2);
	}
	return TCL_OK;

    } else if (strcmp("showname", argv[1]) == 0) {
	Tcl_SetResult(interp, agnameof(g), TCL_STATIC);
	return TCL_OK;
    } else if (strcmp("write", argv[1]) == 0) {
	g = agroot(g);
	if (argc < 3) {
	    Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
	      " write fileHandle ?language ?DOT|NEATO|TWOPI|FDP|CIRCO|NOP??\"",
	      NULL);
	    return TCL_ERROR;
	}

	gvc->write_fn = Tcldot_channel_writer;

	Tcl_Channel chan;
	{
	    int mode;

	    chan = Tcl_GetChannel(interp, argv[2], &mode);

	    if (!chan) {
	        Tcl_AppendResult(interp, "channel not open: \"", argv[2], NULL);
	        return TCL_ERROR;
	    }
	    if (!(mode & TCL_WRITABLE)) {
	        Tcl_AppendResult(interp, "channel not writable: \"", argv[2], NULL);
	        return TCL_ERROR;
	    }
	}

	/* make sure that layout is done  - unless canonical output */
	if (!aggetrec (g, "Agraphinfo_t",0) || argc > 4)
	    tcldot_layout(gvc, g, (argc > 4) ? argv[4] : NULL);

	gvc->common.viewNum = 0;
	if (gvRender(gvc, g, argc < 4 ? "dot" : argv[3], (FILE *)chan) != 0) {
	    return TCL_ERROR;
	}
	return TCL_OK;

    } else {
	Tcl_AppendResult(interp, "bad option \"", argv[1],
	 "\": must be one of:",
	 "\n\taddedge, addnode, addsubgraph, countedges, countnodes,",
	 "\n\tlayout, listattributes, listedgeattributes, listnodeattributes,",
	 "\n\tlistedges, listnodes, listsubgraphs, render, rendergd,",
	 "\n\tqueryattributes, queryedgeattributes, querynodeattributes,",
	 "\n\tqueryattributevalues, queryedgeattributevalues, querynodeattributevalues,",
	 "\n\tsetattributes, setedgeattributes, setnodeattributes,",
	 "\n\tshowname, write.", NULL);
	return TCL_ERROR;
    }
}				/* graphcmd */

int graphcmd(ClientData clientData, Tcl_Interp * interp, int argc,
             const char *argv[]) {
  char **argv_copy = tcldot_argv_dup(argc, argv);
  int rc = graphcmd_internal(clientData, interp, argc, argv_copy);
  tcldot_argv_free(argc, argv_copy);
  return rc;
}
