/*
    MathTemplateArray - Template for several number array classes
    
    Copyright (C) 1995, Adam Fedor
    
    Should run this through a preprocessor each time defining the
    macro MATH_ORDER to the appropriate values.
    
    $Id: MathTemplateArray.m,v 1.15 2005/04/17 21:22:16 fedor Exp $
*/
# line 12 "MathTemplateArray.m"

#ifndef GNUSTEP
#import <Foundation/Foundation.h>
#include <sys/param.h>
#else
#include <Foundation/NSString.h>
#endif
#include <math.h>
#include <time.h>
#include "MathArrayPrivate.h"
#include "MathArray/MAValue.h"
#include "MathArray/MAValueData.h"
#include "MathArray/array_encoding.h"

#ifdef HAVE_DRAND48_DEF
#include <stdlib.h>
#define GEN_RANDOM drand48()
#else
#if (sun && __svr4__) || defined(__hpux) || defined(_SEQUENT_) || defined(mips)
extern double drand48();
extern void srand48();
#define GEN_RANDOM drand48()
#else
extern long random();
extern void srandom();
#define srand48	srandom
#define RANDOM_MAX (2147483647)            /* (2**31)-1 */
#define GEN_RANDOM (double)(random() & 0xffffffffl)/RANDOM_MAX
#endif
#endif /* not HAVE_DRAND48_DEF */

/* Note: precsion is defined in terms of three words: 0xaabbcc, where cc
   is the storage size of the number. bb is 1 if signed and aa is an
   arbitrary order that increases for more "precise" numbers */
#if MATH_ORDER == 1
#  define MathTemplateArray	MathUCharArray
#  define MATH_TYPE		unsigned char
#  define MATH_CAST_METHOD 	unsignedCharValue
static precision_t precision = 	0x040001;
#elif MATH_ORDER == 2
#  define MathTemplateArray	MathCharArray
#  define MATH_TYPE		char
#  define MATH_CAST_METHOD 	charValue
static precision_t precision = 	0x080102;
#elif MATH_ORDER == 3
#  define MathTemplateArray	MathUShortArray
#  define MATH_TYPE		unsigned short
#  define MATH_CAST_METHOD 	unsignedShortValue
static precision_t precision = 	0x0c0002;
#elif MATH_ORDER == 4
#  define MathTemplateArray	MathShortArray
#  define MATH_TYPE		short
#  define MATH_CAST_METHOD 	shortValue
static precision_t precision = 	0x100102;
#elif MATH_ORDER == 5
#  define MathTemplateArray	MathUIntArray
#  define MATH_TYPE		unsigned int
#  define MATH_CAST_METHOD 	unsignedIntValue
static precision_t precision = 	0x140004;
#elif MATH_ORDER == 6
#  define MathTemplateArray	MathIntArray
#  define MATH_TYPE		int
#  define MATH_CAST_METHOD 	intValue
static precision_t precision = 	0x180104;
#elif MATH_ORDER == 7
#  define MathTemplateArray	MathULongArray
#  define MATH_TYPE		unsigned long
#  define MATH_CAST_METHOD 	unsignedLongValue
static precision_t precision = 	0x1c0004;
#elif MATH_ORDER == 8
#  define MathTemplateArray	MathLongArray
#  define MATH_TYPE		long
#  define MATH_CAST_METHOD 	longValue
static precision_t precision = 	0x200104;
#elif MATH_ORDER == 9
#  define MathTemplateArray	MathULongLongArray
#  define MATH_TYPE		unsigned long long
#  define MATH_CAST_METHOD 	unsignedLongLongValue
static precision_t precision = 	0x240008;
#elif MATH_ORDER == 10
#  define MathTemplateArray	MathLongLongArray
#  define MATH_TYPE		long long
#  define MATH_CAST_METHOD 	longLongValue
static precision_t precision = 	0x280108;
#elif MATH_ORDER == 11
#  define MathTemplateArray	MathFloatArray
#  define MATH_TYPE		float
#  define MATH_CAST_METHOD 	floatValue
static precision_t precision = 	0x2c0108;
#elif MATH_ORDER == 12
#  define MathTemplateArray	MathDoubleArray
#  define MATH_TYPE		double
#  define MATH_CAST_METHOD 	doubleValue
static precision_t precision = 	0x300110;
#endif

inline static MATH_TYPE find_min(MATH_TYPE a, MATH_TYPE b); 
inline static MATH_TYPE find_max(MATH_TYPE a, MATH_TYPE b); 
inline static MATH_TYPE find_sum(MATH_TYPE sum, MATH_TYPE num); 
typedef MATH_TYPE (*caster_f_t)(void *, unsigned long);
typedef MATH_TYPE (*operate_f_t)(MATH_TYPE, MATH_TYPE);
static caster_f_t cast_function(const char *);
static operate_f_t operate_function(ma_operator_t);

@interface MAMutableValueData(MutableData)
- (void) setLength: (unsigned)length;
@end

@interface MathTemplateArray : MathArray
{
}

@end

@implementation MathTemplateArray : MathArray

+ (precision_t)precision
{
    return precision;
}

+ (const char *)objCType
{
    return @encode(MATH_TYPE);
}

- convertFromObjCType:(const char *)oldType
{
    ordered_index_t i, max_elements;
    const char* newType;
    MATH_TYPE *data;
    MATH_TYPE (*caster)(void *, unsigned long) = cast_function(oldType);
		
    newType = [self objCType];
    max_elements = array_num_elements(dimension, [size bytes]);
    if (array_aligned_sizeof_elements(oldType) 
    	    	< array_aligned_sizeof_elements(newType)) {
        [arrayData setLength: max_elements * math_aligned_size(newType)];
	data = [arrayData mutableBytes];
	for (i=0; i < max_elements; i++)
    	    data[max_elements-i-1] = caster(data, max_elements-i-1);
    } else {
	data = [arrayData mutableBytes];
	for (i=0; i < max_elements; i++)
    	    data[i] = caster(data, i);
        [arrayData setLength: max_elements * math_aligned_size(newType)];
    }
    [arrayData setObjCType: newType];
    return self;
}

- maMatrixMultiply:(MathArray *)otherArray
{
    unsigned i, j, k;
    unsigned a_size[2];
    unsigned b_size[2];
    void *b_data;
    MAMutableValueData *cMatrix;
    MATH_TYPE *a_data;
    MATH_TYPE *c_data;
    MATH_TYPE (*caster)(void *, unsigned long) 
    		= cast_function([otherArray objCType]);

    [super maMatrixMultiply:otherArray];
    a_size[0] = [self sizes][0];
    if (dimension == 2)
    	a_size[1] = [self sizes][1];
    else
    	a_size[1] = 1;
    b_size[0] = [otherArray sizes][0];
    if ([otherArray dimension] == 2)
    	b_size[1] = [otherArray sizes][1];
    else
    	b_size[1] = 1;
    cMatrix = [MAMutableValueData dataWithCount:a_size[0]*b_size[1]
    		objCType:[self objCType]];
    a_data = [arrayData mutableBytes];
    b_data = [[otherArray mathData] mutableBytes];
    c_data = [cMatrix mutableBytes];
    [MathArray _startMath];
    for (i=0; i < a_size[0]; i++) {
      for (j=0; j < b_size[1]; j++) {
	unsigned loc = i*b_size[1]+j;
	c_data[loc] = 0;
	for (k=0; k < a_size[1]; k++) {
	    c_data[loc] += a_data[i*a_size[1]+k] 
	    	* caster(b_data, k*b_size[1]+j);
#ifdef MATH_CHECK_FPE
	if (!finite(c_data[loc]))
	    ma_fpe_errno = FLT_OVERFLOW;
#endif
	}
      }
    }

    [arrayData release];
    arrayData = [cMatrix retain];
    a_size[1] = b_size[1];
    if (a_size[1] == 1)
    	dimension = 1;
    [size release];
    size = [[MAValueData dataWithValues:a_size
    		count:dimension
		objCType:@encode(unsigned)] retain];
    [MathArray _finishMath];
    return self;
}

- _maOperate:(ma_operator_t)operator with:(MathArray *)value
{
    unsigned long i, j, max_elements;
    unsigned long increment;
    void *otherData;
    MATH_TYPE *data;
    MATH_TYPE (*operate)(MATH_TYPE, MATH_TYPE) 
    		= operate_function(operator);
    MATH_TYPE (*caster)(void *, unsigned long) 
    		= cast_function([value objCType]);
	
    if (operate == 0) {
    	MA_RAISE_ILLEGAL;
	/* NOT REACHED */
    }
    if (operator == ma_matrix_multiply) {
    	return [self maMatrixMultiply:value];
    }
    	
    data = [arrayData mutableBytes];
    increment = 1;
    otherData = [[value mathData] mutableBytes];
    if ([value dimension] == 0)
	    increment = 0;
    max_elements = array_num_elements(dimension, [size bytes]);
    j = 0;
    for (i=0; i < max_elements; i++) {
    	data[i] = operate(data[i], caster(otherData, j));
#ifdef MATH_CHECK_FPE
	if (!finite(data[i]))
	    ma_fpe_errno = FLT_OTHER;
#endif
	j += increment;
    }
    return self;
}

- _maPerform: (double_func_t)mathFunction 
{
    unsigned long i, max_elements;
    MATH_TYPE *data;
		
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    for (i=0; i < max_elements; i++) {
    	data[i] = (MATH_TYPE)mathFunction((double)data[i]);
#ifdef MATH_CHECK_FPE
	if (!finite(data[i]))
	    ma_fpe_errno = FLT_OTHER;
#endif
    }
    return self;
}

- _maPerformFunction:(perform_func_t)perform_func userInfo:(void *)info
{
    unsigned long i, max_elements;
    unsigned  *index = [[[size mutableCopy] autorelease] mutableBytes];
    MATH_TYPE *data;
    NSRange   *range;
    
    range = [[MAMutableValueData dataWithCount:dimension 
		objCType:@encode(NSRange)] mutableBytes];

    for (i=0; i < dimension; i++) {
        range[i].location = 0;
	range[i].length = ((unsigned *)[size bytes])[i];
    }
    index = start_index_from_range(dimension, range, index);
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    // FIXME: need to set up autorelease pool to get rid of extra objects
    for (i=0; i < max_elements; i++) {
        id <NSNumber,ComplexNumber> number;
	number = (id <NSNumber,ComplexNumber>)[arrayData valueAtIndex:i];
    	number = perform_func(number, index, info);
	data[i] = [number MATH_CAST_METHOD];
#ifdef MATH_CHECK_FPE
	if (!finite(data[i]))
	    ma_fpe_errno = FLT_OTHER;
#endif
	increment_index_in_range(dimension, range, index, 1);
    }
    return self;
}

- maAbs
{
    unsigned long i, max_elements;
    MATH_TYPE *data;

    /* If we're floating, use fabs instead of abs */
    if (precision >= [[[self class] classForObjCType:@encode(float)] precision])
    	return [self maPerform: (double_func_t)fabs];

    /* if we're unsigned, what's the point? */
    if (MATH_SIGNED(precision) == 0)
    	return self;
    [self _updateLazyArray];
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    for (i=0; i < max_elements; i++)
    	data[i] = abs(data[i]);
    return self;
}

- maInvert
{
    [self maNotImplemented:_cmd];
    return self;
}

/* Generate a uniform random number n, such that min <= n < max */
- maRandomUniformMin: (double)min toMax: (double)max
{
    unsigned long i, max_elements;
    MATH_TYPE *data;
		
    [self _updateLazyArray];
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    if (ma_random_seed == 0)
      ma_random_seed = time(NULL);
    srand48(ma_random_seed);
    for (i=0; i < max_elements; i++) 
      {
    	data[i] = GEN_RANDOM * (max - min) + min;
      }
    return self;
}

/* Generate a normail random number with the given mean and standard
   deviation.  Uses the Box-Miller method which produces two numbers
   each iteration */
- maRandomNormalMean: (double)mean deviation: (double)dev
{
    unsigned long i, max_elements;
    MATH_TYPE *data;
		
    [self _updateLazyArray];
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    if (ma_random_seed == 0)
      ma_random_seed = time(NULL);
    srand48(ma_random_seed);
    for (i=0; i < max_elements/2; i++) 
      {
	double r, theta;

	r = dev * sqrt( -2.0 * log(GEN_RANDOM) );
	theta = 2.0 * M_PI * GEN_RANDOM;
    	data[i*2] = r * cos(theta) + mean;
    	data[i*2+1] = r * sin(theta) + mean;
      }
    if (max_elements & 0x01)
      {
	double r, theta;

	r = dev * sqrt( -2.0 * log(GEN_RANDOM) );
	theta = 2.0 * M_PI * GEN_RANDOM;
    	data[max_elements - 1] = r * cos(theta) + mean;
      }
    return self;
}

- (id <NSNumber,ComplexNumber>)_loopArrayWith:
	(MATH_TYPE (*)(MATH_TYPE, MATH_TYPE))loopFunc
{
    unsigned long i, max_elements;
    const MATH_TYPE *data;
    MATH_TYPE number;
		
    [self _updateLazyArray];
    [MathArray _startMath];
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData bytes];
    number = data[0];
    for (i=1; i < max_elements; i++)
        number = loopFunc(number, data[i]);
    [MathArray _finishMath];
	    
    return (MANumber *)[MANumber value:&number withObjCType:@encode(MATH_TYPE)];
}

- (id <NSNumber,ComplexNumber>)maMinimumValue
{
    return [self _loopArrayWith:find_min];
}

- (id <NSNumber,ComplexNumber>)maMaximumValue
{
    return [self _loopArrayWith:find_max];
}

- (id <NSNumber,ComplexNumber>)maTotal
{
    return [self _loopArrayWith:find_sum];
}

- maWhere
{
    ordered_index_t i, max_elements;
    unsigned j;
    MATH_TYPE *data;

    /* Make sure we can handle the ordered index values */
    if ([self precision] 
    	    < [[MathArray classForObjCType:@encode(unsigned)] precision]
	    && math_flags.promote == 1) {
	[self castToObjCType:@encode(unsigned)];
	return [self maWhere];
    }

    j = 0;
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    for (i=0; i < max_elements; i++)
        if (data[i])
	    data[j++] = i;
    
    [arrayData setCount:j];
    [size release];
    dimension = 1;
    size = [[MAValueData dataWithValues:&j
    		count:dimension
		objCType:@encode(unsigned)] retain];
    return self;
}

@end

inline static 
MATH_TYPE find_min(MATH_TYPE a, MATH_TYPE b)
{
    return MIN(a,b);
}

inline static 
MATH_TYPE find_max(MATH_TYPE a, MATH_TYPE b)
{
    return MAX(a,b);
}

inline static 
MATH_TYPE find_sum(MATH_TYPE sum, MATH_TYPE num)
{
    return sum+num;
}

/* All the functions which know how to cast from any known type to our type */
static inline MATH_TYPE
cast_from_uchar(void *data, unsigned long loc) 
	{return *((unsigned char*)data + loc);}
static inline MATH_TYPE
cast_from_char(void *data, unsigned long loc) 
	{return *((char*)data + loc);}
static inline MATH_TYPE
cast_from_ushort(void *data, unsigned long loc) 
	{return *((unsigned short*)data + loc);}
static inline MATH_TYPE
cast_from_short(void *data, unsigned long loc) 
	{return *((short*)data + loc);}
static inline MATH_TYPE
cast_from_uint(void *data, unsigned long loc) 
	{return *((unsigned int*)data + loc);}
static inline MATH_TYPE
cast_from_int(void *data, unsigned long loc) 
	{return *((int*)data + loc);}
static inline MATH_TYPE
cast_from_ulong(void *data, unsigned long loc) 
	{return *((unsigned long*)data + loc);}
static inline MATH_TYPE
cast_from_long(void *data, unsigned long loc) 
	{return *((long*)data + loc);}
static inline MATH_TYPE
cast_from_ulonglong(void *data, unsigned long loc) 
	{return *((unsigned long long*)data + loc);}
static inline MATH_TYPE
cast_from_longlong(void *data, unsigned long loc) 
	{return *((long long*)data + loc);}
static inline MATH_TYPE
cast_from_float(void *data, unsigned long loc) 
	{return *((float*)data + loc);}
static inline MATH_TYPE
cast_from_double(void *data, unsigned long loc) 
	{return *((double*)data + loc);}
static inline MATH_TYPE
cast_from_complexfloat(void *data, unsigned long loc) 
	{return *((float*)data + 2*loc);}
static inline MATH_TYPE
cast_from_complexdouble(void *data, unsigned long loc) 
	{return *((double*)data + 2*loc);}

static caster_f_t
cast_function(const char *type)
{
  switch(*type) {
  case _C_CHR:
    return cast_from_char;
    break;
  case _C_UCHR:
    return cast_from_uchar;
    break;
  case _C_SHT:
    return cast_from_short;
    break;
  case _C_USHT:
    return cast_from_ushort;
    break;
  case _C_INT:
    return cast_from_int;
    break;
  case _C_UINT:
    return cast_from_uint;
    break;
  case _C_LNG:
    return cast_from_long;
    break;
  case _C_ULNG:
    return cast_from_ulong;
    break;
  case 'q':
    return cast_from_longlong;
    break;
  case 'Q':
    return cast_from_ulonglong;
    break;
  case _C_FLT:
    return cast_from_float;
    break;
  case _C_DBL:
    return cast_from_double;
    break;
  case _C_STRUCT_B:
      // FIXME: need better check for complex
      if (math_aligned_size (type) 
      		== math_aligned_size (@encode(complex_float)))
	return cast_from_complexfloat;
      else if (math_aligned_size (type) 
      		== math_aligned_size (@encode(complex_double)))
	return cast_from_complexdouble;
    break;
  }

  MA_RAISE_CAST;
  /* NOT REACHED */
  return 0;
}

/* All the functions which know how to operate on this type */
// FIXME: need pow for natural numbers
static inline MATH_TYPE
op_exponent(MATH_TYPE a, MATH_TYPE b) {return pow(a,b);}

static inline MATH_TYPE
op_multiply(MATH_TYPE a, MATH_TYPE b) {return a*b;}

static inline MATH_TYPE
op_divide(MATH_TYPE a, MATH_TYPE b) {return a/b;}

static inline MATH_TYPE
op_add(MATH_TYPE a, MATH_TYPE b) {return a+b;}

static inline MATH_TYPE
op_subtract(MATH_TYPE a, MATH_TYPE b) {return a-b;}

static inline MATH_TYPE
op_mod(MATH_TYPE a, MATH_TYPE b) 
{
#if MATH_ORDER == 11
    	return fmod(a,b);
#elif MATH_ORDER == 12
    	return fmod(a,b);
#else
    return a % b;
#endif
}

static inline MATH_TYPE
op_minimum(MATH_TYPE a, MATH_TYPE b) {return MIN(a,b);}

static inline MATH_TYPE
op_maximum(MATH_TYPE a, MATH_TYPE b) {return MAX(a,b);}

static inline MATH_TYPE
op_not(MATH_TYPE a, MATH_TYPE b)
{
#if MATH_ORDER == 11
    return (a == 0) ? 1.0 : 0.0;
#elif MATH_ORDER == 12
    return (a == 0) ? 1.0 : 0.0;
#else
    return ~a;
#endif
}

static inline MATH_TYPE
op_equal(MATH_TYPE a, MATH_TYPE b)
{
    return (a == b);
}

static inline MATH_TYPE
op_not_equal(MATH_TYPE a, MATH_TYPE b)
{
    return (a != b);
}

static inline MATH_TYPE
op_less_or_equal(MATH_TYPE a, MATH_TYPE b)
{
    return (a <= b);
}

static inline MATH_TYPE
op_less(MATH_TYPE a, MATH_TYPE b)
{
    return (a < b);
}

static inline MATH_TYPE
op_greater_or_equal(MATH_TYPE a, MATH_TYPE b)
{
    return (a >= b);
}

static inline MATH_TYPE
op_greater(MATH_TYPE a, MATH_TYPE b)
{
    return (a > b);
}

// FIXME: How do you do logical ops on floats?
static inline MATH_TYPE
op_and(MATH_TYPE a, MATH_TYPE b)
{
#if MATH_ORDER == 11
    return (a != 0 && b != 0) ? b : 0.0;
#elif MATH_ORDER == 12
    return (a != 0 && b != 0) ? b : 0.0;
#else
    return (a & b);
#endif
}

static inline MATH_TYPE
op_or(MATH_TYPE a, MATH_TYPE b)
{
#if MATH_ORDER == 11
    return (a != 0 || b != 0) ? 1.0 : 0.0;
#elif MATH_ORDER == 12
    return (a != 0 || b != 0) ? 1.0 : 0.0;
#else
    return (a | b);
#endif
}

static inline MATH_TYPE
op_xor(MATH_TYPE a, MATH_TYPE b)
{
#if MATH_ORDER == 11
    return ((a != 0 || b != 0) && !(a != 0 && b != 0)) ? 1.0 : 0.0;
#elif MATH_ORDER == 12
    return ((a != 0 || b != 0) && !(a != 0 && b != 0)) ? 1.0 : 0.0;
#else
    return (a ^ b);
#endif
}

static operate_f_t
operate_function(ma_operator_t operator)
{

    switch(operator) {
    case ma_exponent:
	return op_exponent;
	break;
    case ma_multiply:
	return op_multiply;
	break;
    case ma_matrix_multiply:
	NSCAssert(0, @"Invalid operator reference");
	break;
    case ma_divide:
	return op_divide;
	break;
    case ma_add:
	return op_add;
	break;
    case ma_subtract:
	return op_subtract;
	break;
    case ma_mod:
	return op_mod;
	break;
    case ma_minimum:
	return op_minimum;
	break;
    case ma_maximum:
	return op_maximum;
	break;
    case ma_not:
	return op_not;
	break;
    case ma_equal:
	return op_equal;
	break;
    case ma_not_equal:
	return op_not_equal;
	break;
    case ma_less_or_equal:
	return op_less_or_equal;
	break;
    case ma_less:
	return op_less;
	break;
    case ma_greater_or_equal:
	return op_greater_or_equal;
	break;
    case ma_greater:
	return op_greater;
	break;
    case ma_and:
	return op_and;
	break;
    case ma_or:
	return op_or;
	break;
    case ma_xor:
	return op_xor;
	break;
    }

    return 0;
}

