#include "xbasic_types.h"
#include "xio.h"
#include "xintc_l.h"
#include "xuartlite_l.h"
#include "xparameters.h"
#define REG1ADDR 0x00800000
#define REG2ADDR 0x00800004
#define REG3ADDR 0x00800008
#define REG4ADDR 0x0080000C
#define REG5ADDR 0x00800010
#define REG6ADDR 0x00800014
#define REG7ADDR 0x00800018
#define REG8ADDR 0x0080001C
#define W 640
#define H 480
#define l_maze 10
#define w_maze 10
#define XMIN 130
#define XMAX 510
#define YMIN 50
#define YMAX 430
#define buffer_boundary 5
#define shift_x 256
#define shift_y 64

int x0_pli, y0_pli, x1_pli, y1_pli;
int volatile uart_interrupt_count = 0;
char uart_character;
char uart_buffer[5];
int volatile cp_i;
int rp_i;
volatile mutex;
int BLOCK_SIZE;
short draw_read=0;

int half_len;
int square_half_len;

/*
 * Interrupt service routine for the UART
 */
void uart_handler(void *callback)
{
  Xuint32 IsrStatus;
  Xuint8 incoming_character;
  mutex=0;

  /* Check the ISR status register so we can identify the interrupt source */
  IsrStatus = XIo_In32(XPAR_MYUART_BASEADDR + XUL_STATUS_REG_OFFSET);

  if ((IsrStatus & (XUL_SR_RX_FIFO_FULL | XUL_SR_RX_FIFO_VALID_DATA)) != 0) {
    /* The input FIFO contains data: read it */
    incoming_character =
      (Xuint8) XIo_In32( XPAR_MYUART_BASEADDR + XUL_RX_FIFO_OFFSET );

    uart_character = incoming_character;
    ++uart_interrupt_count;

    uart_buffer[cp_i]=incoming_character;
    cp_i++;
    if(cp_i==buffer_boundary)
    	cp_i=0;
  }
  mutex=1;
}

void draw_line_ver(int x0, int y0, int y1, int color) // |
{
	x0_pli=(x0 >> 1) - (y0 >> 1) + shift_x;
  	y0_pli=(x0 >> 1) + (y0 >> 1) - shift_y;
  	x1_pli=(x0 >> 1) - (y1 >> 1) + shift_x;
  	y1_pli=(x0 >> 1) + (y1 >> 1) - shift_y;

	XIo_Out16(REG1ADDR, x0_pli);
	XIo_Out16(REG2ADDR, x1_pli);
	XIo_Out16(REG3ADDR, y0_pli);
	XIo_Out16(REG4ADDR, y1_pli);
	XIo_Out16(REG5ADDR, color);
	draw_read=XIo_In16(REG5ADDR);
	while(draw_read!=0)
		draw_read=XIo_In16(REG5ADDR);
}

//void draw_line_hon(int x0, int y0, int x1, int color) // --
void draw_line_hon(int x0, int y0, int x1, int color) // --
{
	x0_pli=(x0 >> 1) - (y0 >> 1) + shift_x;
  	y0_pli=(x0 >> 1) + (y0 >> 1) - shift_y;
  	x1_pli=(x1 >> 1) - (y0 >> 1) + shift_x;
  	y1_pli=(x1 >> 1) + (y0 >> 1) - shift_y;

	XIo_Out16(REG1ADDR, x0_pli);
	XIo_Out16(REG2ADDR, x1_pli);
	XIo_Out16(REG3ADDR, y0_pli);
	XIo_Out16(REG4ADDR, y1_pli);
	XIo_Out16(REG5ADDR, color);
	draw_read=XIo_In16(REG5ADDR);
	while(draw_read!=0)
		draw_read=XIo_In16(REG5ADDR);
}

void draw_circle(int center_x, int center_y, int color)
{
	int i;


	int x, y, x0, y0, x1, y1;
	for(y=-half_len;y<half_len;y++)
	{
	  for(x=0;x*x+y*y<square_half_len;x++);
	     draw_line_hon(center_x-x, center_y+y, center_x+x, color);
/*
	  x0 = center_x-x;
	  y0 = center_y+y;
	  x1 = center_x+x;
	  y1 = center_y+y;

	  x0_pli=(x0 >> 1) - (y0 >> 1) + shift_x;
  	y0_pli=(x0 >> 1) + (y0 >> 1) - shift_y;
  	x1_pli=(x0 >> 1) - (y1 >> 1) + shift_x;
  	y1_pli=(x0 >> 1) + (y1 >> 1) - shift_y;

	XIo_Out16(REG1ADDR, x0_pli);
	XIo_Out16(REG2ADDR, x1_pli);
	XIo_Out16(REG3ADDR, y0_pli);
	XIo_Out16(REG4ADDR, y1_pli);
	XIo_Out16(REG5ADDR, color);
	draw_read=XIo_In16(REG5ADDR);
	while(draw_read!=0)
		draw_read=XIo_In16(REG5ADDR);
*/

	}


	/*
	for(i=0;i<half_len;i++)
	{
		draw_line_hon(center_x-half_len+i, center_y-i, center_x+half_len-i, color);
		draw_line_hon(center_x-half_len+i, center_y+i, center_x+half_len-i, color);
	}
	*/
}

struct myUnit
{
   short setnum;
   char bottom;
   char right;
};
struct myUnit myMaze[l_maze][w_maze];

//checks if all units are in the same set
int SameSet(void){
  int i, j;
  for(i=0;i<l_maze;i++){
    for(j=0;j<w_maze;j++){
      if(i==l_maze-1 && j==w_maze-1)
	return 1;
      else if(j!=w_maze-1){
	if((myMaze[i][j].setnum) != (myMaze[i][j+1].setnum))
	  return 0;
      }
      else{
	if((myMaze[i][j].setnum) != (myMaze[i+1][0].setnum))
	  return 0;
      }
    }
  }
  return 1;
}

void draw_hor(int i, int j)
{
	draw_line_hon(j*BLOCK_SIZE+XMIN, YMIN+(i+1)*BLOCK_SIZE, (j+1)*BLOCK_SIZE+XMIN+2, 0x1111);
}

void draw_ver(int i, int j)
{
	draw_line_ver((j+1)*BLOCK_SIZE+XMIN, YMIN+i*BLOCK_SIZE, YMIN+(i+1)*BLOCK_SIZE+2, 0x1111);
}

void printMaze(void){
  int i,j,r,b;

  for(i=0;i<l_maze;i++){
    for(j=0;j<w_maze;j++){

	r=myMaze[i][j].right;
	b=myMaze[i][j].bottom;

	if(b && !(i==l_maze-1 && j==w_maze-1))
		draw_hor(i,j);
	if(r)
		draw_ver(i,j);
     }
  }
  draw_line_ver(XMIN, YMIN, YMAX, 0x1111);
  draw_line_hon(XMIN+BLOCK_SIZE, YMIN, XMAX, 0x1111);
}

void create_maze()
{
  int i, j,randUnit2,r, c, k, temp;
  k=1;

  //maze initialization, sets every wall to 1
  for(i=0;i<l_maze;i++)
    for(j=0;j<w_maze;j++){
      myMaze[i][j].setnum=k;
      myMaze[i][j].right=1;
      myMaze[i][j].bottom=1;
      k++;
    }

  while(SameSet()!=1){
    r=rand() % l_maze;
    c=rand() % w_maze;
    randUnit2 = rand() % 2;

    if(randUnit2==0 && myMaze[r][c].right==1 && c!=w_maze-1 && myMaze[r][c].setnum != myMaze[r][c+1].setnum){
      temp=myMaze[r][c+1].setnum;
      myMaze[r][c].right=0;

      for(i=0;i<l_maze;i++)
	for(j=0;j<w_maze;j++){
	  if(myMaze[i][j].setnum == temp)
	    myMaze[i][j].setnum = myMaze[r][c].setnum;
	}
    }
    else if(randUnit2==1 && myMaze[r][c].bottom==1 && r!=l_maze-1 &&  myMaze[r][c].setnum != myMaze[r+1][c].setnum){
      temp=myMaze[r+1][c].setnum;
      myMaze[r][c].bottom=0;

      for(i=0;i<l_maze;i++)
	for(j=0;j<w_maze;j++){
	  if(myMaze[i][j].setnum == temp)
	    myMaze[i][j].setnum = myMaze[r][c].setnum;
	}
    }
  }

  //while(mutex==0) ;
  //microblaze_disable_interrupts();
  printMaze();
  //microblaze_enable_interrupts();
}


int main()
{
  char buf[2];
  int i, center_x, center_y, tempx, tempy;
  cp_i=0;
  rp_i=0;

  XIntc_RegisterHandler( XPAR_INTC_BASEADDR, XPAR_MYUART_DEVICE_ID,
	 		 (XInterruptHandler)uart_handler, (void *)0);
  XIntc_mEnableIntr( XPAR_INTC_BASEADDR, XPAR_MYUART_INTERRUPT_MASK);
  XIntc_mMasterEnable( XPAR_INTC_BASEADDR );
  XIntc_Out32(XPAR_INTC_BASEADDR + XIN_MER_OFFSET, XIN_INT_MASTER_ENABLE_MASK);
  microblaze_enable_interrupts();
  XUartLite_mEnableIntr(XPAR_MYUART_BASEADDR);
  //mutex=1;

  //while(mutex==0) ;
  //microblaze_disable_interrupts();
  //clean the screen
  for(i=0;i<480;i++)
  {
	XIo_Out16(REG1ADDR, 0);
  	XIo_Out16(REG2ADDR, 639);
  	XIo_Out16(REG3ADDR, i);
  	XIo_Out16(REG4ADDR, i);
  	XIo_Out16(REG5ADDR, 0x0000);
	draw_read=XIo_In16(REG5ADDR);
	while(draw_read!=0)
		draw_read=XIo_In16(REG5ADDR);
  }
  //microblaze_enable_interrupts();

  BLOCK_SIZE= (XMAX - XMIN)/w_maze;
  center_x=XMIN+BLOCK_SIZE/2;
  center_y=YMIN+BLOCK_SIZE/2;
  half_len= (BLOCK_SIZE>>1) - 7;
  square_half_len = half_len*half_len;

  create_maze();

  //microblaze_enable_interrupts();
  //while(mutex==0) ;
  //microblaze_disable_interrupts();
  draw_circle(center_x, center_y, 0xaaaa);
  //microblaze_enable_interrupts();

  for (;;) {
    buf[1] = '\0';
    print(""); //strange, we need this action to get UART input
    while (rp_i != cp_i) //rp_i, cp_i are the two indexes for competing method in circular buffer implementation
    {
    	while(mutex==0) ;
    	//microblaze_disable_interrupts();
	buf[0] = uart_buffer[rp_i];
	draw_circle(center_x, center_y, 0);

	tempx=center_x;
	tempy=center_y;

	if (buf[0] == 'B') // 2, down
		center_y = center_y + BLOCK_SIZE;
	else if (buf[0] == 'D') // 4, left
		center_x = center_x - BLOCK_SIZE;
	else if (buf[0] == 'C') // 6, right
		center_x = center_x + BLOCK_SIZE;
	else if (buf[0] == 'A') // 8, up
		center_y = center_y - BLOCK_SIZE;

            // boundary conditions
        if (center_y > YMAX - BLOCK_SIZE / 2)
		center_y = YMAX - BLOCK_SIZE / 2;
        else if (center_y < YMIN + BLOCK_SIZE / 2)
    	        center_y = YMIN + BLOCK_SIZE / 2;

        if (center_x > XMAX - BLOCK_SIZE / 2)
	        center_x = XMAX - BLOCK_SIZE / 2;
        else if (center_x < XMIN + BLOCK_SIZE / 2)
    	        center_x = XMIN + BLOCK_SIZE / 2;

	if(myMaze[(tempy-YMIN)/BLOCK_SIZE][(tempx-XMIN)/BLOCK_SIZE].right==1 && buf[0]=='C')
	      center_x=tempx;

	if(myMaze[(tempy-YMIN)/BLOCK_SIZE][(tempx-XMIN)/BLOCK_SIZE-1].right==1 && buf[0]=='D')
	      center_x=tempx;

	if(myMaze[(tempy-YMIN)/BLOCK_SIZE][(tempx-XMIN)/BLOCK_SIZE].bottom==1 && buf[0] == 'B')
	      center_y=tempy;

	if(myMaze[(tempy-YMIN)/BLOCK_SIZE-1][(tempx-XMIN)/BLOCK_SIZE].bottom==1 && buf[0] == 'A')
	      center_y=tempy;

        // draw the most recent circle
	if((center_x-XMIN)/BLOCK_SIZE==w_maze-1 && (center_y-YMIN)/BLOCK_SIZE==l_maze-1)
	      draw_circle(center_x, center_y, 0x6666);
	else
	      draw_circle(center_x, center_y, 0xaaaa);

	//move the "reading pointer index" rp_i one more char right
	rp_i++;
    	if(rp_i==buffer_boundary) //when it encoutner the buffer boundary, return to the header, this is why so called circular buffer
    		rp_i=0;

	//microblaze_enable_interrupts();
    }

    //for (i = 0 ; i < 10000000 ; i++ )  ;
  }

  return 0;
}



