
@ensure_csrf_cookie  # Because the form/render combination is not used.
@login_required()
def showOutline(request):
    try:
        mapId = currently_opened_map(request)
        outlineFile = makeOutline(mapId)        # writes new graph to disk
        out = "<div class='textgraph sizeText120percent'>"
        outlineGraph = open(outlineFile,'r').read()
        out += outlineGraph
        out += "</div>" 
        return HttpResponse(out, status=200)    
    except Warning as err:
        context = {'error':str(err)}
        return render(request, "showWarning.html", context, content_type="application/html")
    except Exception as err:
        # FIXME: check places where HttpResponse returns 600.  That's not allowed.
        return HttpResponse(str(err), status=406)   

showOutline.alters_data = True



def makeLabel(obj, labelLen=None):
    try:
        label = ""
        if obj._meta.model_name=="node":
            if obj.nodetype == settings.TUNNEL_NODE: # FIXME: never used.  See massageChangeValues2BeTarget()
                tunnelTargetMap = obj.tunnelfarendmap
                # FIXME: another reason to just put tunnel labels in their label field like other nodes.
                label = settings.DEFAULT_TUNNEL_LABEL + " to '" + tunnelTargetMap.mapname + "'"

            elif obj.label:
                label = obj.label
            else:
                label = settings.DEFAULTLABELS[obj.nodetype]

        elif obj.label:  # some other object that can have a label, like an edge
            label = obj.label

        if labelLen:
            label = shortLabel(label, labelLen)

        return(label)
    except Exception as err:
        raise


#**
# recursively traverses a multi-child tree (a non-directed multigraph)
#
# @param    int     edge the key into the Edge table
# @param    boolean $swap I don't know
# @author   Ron Newman <ron.newman@imt.net>
#/

# FIXME: make these args **args
# FIXME: more efficient to make out variable global rather than recursive?
def climbTree(mapId, edge, lastWroteNewline, indentlevel):  # this function is recursive, depth-first 
    try:
        if edge:
            # Mark our trail so that we don't retrieve this edge again 
            Edge.objects.filter(pk=edge.id, ofmap=mapId).update(pheremone=True)

            indentlevel += 1
            if edge.pheremone:
                return(True)
            originId = edge.origin_id
            targetId = edge.target_id
            edgelabel = makeLabel(edge, settings.OUTLINE_EDGE_LABEL_LENGTH)
            if not edgelabel:
                edgelabel = "&rarr;"
            else:
                edgelabel = "&horbar;<span class='italicText'>"+ edgelabel +"</span>&rarr;"

            # TODO: Look into double direction tree traversal for possible greater efficiency.
            # See graphviz.org archives, search for 'dijkstra'
            # if(swap) { temp=origin; origin=target; target=temp; }
            origin = Node.objects.get(pk=originId, ofmap=mapId)
            originLabel = makeLabel(origin, settings.OUTLINE_NODE_LABEL_LENGTH)
            if origin.url:
                originLabel = "<a href='"+ str(origin.url) + "'>"+ originLabel + "</a>"

            # show the origin node
            out = ''
            if lastWroteNewline:
                for i in range(1,indentlevel,1):
                    # matching indent of the corresponding cell in the row above,
                    # one cell for content of the row above, one for edge 
                    out += "<div class='tgCell'>&nbsp;</div><div class='tgPredCell'>&nbsp;</div>"

            lastWroteNewline = False
            out += "<div class='tgCell'>"+ originLabel + "</div>"


            # show the edge
            out += " <div class='tgPredCell'>"+ edgelabel + "</div> "


            # show the target node
            # See if this target node has children by looking for edges emanating from it.
            edgesFromChild = Edge.objects.filter(origin=targetId, pheremone=False, status=settings.EDGE_ACTIVE, ofmap=mapId)

            # Notice that target becomes origin on the recursion
            if not edgesFromChild.count():  # no grandchildren to continue searching, just get the child 
                target = Node.objects.get(pk=targetId, ofmap=mapId)
                targetLabel = makeLabel(target, settings.OUTLINE_NODE_LABEL_LENGTH)
                out += "<div class='tgCell'>" + targetLabel + "</div>"
                out += "<br clear='both'/>"
                lastWroteNewline = True
            else:  # has children
                for edge in edgesFromChild:
                    out += climbTree(mapId, edge, lastWroteNewline, indentlevel)        # start the recursion
                    # unwinding recursive call stack
                    lastWroteNewline = True
            return(out)

    except Exception as err:
        return HttpResponse(str(err), status=406)   




@transaction.atomic()
def makeOutline(mapId, forExport=False):
# FIXME: make it read from file if map hasn't changed.
#
# @author   Ron Newman <ron.newman@imt.net>
#/
    try:
        out=""

        # FIXME: do pheremones in Python and delete the field from the DB
        # Init all edges to not have a pheremone
        Edge.objects.filter(ofmap=mapId, status=settings.EDGE_ACTIVE).update(pheremone=False)
         
        #FIXME: a slight performance boost could be had by changing this outer loop to a do-while and re-querying 
        # every time through the outer loop for non-pheremones

        # LOOK FOR HIERARCHICAL STRUCTURES **************************
        # Get nodes which are the heads of branches (vectors out, but not in).  These will form the left displayed column.
        edges = Edge.objects.filter(ofmap=mapId, status=settings.EDGE_ACTIVE, pheremone=False)
        indentlevel=0
        for edge in edges:
            origin=edge.origin
            # Do NOT check for pheremone in the following query.  That causes vectors to be checked twice when a non-cycle 
            # branches off from a cycle.
            numParents = Edge.objects.filter(target=edge.origin, ofmap=mapId, status=settings.EDGE_ACTIVE, pheremone=False).count()
            if not numParents: # The origin node is not pointed to by another node, so start traversing its children
                lastWroteNewline = False # init
                out += climbTree(mapId, edge, lastWroteNewline, indentlevel)        # start the recursion

        # HIERARCHICAL STRUCTURES FINISHED **************************


        # NOW LOOK FOR CIRCULAR STRUCTURES *************************************
        edges = Edge.objects.filter(ofmap=mapId, pheremone=False, status=settings.EDGE_ACTIVE)
        for edge in edges:
            origin = edge.origin
            # Take one, consider it a parent and start looking for children.
            edgesFromChild = Edge.objects.filter(ofmap=mapId, origin=edge.origin, pheremone=False, status=settings.EDGE_ACTIVE)
            for childedge in edgesFromChild:
                lastWroteNewline=True
                out += climbTree(mapId, childedge, lastWroteNewline, indentlevel)       # start the recursion
                # FIXME: true?:  We need a secondary pheremone in case this vector is reached in the recursion and 
                # then is still in "cycle", above, in which case it is handled twice. 
                # Edge.objects.filter(pk=childedge.id, ofmap=mapId).update(pheremone=True)
        # CIRCULAR STRUCTURES FINISHED *********************



        # NOW GET SINGLETON NODES *********************
        sqlQuery = "select ideatree_node.id, ideatree_edge.id from ideatree_node LEFT JOIN ideatree_edge ON ((ideatree_node.id=ideatree_edge.origin_id OR ideatree_node.id=ideatree_edge.target_id) AND ideatree_edge.status=%s) WHERE (ideatree_edge.id IS NULL) AND (ideatree_node.nodetype !=%s) AND (ideatree_node.nodetype !=%s) AND (ideatree_node.status=%s) AND (ideatree_node.ofmap_id=%s)"
        # NOTE: if there are no singletons, this query will issue an 'Internal StopIteration' if stepped through in Pdb, but doesn't issue a 
        # StopIteration exception, and running the same query in the PgAdmin tool for Postgresql shows no error.  There seems to be no harm.
        # There's a warning to that effect in the Django docs:  https://docs.djangoproject.com/en/2.0/topics/db/sql/
        edgeStatus = settings.EDGE_ACTIVE 
        provisional = settings.PROVISIONAL
        cluster = settings.CLUSTER
        nodeActive = settings.NODE_ACTIVE
        nodes = Node.objects.raw(sqlQuery, [edgeStatus,provisional,cluster,nodeActive,mapId])
        for node in nodes:
            nodelabel = makeLabel(node, settings.OUTLINE_NODE_LABEL_LENGTH)
            out += " <div class='tgCell'>" + nodelabel + "</div>"
            out += "<br clear='both'/>";

        out += "<p/>"
        # SINGLETONS FINISHED ******************************************

        clearOutputFiles(mapId)

        # SAVE THE GRAPH TO DISK
        exporting = False  #FIXME
        if exporting:
            make_absoluteExportDir(mapId)
            clear_absoluteExportDir(mapId)  # FIXME: do this in the __del__ method of an export class
            # FIXME: test with international characters
            outline_fp = open(outlineFileName,"wt", encoding="UTF-8")
            outline_fp.write(out)
            outline_fp.close()

        # FIXME: test with international characters
        else:
            make_absoluteOutfileDir(mapId)
            outlineFileName = make_absoluteOutfilePath(mapId, settings.OUTLINE_FILETYPE)
            outline_fp = open(outlineFileName,"wt", encoding="UTF-8")
            outline_fp.write(out)
            outline_fp.close()
        return(outlineFileName)

    except StopIteration as err:
        return HttpResponse(str(err), status=406)   
    except Exception as err:
        return HttpResponse(str(err), status=406)   


