#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "util.h"
#include "vpr_types.h"
#include "globals.h"
#include "ff_pack.h"

void absorb_buffer_luts() {
	/* This routine uses a simple pattern matching algorithm to remove buffer LUTs where possible (single-input LUTs that are programmed to be a wire) */

	int bnum, in_blk, out_blk, ipin, out_net, in_net;
	int removed = 0;

	/* Pin ordering for the clb blocks (1 VPACK_LUT + 1 FF in each logical_block) is      *
	* output, n VPACK_LUT inputs, clock input.                                   */

	for (bnum=0;bnum<num_logical_blocks;bnum++) {
		if (strcmp(logical_block[bnum].model->name, "names") == 0) {
			if(logical_block[bnum].truth_table != NULL && logical_block[bnum].truth_table->data_vptr) {
				if(strcmp("0 0", (char*)logical_block[bnum].truth_table->data_vptr) == 0 ||
					strcmp("1 1", (char*)logical_block[bnum].truth_table->data_vptr) == 0)
				{
					for(ipin = 0; ipin < logical_block[bnum].model->inputs->size; ipin++) {
						if(logical_block[bnum].input_nets[0][ipin] == OPEN)
							break;
					}
					assert(ipin == 1);

					assert(logical_block[bnum].clock_net == OPEN);
					assert(logical_block[bnum].model->inputs->next == NULL);
					assert(logical_block[bnum].model->outputs->size == 1);
					assert(logical_block[bnum].model->outputs->next == NULL);
					
					in_net = logical_block[bnum].input_nets[0][0];   /* Net driving the buffer */
					out_net = logical_block[bnum].output_nets[0][0];   /* Net the buffer us driving */
					out_blk = vpack_net[out_net].node_block[1];
					in_blk = vpack_net[in_net].node_block[0];

					assert(in_net != OPEN);
					assert(out_net != OPEN);
					assert(out_blk != OPEN);
					assert(in_blk != OPEN);
					
					/* TODO: Make this handle general cases, due to time reasons I can only handle buffers with single outputs */
					if (vpack_net[out_net].num_sinks == 1) {
						for(ipin = 1; ipin <= vpack_net[in_net].num_sinks; ipin++) {
							if(vpack_net[in_net].node_block[ipin] == bnum) {
								break;
							}
						}
						assert(ipin <= vpack_net[in_net].num_sinks);
						 
						vpack_net[in_net].node_block[ipin] = vpack_net[out_net].node_block[1];  /* New output */
						vpack_net[in_net].node_block_port[ipin] = vpack_net[out_net].node_block_port[1];
						vpack_net[in_net].node_block_pin[ipin] = vpack_net[out_net].node_block_pin[1];

						assert(logical_block[out_blk].input_nets[vpack_net[out_net].node_block_port[1]][vpack_net[out_net].node_block_pin[1]] == out_net);
						logical_block[out_blk].input_nets[vpack_net[out_net].node_block_port[1]][vpack_net[out_net].node_block_pin[1]] = in_net;
						
						vpack_net[out_net].node_block[0] = OPEN; /* This vpack_net disappears; mark. */
						vpack_net[out_net].node_block_pin[0] = OPEN; /* This vpack_net disappears; mark. */
						vpack_net[out_net].node_block_port[0] = OPEN; /* This vpack_net disappears; mark. */
						vpack_net[out_net].num_sinks = 0; /* This vpack_net disappears; mark. */
						
						logical_block[bnum].type = VPACK_EMPTY;   /* Mark logical_block that had LUT */
						
						/* error checking */
						for (ipin=0;ipin<=vpack_net[out_net].num_sinks;ipin++) {
							assert(vpack_net[out_net].node_block[ipin] != bnum);
						}
						removed++;
					}
				}
			}
		}
	}
	printf("Removed %d LUT buffers \n", removed);
}

void compress_netlist () {

/* This routine removes all the VPACK_EMPTY blocks and OPEN nets that *
 * may have been created by the ff_pack routine.  After this    *
 * routine, all the VPACK_LUT+FF blocks that exist in the netlist     *
 * are in a contiguous list with no unused spots.  The same     *
 * goes for the list of nets.  This means that blocks and nets  *
 * have to be renumbered somewhat.                              */

 int inet, iblk, index, ipin, new_num_nets, new_num_blocks, i;
 int *net_remap, *block_remap;
 int num_nets;
 t_model_ports *port;
 struct s_linked_vptr *tvptr, *next;

 new_num_nets = 0;
 new_num_blocks = 0;
 net_remap = my_malloc (num_logical_nets * sizeof(int));
 block_remap = my_malloc (num_logical_blocks * sizeof(int));

 for (inet=0;inet<num_logical_nets;inet++) {
	 if (vpack_net[inet].node_block[0] != OPEN) {
       net_remap[inet] = new_num_nets;
       new_num_nets++;
    }
    else {
       net_remap[inet] = OPEN;
    }
 }

 for (iblk=0;iblk<num_logical_blocks;iblk++) {
    if (logical_block[iblk].type != VPACK_EMPTY) {
       block_remap[iblk] = new_num_blocks;
       new_num_blocks++;
    }
    else {
       block_remap[iblk] = OPEN;
    }
 }

 
 if (new_num_nets != num_logical_nets || new_num_blocks != num_logical_blocks) {

    for (inet=0;inet<num_logical_nets;inet++) {
		if (vpack_net[inet].node_block[0] != OPEN) {
          index = net_remap[inet];
          vpack_net[index] = vpack_net[inet];
		  for (ipin = 0; ipin <= vpack_net[index].num_sinks; ipin++) 
		  {
			  vpack_net[index].node_block[ipin] = block_remap[vpack_net[index].node_block[ipin]];
		  }
       }
       else {
          free (vpack_net[inet].name);
		  free (vpack_net[inet].node_block);
		  free (vpack_net[inet].node_block_port);
		  free (vpack_net[inet].node_block_pin);
       }
    }
  
    num_logical_nets = new_num_nets;
    vpack_net = (struct s_net *) my_realloc (vpack_net, 
             num_logical_nets * sizeof (struct s_net));

    for (iblk=0;iblk<num_logical_blocks;iblk++) {
       if (logical_block[iblk].type != VPACK_EMPTY) {
          index = block_remap[iblk];
		  if(index != iblk) {
			logical_block[index] = logical_block[iblk];
		  }

		  num_nets = 0;
		  port = logical_block[index].model->inputs;
		  while(port) {
			  for(ipin = 0; ipin < port->size; ipin++) {
				  if(port->is_clock) {
					  assert(port->size == 1 && port->index == 0 && ipin == 0);
					  if(logical_block[index].clock_net == OPEN)
						continue;
					  logical_block[index].clock_net =
						net_remap[logical_block[index].clock_net];
				  } else {
					if(logical_block[index].input_nets[port->index][ipin] == OPEN)
					  continue;
					logical_block[index].input_nets[port->index][ipin] =
						net_remap[logical_block[index].input_nets[port->index][ipin]];
				  }
				num_nets++;
    		  }
			  port = port->next;
		  }

		  port = logical_block[index].model->outputs;
		  while(port) {
			  for(ipin = 0; ipin < port->size; ipin++) {
				  if(logical_block[index].output_nets[port->index][ipin] == OPEN)
					  continue;
				logical_block[index].output_nets[port->index][ipin] =
					net_remap[logical_block[index].output_nets[port->index][ipin]];
				num_nets++;
    		  }
			  port = port->next;
		  }
       }

       else {
			free (logical_block[iblk].name);
			port = logical_block[iblk].model->inputs;
			i = 0;
			while(port) {
				if(!port->is_clock) {
					if(logical_block[iblk].input_nets) {
						if(logical_block[iblk].input_nets[i]) {
							free(logical_block[iblk].input_nets[i]);
							logical_block[iblk].input_nets[i] = NULL;
						}
					}
					i++;
				}
				port = port->next;
			}
			if(logical_block[iblk].input_nets)
				free(logical_block[iblk].input_nets);
			port = logical_block[iblk].model->outputs;
			i = 0;
			while(port) {
				if(logical_block[iblk].output_nets) {
					if(logical_block[iblk].output_nets[i]) {
						free(logical_block[iblk].output_nets[i]);
						logical_block[iblk].output_nets[i] = NULL;
					}
				}
				i++;
				port = port->next;
			}
			if(logical_block[iblk].output_nets)
				free(logical_block[iblk].output_nets);
			tvptr = logical_block[iblk].truth_table;
			while(tvptr != NULL) {
				if(tvptr->data_vptr)
					free(tvptr->data_vptr);
				next = tvptr->next;
				free(tvptr);
				tvptr = next;
			}
       }
    }

	printf("Sweeped away %d nodes\n", num_logical_blocks - new_num_blocks);

    num_logical_blocks = new_num_blocks;
    logical_block = (struct s_logical_block *) my_realloc (logical_block, 
              num_logical_blocks * sizeof (struct s_logical_block));
 }

/* Now I have to recompute the number of primary inputs and outputs, since *
 * some inputs may have been unused and been removed.  No real need to     *
 * recount primary outputs -- it's just done as defensive coding.          */

 num_p_inputs = 0;
 num_p_outputs = 0;
 
 for (iblk=0;iblk<num_logical_blocks;iblk++) {
    if (logical_block[iblk].type == VPACK_INPAD) 
       num_p_inputs++;
    else if (logical_block[iblk].type == VPACK_OUTPAD) 
       num_p_outputs++;
 }
 
 
 free (net_remap);
 free (block_remap);
}

boolean *alloc_and_load_is_clock (boolean global_clocks) {

	/* Looks through all the logical_block to find and mark all the clocks, by setting *
	 * the corresponding entry in is_clock to true.  global_clocks is used     *
	 * only for an error check.                                                */

	int num_clocks, bnum, clock_net;
	boolean *is_clock;

	num_clocks = 0;

	is_clock = (boolean *) my_calloc (num_logical_nets, sizeof(boolean));

	/* Want to identify all the clock nets.  */

	for (bnum=0;bnum<num_logical_blocks;bnum++) {
		if(logical_block[bnum].type == VPACK_LATCH) {
			clock_net = logical_block[bnum].clock_net;
			assert(clock_net != OPEN);
			if (is_clock[clock_net] == FALSE) {
				is_clock[clock_net] = TRUE;
				num_clocks++;
			}
		} else {
			if(logical_block[bnum].clock_net != OPEN) {
				clock_net = logical_block[bnum].clock_net;
				if (is_clock[clock_net] == FALSE) {
					is_clock[clock_net] = TRUE;
					num_clocks++;
				}
			}
		}
	}

	/* If we have multiple clocks and we're supposed to declare them global, *
	* print a warning message, since it looks like this circuit may have    *
	* locally generated clocks.                                             */

	if (num_clocks > 1 && global_clocks) {
		printf("Warning:  circuit contains %d clocks.\n", num_clocks);
		printf("          All will be marked global.\n");
	}

	return (is_clock);
}
