/**@file
 *
 * Some CRS-matrix support (allocation, printing, far from complete)
 *
 *
 *      authors: Claus-Justus Heine
 *               Abteilung fuer Angewandte Mathematik                     
 *               Albert-Ludwigs-Universitaet Freiburg
 *               Hermann-Herder-Str. 10
 *               79104 Freiburg
 *               Germany
 *               claus@mathematik.uni-freiburg.de
 *
 *      Copyright (c) by C.-J. Heine (2003-2009)
 */

#if HAVE_CONFIG_H
# include <config.h>
#endif

#include <alberta.h>
#include "crs_matrix.h"

/** Allocate a CRS_MATRIX_INFO structure.
 *
 * @param[in] dim Dimension of the underlying vector space.
 * @param[in] n_alloc Specifies how much memory is allocated initially
 *                for the column index mapping.
 *
 * @return The newly allocated CRS_MATRIX_INFO structure.
 */
CRS_MATRIX_INFO *crs_matrix_info_alloc(int dim, int n_alloc)
{
  CRS_MATRIX_INFO *info;

  info = MEM_CALLOC(1, CRS_MATRIX_INFO);
  DBL_LIST_INIT(&info->matrices);
  info->dim = dim;
  info->n_alloc = n_alloc;
  if (n_alloc) {
    info->col = MEM_ALLOC(n_alloc, CRS_IDX);
  }
  info->row = MEM_ALLOC(dim+1, CRS_OFFSET);

  return info;
}

/** Free up unused memory. This means to trim info->col down to
 * info->n_entries.
 *
 * @param[in,out] info CRS_MATRIX_INFO structure to modify.
 */
void crs_matrix_info_trim(CRS_MATRIX_INFO *info)
{
  FUNCNAME("crs_matrix_trim");
  CRS_MATRIX *mat;

  if ((CRS_IDX)(info->n_alloc - info->n_entries) > info->dim) {
    info->col = MEM_REALLOC(info->col, info->n_alloc, info->n_entries, int);
  }
  dbl_list_for_each_entry(mat, &info->matrices, CRS_MATRIX, node) {
    if ((CRS_IDX)(mat->n_alloc - info->n_entries) > info->dim) {
      mat->entries = MEM_REALLOC(mat->entries,
				 mat->n_alloc*mat->entry_size,
				 info->n_entries*mat->entry_size, char);
      mat->n_alloc = info->n_entries;
    }
  }
}

/** Free a CRS_MATRIX_INFO structure and all associated matrices.
 * NOTE: It is an application error to call this function for a
 * ::CRS_MATRIX_INFO object automatically generated by
 * crs_matrix_get(). Call this function only for ::CRS_MATRIX_INFO
 * objects previously allocated by crs_matrix_info_alloc().
 *
 * @param[in,out] info CRS_MATRIX_INFO pointer to be deleted.
 */
void crs_matrix_info_free(CRS_MATRIX_INFO *info)
{
  CRS_MATRIX *mat;
  DBL_LIST_NODE *next;

  MEM_FREE(info->col, info->n_alloc, int);
  MEM_FREE(info->row, info->dim + 1, int);
  if (info->P) {
    MEM_FREE(info->P, info->dim, int);
  }
  if (info->PI) {
    MEM_FREE(info->PI, info->dim, int);
  }

  dbl_list_for_each_entry_safe(mat, next, &info->matrices, CRS_MATRIX, node) {
    crs_matrix_free(mat);
  }

  MEM_FREE(info, 1, CRS_MATRIX_INFO);
}

/* Allocate a new info record. */
CRS_MATRIX_INFO *get_crs_matrix_info(const FE_SPACE *fe_space,
				     const DOF_SCHAR_VEC *bound,
				     const FE_SPACE *col_fe_space)
{
  FUNCNAME("get_crs_matrix_info");
  CRS_MATRIX_INFO *info;

  /* need a new one */
  info = crs_matrix_info_alloc(fe_space->admin->size_used, 0);
  info->row_fe_space = get_fe_space(fe_space->mesh,
				    fe_space->name,
				    fe_space->bas_fcts,
				    fe_space->rdim,
				    fe_space->admin->flags);
  info->bound = bound;
  if (col_fe_space) {
    info->col_fe_space = get_fe_space(col_fe_space->mesh,
				      col_fe_space->name,
				      col_fe_space->bas_fcts,
				      col_fe_space->rdim,
				      col_fe_space->admin->flags);
  } else {
    info->col_fe_space = info->row_fe_space;
  }

  return info;
}

/** Allocate a new CRS_MATRIX.
 *
 * @param[in] name A name for this structure (for debugging and
 *                 pretty-printing).
 *
 * @param[in] entry_size The size of a matrix entry. Normally
 *                 sizeof(REAL) or sizeof(REAL_DD) but can be any
 *                 integer.
 *
 * @param[in] fe_space The finite element space defining the
 *                 connectivity structure of this matrix.
 *
 * @param[in] bound Boundary information. May be NULL in which case
 *                 the implied boundary information of the
 *                 macro-triangulation is used.
 *
 * @param[in] col_fe_space Optional. If set specifies the ::FE_SPACE
 *                 for the columns of the matrix. If @a col_fe_space
 *                 == NULL the column ::FE_SPACE is the same as the
 *                 row ::FE_SPACE as specified by the parameter @a
 *                 fe_space.
 *
 * @param[in] flags One of CRS_MAT_FE (finite element matrix) of
 *                 CRS_MAT_FV (finite volume matrix).
 *
 * @param[in] info ::CRS_MATRIX_INFO structure for this matrix. May be
 *                 NULL in which case an appropriate info structure is
 *                 generated. See also crs_matrix_info_alloc(). If @a
 *                 info != NULL then the parameters @a fe_space, @a
 *                 bound, @a flags and @a col_fe_space are ignored.
 *
 * @return The newly allocated CRS matrix. The function terminates the
 * calling process in case of an error.
 */
CRS_MATRIX *crs_matrix_get(const char *name,
			   size_t entry_size,
			   const FE_SPACE *fe_space,
			   const DOF_SCHAR_VEC *bound,
			   const FE_SPACE *col_fe_space,
			   const CRS_MATRIX_INFO *info)
{
  FUNCNAME("crs_matrix_get");
  CRS_MATRIX *matrix;

  TEST_EXIT((void *)fe_space != (void *)info,
	    "Either fe_space or info have to be specified");

  matrix = MEM_CALLOC(1, CRS_MATRIX);

  matrix->entry_size = entry_size;

  if (name) {
    matrix->name = strdup(name);
  }

  if (info) {
    matrix->info = info;
  } else if (fe_space) {
    matrix->info = get_crs_matrix_info(fe_space, bound, col_fe_space);
  }
  if (matrix->info->n_alloc) {
    matrix->entries =
      (void *)MEM_ALLOC(matrix->info->n_alloc*matrix->entry_size, char);
    matrix->n_alloc = matrix->info->n_alloc;
  }

  dbl_list_add_tail((DBL_LIST_NODE *)&matrix->info->matrices, &matrix->node);

  return matrix;
}

/** Free a matrix previously allocated by crs_matrix_get().
 *
 * @param[in,out] mat The CRS_MATRIX to delete.
 */
void crs_matrix_free(CRS_MATRIX *mat)
{
  if (!dbl_list_empty(&mat->node)) {
    dbl_list_del(&mat->node);
  }
  MEM_FREE(mat->entries, mat->n_alloc*mat->entry_size, char);
  if (mat->name) {
    free((char *)mat->name);
  }
  MEM_FREE(mat, 1, CRS_MATRIX);
}

/** Pretty-print a CRS_MATRIX with REAL entries.
 */
static void print_s_matrix(const char *funcName, const CRS_MATRIX *mat)
{
  int  i, j;
  REAL *entries = (REAL *)mat->entries;

  if (mat->name)
    MSG("matrix %s\n", mat->name);
  else
    MSG("matrix at %8X", mat);

  for (i = 0; i < mat->info->dim; i++) {
    int start = mat->info->row[i], end = mat->info->row[i+1];
    MSG("%4d: ", i);
    for (j = start; j < end; j++)
      print_msg("%12.5e%s", entries[j], j < end-1 ? ", " : "\n");
    MSG("      ");
    for (j = start; j < end; j++)
      print_msg("%12d%s", mat->info->col[j], j < end-1 ? ", " : "\n");
  }
  return;
}

#if DIM_OF_WORLD != 1
/** Pretty-print a CRS_MATRIX with REAL_DD entries.
 */
static void print_b_matrix(const char *funcName, const CRS_MATRIX *mat)
{
  int  i, j, n, m;
  REAL_DD *entries = (REAL_DD *)mat->entries;

  if (mat->name)
    MSG("matrix %s\n", mat->name);
  else
    MSG("matrix at %8X", mat);

  for (i = 0; i < mat->info->dim; i++) {
    int start = mat->info->row[i], end = mat->info->row[i+1];
    for (j = start; j < end; j++) {
      MSG("row %d and column %d:\n", i, mat->info->col[j]);
      for (n = 0; n < DIM_OF_WORLD; n++) {
	MSG("[");
	for (m = 0; m < DIM_OF_WORLD; m++)
	  print_msg("%12.5e%s", entries[j][n][m], 
		    m < DIM_OF_WORLD-1 ? ", " : "]");
	print_msg("\n");
      }
    }
  }
  return;
}
#endif

/**@cond */
#define LINE_LENGTH 5
/**@endcond */

#if DIM_OF_WORLD != 1
/** Another pretty-printer, REAL_DD entries */
static int debug_print_b_matrix(const CRS_MATRIX *mat)
{
  int  i, j, n, m, lines, cnt, start, end;
  REAL_DD *entries = (REAL_DD *)mat->entries;

  if (mat->name)
    printf("matrix %s\n", mat->name);
  else
    printf("matrix at %p", (void *)mat);

  for (i = 0; i < mat->info->dim; i++) {
    start = mat->info->row[i];
    end = mat->info->row[i+1];
    cnt = end - start;
    for (lines = 0; lines <= cnt / LINE_LENGTH; lines++) {
      for (n = 0; n < DIM_OF_WORLD; n++) {
	if (n == 0 && lines == 0) {
	  printf("row %3d", i);
	} else {
	  printf("       ");
	}
	for (j = start+lines*LINE_LENGTH; j < end && j < start+(lines+1)*LINE_LENGTH; j++) {
	  if (n == 0) {
	    printf("%3d ", mat->info->col[j]);
	  } else {
	    printf("    ");
	  }
	  printf("[");
	  for (m = 0; m < DIM_OF_WORLD; m++) {
	    printf("%9.02e%s",
		   entries[j][n][m],
		   m < DIM_OF_WORLD-1 ? "," : "]");
	  }
	}
	printf("\n");
      }
    }
  }
  return 0;
}
#endif

#if DIM_OF_WORLD != 1
/** Print the block-matrix as Maple input. */
static int maple_print_b_matrix(const CRS_MATRIX *mat)
{
  int  i, j, n, m, start, end;
  REAL_DD *entries = (REAL_DD *)mat->entries;

  printf("B:=array(sparse, 1..%d, 1..%d);\n",
	 mat->info->dim*DIM_OF_WORLD, mat->info->dim*DIM_OF_WORLD);

  for (i = 0; i < mat->info->dim; i++) {
    start = mat->info->row[i];
    end = mat->info->row[i+1];
    for (j = start; j < end; j++) {
      for (n = 0; n < DIM_OF_WORLD; n++) {
	for (m = 0; m < DIM_OF_WORLD; m++) {
	  printf("B[%d, %d] := %e: ",
		 i*DIM_OF_WORLD+n+1,mat->info->col[j]*DIM_OF_WORLD+m+1,
		 entries[j][n][m]);
	}
      }
    }
  }
  printf("\n");
  return 0;
}
#endif

/**@cond */
#undef LINE_LENGTH
#define LINE_LENGTH 10
/**@endcond */

/** Yet another pretty printer. */
static int debug_print_s_matrix(const CRS_MATRIX *mat)
{
  int  i, j, lines, cnt, start, end;
  REAL *entries = (REAL  *)mat->entries;

  if (mat->name)
    printf("matrix %s\n", mat->name);
  else
    printf("matrix at %p", (void *)mat);

  for (i = 0; i < mat->info->dim; i++) {
    start = mat->info->row[i];
    end = mat->info->row[i+1];
    cnt = end - start;
    for (lines = 0; lines <= cnt / LINE_LENGTH; lines++) {
      if (lines == 0) {
	printf("row %3d", i);
      }
      for (j = start+lines*LINE_LENGTH; j < end && j < start+(lines+1)*LINE_LENGTH; j++) {
	printf("%3d ", mat->info->col[j]);
	printf("%9.02e", entries[j]);
      }
      printf("\n");
    }
  }
  return 0;
}

static int maple_print_s_matrix(const CRS_MATRIX *mat)
{
  int  i, j, start, end;
  REAL *entries = (REAL *)mat->entries;

  printf("P:=array(sparse, 1..%d, 1..%d);\n", mat->info->dim, mat->info->dim);

  for (i = 0; i < mat->info->dim; i++) {
    start = mat->info->row[i];
    end = mat->info->row[i+1];
    printf("P[%d,%d]:=%.16e: ", i+1, i+1, entries[start]);
    for (j = start+1; j < end; j++) {
      printf("P[%d,%d]:=%.16e: ", i+1, mat->info->col[j]+1, entries[j]);
    }
  }
  printf("\n");
  return 0;
}

/** TO BE DOCUMENTED. */
void crs_matrix_print_maple(const CRS_MATRIX *mat)
{
  FUNCNAME("crs_matrix_print_maple");
  switch (mat->entry_size) {
  case sizeof(REAL):
    maple_print_s_matrix(mat);
    break;
#if DIM_OF_WORLD != 1
  case sizeof(REAL_DD):
    maple_print_b_matrix(mat);
    break;
#endif
  default:
    ERROR_EXIT("Do not know how to print this CRS-matrix with entry_size %d.\n",
	       mat->entry_size);
  }
}

/** TO BE DOCUMENTED. */
void crs_matrix_print_debug(const CRS_MATRIX *mat)
{
  FUNCNAME("crs_matrix_print_debug");

  switch (mat->entry_size) {
  case sizeof(REAL):
    debug_print_s_matrix(mat);
    break;
#if DIM_OF_WORLD != 1
  case sizeof(REAL_DD):
    debug_print_b_matrix(mat);
    break;
#endif
  default:
    ERROR_EXIT("Do not know how to print this CRS-matrix with entry_size %d.\n",
	       mat->entry_size);
  }
}

/** TO BE DOCUMENTED. */
void crs_matrix_print(const CRS_MATRIX *mat)
{
  FUNCNAME("crs_matrix_print_maple");

  switch (mat->entry_size) {
  case sizeof(REAL):
    print_s_matrix(funcName, mat);
    break;
#if DIM_OF_WORLD != 1
  case sizeof(REAL_DD):
    print_b_matrix(funcName, mat);
    break;
#endif
  default:
    ERROR_EXIT("Do not know how to print this CRS-matrix with entry_size %d.\n",
	       mat->entry_size);
  }
}
