#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define num 64 //matrix size
//#include <valgrind/callgrind.h>
//clock_t start, end;
//double cpu_time_used; //cannot use time.h in syscall emulation mode of GEM5. Must check the time in stats.txt 

void matmul_unopt(int**  mat_A , int** mat_B, int** product_unopt, int N)
{
    for (int i = 0; i < N; i++) {
        for (int  k= 0; k < N; k++) {
            //#pragma GCC unroll 8
            for (int j = 0; j < N;j++)
			{
                product_unopt[i][j] += mat_A[i][k] * mat_B[k][j];
			} 
		}
    }
    return;
}
 
void matmul_opt(int** mat_A , int** mat_B, int** product_opt, int N)
{
     for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            for (int k = 0; k < N; k++)
                product_opt[i][j] += mat_A[i][k] * mat_B[k][j];
        }
    }
    return;
}


void correctness_test(int** product_unopt, int** product_opt, int N){
     
      int threshold = 0; 
      //(10^-6), ideally both should be equal but giving this room because of the single precison inting points)
      for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
	    //if( fabsf( fabs(product_unopt[i][j]) - fabsf(product_opt[i][j])) > threshold){
		//printf("Optimized implementation is incorrect\n");
		return;
               }
	     }
       printf("The implementation is correct\n");
       return;

}


int main (int argc, char *argv[])

{
  
  //srand((unsigned int)time(NULL));
  //srand won't work in the SE mode of GEM5. Going to just initialize matrices using iterative variables
  //read the size of the square matrix from command line 
  //if (argc > 1)
  //{
  //	char *a = argv[1];
  // 	num = atoi(a);
  //}
  //else 
  //setting  matrix size to 64 for now. 
   	  
  
  
  printf("Generating matrices of size %d * %d \n",num,num);

  int **mat_A = (int **)malloc(num * sizeof(int *)); 
  int **mat_B = (int **)malloc(num * sizeof(int *));
  int **product_unopt = (int **)malloc(num * sizeof(int *));
  //int **product_opt = (int **)malloc(num * sizeof(int *));
  
  for (int i=0; i<num; i++){ 
    mat_A[i] = (int *)malloc(num * sizeof(int)); 
	mat_B[i] = (int *)malloc(num * sizeof(int));
	product_unopt[i] = (int *)malloc(num * sizeof(int));
	//product_opt[i] = (int *)malloc(num * sizeof(int));
  }

  //
  for( int i=0; i<num; i++){
	for(int j=0; j<num; j++){
		mat_A[i][j]= i-j;
		mat_B[i][j]= 1;
        product_unopt[i][j]=0;  
    //    product_opt[i][j]=0;    
	}
  }
  
  printf("computing the results\n");

  //compute the product
  //TODO: add timers here to measure execution time
  // start = clock();
  //CALLGRIND_START_INSTRUMENTATION;
    matmul_unopt(mat_A, mat_B, product_unopt, num);
  //CALLGRIND_STOP_INSTRUMENTATION;
  // end = clock();
  // cpu_time_used = ((end - start)) / CLOCKS_PER_SEC;
  //printf("order i j k took %f seconds to execute \n", cpu_time_used);
   
   //start = clock();
   // matmul_opt(mat_A, mat_B, product_unopt, num);
   //end = clock();
   //cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
   // printf("order j k i took %f seconds to execute \n", cpu_time_used);
   

  //return 0;
  //TODO: add timers here to measure execution time
  //matmul_opt(mat_A, mat_B, product_opt, num);
  //correctness_test(product_unopt, product_opt, num);

 
 // printf("printng result \n");
 // for (int i = 0; i < num; i++) {
 //       for (int j = 0; j < num; j++) {	
 // 		printf("%f ", product_opt[i][j]);
 // 		}
 // 	 printf("\n");
 //  	}
  
  for (int i=0; i<num; i++)
  {
  
   free(mat_A[i]);
   free(mat_B[i]);
   free(product_unopt[i]);
   //free(product_opt[i]);
  }

   free(mat_A);
   free(mat_B);
   free(product_unopt);
   //free(product_opt);
  return (0);

}
