/*
	Search::ContextGraph - XS implementation

	(C) 2003 Schuyler Erle, Maciej Ceglowski
	
	This is free software, released
	under the GPL.  See LICENSE for more
	information.
	
*/
  
# include "cns.h"
# include <unistd.h>
# include <stdlib.h>
# include <stdio.h>
# include <string.h>
# include <math.h>

# define debug 0



Edge *new_edge ( Edge *edge, long sink, float weight ) {
    /* Initialize a given edge struct. */
    edge->sink   = sink;
    edge->weight = weight;
    return edge;
}

Node *new_node ( Node *node, NodeType type, long capacity ) {
    /* Initialize a given node struct. */
    node->type	    = type;
    node->degree    = 0;
    node->capacity  = capacity;
    node->edges	    = calloc( capacity, sizeof(Node) );
    memset( node->edges, 0, capacity * sizeof(Node) );
    if (debug)
    	fprintf(stderr, "%p new_node(%d) capacity = %ld\n", node, type, capacity );
    return node;
}

Graph *new_graph ( Graph *graph, long capacity,
	float activationThreshold, float collectionThreshold ) {
    /* Initialize a given graph struct. */
    graph->size	    = 0;
    graph->capacity = capacity;
    graph->activationThreshold = activationThreshold;
    graph->collectionThreshold = collectionThreshold;
    graph->nodes    = calloc( capacity, sizeof(Node)  );
    graph->energy   = calloc( capacity, sizeof(float) );
    memset( graph->nodes, 0, capacity * sizeof(Node) );
    return graph;
}

void free_node (Node *node) {
    free( node->edges );
}

void free_graph (Graph *graph) {
    long n;
    /*fprintf( stderr, "%p graph size: %ld\n", graph, graph->size );*/
    for (n = 0; n < graph->size; n++)
		free_node( graph->nodes + n );
	free( graph->energy);
    free( graph->nodes );
    free( graph );
}


void reset_graph( Graph *graph ) {
    int i;
    graph->energy = realloc( graph->energy, graph->size * sizeof(float) );
    memset( graph->energy, 0, graph->size * sizeof(float) );
}

Edge *add_edge ( Node *node, long sink, float weight ) {
    long deg = ++(node->degree);
    long cap = node->capacity;
    Edge *edge;

	if (debug)
    printf( "* adding edge -> %ld(%f) to %p (%ld / %ld)\n", 
	sink, weight, node, deg, cap );

    /* If we have more edges than we've allocated memory for, reallocate. */
    if (deg > cap) {
	node->capacity *= 2;
	node->edges = realloc( node->edges, node->capacity * sizeof(Edge) );
	memset( node->edges + cap, 0, cap * sizeof(Edge) );
    }

    /* Initialize the new edge. */
    edge = node->edges + deg - 1;
    new_edge( edge, sink, weight );
    return edge;
}

Node *add_node ( Graph *graph, long id, NodeType type, long capacity ) {
    Node *node;

    /* If the index of the to-be-created node is bigger than we've allocated,
     * we need to reallocated. */
    if (debug)
    	fprintf( stderr, "%p add_node %ld/%ld (%ld)\n",
			graph, id, graph->size, graph->capacity);
	
	
    if (id >= graph->size) {
	long cap = graph->capacity;
	graph->size = id + 1;

	/* Increase the capacity until it's bigger than the number of nodes
	 * we need, if such need be, and then resize the lists if the capacity
	 * changed. */
	while (graph->size > graph->capacity )
	    graph->capacity *= 2;

		if (graph->capacity > cap) {
			graph->nodes = realloc(
				graph->nodes, graph->capacity * sizeof(Node));
			memset( graph->nodes + cap, 0, (graph->capacity - cap) * sizeof(Node) );
		}
    }
    if (debug)
    	fprintf( stderr, "%p --> size: %ld capacity: %ld (%ld)\n",
	    graph, graph->size, graph->capacity, 0);

    /* Initialize the new node, unless there already is such a node. */
    node = graph->nodes + id;
    if ( node->type == UNUSED ){
		new_node( node, type, capacity );
	 	if (debug)
    	fprintf( stderr, "%p --> new node\n",
	   	 node);
	} else {
		fprintf( stderr, "node %p had type %ld", node, node->type );
	}
    return node;
}

int energize_node( Graph *graph, long id, float energy, int isStartingPoint) {
    Node *node = graph->nodes + id;
    float *slot = graph->energy + id;
    
    float subenergy = 0;
    static int depth = 0;
    long n;
    
	depth++;
	if (debug){
    	int i;
    	for (i = 0; i< depth;i++)
    		fprintf( stderr, "   ");
    	fprintf(stderr, "%ld: energizing %f + %f\n", id, node->energy, energy, isStartingPoint);
    }
    
    /* Activate the node and calculate the propagating energy. */
    *slot += energy;
    if (node->degree) {
    	
    	//printf( "* denominator is %f\n", denom );
		subenergy = energy / (log( node->degree ) + 1);    
	}

	/* Special case handling for nodes with just one neighbor 
	/* Normally, we don't propagate energy at a singleton node 
	/* But if it's the query node, we want to continue the search */
	
	if ( node->degree == 1 ) {
	
		if ( isStartingPoint == 0 ){
			depth--;
			return 0;	
		} else {
			subenergy = energy;
		}
	}
	/*			     **********************/

	 /* Bail if the subenergy isn't big enough to propagate. */
    if (subenergy < graph->activationThreshold){
    	depth--;
		return 0;
	}
	
    /* Otherwise, recurse through our neighbors. */
    if (debug){
    	int i;
    	for (i = 0; i< depth;i++)
    		fprintf( stderr, "   ");
    	fprintf(stderr, "%ld: propagating subenergy %f to %ld neighbors\n", 
    		id, subenergy, node->degree);
    }
    
    for (n = 0; n < node->degree; n++) {
		energize_node( graph,
			node->edges[n].sink, node->edges[n].weight * subenergy , 0 );
    }
	
	if (debug){
    	int i;
    	for (i = 0; i< depth;i++)
    		fprintf( stderr, "   ");
    	fprintf(stderr, "%ld: finished\n", id);
    }
    depth--;
    return 1;
}

int compare_results( const void *a, const void *b ) {
    const Edge *x = a, *y = b;
    /* Sort nodes in descending order by energy. */
    return (x->weight > y->weight ? -1 :
	   (x->weight < y->weight ?  1 : 0));
}

 Edge *collect_results( Graph *graph ) {
 
    /* The result list will be a NULL-terminated list of edge structs. The
     * weight entry in each struct will contain the accumulated activation energy
     * of each sink. */
    long n, m;

    int result_size = 64;
      
    Edge *result = calloc( result_size, sizeof(Edge) );
    
    /* Find the nodes that scored higher than the threshold. */
    for (n = 0, m = 0; n < graph->size; n++ ) {
    	float *energy = graph->energy + n;
		if ( *( graph->energy + n) > graph->collectionThreshold ) {

			Edge *e = result + m++;
			e->sink = n;
			e->weight = *( graph->energy + n);
			
		} 
		
		/* Expand as necessary to fit the result list */
		while ( (m+1) > result_size ) {
			result_size *= 2;
			result = realloc( result, result_size * sizeof( Edge ));
		}
    }	
   
    memset( result + m, 0,   sizeof(Edge) );
    return result;

}

