/* -*- mode: C -*- */

/* Some "inline" functions for generic scalar types */

#if SLANG_HAS_FLOAT
# ifndef IS_INFINITY
#  ifdef HAVE_INF
#   define IS_INFINITY(x) isinf(x)
#  else
#   define IS_INFINITY(x) _pSLmath_isinf(x)
#  endif
# endif
#endif

#ifdef TRANSPOSE_2D_ARRAY
static SLang_Array_Type *TRANSPOSE_2D_ARRAY (SLang_Array_Type *at, SLang_Array_Type *bt)
{
   GENERIC_TYPE *a_data, *b_data;
   SLindex_Type nr, nc, i;

   nr = at->dims[0];
   nc = at->dims[1];

   a_data = (GENERIC_TYPE *) at->data;
   b_data = (GENERIC_TYPE *) bt->data;

   for (i = 0; i < nr; i++)
     {
	GENERIC_TYPE *offset = b_data + i;
	int j;
	for (j = 0; j < nc; j++)
	  {
	     *offset = *a_data++;
	     offset += nr;
	  }
     }
   return bt;
}
#undef TRANSPOSE_2D_ARRAY
#endif

#ifdef INNERPROD_FUNCTION

static void INNERPROD_FUNCTION
  (SLang_Array_Type *at, SLang_Array_Type *bt, SLang_Array_Type *ct,
   SLuindex_Type a_loops, SLuindex_Type a_stride,
   SLuindex_Type b_loops, SLuindex_Type b_inc,
   SLuindex_Type inner_loops)
{
   GENERIC_TYPE_A *a;
   GENERIC_TYPE_B *b;
   GENERIC_TYPE_C *c;
   SLuindex_Type kmin;
   SLuindex_Type block = Inner_Prod_Block_Size;

   block *= sizeof (double)/sizeof(GENERIC_TYPE_B);

   c = (GENERIC_TYPE_C *) ct->data;
   b = (GENERIC_TYPE_B *) bt->data;
   a = (GENERIC_TYPE_A *) at->data;
#if 1
   for (kmin = 0; kmin < inner_loops; kmin += block)
     {
	SLuindex_Type jmin;
	SLuindex_Type kmax = kmin + block;
	if (kmax > inner_loops) kmax = inner_loops;

	for (jmin = 0; jmin < b_loops; jmin += block)
	  {
	     SLuindex_Type i;
	     SLuindex_Type jmax = jmin + block;
	     if (jmax > b_loops) jmax = b_loops;

	     for (i = 0; i < a_loops; i++)
	       {
		  GENERIC_TYPE_A *aa = a + i * a_stride;
		  GENERIC_TYPE_C *cc = c + i * b_loops;
		  SLuindex_Type k;

		  for (k = kmin; k < kmax; k++)
		    {
		       double x = (double) aa[k];

		       if (x != 0.0)
			 {
			    SLuindex_Type j;
			    GENERIC_TYPE_B *bb = b + b_inc*k;

			    j = jmin;
			    if (j + 8 < jmax)
			      {
				 SLuindex_Type jmax1 = jmax - 8;
				 while (j < jmax1)
				   {
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				      cc[j] += x * bb[j]; j++;
				   }
			      }
			    while (j < jmax)
			      {
				 cc[j] += x * bb[j]; j++;
			      }
			 }
		    }
	       }
	  }
     }
#else
   while (a_loops--)
     {
	GENERIC_TYPE_B *bb;
	SLuindex_Type j;

	bb = b;

	for (j = 0; j < inner_loops; j++)
	  {
	     double x = (double) a[j];

	     if (x != 0.0)
	       {
		  SLuindex_Type k;

		  for (k = 0; k < b_loops; k++)
		    c[k] += x * bb[k];
	       }
	     bb += b_inc;
	  }
	c += b_loops;
	a += a_stride;
     }
#endif
}
#undef INNERPROD_FUNCTION

#undef GENERIC_TYPE_A
#undef GENERIC_TYPE_B
#undef GENERIC_TYPE_C
#endif

#ifdef INNERPROD_COMPLEX_A
static void INNERPROD_COMPLEX_A
  (SLang_Array_Type *at, SLang_Array_Type *bt, SLang_Array_Type *ct,
   SLuindex_Type a_loops, SLuindex_Type a_stride,
   SLuindex_Type b_loops, SLuindex_Type b_inc,
   SLuindex_Type inner_loops)
{
   double *a;
   GENERIC_TYPE *b;
   double *c;

   c = (double *) ct->data;
   b = (GENERIC_TYPE *) bt->data;
   a = (double *) at->data;

   a_stride *= 2;

   while (a_loops--)
     {
	GENERIC_TYPE *bb;
	SLuindex_Type bb_loops;

	bb = b;
	bb_loops = b_loops;

	while (bb_loops--)
	  {
	     double real_sum;
	     double imag_sum;
	     SLuindex_Type iloops;
	     double *aa;
	     GENERIC_TYPE *bbb;

	     aa = a;
	     bbb = bb;
	     iloops = inner_loops;

	     real_sum = 0.0;
	     imag_sum = 0.0;
	     while (iloops--)
	       {
		  real_sum += aa[0] * (double)bbb[0];
		  imag_sum += aa[1] * (double)bbb[0];
		  aa += 2;
		  bbb += b_inc;
	       }

	     *c++ = real_sum;
	     *c++ = imag_sum;
	     bb++;
	  }

	a += a_stride;
     }
}

static void INNERPROD_A_COMPLEX
  (SLang_Array_Type *at, SLang_Array_Type *bt, SLang_Array_Type *ct,
   SLuindex_Type a_loops, SLuindex_Type a_stride,
   SLuindex_Type b_loops, SLuindex_Type b_inc,
   SLuindex_Type inner_loops)
{
   GENERIC_TYPE *a;
   double *b;
   double *c;

   c = (double *) ct->data;
   b = (double *) bt->data;
   a = (GENERIC_TYPE *) at->data;

   b_inc *= 2;

   while (a_loops--)
     {
	double *bb;
	SLuindex_Type bb_loops;

	bb = b;
	bb_loops = b_loops;

	while (bb_loops--)
	  {
	     double real_sum;
	     double imag_sum;
	     SLuindex_Type iloops;
	     GENERIC_TYPE *aa;
	     double *bbb;

	     aa = a;
	     bbb = bb;
	     iloops = inner_loops;

	     real_sum = 0.0;
	     imag_sum = 0.0;
	     while (iloops--)
	       {
		  real_sum += (double)aa[0] * bbb[0];
		  imag_sum += (double)aa[0] * bbb[1];
		  aa += 1;
		  bbb += b_inc;
	       }

	     *c++ = real_sum;
	     *c++ = imag_sum;
	     bb += 2;
	  }

	a += a_stride;
     }
}

#undef INNERPROD_A_COMPLEX
#undef INNERPROD_COMPLEX_A
#endif				       /* INNERPROD_COMPLEX_A */

#ifdef INNERPROD_COMPLEX_COMPLEX
static void INNERPROD_COMPLEX_COMPLEX
  (SLang_Array_Type *at, SLang_Array_Type *bt, SLang_Array_Type *ct,
   SLuindex_Type a_loops, SLuindex_Type a_stride,
   SLuindex_Type b_loops, SLuindex_Type b_inc,
   SLuindex_Type inner_loops)
{
   double *a;
   double *b;
   double *c;

   c = (double *) ct->data;
   b = (double *) bt->data;
   a = (double *) at->data;

   a_stride *= 2;
   b_inc *= 2;

   while (a_loops--)
     {
	double *bb;
	SLuindex_Type bb_loops;

	bb = b;
	bb_loops = b_loops;

	while (bb_loops--)
	  {
	     double real_sum;
	     double imag_sum;
	     SLuindex_Type iloops;
	     double *aa;
	     double *bbb;

	     aa = a;
	     bbb = bb;
	     iloops = inner_loops;

	     real_sum = 0.0;
	     imag_sum = 0.0;
	     while (iloops--)
	       {
		  real_sum += aa[0]*bbb[0] - aa[1]*bbb[1];
		  imag_sum += aa[0]*bbb[1] + aa[1]*bbb[0];
		  aa += 2;
		  bbb += b_inc;
	       }

	     *c++ = real_sum;
	     *c++ = imag_sum;
	     bb += 2;
	  }

	a += a_stride;
     }
}
#undef INNERPROD_COMPLEX_COMPLEX
#endif

#ifdef SUM_FUNCTION
#if SLANG_HAS_FLOAT
static int SUM_FUNCTION (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x, *xmax;
   double sum, sumerr;

   x = (GENERIC_TYPE *) xp;
   xmax = x + num;

   sumerr = 0.0;
   sum = 0.0;
   while (x < xmax)
     {
	double v = *x - sumerr;
	double new_sum = sum + v;
	sumerr = (new_sum - sum) - v;
	sum = new_sum;
	x += inc;
     }
   *(SUM_RESULT_TYPE *)yp = (SUM_RESULT_TYPE) sum;
   return 0;
}
#endif				       /* SLANG_HAS_FLOAT */
#undef SUM_FUNCTION
#endif

#ifdef SUMSQ_FUNCTION
#if SLANG_HAS_FLOAT
static int SUMSQ_FUNCTION (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x, *xmax;
   double sum, sumerr;

   sum = 0.0;
   sumerr = 0.0;

   x = (GENERIC_TYPE *) xp;
   xmax = x + num;
   while (x < xmax)
     {
	double v = (double)(*x) * (double)(*x) - sumerr;
	double new_sum = sum + v;
	sumerr = (new_sum - sum) - v;
	sum = new_sum;
	x += inc;
     }
   *(SUM_RESULT_TYPE *)yp = (SUM_RESULT_TYPE) sum;
   return 0;
}
#endif				       /* SLANG_HAS_FLOAT */
#undef SUMSQ_FUNCTION
#endif
#undef SUM_RESULT_TYPE

#ifdef MIN_FUNCTION
static int
MIN_FUNCTION (VOID_STAR ip, SLuindex_Type inc, SLuindex_Type num, VOID_STAR sp)
{
   SLuindex_Type n, n0;
   GENERIC_TYPE m;
   GENERIC_TYPE *i = (GENERIC_TYPE *)ip;

   if (-1 == check_for_empty_array ("min", num))
     return -1;

# ifdef IS_NAN_FUNCTION
   n0 = 0;
   do
     {
	m = i[n0];
	n0 += inc;
     }
   while (IS_NAN_FUNCTION(m) && (n0 < num));
# else
   m = i[0];
   n0 = inc;
# endif

   for (n = n0; n < num; n += inc)
     if (m > i[n]) m = i[n];

   *(GENERIC_TYPE *)sp = m;
   return 0;
}
#undef MIN_FUNCTION
#endif

#ifdef MINABS_FUNCTION
static int
MINABS_FUNCTION (VOID_STAR ip, SLuindex_Type inc, SLuindex_Type num, VOID_STAR sp)
{
   SLuindex_Type n, n0;
   GENERIC_TYPE m;
   GENERIC_TYPE *i = (GENERIC_TYPE *)ip;

   if (-1 == check_for_empty_array ("minabs", num))
     return -1;

# ifdef IS_NAN_FUNCTION
   n0 = 0;
   do
     {
	m = ABS_FUNCTION(i[n0]);
	n0 += inc;
     }
   while (IS_NAN_FUNCTION(m) && (n0 < num));
# else
   m = ABS_FUNCTION(i[0]);
   n0 = inc;
# endif

   for (n = n0; n < num; n += inc)
     if (m > ABS_FUNCTION(i[n])) m = ABS_FUNCTION(i[n]);

   *(GENERIC_TYPE *)sp = m;
   return 0;
}
#undef MINABS_FUNCTION
#endif

#ifdef MAX_FUNCTION
static int
MAX_FUNCTION (VOID_STAR ip, SLuindex_Type inc, SLuindex_Type num, VOID_STAR s)
{
   SLuindex_Type n, n0;
   GENERIC_TYPE m;
   GENERIC_TYPE *i = (GENERIC_TYPE *) ip;

   if (-1 == check_for_empty_array ("max", num))
     return -1;

# ifdef IS_NAN_FUNCTION
   n0 = 0;
   do
     {
	m = i[n0];
	n0 += inc;
     }
   while (IS_NAN_FUNCTION(m) && (n0 < num));
# else
   m = i[0];
   n0 = inc;
# endif

   for (n = n0; n < num; n += inc)
     if (m < i[n]) m = i[n];

   *(GENERIC_TYPE *)s = m;
   return 0;
}
#undef MAX_FUNCTION
#endif

#ifdef MAXABS_FUNCTION
static int
MAXABS_FUNCTION (VOID_STAR ip, SLuindex_Type inc, SLuindex_Type num, VOID_STAR s)
{
   SLuindex_Type n, n0;
   GENERIC_TYPE m;
   GENERIC_TYPE *i = (GENERIC_TYPE *) ip;

   if (-1 == check_for_empty_array ("maxabs", num))
     return -1;

# ifdef IS_NAN_FUNCTION
   n0 = 0;
   do
     {
	m = ABS_FUNCTION(i[n0]);
	n0 += inc;
     }
   while (IS_NAN_FUNCTION(m) && (n0 < num));
# else
   m = ABS_FUNCTION(i[0]);
   n0 = inc;
# endif

   for (n = n0; n < num; n += inc)
     if (m < ABS_FUNCTION(i[n])) m = ABS_FUNCTION(i[n]);

   *(GENERIC_TYPE *)s = m;
   return 0;
}
#undef MAXABS_FUNCTION
#endif

#ifdef ANY_FUNCTION
static int
ANY_FUNCTION (VOID_STAR ip, SLuindex_Type inc, SLuindex_Type num, VOID_STAR s)
{
   SLuindex_Type n;
   GENERIC_TYPE *i = (GENERIC_TYPE *) ip;

   for (n = 0; n < num; n += inc)
     if (i[n] != 0)
       {
#ifdef IS_NAN_FUNCTION
	  if (IS_NAN_FUNCTION(i[n]))
	    continue;
#endif
	  *(char *)s = 1;
	  return 0;
       }

   *(char *)s = 0;
   return 0;
}
#undef ANY_FUNCTION
#endif

#ifdef ALL_FUNCTION
static int
ALL_FUNCTION (VOID_STAR ip, SLuindex_Type inc, SLuindex_Type num, VOID_STAR s)
{
   SLuindex_Type n;
   GENERIC_TYPE *i = (GENERIC_TYPE *) ip;

   if (num == 0)
     {
	*(char *)s = 0;
	return 0;
     }
   for (n = 0; n < num; n += inc)
     {
	if (i[n] == (GENERIC_TYPE)0)
	  {
	     *(char *)s = 0;
	     return 0;
	  }
#ifdef IS_NAN_FUNCTION
	/* I really do not want to call this for all numbers, nor do I know
	 * what makes most sense.  Doing nothing means that all(_NaN) is 1.
	 * Such an interpretation is consistent with using
	 *   length(x) == length(where (x))
	 */
#endif
     }

   *(char *)s = 1;
   return 0;
}
#undef ALL_FUNCTION
#endif

#ifdef CUMSUM_FUNCTION
#ifdef SLANG_HAS_FLOAT
static int
CUMSUM_FUNCTION (SLtype xtype, VOID_STAR xp, SLuindex_Type inc,
		 SLuindex_Type num,
		 SLtype ytype, VOID_STAR yp, VOID_STAR clientdata)
{
   GENERIC_TYPE *x, *xmax;
   CUMSUM_RESULT_TYPE *y;
   double c;
   double cerr;

   (void) xtype;
   (void) ytype;
   (void) clientdata;

   x = (GENERIC_TYPE *) xp;
   y = (CUMSUM_RESULT_TYPE *) yp;
   xmax = x + num;

   c = 0.0;
   cerr = 0.0;
   while (x < xmax)
     {
	double d = (double) *x - cerr;
	double c1 = c + d;
	cerr = (c1 - c) - d;
	c = c1;
	*y = (CUMSUM_RESULT_TYPE) c;
	x += inc;
	y += inc;
     }
   return 0;
}
#endif				       /* SLANG_HAS_FLOAT */
#undef CUMSUM_FUNCTION
#undef CUMSUM_RESULT_TYPE
#endif

#ifdef PROD_FUNCTION
#if SLANG_HAS_FLOAT
static int PROD_FUNCTION (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x, *xmax;
   double prod;

   prod = 1.0;

   x = (GENERIC_TYPE *) xp;
   xmax = x + num;
   while (x < xmax)
     {
	prod *= (double) *x;
	x += inc;
     }
   *(PROD_RESULT_TYPE *)yp = (PROD_RESULT_TYPE) (prod);
   return 0;
}
#endif				       /* SLANG_HAS_FLOAT */
#undef PROD_FUNCTION
#undef PROD_RESULT_TYPE
#endif

#ifdef WHEREFIRSTMAX_FUNC
static int WHEREFIRSTMAX_FUNC (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x;
   SLuindex_Type i, imax;
   GENERIC_TYPE maxval;

   if (-1 == check_for_empty_array ("wherefirstmax", num))
     return -1;

   x = (GENERIC_TYPE *) xp;

# ifdef IS_NAN_FUNCTION
   i = 0;
   do
     {
	maxval = x[i];
	imax = i;
	i += inc;
     }
   while (IS_NAN_FUNCTION(maxval) && (i < num));
# else
   maxval = x[0];
   imax = 0;
# endif

   for (i = imax+inc; i < num; i += inc)
     {
	if (maxval < x[i])
	  {
	     imax = i;
	     maxval = x[i];
	  }
     }
   *(SLuindex_Type *)yp = imax;
   return 0;
}
# undef WHEREFIRSTMAX_FUNC
#endif

#ifdef WHERELASTMAX_FUNC
static int WHERELASTMAX_FUNC (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x;
   SLuindex_Type i, imax;
   GENERIC_TYPE maxval;

   if (-1 == check_for_empty_array ("wherelastmax", num))
     return -1;

   x = (GENERIC_TYPE *) xp;

# ifdef IS_NAN_FUNCTION
   i = 0;
   do
     {
	maxval = x[i];
	imax = i;
	i += inc;
     }
   while (IS_NAN_FUNCTION(maxval) && (i < num));
# else
   maxval = x[0];
   imax = 0;
# endif

   for (i = imax+inc; i < num; i += inc)
     {
	if (maxval <= x[i])
	  {
	     imax = i;
	     maxval = x[i];
	  }
     }
   *(SLuindex_Type *)yp = imax;
   return 0;
}
# undef WHERELASTMAX_FUNC
#endif

#ifdef WHEREFIRSTMIN_FUNC
static int WHEREFIRSTMIN_FUNC (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x;
   SLuindex_Type i, imin;
   GENERIC_TYPE minval;

   if (-1 == check_for_empty_array ("wherefirstmin", num))
     return -1;

   x = (GENERIC_TYPE *) xp;

# ifdef IS_NAN_FUNCTION
   i = 0;
   do
     {
	minval = x[i];
	imin = i;
	i += inc;
     }
   while (IS_NAN_FUNCTION(minval) && (i < num));
# else
   minval = x[0];
   imin = 0;
# endif

   for (i = imin+inc; i < num; i += inc)
     {
	if (minval > x[i])
	  {
	     imin = i;
	     minval = x[i];
	  }
     }
   *(SLuindex_Type *)yp = imin;
   return 0;
}
# undef WHEREFIRSTMIN_FUNC
#endif

#ifdef WHERELASTMIN_FUNC
static int WHERELASTMIN_FUNC (VOID_STAR xp, SLuindex_Type inc, SLuindex_Type num, VOID_STAR yp)
{
   GENERIC_TYPE *x;
   SLuindex_Type i, imin;
   GENERIC_TYPE minval;

   if (-1 == check_for_empty_array ("wherefirstmin", num))
     return -1;

   x = (GENERIC_TYPE *) xp;

# ifdef IS_NAN_FUNCTION
   i = 0;
   do
     {
	minval = x[i];
	imin = i;
	i += inc;
     }
   while (IS_NAN_FUNCTION(minval) && (i < num));
# else
   minval = x[0];
   imin = 0;
# endif

   for (i = imin+inc; i < num; i += inc)
     {
	if (minval >= x[i])
	  {
	     imin = i;
	     minval = x[i];
	  }
     }
   *(SLuindex_Type *)yp = imin;
   return 0;
}
# undef WHERELASTMIN_FUNC
#endif

#ifdef DO_WHEREFIRST_OP_FUNC
static int DO_WHEREFIRST_OP_FUNC (SLang_Array_Type *at, int op, GENERIC_TYPE_B b, SLindex_Type istart)
{
   GENERIC_TYPE_A *a;
   SLindex_Type i, num_elements;

   a = (GENERIC_TYPE_A *) at->data;
   num_elements = (SLindex_Type) at->num_elements;

# define WHEREFIRST_CASE_BODY(cop) \
   i = istart; while ((i < num_elements) && (0 == (a[i] cop b))) i++;

   switch (op)
     {
      case SLANG_EQ: WHEREFIRST_CASE_BODY(==); break;
      case SLANG_NE: WHEREFIRST_CASE_BODY(!=); break;
      case SLANG_GT: WHEREFIRST_CASE_BODY( >); break;
      case SLANG_GE: WHEREFIRST_CASE_BODY(>=); break;
      case SLANG_LT: WHEREFIRST_CASE_BODY( <); break;
      case SLANG_LE: WHEREFIRST_CASE_BODY(<=); break;
      default:
	SLang_verror (SL_Internal_Error, "Unexpected op: %d\n", op);
	return -1;
     }

   if (i < num_elements)
     return SLang_push_array_index (i);

   return SLang_push_null ();
}
#undef WHEREFIRST_CASE_BODY
#undef DO_WHEREFIRST_OP_FUNC
#endif

#ifdef DO_WHERELAST_OP_FUNC
static int DO_WHERELAST_OP_FUNC (SLang_Array_Type *at, int op, GENERIC_TYPE_B b, SLindex_Type istart)
{
   GENERIC_TYPE_A *a;
   SLindex_Type num_elements;

   a = (GENERIC_TYPE_A *) at->data;
   num_elements = (SLindex_Type) at->num_elements;
   if (istart >= num_elements) istart = num_elements-1;

# define WHERELAST_CASE_BODY(cop) \
   while ((istart >= 0) && (0 == (a[istart] cop b))) istart--

   switch (op)
     {
      case SLANG_EQ: WHERELAST_CASE_BODY(==); break;
      case SLANG_NE: WHERELAST_CASE_BODY(!=); break;
      case SLANG_GT: WHERELAST_CASE_BODY( >); break;
      case SLANG_GE: WHERELAST_CASE_BODY(>=); break;
      case SLANG_LT: WHERELAST_CASE_BODY( <); break;
      case SLANG_LE: WHERELAST_CASE_BODY(<=); break;
      default:
	SLang_verror (SL_Internal_Error, "Unexpected op: %d\n", op);
	return -1;
     }

   if (istart >= 0)
     return SLang_push_array_index (istart);

   return SLang_push_null ();
}
#undef WHERELAST_CASE_BODY
#undef DO_WHERELAST_OP_FUNC
#endif

#ifdef GENERIC_TYPE_A
# undef GENERIC_TYPE_A
#endif

#ifdef GENERIC_TYPE_B
# undef GENERIC_TYPE_B
#endif

#ifdef GENERIC_TYPE
# undef GENERIC_TYPE
#endif

#ifdef IS_NAN_FUNCTION
# undef IS_NAN_FUNCTION
#endif

#ifdef ABS_FUNCTION
# undef ABS_FUNCTION
#endif
