/* -*- c++ -*- (enables emacs c++ mode) */
/*========================================================================

 Copyright (C) 2006-2009 Yves Renard, Julien Pommier.

 This file is a part of GETFEM++

 Getfem++ is free software; you can redistribute it and/or modify
 it under the terms of the GNU Lesser General Public License as
 published by the Free Software Foundation; version 2.1 of the License,
 or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Lesser General Public License for more details.
 You should have received a copy of the GNU Lesser General Public
 License along with this program; if not, write to the Free Software
 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301,
 USA.

 ========================================================================*/

#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "gfi_array.h"


/* -------------------- creation ------------------------*/

void* gfi_malloc(size_t sz) {
  if (sz == 0) return malloc(1);
  else return malloc(sz);
}

void* gfi_calloc(size_t n, size_t m) {
  void *p = (n*m == 0) ? malloc(1) : calloc(n,m);
  /*printf("gfi_calloc(%d,%d) -> %p\n", n, m, p);*/
  return p;
}

void gfi_free(void *p) {
  /*printf("gfi_free  (%p)\n", p);*/
  if (p) free(p); 
}

#define FREE(p) { /*printf("%s@%d ", __FILE__, __LINE__); fflush(stdout); */gfi_free(p); p = NULL; }

gfi_array*
gfi_array_create(int ndim, int *dims, 
                 gfi_type_id type, gfi_complex_flag is_complex) {
  int i,sz;
  gfi_array *t = gfi_calloc(1, sizeof(gfi_array)); if (t == NULL) return NULL;
  t->dim.dim_len = ndim;
  t->dim.dim_val = gfi_calloc(ndim, sizeof(int)); if ( t->dim.dim_val == NULL) { gfi_free(t); return NULL; }

  /*printf("gfi_array_create(ndim = %d, type = %d, @%p %p\n", ndim, type, t, t->dim.dim_val);*/

  for (i=0,sz=1; i < ndim; ++i) { t->dim.dim_val[i] = dims[i]; sz *= dims[i]; }
  t->storage.type = type;
  switch (t->storage.type) {
  case GFI_CHAR: {
    t->storage.gfi_storage_u.data_char.data_char_len = sz;
    t->storage.gfi_storage_u.data_char.data_char_val = gfi_malloc(sz*sizeof(char));
    if (t->storage.gfi_storage_u.data_char.data_char_val == NULL) goto not_enough_mem;
  } break;
  case GFI_INT32: {
    t->storage.gfi_storage_u.data_int32.data_int32_len = sz;
    t->storage.gfi_storage_u.data_int32.data_int32_val = gfi_malloc(sz*sizeof(int));
    if (t->storage.gfi_storage_u.data_int32.data_int32_val == NULL) goto not_enough_mem;
  } break;
  case GFI_UINT32: {
    t->storage.gfi_storage_u.data_uint32.data_uint32_len = sz;
    t->storage.gfi_storage_u.data_uint32.data_uint32_val = gfi_malloc(sz*sizeof(int));
    if (t->storage.gfi_storage_u.data_uint32.data_uint32_val == NULL) goto not_enough_mem;
  } break;
  case GFI_CELL: {
    t->storage.gfi_storage_u.data_cell.data_cell_len = sz;
    t->storage.gfi_storage_u.data_cell.data_cell_val = (gfi_array**)gfi_calloc(sz,sizeof(gfi_array*));
    if (t->storage.gfi_storage_u.data_cell.data_cell_val == NULL) goto not_enough_mem;
  } break;
  case GFI_DOUBLE: {
    t->storage.gfi_storage_u.data_double.is_complex = is_complex;
    t->storage.gfi_storage_u.data_double.data_double_len = sz * (is_complex ? 2 : 1);
    t->storage.gfi_storage_u.data_double.data_double_val = gfi_calloc(sz,sizeof(double) * (is_complex ? 2 : 1));
    if (t->storage.gfi_storage_u.data_double.data_double_val == NULL) goto not_enough_mem;
  } break;
  case GFI_OBJID: {
    t->storage.gfi_storage_u.objid.objid_len = sz;
    t->storage.gfi_storage_u.objid.objid_val = gfi_calloc(sz, sizeof(gfi_object_id));  
    if (t->storage.gfi_storage_u.objid.objid_val == NULL) goto not_enough_mem;
  } break;
  default: {
    printf("internal error"); return NULL; 
  } break;
  }  
  return t;
 not_enough_mem:
  gfi_array_destroy(t); gfi_free(t); return NULL;
}

/* 0-D arrays are used for non-array scalars */
gfi_array*
gfi_array_create_0(gfi_type_id type, gfi_complex_flag is_complex) {
  return gfi_array_create(0, NULL, type, is_complex);
}

gfi_array*
gfi_array_create_1(int M, gfi_type_id type, gfi_complex_flag is_complex) {
  return gfi_array_create(1, &M, type, is_complex);
}

gfi_array*
gfi_array_create_2(int M, int N, gfi_type_id type, gfi_complex_flag is_complex) {
  int d[2]; d[0]=M; d[1]=N;
  return gfi_array_create(2, d, type, is_complex);
}

gfi_array*
gfi_array_from_string(const char *s) {
  gfi_array*t;
  int n = (int)strlen(s);
  t = gfi_array_create_1(n,GFI_CHAR, GFI_REAL);
  if (t) memcpy(gfi_char_get_data(t), s, n);
  return t;
}

gfi_array*
gfi_create_sparse(int m, int n, int nzmax, gfi_complex_flag is_complex) {
  gfi_array *t = gfi_calloc(1, sizeof(gfi_array));
  t->dim.dim_len = 2; 
  t->dim.dim_val = gfi_calloc(2, sizeof(int)); 
  t->dim.dim_val[0] = m; t->dim.dim_val[1] = n;
  t->storage.type = GFI_SPARSE;
  t->storage.gfi_storage_u.sp.is_complex = is_complex;
  t->storage.gfi_storage_u.sp.ir.ir_len = nzmax; t->storage.gfi_storage_u.sp.ir.ir_val = gfi_calloc(nzmax,sizeof(int));
  t->storage.gfi_storage_u.sp.jc.jc_len = n+1;   t->storage.gfi_storage_u.sp.jc.jc_val = gfi_calloc(n+1,sizeof(int));
  t->storage.gfi_storage_u.sp.pr.pr_len = nzmax * (is_complex ? 2 : 1); 
  t->storage.gfi_storage_u.sp.pr.pr_val = gfi_calloc(nzmax,sizeof(double) * (is_complex ? 2 : 1));
  if ((nzmax && (t->storage.gfi_storage_u.sp.ir.ir_val == NULL ||
		 t->storage.gfi_storage_u.sp.pr.pr_val == NULL)) ||
      t->storage.gfi_storage_u.sp.jc.jc_val == NULL) { gfi_array_destroy(t); t = NULL; }
  return t;
}
/*
gfi_array*
gfi_create_objid(int nid, unsigned *ids, unsigned cid) {
  int i;
  gfi_array *t = gfi_calloc(1, sizeof(gfi_array));
  t->dim.dim_len = nid; 
  t->dim.dim_val = gfi_calloc(1, sizeof(int)); t->dim.dim_val[0]=1;
  t->storage.type = GFI_OBJID;
  t->storage.gfi_storage_u.objid.objid_len = nid;
  t->storage.gfi_storage_u.objid.objid_val = gfi_calloc(nid, sizeof(gfi_object_id));
  for (i=0; i < nid; ++i) {
    t->storage.gfi_storage_u.objid.objid_val[i].id = ids[i];
    t->storage.gfi_storage_u.objid.objid_val[i].cid = cid;
  }
  return t;
}
*/
/* ----------------- destruction ------------ */

void 
gfi_array_destroy(gfi_array *t) {
  if (t == NULL) return;
  FREE(t->dim.dim_val);
  switch (t->storage.type) {
  case GFI_CHAR: {
    FREE(t->storage.gfi_storage_u.data_char.data_char_val);
  } break;
  case GFI_INT32: {
    FREE(t->storage.gfi_storage_u.data_int32.data_int32_val);
  } break;
  case GFI_UINT32: {
    FREE(t->storage.gfi_storage_u.data_uint32.data_uint32_val);
  } break;
  case GFI_CELL: {
    int i;
    if (t->storage.gfi_storage_u.data_cell.data_cell_len)
      assert(t->storage.gfi_storage_u.data_cell.data_cell_val);
    for (i=0; i < t->storage.gfi_storage_u.data_cell.data_cell_len; ++i)
      gfi_array_destroy(t->storage.gfi_storage_u.data_cell.data_cell_val[i]);
    FREE(t->storage.gfi_storage_u.data_cell.data_cell_val);
  } break;
  case GFI_DOUBLE: {
    FREE(t->storage.gfi_storage_u.data_double.data_double_val);
  } break;
  case GFI_SPARSE: {
    FREE(t->storage.gfi_storage_u.sp.ir.ir_val);
    FREE(t->storage.gfi_storage_u.sp.jc.jc_val);
    FREE(t->storage.gfi_storage_u.sp.pr.pr_val);
  }
  case GFI_OBJID: {
    FREE(t->storage.gfi_storage_u.objid.objid_val);
  } break;
  default: {
    assert(0);
  }
  }
}

/* ----------------- inquiry ----------------- */

int
gfi_array_get_ndim(const gfi_array*t) {
  assert(t);
  return t->dim.dim_len;
}

const int*
gfi_array_get_dim(const gfi_array*t) {
  assert(t);
  return (const int*)t->dim.dim_val;
}

unsigned 
gfi_array_nb_of_elements(const gfi_array *t) {
  unsigned i,sz=1;
  assert(t);
  if (t->storage.type != GFI_SPARSE) {
    for (i=0,sz=1; i < t->dim.dim_len; ++i) { sz *= t->dim.dim_val[i]; }
    return sz;
  } else return t->storage.gfi_storage_u.sp.pr.pr_len;
}

unsigned int*
gfi_sparse_get_ir(const gfi_array *t) {
  assert(t); 
  assert(t->storage.type == GFI_SPARSE);
  return (unsigned int*)t->storage.gfi_storage_u.sp.ir.ir_val;
}

unsigned int*
gfi_sparse_get_jc(const gfi_array *t) {
  assert(t); 
  assert(t->storage.type == GFI_SPARSE);
  return (unsigned int*)t->storage.gfi_storage_u.sp.jc.jc_val;
}

double*
gfi_sparse_get_pr(const gfi_array *t) {
  assert(t); 
  assert(t->storage.type == GFI_SPARSE);
  return t->storage.gfi_storage_u.sp.pr.pr_val;
}

char*
gfi_char_get_data(const gfi_array* t) {
  assert(t);
  assert(t->storage.type == GFI_CHAR);
  return t->storage.gfi_storage_u.data_char.data_char_val;
}

int*
gfi_int32_get_data(const gfi_array *t) {
  assert(t);
  assert(t->storage.type == GFI_INT32);
  return t->storage.gfi_storage_u.data_int32.data_int32_val;
}

unsigned *
gfi_uint32_get_data(const gfi_array *t) {
  assert(t);
  assert(t->storage.type == GFI_UINT32);
  return (unsigned*)t->storage.gfi_storage_u.data_uint32.data_uint32_val;
}

double*
gfi_double_get_data(const gfi_array* t) {
  assert(t);
  assert(t->storage.type == GFI_DOUBLE);
  return t->storage.gfi_storage_u.data_double.data_double_val;
}

int
gfi_array_is_complex(const gfi_array* t) {
  assert(t);
  if (t->storage.type == GFI_DOUBLE) 
    return t->storage.gfi_storage_u.data_double.is_complex;
  else if (t->storage.type == GFI_SPARSE)
    return t->storage.gfi_storage_u.sp.is_complex;
  else return GFI_REAL;
}

gfi_array**
gfi_cell_get_data(const gfi_array *t) {
  assert(t);
  assert(t->storage.type == GFI_CELL);
  return t->storage.gfi_storage_u.data_cell.data_cell_val;
}

gfi_object_id*
gfi_objid_get_data(const gfi_array *t) {
  assert(t);
  assert(t->storage.type == GFI_OBJID);
  return t->storage.gfi_storage_u.objid.objid_val;
}

gfi_type_id
gfi_array_get_class(const gfi_array *t) {
  assert(t);
  return (t->storage.type);
}

/* ----------------- debugging ------------------- */

const char*
gfi_array_get_class_name(const gfi_array *t) {
  assert(t);
  return gfi_type_id_name(gfi_array_get_class(t), gfi_array_is_complex(t));
}

const char *
gfi_type_id_name(gfi_type_id id, gfi_complex_flag is_complex) {
  switch (id) {
    case GFI_CHAR: return "CHAR";
    case GFI_INT32: return "INT32";
    case GFI_UINT32: return "UINT32";
    case GFI_CELL: return "CELL";
    case GFI_DOUBLE: return is_complex == GFI_REAL ? "DOUBLE" : "DOUBLE COMPLEX";
    case GFI_SPARSE: return is_complex == GFI_REAL ? "SPARSE" : "SPARSE COMPLEX";
    case GFI_OBJID: return "GETFEM OBJECT ID";
    default: return "UNKNOWN..";
  }
}

#define PRINT_ARR(format,separ,len,maxprintlen,nbperline,p) { \
int i; for (i=0; i < len && i < maxprintlen; ++i) { \
if ((i+1) % nbperline == 0) printf("\n"); else if (i>0) printf("%s",separ); \
printf(format,p[i]); } if (i<len) printf("...");}

void gfi_array_print_(gfi_array *t, int lev) {
  unsigned int i;
  if (t == NULL) { printf("NULL array ...\n"); return; }
  for (i=0; i < lev; ++i) printf("  ");
  printf("dim : "); 
  for (i=0; i < t->dim.dim_len; ++i) printf("%s%d",i>0?"x":"", t->dim.dim_val[i]);
  printf(" of %s, content={", gfi_array_get_class_name(t));
  switch (t->storage.type) {
  case GFI_CHAR: {    
    PRINT_ARR("%c","",t->storage.gfi_storage_u.data_char.data_char_len,400,80,t->storage.gfi_storage_u.data_char.data_char_val);
  } break;
  case GFI_INT32: {
    PRINT_ARR("%4d",", ",t->storage.gfi_storage_u.data_int32.data_int32_len,60,15,t->storage.gfi_storage_u.data_int32.data_int32_val);
  } break;
  case GFI_UINT32: {
    PRINT_ARR("%4d",", ",t->storage.gfi_storage_u.data_uint32.data_uint32_len,60,15,t->storage.gfi_storage_u.data_uint32.data_uint32_val);
  } break;
  case GFI_CELL: {
    int i;
    printf("\n");
    for (i=0; i < t->storage.gfi_storage_u.data_cell.data_cell_len; ++i) {
      gfi_array_print_(t->storage.gfi_storage_u.data_cell.data_cell_val[i],lev+1);
    }
    printf("\n"); for (i=0; i < lev; ++i) printf("  ");
  } break;
  case GFI_DOUBLE: {
    PRINT_ARR("%8g",", ",t->storage.gfi_storage_u.data_double.data_double_len,40,10,t->storage.gfi_storage_u.data_double.data_double_val);    
  } break;
  case GFI_SPARSE: {
    printf("\n"); for (i=0; i < lev+1; ++i) printf("  "); printf("ir="); 
    PRINT_ARR("%4d",", ",t->storage.gfi_storage_u.sp.ir.ir_len,15,16,t->storage.gfi_storage_u.sp.ir.ir_val);
    printf("\n"); for (i=0; i < lev+1; ++i) printf("  "); printf("jc="); 
    PRINT_ARR("%4d",", ",t->storage.gfi_storage_u.sp.jc.jc_len,15,16,t->storage.gfi_storage_u.sp.jc.jc_val);
    printf("\n"); for (i=0; i < lev+1; ++i) printf("  "); printf("pr="); 
    PRINT_ARR("%8g",", ",t->storage.gfi_storage_u.sp.pr.pr_len,15,8,t->storage.gfi_storage_u.sp.pr.pr_val);
    printf("\n"); for (i=0; i < lev; ++i) printf("  ");
  } break;
  case GFI_OBJID: {
    int i;
    printf("cid,id=[");
    for (i=0; i < gfi_array_nb_of_elements(t); ++i) printf("%s{%d,%d}", i?", ":"", gfi_objid_get_data(t)[i].cid, gfi_objid_get_data(t)[i].id);
    printf("]\n");
  } break;
  default: {
    printf("internal error"); return; 
  } break;
  }
  printf("}\n");
}

void 
gfi_array_print(gfi_array *t) {
  gfi_array_print_(t,0);
}

static int cancel_flag = 0;

int is_cancel_flag_set() {
  return cancel_flag;
}

/* set by ctrl-c handlers when a long computation should be interrupted
   (typically mdbrick.solve) */
void set_cancel_flag(int v) {
  cancel_flag = v;
}
