/**
   NSDecimalNumber class
   Copyright (C) 2000 Free Software Foundation, Inc.

   Written by: Fred Kiefer <FredKiefer@gmx.de>
   Created: July 2000

   This file is part of the GNUstep Base Library.

   This library 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; either
   version 3 of the License, or (at your option) any later version.

   This library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with this library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02111 USA.

   <title>NSDecimalNumber class reference</title>
   $Date: 2007-12-05 04:17:26 -0600 (Wed, 05 Dec 2007) $ $Revision: 25680 $
   */

#define _GNU_SOURCE
#define _ISOC99_SOURCE
#include <math.h>

#include "Foundation/NSCoder.h"
#include "Foundation/NSDecimal.h"
#include "Foundation/NSDecimalNumber.h"
#include "Foundation/NSException.h"
#include "Foundation/NSPortCoder.h"

#include "GSPrivate.h"

#ifdef fpclassify
#define GSIsNAN(n) (fpclassify(n) == FP_NAN)
#define GSIsInf(n) (fpclassify(n) == FP_INFINITE)
#else
#warning C99 macro fpclassify not found: Cannot determine NAN/Inf float-values
#define GSIsNAN(n) (0)
#define GSIsInf(n) (0)
#endif

// shared default behavior for NSDecimalNumber class
static NSDecimalNumberHandler *handler;

@implementation NSDecimalNumberHandler

+ (id) defaultDecimalNumberHandler
{
  if (handler == nil)
    handler = [[self alloc] initWithRoundingMode: NSRoundPlain
			    scale: 38
			    raiseOnExactness: NO
			    raiseOnOverflow: YES
			    raiseOnUnderflow: YES
			    raiseOnDivideByZero: YES];

  return handler;
}

+ (id) decimalNumberHandlerWithRoundingMode: (NSRoundingMode)roundingMode
				      scale: (short)scale
			   raiseOnExactness: (BOOL)raiseOnExactness
			    raiseOnOverflow: (BOOL)raiseOnOverflow
			   raiseOnUnderflow: (BOOL)raiseOnUnderflow
			raiseOnDivideByZero: (BOOL)raiseOnDivideByZero
{
  return AUTORELEASE([[self alloc] initWithRoundingMode: roundingMode
				   scale: scale
				   raiseOnExactness: raiseOnExactness
				   raiseOnOverflow: raiseOnOverflow
				   raiseOnUnderflow: raiseOnUnderflow
				   raiseOnDivideByZero: raiseOnDivideByZero]);
}

- (id) initWithRoundingMode: (NSRoundingMode)roundingMode
		      scale: (short)scale
	   raiseOnExactness: (BOOL)raiseOnExactness
	    raiseOnOverflow: (BOOL)raiseOnOverflow
	   raiseOnUnderflow: (BOOL)raiseOnUnderflow
	raiseOnDivideByZero: (BOOL)raiseOnDivideByZero
{
  _roundingMode = roundingMode;
  _scale = scale;
  _raiseOnExactness = raiseOnExactness;
  _raiseOnOverflow = raiseOnOverflow;
  _raiseOnUnderflow = raiseOnUnderflow;
  _raiseOnDivideByZero = raiseOnDivideByZero;

  return self;
}

- (NSDecimalNumber*) exceptionDuringOperation: (SEL)method
					error: (NSCalculationError)error
				  leftOperand: (NSDecimalNumber*)leftOperand
				 rightOperand: (NSDecimalNumber*)rightOperand
{
  switch (error)
    {
      case NSCalculationNoError: return nil;
      case NSCalculationUnderflow:
	if (_raiseOnUnderflow)
	  // FIXME: What exception to raise?
	  [NSException raise: @"NSDecimalNumberException"
		      format: @"Underflow"];
	else
	  return [NSDecimalNumber minimumDecimalNumber];
	break;
      case NSCalculationOverflow:
	if (_raiseOnOverflow)
	  [NSException raise: @"NSDecimalNumberException"
		      format: @"Overflow"];
	else
	  return [NSDecimalNumber maximumDecimalNumber];
	break;
      case NSCalculationLossOfPrecision:
	if (_raiseOnExactness)
	  [NSException raise: @"NSDecimalNumberException"
		      format: @"Loss of precision"];
	else
	  return nil;
	break;
      case NSCalculationDivideByZero:
	if (_raiseOnDivideByZero)
	  [NSException raise: @"NSDecimalNumberException"
		      format: @"Divide by zero"];
	else
	  return [NSDecimalNumber notANumber];
	break;
    }

  return nil;
}

- (NSRoundingMode) roundingMode
{
  return _roundingMode;
}

- (short) scale
{
  return _scale;
}

@end


@implementation NSDecimalNumber

static Class NSDecimalNumberClass;
static NSDecimalNumber *maxNumber;
static NSDecimalNumber *minNumber;
static NSDecimalNumber *notANumber;
static NSDecimalNumber *zero;
static NSDecimalNumber *one;

+ (void) initialize
{
  NSDecimal d;

  d.validNumber = NO;
  notANumber = [[self alloc] initWithDecimal: d];
  NSDecimalMax(&d);
  maxNumber = [[self alloc] initWithDecimal: d];
  NSDecimalMin(&d);
  minNumber = [[self alloc] initWithDecimal: d];
  zero = [[self alloc] initWithMantissa: 0
			       exponent: 0
			     isNegative: NO];
  one = [[self alloc] initWithMantissa: 1
			      exponent: 0
			    isNegative: NO];
  NSDecimalNumberClass = [NSDecimalNumber class];
}

+ (id <NSDecimalNumberBehaviors>) defaultBehavior
{
  // Reuse the handler from the class NSDecimalNumberHandler
  return [NSDecimalNumberHandler defaultDecimalNumberHandler];
}

+ (void) setDefaultBehavior: (id <NSDecimalNumberBehaviors>)behavior
{
  // Reuse the handler from the class NSDecimalNumberHandler
  // Might give interessting result on this class as behavior may came
  // from a different class
  ASSIGN(handler, behavior);
}

+ (NSDecimalNumber*) maximumDecimalNumber
{
  return maxNumber;
}
+ (NSDecimalNumber*) minimumDecimalNumber
{
  return minNumber;
}

+ (NSDecimalNumber*) notANumber
{
  return notANumber;
}

+ (NSDecimalNumber*) zero
{
  return zero;
}

+ (NSDecimalNumber*) one
{
  return one;
}

+ (NSDecimalNumber*) decimalNumberWithDecimal: (NSDecimal)decimal
{
  return AUTORELEASE([[self alloc] initWithDecimal: decimal]);
}

+ (NSDecimalNumber*) decimalNumberWithMantissa: (unsigned long long)mantissa
				      exponent: (short)exponent
				    isNegative: (BOOL)isNegative
{
  return AUTORELEASE([[self alloc] initWithMantissa: mantissa
					   exponent: exponent
					 isNegative: isNegative]);
}

+ (NSDecimalNumber*) decimalNumberWithString: (NSString*)numericString
{
  return AUTORELEASE([[self alloc] initWithString: numericString]);
}

+ (NSDecimalNumber*) decimalNumberWithString: (NSString*)numericString
				      locale: (NSDictionary*)locale
{
  return AUTORELEASE([[self alloc] initWithString: numericString
					   locale: locale]);
}

/**
 * Inefficient ... quick hack by converting double value to string,
 * then initialising from string.
 */
- (id) initWithBytes: (const void*)value objCType: (const char*)type
{
  unsigned long long val;
  long long llval;
  NSDecimal decimal;
  BOOL negative, llvalSet;

  if (strlen(type) != 1)
    {
      DESTROY(self);
      return nil;
    }

  llvalSet = YES;
  negative = NO;

  switch (*type)
    {
    case _C_CHR:
      {
	signed char v = *(signed char *)value;
	llval = (long long)v;
	break;
      }
    case _C_UCHR:
      {
	unsigned char v = *(unsigned char *)value;
	llval = (long long)v;
	break;
      }
    case _C_SHT:
      {
	short v = *(short *)value;
	llval = (long long)v;
	break;
      }
    case _C_USHT:
      {
	unsigned short v = *(unsigned short *)value;
	llval = (long long)v;
	break;
      }
    case _C_INT:
      {
	int v = *(int *)value;
	llval = (long long)v;
	break;
      }
    case _C_UINT:
      {
	unsigned int v = *(unsigned int *)value;
	llval = (long long)v;
	break;
      }
    case _C_LNG:
      {
	long v = *(long *)value;
	llval = (long long)v;
	break;
      }
    case _C_ULNG:
      {
	unsigned long v = *(unsigned long *)value;
	llval = (long long)v;
	break;
      }
#ifdef _C_LNGLNG
    case _C_LNGLNG:
#else
    case 'q':
#endif
      {
	long long v = *(long long *)value;
	llval = (long long)v;
	break;
      }
#ifdef	_C_ULNGLNG
    case _C_ULNGLNG:
#else
    case 'Q':
#endif
    default:
      {
	llvalSet = NO;
	break;

      }
    }
  if (llvalSet)
    {
      if (llval<0)
	{
	  negative = YES;
	  llval *= -1;
	}
      val = llval;
    }
  else
    {
      switch (*type)
	{
	case _C_FLT:
	  /* FIXME: This is better implemented with GMP where available.  */
	  {
	    NSString *s;
	    float v = *(float *)value;
	    if (GSIsNAN(v)) return notANumber;
	    if (GSIsInf(v)) return (v < 0.0) ? minNumber : maxNumber;
	    s = [[NSString alloc] initWithFormat: @"%g"
				  locale: GSPrivateDefaultLocale(), (double)v];
	    self = [self initWithString: s];
	    RELEASE(s);
	    return self;
	    break;
	  }
	case _C_DBL:
	  /* FIXME: This is better implemented with GMP where available.  */
	  {
	    NSString *s;
	    double v = *(double *)value;
	    if (GSIsNAN(v)) return notANumber;
	    if (GSIsInf(v)) return (v < 0.0) ? minNumber : maxNumber;
	    s = [[NSString alloc] initWithFormat: @"%g"
				  locale: GSPrivateDefaultLocale(), v];
	    self = [self initWithString: s];
	    RELEASE(s);
	    return self;
	    break;
	  }
#ifdef  _C_ULNGLNG
	case _C_ULNGLNG: 
#else
	case 'Q':
#endif
	  {
	    val = *(unsigned long long *)value;
	    break;
	  }
	}
    }

  NSDecimalFromComponents(&decimal, val,
			  0, negative);
  return [self initWithDecimal: decimal];
}

- (id) initWithDecimal: (NSDecimal)decimal
{
  NSDecimalCopy(&data, &decimal);
  return self;
}

- (id) initWithMantissa: (unsigned long long)mantissa
	       exponent: (short)exponent
	     isNegative: (BOOL)flag
{
  NSDecimal decimal;

  NSDecimalFromComponents(&decimal, mantissa, exponent, flag);
  return [self initWithDecimal: decimal];
}

- (id) initWithString: (NSString*)numberValue
{
  return [self initWithString: numberValue
    locale: GSPrivateDefaultLocale()];
}

- (id) initWithString: (NSString*)numberValue
	       locale: (NSDictionary*)locale
{
  NSDecimal decimal;

  NSDecimalFromString(&decimal, numberValue, locale);
  return [self initWithDecimal: decimal];
}

- (id) initWithBool: (BOOL)value
{
  return [self initWithMantissa: (value == YES) ? 1 : 0
		       exponent: 0
		     isNegative: NO];
}

- (id) initWithChar: (signed char)value
{
  if (value < 0)
    {
      return [self initWithMantissa: -value
			   exponent: 0
			 isNegative: YES];
    }
  else
    {
      return [self initWithMantissa: value
			   exponent: 0
			 isNegative: NO];
    }
}

- (id) initWithDouble: (double)value
{
  return [self initWithBytes: &value objCType: "d"];
}

- (id) initWithFloat: (float)value
{
  double	d = (double)value;

  return [self initWithBytes: &d objCType: "d"];
}

- (id) initWithInt: (signed int)value
{
  if (value < 0)
    {
      return [self initWithMantissa: -value
			   exponent: 0
			 isNegative: YES];
    }
  else
    {
      return [self initWithMantissa: value
			   exponent: 0
			 isNegative: NO];
    }
}

- (id) initWithLong: (signed long)value
{
  if (value < 0)
    {
      return [self initWithMantissa: -value
			   exponent: 0
			 isNegative: YES];
    }
  else
    {
      return [self initWithMantissa: value
			   exponent: 0
			 isNegative: NO];
    }
}

- (id) initWithLongLong: (signed long long)value
{
  if (value < 0)
    {
      return [self initWithMantissa: -value
			   exponent: 0
			 isNegative: YES];
    }
  else
    {
      return [self initWithMantissa: value
			   exponent: 0
			 isNegative: NO];
    }
}

- (id) initWithShort: (signed short)value
{
  if (value < 0)
    {
      return [self initWithMantissa: -value
			   exponent: 0
			 isNegative: YES];
    }
  else
    {
      return [self initWithMantissa: value
			   exponent: 0
			 isNegative: NO];
    }
}

- (id) initWithUnsignedChar: (unsigned char)value
{
  return [self initWithMantissa: value
		       exponent: 0
		     isNegative: NO];
}

- (id) initWithUnsignedInt: (unsigned int)value
{
  return [self initWithMantissa: value
		       exponent: 0
		     isNegative: NO];
}

- (id) initWithUnsignedLong: (unsigned long)value
{
  return [self initWithMantissa: value
		       exponent: 0
		     isNegative: NO];
}

- (id) initWithUnsignedLongLong: (unsigned long long)value
{
  return [self initWithMantissa: value
		       exponent: 0
		     isNegative: NO];
}

- (id) initWithUnsignedShort: (unsigned short)value
{
  return [self initWithMantissa: value
		       exponent: 0
		     isNegative: NO];
}

- (NSString*) descriptionWithLocale: (NSDictionary*)locale
{
  return NSDecimalString(&data, locale);
}

- (const char*) objCType
{
  return "d";
}

- (NSDecimal) decimalValue
{
  NSDecimal decimal;

  NSDecimalCopy(&decimal, &data);
  return decimal;
}

- (double) doubleValue
{
  return NSDecimalDouble(&data);
}

/**
 * Get the approximate value of the decimal number into a buffer
 * as a double.
 */
- (void) getValue: (void*)buffer
{
  double	tmp = NSDecimalDouble(&data);

  memcpy(buffer, &tmp, sizeof(tmp));
}

- (NSComparisonResult) compare: (NSNumber*)decimalNumber
{
  if ([decimalNumber isKindOfClass: NSDecimalNumberClass])
    {
      NSDecimal d1 = [self decimalValue];
      NSDecimal d2 = [(NSDecimalNumber*)decimalNumber decimalValue];

      return NSDecimalCompare(&d1, &d2);
    }
  else
    return [super compare: decimalNumber];
}

- (NSDecimalNumber*) decimalNumberByAdding: (NSDecimalNumber*)decimalNumber
{
  return [self decimalNumberByAdding: decimalNumber
			withBehavior: [isa defaultBehavior]];
}

- (NSDecimalNumber*) decimalNumberByAdding: (NSDecimalNumber*)decimalNumber
  withBehavior: (id<NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];
  NSDecimal d2 = [decimalNumber decimalValue];
  NSCalculationError error;
  NSDecimalNumber *res;

  error = NSDecimalAdd(&result, &d1, &d2, [behavior roundingMode]);
  if (error)
    {
      res = [behavior exceptionDuringOperation: _cmd
					 error: error
				   leftOperand: self
				  rightOperand: decimalNumber];
      if (res != nil)
	return res;
    }

  return [NSDecimalNumber decimalNumberWithDecimal: result];
}

- (NSDecimalNumber*) decimalNumberBySubtracting: (NSDecimalNumber*)decimalNumber
{
  return [self decimalNumberBySubtracting: decimalNumber
			     withBehavior: [isa defaultBehavior]];
}

- (NSDecimalNumber*) decimalNumberBySubtracting: (NSDecimalNumber*)decimalNumber
  withBehavior: (id <NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];
  NSDecimal d2 = [decimalNumber decimalValue];
  NSCalculationError error;
  NSDecimalNumber *res;

  error = NSDecimalSubtract(&result, &d1, &d2, [behavior roundingMode]);
  if (error)
    {
      res = [behavior exceptionDuringOperation: _cmd
					 error: error
				   leftOperand: self
				  rightOperand: decimalNumber];
      if (res != nil)
	return res;
    }

  return [NSDecimalNumber decimalNumberWithDecimal: result];
}

- (NSDecimalNumber*) decimalNumberByMultiplyingBy:
  (NSDecimalNumber*)decimalNumber
{
  return [self decimalNumberByMultiplyingBy: decimalNumber
			       withBehavior: [isa defaultBehavior]];
}

- (NSDecimalNumber*) decimalNumberByMultiplyingBy:
  (NSDecimalNumber*)decimalNumber
  withBehavior: (id <NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];
  NSDecimal d2 = [decimalNumber decimalValue];
  NSCalculationError error;
  NSDecimalNumber *res;

  error = NSDecimalMultiply(&result, &d1, &d2, [behavior roundingMode]);
  if (error)
    {
      res = [behavior exceptionDuringOperation: _cmd
					 error: error
				   leftOperand: self
				  rightOperand: decimalNumber];
      if (res != nil)
	return res;
    }

  return [NSDecimalNumber decimalNumberWithDecimal: result];
}

- (NSDecimalNumber*) decimalNumberByDividingBy: (NSDecimalNumber*)decimalNumber
{
  return [self decimalNumberByDividingBy: decimalNumber
			    withBehavior: [isa defaultBehavior]];
}

- (NSDecimalNumber*) decimalNumberByDividingBy: (NSDecimalNumber*)decimalNumber
  withBehavior: (id <NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];
  NSDecimal d2 = [decimalNumber decimalValue];
  NSCalculationError error;
  NSDecimalNumber *res;

  error = NSDecimalDivide(&result, &d1, &d2, [behavior roundingMode]);
  if (error)
    {
      res = [behavior exceptionDuringOperation: _cmd
					 error: error
				   leftOperand: self
				  rightOperand: decimalNumber];
      if (res != nil)
	return res;
    }

  return [NSDecimalNumber decimalNumberWithDecimal: result];
}

- (NSDecimalNumber*) decimalNumberByMultiplyingByPowerOf10: (short)power
{
  return [self decimalNumberByMultiplyingByPowerOf10: power
					withBehavior: [isa defaultBehavior]];
}

- (NSDecimalNumber*) decimalNumberByMultiplyingByPowerOf10: (short)power
  withBehavior: (id <NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];
  NSCalculationError error;
  NSDecimalNumber *res;

  error = NSDecimalMultiplyByPowerOf10(&result, &d1,
    power, [behavior roundingMode]);
  if (error)
    {
      res = [behavior exceptionDuringOperation: _cmd
					 error: error
				   leftOperand: self
				  rightOperand: nil];
      if (res != nil)
	return res;
    }

  return [NSDecimalNumber decimalNumberWithDecimal: result];
}

- (NSDecimalNumber*) decimalNumberByRaisingToPower: (unsigned)power
{
  return [self decimalNumberByRaisingToPower: power
				withBehavior: [isa defaultBehavior]];
}

- (NSDecimalNumber*) decimalNumberByRaisingToPower: (unsigned)power
  withBehavior: (id <NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];
  NSCalculationError error;
  NSDecimalNumber *res;

  error = NSDecimalPower(&result, &d1,
    power, [behavior roundingMode]);
  if (error)
    {
      res = [behavior exceptionDuringOperation: _cmd
					 error: error
				   leftOperand: self
				  rightOperand: nil];
      if (res != nil)
	return res;
    }

  return [NSDecimalNumber decimalNumberWithDecimal: result];
}

- (NSDecimalNumber*) decimalNumberByRoundingAccordingToBehavior:
  (id <NSDecimalNumberBehaviors>)behavior
{
  NSDecimal result;
  NSDecimal d1 = [self decimalValue];

  NSDecimalRound(&result, &d1, [behavior scale], [behavior roundingMode]);
  return [NSDecimalNumber decimalNumberWithDecimal: result];
}


// Methods for NSDecimalNumberBehaviors
- (NSDecimalNumber*) exceptionDuringOperation: (SEL)method
					error: (NSCalculationError)error
				  leftOperand: (NSDecimalNumber*)leftOperand
				 rightOperand: (NSDecimalNumber*)rightOperand
{
  return [[isa defaultBehavior] exceptionDuringOperation: method
						   error: error
					     leftOperand: leftOperand
					    rightOperand: rightOperand];
}

- (NSRoundingMode) roundingMode
{
  return [[isa defaultBehavior] roundingMode];
}

- (short) scale
{
  return [[isa defaultBehavior] scale];
}

- (Class) classForCoder
{
  return [NSDecimalNumber class];
}

- (id) replacementObjectForPortCoder: (NSPortCoder*)aCoder
{
  if ([aCoder isByref] == NO)
    return self;
  return [super replacementObjectForPortCoder: aCoder];
}

- (void) encodeWithCoder: (NSCoder*)coder
{
  NSString	*s = [self descriptionWithLocale: nil];

  [coder encodeObject: s];
}

- (id) initWithCoder: (NSCoder*)coder
{
  NSString	*s = [coder decodeObject];

  return [self initWithString: s locale: nil];
}

@end

@implementation NSNumber (NSDecimalNumber)
/** Returns an NSDecimal representation of the number. Float and double
    values may not be converted exactly */
- (NSDecimal) decimalValue
{
  double num;
  NSDecimalNumber *dnum;
  num = [self doubleValue];
  dnum = [[NSDecimalNumber alloc] initWithBytes: &num objCType: "d"];
  AUTORELEASE(dnum);
  return [dnum decimalValue];
}
@end

