#include "common.h"
#include "util.h"

SEXP getBoundBox(Agraph_t *g) {
    /* Determine the graphviz determiend bounding box and */
    /* assign it to the appropriate Ragraph structure */
    SEXP bbClass, xyClass, curBB, LLXY, URXY;

    xyClass = MAKE_CLASS("xyPoint");
    bbClass = MAKE_CLASS("boundingBox");

    PROTECT(curBB = NEW_OBJECT(bbClass));
    PROTECT(LLXY = NEW_OBJECT(xyClass));
    PROTECT(URXY = NEW_OBJECT(xyClass));

    SET_SLOT(LLXY,Rf_install("x"),Rf_ScalarInteger(g->u.bb.LL.x));
    SET_SLOT(LLXY,Rf_install("y"),Rf_ScalarInteger(g->u.bb.LL.y));
    SET_SLOT(URXY,Rf_install("x"),Rf_ScalarInteger(g->u.bb.UR.x));
    SET_SLOT(URXY,Rf_install("y"),Rf_ScalarInteger(g->u.bb.UR.y));

    SET_SLOT(curBB,Rf_install("botLeft"), LLXY);
    SET_SLOT(curBB,Rf_install("upRight"), URXY);

    UNPROTECT(3);
    return(curBB);
}

SEXP getEdgeLocs(Agraph_t *g) {
    SEXP outList, curCP, curEP, pntList, pntSet, curXY, curLab;
    SEXP epClass, cpClass, xyClass, labClass;
    Agnode_t *node, *head;
    Agedge_t *edge;
    char *tmpString;
    bezier bez;
    int nodes;
    int i,k,l,pntLstEl;
    int curEle = 0;

    epClass = MAKE_CLASS("AgEdge");
    cpClass = MAKE_CLASS("BezierCurve");
    xyClass = MAKE_CLASS("xyPoint");
    labClass = MAKE_CLASS("AgTextLabel");

    /* tmpString is used to convert a char to a char* w/ labels */
    tmpString = (char *)R_alloc(2, sizeof(char));
    if (tmpString == NULL) error("Allocation error in getEdgeLocs");

    PROTECT(outList = allocVector(VECSXP, agnedges(g)));

    nodes = agnnodes(g);
    node = agfstnode(g);

    for (i = 0; i < nodes; i++) {
        edge = agfstout(g, node);
        while (edge != NULL && edge->u.spl != NULL) {
            PROTECT(curEP = NEW_OBJECT(epClass));
            bez = edge->u.spl->list[0];
            PROTECT(pntList = allocVector(VECSXP, ((bez.size-1)/3)));
            pntLstEl = 0;

            /* There are really (bez.size-1)/3 sets of control */
            /* points, with the first set containing teh first 4 */
            /* points, and then every other set starting with the */
            /* last point from the previous set and then the next 3 */
            for (k = 1; k < bez.size; k += 3) {
                PROTECT(curCP = NEW_OBJECT(cpClass));
                PROTECT(pntSet = allocVector(VECSXP, 4));
                for (l = -1; l < 3; l++) {
                    PROTECT(curXY = NEW_OBJECT(xyClass));
                    SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(bez.list[k+l].x));
                    SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(bez.list[k+l].y));
                    SET_ELEMENT(pntSet, l+1, curXY);
                    UNPROTECT(1);
                }
                SET_SLOT(curCP, Rf_install("cPoints"), pntSet);
                SET_ELEMENT(pntList, pntLstEl++, curCP);
                UNPROTECT(2);
            }
            SET_SLOT(curEP, Rf_install("splines"), pntList);
            /* get the sp and ep */
            PROTECT(curXY = NEW_OBJECT(xyClass));
            SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(bez.sp.x));
            SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(bez.sp.y));
            SET_SLOT(curEP, Rf_install("sp"), curXY);
            UNPROTECT(1);
            PROTECT(curXY = NEW_OBJECT(xyClass));
            SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(bez.ep.x));
            SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(bez.ep.y));
            SET_SLOT(curEP, Rf_install("ep"), curXY);
            UNPROTECT(1);

            SET_SLOT(curEP, Rf_install("tail"), Rgraphviz_ScalarStringOrNull(node->name));
            head = edge->head;
            SET_SLOT(curEP, Rf_install("head"), Rgraphviz_ScalarStringOrNull(head->name));

            /* TODO: clean up the use of attrs: dir, arrowhead, arrowtail.
             * the following are for interactive plotting in R-env, not needed 
             * for output to files.  The existing codes set "dir"-attr, but use
             * "arrowhead"/"arrowtail" instead.  Quite confusing.  
             */
            SET_SLOT(curEP, Rf_install("dir"), Rgraphviz_ScalarStringOrNull(agget(edge, "dir")));
            SET_SLOT(curEP, Rf_install("arrowhead"), Rgraphviz_ScalarStringOrNull(agget(edge, "arrowhead")));
            SET_SLOT(curEP, Rf_install("arrowtail"), Rgraphviz_ScalarStringOrNull(agget(edge, "arrowtail")));
            SET_SLOT(curEP, Rf_install("arrowsize"), Rgraphviz_ScalarStringOrNull(agget(edge, "arrowsize")));

            SET_SLOT(curEP, Rf_install("color"), Rgraphviz_ScalarStringOrNull(agget(edge, "color")));

            /* get lty/lwd info */
            if ( agget(edge, "lty") )
               SET_SLOT(curEP, Rf_install("lty"), Rgraphviz_ScalarStringOrNull(agget(edge, "lty")));

            if ( agget(edge, "lwd") )
               SET_SLOT(curEP, Rf_install("lwd"), Rgraphviz_ScalarStringOrNull(agget(edge, "lwd")));

            /* Get the label information */
            if (edge->u.label != NULL) {
                PROTECT(curLab = NEW_OBJECT(labClass));
                SET_SLOT(curLab, Rf_install("labelText"),
                         Rgraphviz_ScalarStringOrNull(ED_label(edge)->u.txt.para->str));
                /* Get the X/Y location of the label */
                PROTECT(curXY = NEW_OBJECT(xyClass));
#if GRAPHVIZ_MAJOR == 2 && GRAPHVIZ_MINOR > 20
                SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(ED_label(edge)->pos.x));
                SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(ED_label(edge)->pos.y));
#else
                SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(edge->u.label->p.x));
                SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(edge->u.label->p.y));
#endif
                SET_SLOT(curLab, Rf_install("labelLoc"), curXY);
                UNPROTECT(1);

                snprintf(tmpString, 2, "%c",ED_label(edge)->u.txt.para->just);
                SET_SLOT(curLab, Rf_install("labelJust"),
                         Rgraphviz_ScalarStringOrNull(tmpString));

                SET_SLOT(curLab, Rf_install("labelWidth"),
                         Rf_ScalarInteger(ED_label(edge)->u.txt.para->width));

                SET_SLOT(curLab, Rf_install("labelColor"),
                         Rgraphviz_ScalarStringOrNull(edge->u.label->fontcolor));

                SET_SLOT(curLab, Rf_install("labelFontsize"), Rf_ScalarReal(edge->u.label->fontsize));

                SET_SLOT(curEP, Rf_install("txtLabel"), curLab);
                UNPROTECT(1);
            }

            SET_ELEMENT(outList, curEle++, curEP);
            UNPROTECT(2);
            edge = agnxtout(g, edge);
        }
        node = agnxtnode(g, node);
    }
    UNPROTECT(1);

    return(outList);
}

SEXP getNodeLayouts(Agraph_t *g) {
    Agnode_t *node;
    SEXP outLst, nlClass, xyClass, curXY, curNL;
    SEXP curLab, labClass;
    int i, nodes;
    char *tmpString;

    if (g == NULL) error("getNodeLayouts passed a NULL graph");

    nlClass = MAKE_CLASS("AgNode");
    xyClass = MAKE_CLASS("xyPoint");
    labClass = MAKE_CLASS("AgTextLabel");

    /* tmpString is used to convert a char to a char* w/ labels */
    tmpString = (char *)R_alloc(2, sizeof(char));
    if (tmpString == NULL) error("Allocation error in getNodeLayouts");

    nodes = agnnodes(g);
    node = agfstnode(g);

    PROTECT(outLst = allocVector(VECSXP, nodes));

    for (i = 0; i < nodes; i++) {
        PROTECT(curNL = NEW_OBJECT(nlClass));
        PROTECT(curXY = NEW_OBJECT(xyClass));
        SET_SLOT(curXY,Rf_install("x"),Rf_ScalarInteger(node->u.coord.x));
        SET_SLOT(curXY,Rf_install("y"),Rf_ScalarInteger(node->u.coord.y));
        SET_SLOT(curNL,Rf_install("center"),curXY);
        SET_SLOT(curNL,Rf_install("height"),Rf_ScalarInteger(node->u.ht));
        SET_SLOT(curNL,Rf_install("rWidth"),Rf_ScalarInteger(node->u.rw));
        SET_SLOT(curNL,Rf_install("lWidth"),Rf_ScalarInteger(node->u.lw));
        SET_SLOT(curNL,Rf_install("name"), Rgraphviz_ScalarStringOrNull(node->name));

        SET_SLOT(curNL, Rf_install("color"), Rgraphviz_ScalarStringOrNull(agget(node, "color")));
        SET_SLOT(curNL, Rf_install("fillcolor"), Rgraphviz_ScalarStringOrNull(agget(node, "fillcolor")));
        SET_SLOT(curNL, Rf_install("shape"), Rgraphviz_ScalarStringOrNull(agget(node, "shape")));
        SET_SLOT(curNL, Rf_install("style"), Rgraphviz_ScalarStringOrNull(agget(node, "style")));

        PROTECT(curLab = NEW_OBJECT(labClass));

	if (ND_label(node)  == NULL) {
	} else if (ND_label(node)->u.txt.para != NULL) {
            SET_SLOT(curLab, Rf_install("labelText"),
                     Rgraphviz_ScalarStringOrNull(ND_label(node)->u.txt.para->str));
            snprintf(tmpString, 2, "%c",ND_label(node)->u.txt.para->just);
            SET_SLOT(curLab, Rf_install("labelJust"), Rgraphviz_ScalarStringOrNull(tmpString));

            SET_SLOT(curLab, Rf_install("labelWidth"),
                     Rf_ScalarInteger(ND_label(node)->u.txt.para->width));
            /* Get the X/Y location of the label */
            PROTECT(curXY = NEW_OBJECT(xyClass));
#if GRAPHVIZ_MAJOR == 2 && GRAPHVIZ_MINOR > 20
	    SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(ND_label(node)->pos.x));
	    SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(ND_label(node)->pos.y));
#else
            SET_SLOT(curXY, Rf_install("x"), Rf_ScalarInteger(node->u.label->p.x));
            SET_SLOT(curXY, Rf_install("y"), Rf_ScalarInteger(node->u.label->p.y));
#endif
            SET_SLOT(curLab, Rf_install("labelLoc"), curXY);
            UNPROTECT(1);

            SET_SLOT(curLab, Rf_install("labelColor"), Rgraphviz_ScalarStringOrNull(node->u.label->fontcolor));

            SET_SLOT(curLab, Rf_install("labelFontsize"), Rf_ScalarReal(node->u.label->fontsize));

        }

        SET_SLOT(curNL, Rf_install("txtLabel"), curLab);

        SET_ELEMENT(outLst, i, curNL);
        node = agnxtnode(g,node);

        UNPROTECT(3);
    }
    UNPROTECT(1);
    return(outLst);
}

/*
static char *layouts[] = { "dot", "neato", "twopi", "circo", "fdp"};
*/

SEXP Rgraphviz_doLayout(SEXP graph, SEXP layoutType) {
    /* Will perform a Graphviz layout on a graph */

    Agraph_t *g;
    SEXP slotTmp, nLayout, cPoints, bb;

    /* Extract the Agraph_t pointer from the S4 object */
    PROTECT(slotTmp = GET_SLOT(graph, install("agraph")));
    CHECK_Rgraphviz_graph(slotTmp);
    g = R_ExternalPtrAddr(slotTmp);

    /* Call the appropriate Graphviz layout routine */
    gvLayout(gvc, g, CHAR(STRING_ELT(layoutType, 0)));

    /*
    if (!isInteger(layoutType))
        error("layoutType must be an integer value");
    else {
        gvLayout(gvc, g, layouts[INTEGER(layoutType)[0]]);
    }
    */

    /* Here we want to extract information for the resultant S4
       object */
    PROTECT(nLayout = getNodeLayouts(g));
    PROTECT(bb = getBoundBox(g));
    PROTECT(cPoints = getEdgeLocs(g));
    SET_SLOT(graph, Rf_install("agraph"), slotTmp);
    SET_SLOT(graph,Rf_install("AgNode"), nLayout);
    SET_SLOT(graph,Rf_install("laidout"), Rgraphviz_ScalarLogicalFromRbool(TRUE));
    SET_SLOT(graph,Rf_install("AgEdge"), cPoints);
    SET_SLOT(graph,Rf_install("boundBox"), bb);
    SET_SLOT(graph,Rf_install("fg"), Rgraphviz_ScalarStringOrNull(agget(g, "fgcolor")));
    SET_SLOT(graph,Rf_install("bg"), Rgraphviz_ScalarStringOrNull(agget(g, "bgcolor")));
    UNPROTECT(4);

    /* free gvc after rendering */
    gvFreeLayout(gvc, g);

    return(graph);
}