/*
    MathDecimalArray - MathArray subclass of NSDecimal numbers
    
    Copyright (C) 1997, Adam Fedor
    
*/

#import <Foundation/NSString.h>
#import <Foundation/NSDecimalNumber.h>
#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 */

static precision_t precision = 	0x320108;

/* A minor non-thread safe variable */
static int rounding;

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

static NSDecimal
NSSetDecimal(double num)
{
  NSDecimal result;
  //FIXME: find a better way of setting
  result = [[NSNumber numberWithDouble: num] decimalValue];
  return result;
}

static ma_trap_t
trap_error_from_decimal(NSCalculationError err)
{
  ma_trap_t trap;
  trap = 0;
  switch(err)
    {
    case NSCalculationLossOfPrecision: trap = FLT_INEXACT; break;
    case NSCalculationUnderflow: trap = FLT_UNDERFLOW; break;
    case NSCalculationOverflow: trap = FLT_OVERFLOW; break;
    case NSCalculationDivideByZero: trap = FLT_DIV0; break;
    default:
      trap = 0;
    }
  return trap;
}

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

@interface MathDecimalArray : MathArray
{
}

@end

@implementation MathDecimalArray : MathArray

+ (precision_t)precision
{
    return precision;
}

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

- convertFromObjCType:(const char *)oldType
{
    ordered_index_t i, max_elements;
    const char* newType;
    NSDecimal *data;
    NSDecimal (*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;
    NSDecimal *a_data;
    NSDecimal *c_data;
    NSDecimal (*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] = NSSetDecimal(0);
	for (k=0; k < a_size[1]; k++) 
	  {
	    NSCalculationError calc_error;
	    NSDecimal result, temp;
	    temp = caster(b_data, k*b_size[1]+j);
	    calc_error = NSDecimalMultiply(&result, &a_data[i*a_size[1]+k],
					   &temp, math_flags.round);
	    ma_fpe_errno |= trap_error_from_decimal(calc_error);
	    calc_error = NSDecimalAdd(&c_data[loc], &c_data[loc], &result,
				      math_flags.round);
	    ma_fpe_errno |= trap_error_from_decimal(calc_error);
	}
      }
    }

    [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;
    NSDecimal *data;
    NSDecimal (*operate)(NSDecimal, NSDecimal) 
    		= operate_function(operator);
    NSDecimal (*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++) {
        rounding = math_flags.round;
    	data[i] = operate(data[i], caster(otherData, j));
	j += increment;
    }
    return self;
}

- _maPerform: (double_func_t)mathFunction 
{
    unsigned long i, max_elements;
    NSDecimal *data;

    /* Can't do functions on NSDecimal numbers */
    MA_RAISE_PERFORM
		
    max_elements = array_num_elements(dimension, [size bytes]);
    data = [arrayData mutableBytes];
    for (i=0; i < max_elements; i++) 
      {
	NSDecimalNumber* number;
	number = [NSDecimalNumber decimalNumberWithDecimal: data[i]];
    	data[i] = NSSetDecimal(mathFunction([number doubleValue]));
      }
    /* Raise an error since we are not really operating on NSDecimal numbers */
    ma_fpe_errno |= FLT_OTHER;
    return self;
}

- _maPerformFunction:(perform_func_t)perform_func userInfo:(void *)info
{
    unsigned long i, max_elements;
    unsigned  *index = [[[size mutableCopy] autorelease] mutableBytes];
    NSDecimal *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++) {
        NSDecimalNumber *number;
	NSDecimal temp;
	[[arrayData valueAtIndex:i] getValue: &temp];
	number = [NSDecimalNumber decimalNumberWithDecimal: temp];
	// FIXME: Catch the exceptions here
    	number = perform_func(number, index, info);
	data[i] = [number decimalValue];
	increment_index_in_range(dimension, range, index, 1);
    }
    return self;
}

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

// FIXME: use NSDecimal to calculate random numbers...
/* Generate a uniform random number n, such that min <= n < max */
- maRandomUniformMin: (double)min toMax: (double)max
{
    unsigned long i, max_elements;
    NSDecimal *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] = NSSetDecimal(GEN_RANDOM * (max - min) + min);
      }
    return self;
}

/* Generate a normal 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;
    NSDecimal *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] = NSSetDecimal(r * cos(theta) + mean);
    	data[i*2+1] = NSSetDecimal(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] = NSSetDecimal(r * cos(theta) + mean);
      }
    return self;
}

- (id <NSNumber,ComplexNumber>)_loopArrayWith:
	(NSDecimal (*)(NSDecimal, NSDecimal))loopFunc
{
    unsigned long i, max_elements;
    const NSDecimal *data;
    NSDecimal 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 [NSDecimalNumber decimalNumberWithDecimal: number];
}

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

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

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

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

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

@end

inline static 
NSDecimal find_min(NSDecimal a, NSDecimal b)
{
    return (NSDecimalCompare(&a, &b) == NSOrderedAscending) ? a : b;
}

inline static 
NSDecimal find_max(NSDecimal a, NSDecimal b)
{
    return (NSDecimalCompare(&a, &b) == NSOrderedAscending) ? b : a;
}

inline static 
NSDecimal find_sum(NSDecimal sum, NSDecimal num)
{
  NSDecimal result;
  NSCalculationError calc_error;
  calc_error = NSDecimalAdd(&result, &sum, &num, rounding);
  ma_fpe_errno |= trap_error_from_decimal(calc_error);
  return result;
}

/* All the functions which know how to cast from any known type to our type */
static inline NSDecimal
cast_from_uchar(void *data, unsigned long loc) 
	{return NSSetDecimal(*((unsigned char*)data + loc));}
static inline NSDecimal
cast_from_char(void *data, unsigned long loc) 
	{return NSSetDecimal(*((char*)data + loc));}
static inline NSDecimal
cast_from_ushort(void *data, unsigned long loc) 
	{return NSSetDecimal(*((unsigned short*)data + loc));}
static inline NSDecimal
cast_from_short(void *data, unsigned long loc) 
	{return NSSetDecimal(*((short*)data + loc));}
static inline NSDecimal
cast_from_uint(void *data, unsigned long loc) 
	{return NSSetDecimal(*((unsigned int*)data + loc));}
static inline NSDecimal
cast_from_int(void *data, unsigned long loc) 
	{return NSSetDecimal(*((int*)data + loc));}
static inline NSDecimal
cast_from_ulong(void *data, unsigned long loc) 
	{return NSSetDecimal(*((unsigned long*)data + loc));}
static inline NSDecimal
cast_from_long(void *data, unsigned long loc) 
	{return NSSetDecimal(*((long*)data + loc));}
static inline NSDecimal
cast_from_ulonglong(void *data, unsigned long loc) 
	{return NSSetDecimal(*((unsigned long long*)data + loc));}
static inline NSDecimal
cast_from_longlong(void *data, unsigned long loc) 
	{return NSSetDecimal(*((long long*)data + loc));}
static inline NSDecimal
cast_from_float(void *data, unsigned long loc) 
	{return NSSetDecimal(*((float*)data + loc));}
static inline NSDecimal
cast_from_double(void *data, unsigned long loc) 
	{return NSSetDecimal(*((double*)data + loc));}
static inline NSDecimal
cast_from_complexfloat(void *data, unsigned long loc) 
	{return NSSetDecimal(*((float*)data + 2*loc));}
static inline NSDecimal
cast_from_complexdouble(void *data, unsigned long loc) 
	{return NSSetDecimal(*((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 */
/*
static inline NSDecimal
op_exponent(NSDecimal a, NSDecimal b)
{ 
  NSDecimal result;
  NSCalculationError calc_error;
  calc_error = NSDecimalPower(&result, &a, &b, rounding);
  ma_fpe_errno |= trap_error_from_decimal(calc_error);
  return result;
}
*/

static inline NSDecimal
op_multiply(NSDecimal a, NSDecimal b) 
{
  NSDecimal result;
  NSCalculationError calc_error;
  calc_error = NSDecimalMultiply(&result, &a, &b, rounding);
  ma_fpe_errno |= trap_error_from_decimal(calc_error);
  return result;
}

static inline NSDecimal
op_divide(NSDecimal a, NSDecimal b) 
{
  NSDecimal result;
  NSCalculationError calc_error;
  calc_error = NSDecimalDivide(&result, &a, &b, rounding);
  ma_fpe_errno |= trap_error_from_decimal(calc_error);
  return result;
}

static inline NSDecimal
op_add(NSDecimal a, NSDecimal b) 
{
  NSDecimal result;
  NSCalculationError calc_error;
  calc_error =  NSDecimalAdd(&result, &a, &b, rounding);
  ma_fpe_errno |= trap_error_from_decimal(calc_error);
  return result;
}

static inline NSDecimal
op_subtract(NSDecimal a, NSDecimal b) 
{
  NSDecimal result;
  NSCalculationError calc_error;
  calc_error = NSDecimalSubtract(&result, &a, &b, rounding);
  ma_fpe_errno |= trap_error_from_decimal(calc_error);
  return result;
}

/*
static inline NSDecimal
op_mod(NSDecimal a, NSDecimal b) 
{
  ma_fpe_errno = FLT_OTHER;
  return NSSetDecimal(0);
}
*/

static inline NSDecimal
op_minimum(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) == NSOrderedAscending) ? a : b;
}

static inline NSDecimal
op_maximum(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) == NSOrderedAscending) ? b : a;
}

static inline NSDecimal
op_not(NSDecimal a, NSDecimal b)
{
  NSDecimal zero = NSSetDecimal(0);
  return (NSDecimalCompare(&a, &zero) == NSOrderedSame) 
    ? NSSetDecimal(1) : zero;
}

static inline NSDecimal
op_equal(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) == NSOrderedSame) 
    ? NSSetDecimal(1) : NSSetDecimal(0);
}

static inline NSDecimal
op_not_equal(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) != NSOrderedSame) 
    ? NSSetDecimal(1) : NSSetDecimal(0);
}

static inline NSDecimal
op_less_or_equal(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) <= NSOrderedSame)
    ? NSSetDecimal(1) : NSSetDecimal(0);
}

static inline NSDecimal
op_less(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) == NSOrderedAscending) 
    ? NSSetDecimal(1) : NSSetDecimal(0);
}

static inline NSDecimal
op_greater_or_equal(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) >= NSOrderedSame)
    ? NSSetDecimal(1) : NSSetDecimal(0);
}

static inline NSDecimal
op_greater(NSDecimal a, NSDecimal b)
{
  return (NSDecimalCompare(&a, &b) == NSOrderedDescending) 
    ? NSSetDecimal(1) : NSSetDecimal(0);
}

// FIXME: How do you do logical ops on floats?
static inline NSDecimal
op_and(NSDecimal a, NSDecimal b)
{
  NSDecimal zero = NSSetDecimal(0);
  return (NSDecimalCompare(&a, &zero) != NSOrderedSame
	  && NSDecimalCompare(&b, &zero) != NSOrderedSame) ? b : zero;
}

static inline NSDecimal
op_or(NSDecimal a, NSDecimal b)
{
  NSDecimal zero = NSSetDecimal(0);
  return (NSDecimalCompare(&a, &zero) != NSOrderedSame
	  || NSDecimalCompare(&b, &zero) != NSOrderedSame) ? b : zero;
}

static inline NSDecimal
op_xor(NSDecimal a, NSDecimal b)
{
  NSDecimal zero = NSSetDecimal(0);
  return ((NSDecimalCompare(&a, &zero) != NSOrderedSame
	   || NSDecimalCompare(&b, &zero) != NSOrderedSame)
	  && !(NSDecimalCompare(&a, &zero) != NSOrderedSame
	       && NSDecimalCompare(&b, &zero) != NSOrderedSame)) ? b : zero;
}

static operate_f_t
operate_function(ma_operator_t operator)
{

    switch(operator) {
    case ma_exponent:
	MA_RAISE_PERFORM
	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:
	MA_RAISE_PERFORM
	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;
}

