#include <assert.h>
#include <stdio.h>
#include <math.h>
#include "util.h"
#include "vpr_types.h"
#include "globals.h"
#include "rr_graph_area.h"
#include "segment_stats.h"
#include "stats.h"
#include "net_delay.h"
#include "path_delay.h"
#include "read_xml_arch_file.h"

/********************** Subroutines local to this module *********************/

static void load_channel_occupancies(int **chanx_occ,
				     int **chany_occ);

static void get_length_and_bends_stats(void);

static void get_channel_occupancy_stats(void);



/************************* Subroutine definitions ****************************/


void
routing_stats(boolean full_stats,
	      enum e_route_type route_type,
	      int num_switch,
	      t_segment_inf * segment_inf,
	      int num_segment,
	      float R_minW_nmos,
	      float R_minW_pmos,
	      enum e_directionality directionality,
	      boolean timing_analysis_enabled,
	      float **net_slack,
	      float **net_delay)
{

/* Prints out various statistics about the current routing.  Both a routing *
 * and an rr_graph must exist when you call this routine.                   */

    float T_crit;
	float area, used_area;
	int i, j;

    get_length_and_bends_stats();
    get_channel_occupancy_stats();

    printf("Logic Area (in minimum width transistor areas, excludes I/Os and empty grid tiles):\n");

	area = 0;
	for(i = 1; i <= nx; i++) {
		for(j = 1; j <= ny; j++) {
			if(grid[i][j].offset == 0) {
				if(grid[i][j].type->area == UNDEFINED) {
					area += grid_logic_tile_area * grid[i][j].type->height;
				} else {
					area += grid[i][j].type->area;
				}
			}
		}
	}
	/* Todo: need to add pitch of routing to blocks with height > 3 */
    printf("Total Logic Block Area (Warning, need to add pitch of routing to blocks with height > 3): %g \n", area);

	used_area = 0;
	for(i = 0; i < num_blocks; i++) {
		if(block[i].type != IO_TYPE) {
			if(block[i].type->area == UNDEFINED) {
				used_area += grid_logic_tile_area * block[i].type->height;
			} else {
				used_area += block[i].type->area;
			}
		}
	}
    printf("Total Used Logic Block Area: %g \n", used_area);

    if(route_type == DETAILED)
	{
	    count_routing_transistors(directionality, num_switch, segment_inf,
				      R_minW_nmos, R_minW_pmos);
	    get_segment_usage_stats(num_segment, segment_inf);

	    if(timing_analysis_enabled)
		{
		    load_net_delay_from_routing(net_delay, clb_net, num_nets);

#ifdef CREATE_ECHO_FILES
		    print_net_delay(net_delay, "net_delay.echo", clb_net, num_nets);
#endif /* CREATE_ECHO_FILES */

		    load_timing_graph_net_delays(net_delay);
		    T_crit = load_net_slack(net_slack, 0);

#ifdef CREATE_ECHO_FILES
		    print_timing_graph("timing_graph.echo");
		    print_net_slack("net_slack.echo", net_slack);
		    print_critical_path("critical_path.echo");
#endif /* CREATE_ECHO_FILES */

		    printf("\n");
			if(pb_max_internal_delay == UNDEFINED || pb_max_internal_delay < T_crit) {
				printf("Critical Path: %g (s)\n", T_crit);
			} else {
				printf("Critical Path: %g (s) - capped by fmax of block type %s\n", pb_max_internal_delay, pbtype_max_internal_delay->name);
			}
		}
	}

    if(full_stats == TRUE)
	print_wirelen_prob_dist();
}


void
get_length_and_bends_stats(void)
{

/* Figures out maximum, minimum and average number of bends and net length   *
 * in the routing.                                                           */

    int inet, bends, total_bends, max_bends;
    int length, total_length, max_length;
    int segments, total_segments, max_segments;
    float av_bends, av_length, av_segments;
    int num_global_nets, num_clb_opins_reserved;


    max_bends = 0;
    total_bends = 0;
    max_length = 0;
    total_length = 0;
    max_segments = 0;
    total_segments = 0;
    num_global_nets = 0;
	num_clb_opins_reserved = 0;

    for(inet = 0; inet < num_nets; inet++)
	{
		if(clb_net[inet].is_global == FALSE && clb_net[inet].num_sinks != 0)
		{		/* Globals don't count. */
		    get_num_bends_and_length(inet, &bends, &length,
					     &segments);

		    total_bends += bends;
		    max_bends = max(bends, max_bends);

		    total_length += length;
		    max_length = max(length, max_length);

		    total_segments += segments;
		    max_segments = max(segments, max_segments);
		}
	    else if (clb_net[inet].is_global)
		{
		    num_global_nets++;
		} else {
			num_clb_opins_reserved++;
		}
	}


    av_bends = (float)total_bends / (float)(num_nets - num_global_nets);
    printf
	("\nAverage number of bends per net: %#g  Maximum # of bends: %d\n\n",
	 av_bends, max_bends);

    av_length = (float)total_length / (float)(num_nets - num_global_nets);
    printf("\nThe number of routed nets (nonglobal): %d\n",
	   num_nets - num_global_nets);
    printf("Wirelength results (all in units of 1 clb segments):\n");
    printf("\tTotal wirelength: %d   Average net length: %#g\n",
	   total_length, av_length);
    printf("\tMaximum net length: %d\n\n", max_length);

    av_segments = (float)total_segments / (float)(num_nets - num_global_nets);
    printf("Wirelength results in terms of physical segments:\n");
    printf("\tTotal wiring segments used: %d   Av. wire segments per net: "
	   "%#g\n", total_segments, av_segments);
    printf("\tMaximum segments used by a net: %d\n\n", max_segments);
	printf("\tTotal local nets with reserved CLB opins: %d\n\n", num_clb_opins_reserved);
}


static void
get_channel_occupancy_stats(void)
{

/* Determines how many tracks are used in each channel.                    */

    int i, j, max_occ, total_x, total_y;
    float av_occ;
    int **chanx_occ;		/* [1..nx][0..ny] */
    int **chany_occ;		/* [0..nx][1..ny] */


    chanx_occ = (int **)alloc_matrix(1, nx, 0, ny, sizeof(int));
    chany_occ = (int **)alloc_matrix(0, nx, 1, ny, sizeof(int));
    load_channel_occupancies(chanx_occ, chany_occ);

    printf("\nX - Directed channels:\n\n");
    printf("j\tmax occ\tav_occ\t\tcapacity\n");

    total_x = 0;

    for(j = 0; j <= ny; j++)
	{
	    total_x += chan_width_x[j];
	    av_occ = 0.;
	    max_occ = -1;

	    for(i = 1; i <= nx; i++)
		{
		    max_occ = max(chanx_occ[i][j], max_occ);
		    av_occ += chanx_occ[i][j];
		}
	    av_occ /= nx;
	    printf("%d\t%d\t%-#9g\t%d\n", j, max_occ, av_occ,
		   chan_width_x[j]);
	}


    printf("\nY - Directed channels:\n\n");
    printf("i\tmax occ\tav_occ\t\tcapacity\n");

    total_y = 0;

    for(i = 0; i <= nx; i++)
	{
	    total_y += chan_width_y[i];
	    av_occ = 0.;
	    max_occ = -1;

	    for(j = 1; j <= ny; j++)
		{
		    max_occ = max(chany_occ[i][j], max_occ);
		    av_occ += chany_occ[i][j];
		}
	    av_occ /= ny;
	    printf("%d\t%d\t%-#9g\t%d\n", i, max_occ, av_occ,
		   chan_width_y[i]);
	}

    printf("\nTotal Tracks in X-direction: %d  in Y-direction: %d\n\n",
	   total_x, total_y);

    free_matrix(chanx_occ, 1, nx, 0, sizeof(int));
    free_matrix(chany_occ, 0, nx, 1, sizeof(int));
}


static void
load_channel_occupancies(int **chanx_occ,
			 int **chany_occ)
{

/* Loads the two arrays passed in with the total occupancy at each of the  *
 * channel segments in the FPGA.                                           */

    int i, j, inode, inet;
    struct s_trace *tptr;
    t_rr_type rr_type;

/* First set the occupancy of everything to zero. */

    for(i = 1; i <= nx; i++)
	for(j = 0; j <= ny; j++)
	    chanx_occ[i][j] = 0;

    for(i = 0; i <= nx; i++)
	for(j = 1; j <= ny; j++)
	    chany_occ[i][j] = 0;

/* Now go through each net and count the tracks and pins used everywhere */

    for(inet = 0; inet < num_nets; inet++)
	{

		if(clb_net[inet].is_global && clb_net[inet].num_sinks != 0)	/* Skip global and empty nets. */
		continue;

	    tptr = trace_head[inet];
	    while(tptr != NULL)
		{
		    inode = tptr->index;
		    rr_type = rr_node[inode].type;

		    if(rr_type == SINK)
			{
			    tptr = tptr->next;	/* Skip next segment. */
			    if(tptr == NULL)
				break;
			}

		    else if(rr_type == CHANX)
			{
			    j = rr_node[inode].ylow;
			    for(i = rr_node[inode].xlow;
				i <= rr_node[inode].xhigh; i++)
				chanx_occ[i][j]++;
			}

		    else if(rr_type == CHANY)
			{
			    i = rr_node[inode].xlow;
			    for(j = rr_node[inode].ylow;
				j <= rr_node[inode].yhigh; j++)
				chany_occ[i][j]++;
			}

		    tptr = tptr->next;
		}
	}
}


void
get_num_bends_and_length(int inet,
			 int *bends_ptr,
			 int *len_ptr,
			 int *segments_ptr)
{

/* Counts and returns the number of bends, wirelength, and number of routing *
 * resource segments in net inet's routing.                                  */

    struct s_trace *tptr, *prevptr;
    int inode;
    t_rr_type curr_type, prev_type;
    int bends, length, segments;

    bends = 0;
    length = 0;
    segments = 0;

    prevptr = trace_head[inet];	/* Should always be SOURCE. */
    if(prevptr == NULL)
	{
	    printf
		("Error in get_num_bends_and_length:  net #%d has no traceback.\n",
		 inet);
	    exit(1);
	}
    inode = prevptr->index;
    prev_type = rr_node[inode].type;

    tptr = prevptr->next;

    while(tptr != NULL)
	{
	    inode = tptr->index;
	    curr_type = rr_node[inode].type;

	    if(curr_type == SINK)
		{		/* Starting a new segment */
		    tptr = tptr->next;	/* Link to existing path - don't add to len. */
		    if(tptr == NULL)
			break;

		    curr_type = rr_node[tptr->index].type;
		}

	    else if(curr_type == CHANX || curr_type == CHANY)
		{
		    segments++;
		    length += 1 + rr_node[inode].xhigh - rr_node[inode].xlow +
			rr_node[inode].yhigh - rr_node[inode].ylow;

		    if(curr_type != prev_type
		       && (prev_type == CHANX || prev_type == CHANY))
			bends++;
		}

	    prev_type = curr_type;
	    tptr = tptr->next;
	}

    *bends_ptr = bends;
    *len_ptr = length;
    *segments_ptr = segments;
}


void
print_wirelen_prob_dist(void)
{

/* Prints out the probability distribution of the wirelength / number   *
 * input pins on a net -- i.e. simulates 2-point net length probability *
 * distribution.                                                        */

    float *prob_dist;
    float norm_fac, two_point_length;
    int inet, bends, length, segments, index;
    float av_length;
    int prob_dist_size, i, incr;

    prob_dist_size = nx + ny + 10;
    prob_dist = (float *)my_calloc(prob_dist_size, sizeof(float));
    norm_fac = 0.;

    for(inet = 0; inet < num_nets; inet++)
	{
		if(clb_net[inet].is_global == FALSE && clb_net[inet].num_sinks != 0)
		{
		    get_num_bends_and_length(inet, &bends, &length,
					     &segments);

/*  Assign probability to two integer lengths proportionately -- i.e.  *
 *  if two_point_length = 1.9, add 0.9 of the pins to prob_dist[2] and *
 *  only 0.1 to prob_dist[1].                                          */

		    two_point_length =
			(float)length / (float)(clb_net[inet].num_sinks);
		    index = (int)two_point_length;
		    if(index >= prob_dist_size)
			{

			    printf
				("Warning: index (%d) to prob_dist exceeds its allocated size (%d)\n",
				 index, prob_dist_size);
			    printf
				("Realloc'ing to increase 2-pin wirelen prob distribution array\n");
			    incr = index - prob_dist_size + 2;
			    prob_dist_size += incr;
			    prob_dist =
				my_realloc(prob_dist,
					   prob_dist_size * sizeof(float));
			    for(i = prob_dist_size - incr; i < prob_dist_size;
				i++)
				prob_dist[i] = 0.0;
			}
		    prob_dist[index] +=
			(clb_net[inet].num_sinks) * (1 - two_point_length +
						 index);

		    index++;
		    if(index >= prob_dist_size)
			{

			    printf
				("Warning: index (%d) to prob_dist exceeds its allocated size (%d)\n",
				 index, prob_dist_size);
			    printf
				("Realloc'ing to increase 2-pin wirelen prob distribution array\n");
			    incr = index - prob_dist_size + 2;
			    prob_dist_size += incr;
			    prob_dist =
				my_realloc(prob_dist,
					   prob_dist_size * sizeof(float));
			    for(i = prob_dist_size - incr; i < prob_dist_size;
				i++)
				prob_dist[i] = 0.0;
			}
		    prob_dist[index] += (clb_net[inet].num_sinks) * (1 - index +
								 two_point_length);

		    norm_fac += clb_net[inet].num_sinks;
		}
	}

/* Normalize so total probability is 1 and print out. */

    printf("\nProbability distribution of 2-pin net lengths:\n\n");
    printf("Length    p(Lenth)\n");

    av_length = 0;

    for(index = 0; index < prob_dist_size; index++)
	{
	    prob_dist[index] /= norm_fac;
	    printf("%6d  %10.6f\n", index, prob_dist[index]);
	    av_length += prob_dist[index] * index;
	}

    printf("\nThe number of 2-pin nets is ;%g;\n", norm_fac);
    printf("\nExpected value of 2-pin net length (R) is ;%g;\n", av_length);
    printf("\nTotal wire length is ;%g;\n", norm_fac * av_length);

    free(prob_dist);
}


void
print_lambda(void)
{

/* Finds the average number of input pins used per clb.  Does not    *
 * count inputs which are hooked to global nets (i.e. the clock     *
 * when it is marked global).                                       */

    int bnum, ipin;
    int num_inputs_used = 0;
    int iclass, inet;
    float lambda;
    t_type_ptr type;

    for(bnum = 0; bnum < num_blocks; bnum++)
	{
	    type = block[bnum].type;
	    assert(type != NULL);
	    if(type != IO_TYPE)
		{
		    for(ipin = 0; ipin < type->num_pins; ipin++)
			{
			    iclass = type->pin_class[ipin];
			    if(type->class_inf[iclass].type == RECEIVER)
				{
				    inet = block[bnum].nets[ipin];
				    if(inet != OPEN)	/* Pin is connected? */
					if(clb_net[inet].is_global == FALSE)	/* Not a global clock */
					    num_inputs_used++;
				}
			}
		}
	}

    lambda = (float)num_inputs_used / (float)num_blocks;
    printf("Average lambda (input pins used per clb) is: %g\n", lambda);
}
