/******************************************************************************
 * MC514 - Laboratório 1                                                      *
 ******************************************************************************
 * Grupo:                                                                     *
 *    Gustavo Sverzut Barbieri   ra008849                                     *
 *    Ivens Prates Telles Alves  ra008908                                     *
 ******************************************************************************
 *    Para compilar usando Threads, usar -DUSE_PTHREAD na linha de comando do   
 * compilador. Exemplo:
 *
 *        * Compilando usando threads:
 *            gcc mult_matriz.c -o mult_matriz -lpthread -DUSE_PTHREAD
 *
 *        * Compilando sem threads:
 *            gcc mult_matriz.c -o mult_matriz 
 *
 *    OBS: Este arquivo está comentado de tal forma que se pode gerar 
 * documentação usando o Doxygen.
 */

#ifdef USE_PTHREAD
#   include <pthread.h>
#endif
#include <stdio.h>
#include <stdlib.h>

#define matpos(row, col, col_size) ( (row) * (col_size) + (col) )

typedef struct matrix_s {
  int rows; /* number of rows */
  int cols; /* number of cols */
  int *matrix; /* matrix data (linear/1D). 
		  use matpos() (macro) to access data using 2D pairs (row, cool) */
} matrix_t;

/**
 * matrix_create: create a new matrix with size: rows x cols
 *
 * @param rows number of rows 
 * @param cols number of cols
 * @return the new *(malloc)ated* matrix (use matrix_destroy() after use)
 */
matrix_t *matrix_create(int rows, int cols)
{
  matrix_t *mat = NULL;
  int i;


  if ( (rows < 1) && (cols < 1) )
    return NULL;

  if ( (mat = (matrix_t*)calloc(1, sizeof(matrix_t))) == NULL)
    return NULL;

  if ( (mat->matrix = (int*)calloc(rows * cols, sizeof(int))) == NULL)
    {
      free(mat);
      return NULL;
    }

  mat->rows = rows;
  mat->cols = cols;

  return mat;
}

/**
 * matrix_destroy: destroy the given matrix
 * 
 * @param mat the matrix to be destroyed (and freed)
 */
void matrix_destroy(matrix_t *mat)
{
  if (mat)
    {
      if (mat->matrix)
	free(mat->matrix);
      free(mat);
      mat = NULL;
    }
}

/**
 * matrix_print: print the given matrix
 *
 * @param mat the matrix to be printed
 */
void matrix_print(matrix_t *mat)
{
  int i, j;

  if (! mat)
    return;

  printf("%d %d\n", mat->rows, mat->cols);
  for (i=0; i < mat->rows; i++)
    {
      for (j=0; j < mat->cols; j++)
	{
	  printf("%d ", mat->matrix[matpos(i, j, mat->cols)]);
	}
      printf("\n");
    }
  printf("\n");
}

/**
 * matrix_multiply_row: multiply the the src1_mat's row against the src2_mat's col, storing the result at dst_mat, position (row,col)
 *
 * @param src1_mat the source matrix #1 from which we use the 'row'.
 * @param src2_mat the source matrix #2 from which we use the 'col'.
 * @param dst_mat  the destination matrix, in which we store the src1_mat(row) * src2_mat(col).
 * @param row the src1_mat's row number to use.
 * @param col the src2_mat's col number to use.
 */ 
void matrix_multiply_row(matrix_t *src1_mat, matrix_t *src2_mat, matrix_t *dst_mat, int row, int col)
{
  int i, pos, val; 

  pos = matpos(row, col, dst_mat->cols);

  val = 0;

  for (i=0; i < src1_mat->cols; i++)
    {
      val += src1_mat->matrix[matpos(row, i, src1_mat->cols)] * src2_mat->matrix[matpos(i, col, src2_mat->cols)];      
    }
  dst_mat->matrix[pos] = val;

}

#ifdef USE_PTHREAD

/* structure to use as parameter to pthread matrix_multiply_row() version */
typedef struct pthread_matrix_multiply_row_s {
  matrix_t *src1_mat;
  matrix_t *src2_mat;
  matrix_t *dst_mat;
  int row;
  int col;
} pthread_matrix_multiply_row_t;

/**
 * matrix_multiply_row_pthread: a wrapper to matrix_multiply_row to be used with pthread
 *
 * @param v will be converted to pthread_matrix_multiply_row_t, which has all the parameters to be passed to matrix_multiply_row()
 * @return always NULL
 */
void *matrix_multiply_row_pthread(void *v)
{
  pthread_matrix_multiply_row_t *mat_mult = (pthread_matrix_multiply_row_t*)v;

  if (! mat_mult)
    return NULL;

  matrix_multiply_row(mat_mult->src1_mat, 
		      mat_mult->src2_mat, 
		      mat_mult->dst_mat, 
		      mat_mult->row, 
		      mat_mult->col);
  return NULL;
}

#endif

/**
 * matrix_multiply: multiply src1_mat * src2_mat
 * 
 * @param src1_mat the left side matrix
 * @param src2_mat the right side matrix
 * @return the resultant matrix or NULL, if errors occurred.
 */
matrix_t *matrix_multiply(matrix_t *src1_mat, matrix_t *src2_mat)
{
  int i, j;
  matrix_t *dst_mat = NULL;

#ifdef USE_PTHREAD
  int pthread_pos;
  pthread_t *pthread;
  pthread_matrix_multiply_row_t *pthread_param;
#endif

  /* check if matrices are valid */
  if ( (src1_mat == NULL) || (src2_mat == NULL) || (src1_mat->cols != src2_mat->rows) )
    return;

  dst_mat = matrix_create(src1_mat->rows, src2_mat->cols);

#ifdef USE_PTHREAD
  if ( (pthread = (pthread_t*)calloc( src1_mat->rows * src2_mat->cols, sizeof(pthread_t))) == NULL )
    return;
  if ( (pthread_param = (pthread_matrix_multiply_row_t*)calloc( src1_mat->rows * src2_mat->cols, sizeof(pthread_matrix_multiply_row_t))) == NULL)
    return;
#endif


  if (! dst_mat) 
    return NULL;

  /* multiply src1_mat(rows) x src2_mat(cols) */
  for (i=0; i < src1_mat->rows; i++)
    for (j=0; j < src2_mat->cols; j++)

#ifdef USE_PTHREAD
      {
	/* cache this value to avoid various recalculations */
	pthread_pos = j + i * src2_mat->cols;
	pthread_param[ pthread_pos ].src1_mat = src1_mat;
	pthread_param[ pthread_pos ].src2_mat = src2_mat;
	pthread_param[ pthread_pos ].dst_mat  = dst_mat;
	pthread_param[ pthread_pos ].row = i;
	pthread_param[ pthread_pos ].col = j;
	/* create a pthread, passing values as the pthread_matrix_multiply_row_t */
	pthread_create(&pthread[ pthread_pos ], 
		       NULL, 
		       matrix_multiply_row_pthread, 
		       &pthread_param[ pthread_pos ]);
      }
#else
  /* just multiply normally, one by one */
  matrix_multiply_row(src1_mat, src2_mat, dst_mat, i, j);
#endif


#ifdef USE_PTHREAD
  /* wait for threads */
    for (i=0; i < src1_mat->rows * src2_mat->cols; i++)
      pthread_join(pthread[i], NULL);
    
    /* free everything */
    if (pthread_param)
      free(pthread_param);
    if (pthread)
      free(pthread);
#endif
    
  return dst_mat;
}






/**
 * The main loop. 
 * We read user input and output the results of commands
 * @return 0 = ok. -1 = failed
 */
int main()
{
  int rows, cols, value, stop, retval = 0;
  matrix_t *a = NULL, *b = NULL, *c = NULL, *tmp = NULL;
  char opt, name;
  

  stop=0;
  while ( ((opt=getchar()) != EOF) && (!stop) )
    {
      switch (opt)
	{
	case 'q':
	case 'x':
	  stop=1;
	  break;	  
	case 'h':
	  printf("Help:\n"
		 "   q                           quit\n"
		 "   i [a|b] rows cols matrix    insert matrix 'a' or 'b' with rows x cols.\n"
		 "                               Then, follows matrix, which are matrix elements.\n"
		 "   m                           multiply a*b\n"
		 "   p [a|b|c]                   print matrix a, b or c\n"
		 "\n"
		 );
	  break;
	case 'i':
	  {
	    while ( ( (name = getchar()) == ' ' ) || (name == '\n') );

	    if ( name == EOF )
	      {
		retval = -1;
		opt = EOF;
		break;
	      }

	    if (scanf("%d %d", &rows, &cols) == EOF)
	      {
		retval = -1;
		opt = EOF;
		break;
	      }


	    if ( (rows < 1) || (cols < 1) )
	      break;
	    

	    if (name == 'a')
	      {
		if (a)
		  matrix_destroy(a);
		a = matrix_create(rows, cols);
		if (!a)
		  {
		    retval = -1;
		    stop = 1;
		    break;
		  }

		tmp = a;
	      }
	    else if (name == 'b')
	      {
		if (b)
		  matrix_destroy(b);
		b = matrix_create(rows, cols);
		if (!b)
		  {
		    retval = -1;
		    stop = 1;
		    break;
		  }
		tmp = b;
	      }
	    else
	      {
		printf("Invalid matrix name '%c'. Choose between 'a' and 'b'.\n", name);
		break;
	      }

	    for (rows = 0; rows < tmp->rows; rows++)
	      for (cols = 0; cols < tmp->cols; cols++)
		{
		  if (scanf("%d",&value) == EOF)
		    {
		      cols = tmp->cols; 
		      rows = tmp->rows;
		      retval = -1;
		      break;
		    }
		  tmp->matrix[matpos(rows, cols, tmp->cols)] = value;
		}
	    matrix_print(tmp);
	  }
	  break;
	case 'm':
	  {
	    c = matrix_multiply(a, b);
	    if (!c)
	      {
		retval = -1;
		stop = 1;
		break;
	      }    
	    matrix_print(c);
	  }
	  break;
	case 'p':
	  {
	    while ( ( (name = getchar()) == ' ' ) || (name == '\n') );
	    
	    if ( name == EOF )
	      {
		opt = EOF;
		retval = -1;
		break;
	      }
	    tmp = NULL;
	    switch (name)
	      {
	      case 'a':
		tmp = a;
		break;
	      case 'b':
		tmp = b;
		break;
	      case 'c':
		tmp = c;
		break;
	      default:
		printf("Invalid matrix name '%c'. Choose between 'a', 'b' and 'c'.\n",name);
		break;
	      }
	    if (tmp)
	      matrix_print(tmp);	  
	  }
	  break;
	default:
	  break;
	}

      if (opt == EOF) 
	break;
    }
  


  if (a)
    matrix_destroy(a);
  if (b)
    matrix_destroy(b);
  if (c)
    matrix_destroy(c);

  printf("Goodbye!\n");

  return retval;
}

/* author: Gustavo Sverzut Barbieri (http://www.gustavobarbieri.com.br) */

