{-# LANGUAGE Safe, OverloadedStrings #-}
module Deka.Internal.Dec.Ctx where

import qualified Data.ByteString.Char8 as BS8
import Deka.Internal.Context
import Deka.Internal.Mpdec
import Deka.Internal.Util.Ctx
import Data.String

-- | Converts a character string to a 'Dec'.  Implements the
-- _to-number_ conversion from the General Decimal Arithmetic
-- specification.
--
-- The conversion is exact provided that the numeric string has no
-- more significant digits than are specified in the 'Precision' in
-- the 'Ctx' and the adjusted exponent is in the range specified by
-- 'Emin' and 'Emax' in the 'Ctx'. If there are more than
-- 'Precision' digits in the string, or the exponent is out of
-- range, the value will be rounded as necessary using the 'Round'
-- rounding mode. The 'Precision' therefore both determines the
-- maximum precision for unrounded numbers and defines the minimum
-- size of the 'Dec' structure required.
--
-- Possible errors are 'conversionSyntax' (the string does not have
-- the syntax of a number, which depends on 'setExtended' in the
-- 'Ctx'), 'overflow' (the adjusted exponent of the number is larger
-- than 'Emax'), or 'underflow' (the adjusted exponent is less than
-- 'Emin' and the conversion is not exact). If any of these
-- conditions are set, the number structure will have a defined
-- value as described in the arithmetic specification (this may be a
-- subnormal or infinite value).

fromByteString :: BS8.ByteString -> Ctx Dec
fromByteString bs = Ctx $ \pCtx ->
  newDec $ \dn ->
  BS8.useAsCString bs $ \cstr ->
  c'mpd_set_string dn cstr pCtx

-- | Returns the absolute value.  The same effect as 'plus' unless
-- the operand is negative, in which case it is the same as 'minus'.
abs :: Dec -> Ctx Dec
abs = unary c'mpd_abs

-- | Addition.
add :: Dec -> Dec -> Ctx Dec
add = binary c'mpd_add

-- | Digit-wise logical @and@.
and :: Dec -> Dec -> Ctx Dec
and = binary c'mpd_and

-- | @compare x y@ returns @-1@ if a is less than b, 0 if a is equal
-- to b, and 1 if a is greater than b.  'invalidOperation' is set if
-- at least one of the operands is a signaling NaN.
compare :: Dec -> Dec -> Ctx Dec
compare = binary c'mpd_compare

-- | Identical to 'Deka.Dec.compare' except that all NaNs
-- (including quiet NaNs) set the 'invalidOperation' condition.
compareSignal :: Dec -> Dec -> Ctx Dec
compareSignal = binary c'mpd_compare_signal

-- | Division.
divide :: Dec -> Dec -> Ctx Dec
divide = binary c'mpd_div

-- | Returns the integer part of the result of division.  It must be
-- possible to express the result as an integer.  That is, it must
-- have no more digits than 'Precision' in the 'Ctx'.  If it does
-- then 'divisionImpossible' is raised.
divideInteger :: Dec -> Dec -> Ctx Dec
divideInteger = binary c'mpd_divint

-- | Exponentiation.  Result is rounded if necessary using the
-- 'Precision' in the 'Ctx' and using the 'roundHalfEven' rounding
-- method.
--
-- Finite results will always be full precision and inexact, except
-- when rhs is a zero or -Infinity (giving 1 or 0 respectively).
-- Inexact results will almost always be correctly rounded, but may
-- be up to 1 ulp (unit in last place) in error in rare cases.
--
-- This is a mathematical function; the @10 ^ 6@ restrictions on
-- precision and range apply as described above.
exp :: Dec -> Ctx Dec
exp = unary c'mpd_exp

-- | @fma x y z@ multiplies @x@ by @y@ and then adds @z@ to that
-- intermediate result.  It is equivalent to a multiplication
-- followed by an addition except that the intermediate result is
-- not rounded and will not cause overflow or underflow. That is,
-- only the final result is rounded and checked.
--
-- This is a mathematical function; the @10 ^ 6@ restrictions on
-- precision and range apply as described above.
fma :: Dec -> Dec -> Dec -> Ctx Dec
fma = ternary c'mpd_fma

-- | Digit-wise inversion (a @0@ becomes a @1@ and vice versa).
invert :: Dec -> Ctx Dec
invert = unary c'mpd_invert

-- | Natural logarithm.  Results are correctly rounded if
-- 'setAllCorrectRound' is True.
ln :: Dec -> Ctx Dec
ln = unary c'mpd_ln

-- | Returns the adjusted exponent of the operand, according to the
-- rules for @logB@ of IEEE 754.  This returns the exponent of the
-- operand as though its decimal point had been moved to follow the
-- first digit while keeping the same value.  The result is not
-- limited by 'Emin' or 'Emax'.

-- | If operand is an NaN, the general rules apply.  If operand is
-- infinite, the result is +Infinity.  If operand is zero, result is
-- -Infinity and 'invalidOperation' is set.  Otherwise, the result
-- is the same as the adjusted exponent of the operand, or
-- @floor(log10(a))@ where @a@ is the operand.
logB :: Dec -> Ctx Dec
logB = unary c'mpd_logb

-- | Base 10 logarithm.  Results are correctly rounded if
-- 'setAllCorrectRound' is True.
log10 :: Dec -> Ctx Dec
log10 = unary c'mpd_log10

-- | Compares two numbers numerically and returns the larger.  If
-- the numbers compare equal then number is chosen with regard to
-- sign and exponent. Unusually, if one operand is a quiet NaN and
-- the other a number, then the number is returned.
max :: Dec -> Dec -> Ctx Dec
max = binary c'mpd_max

-- | Compares the magnitude of two numbers numerically and sets
-- number to the larger. It is identical to 'Deka.Dec.max' except
-- that the signs of the operands are ignored and taken to be 0
-- (non-negative).
maxMag :: Dec -> Dec -> Ctx Dec
maxMag = binary c'mpd_max_mag

-- | Compares two numbers numerically and sets number to the
-- smaller. If the numbers compare equal then number is chosen with
-- regard to sign and exponent. Unusually, if one operand is a quiet
-- NaN and the other a number, then the number is returned.
min :: Dec -> Dec -> Ctx Dec
min = binary c'mpd_min

-- | Compares the magnitude of two numbers numerically and sets
-- number to the smaller. It is identical to 'Deka.Dec.min' except
-- that the signs of the operands are ignored and taken to be 0
-- (non-negative).
minMag :: Dec -> Dec -> Ctx Dec
minMag = binary c'mpd_min_mag

-- | Returns the result of subtracting the operand from zero.  hat
-- is, it is negated, following the usual arithmetic rules; this may
-- be used for implementing a prefix minus operation.
minus :: Dec -> Ctx Dec
minus = unary c'mpd_minus

-- | Multiplication.
multiply :: Dec -> Dec -> Ctx Dec
multiply = binary c'mpd_mul

-- | Digit-wise logical inclusive or.
or :: Dec -> Dec -> Ctx Dec
or = binary c'mpd_or

-- | Returns the result of adding the operand to zero.  This takes
-- place according to the settings given in the 'Ctx', following the
-- usual arithmetic rules. This may therefore be used for rounding
-- or for implementing a prefix plus operation.
plus :: Dec -> Ctx Dec
plus = unary c'mpd_plus

-- | @power b e@ returns @b@ raised to the power of @e@.  Integer
-- powers are exact, provided that the result is finite and fits
-- into 'Precision'.
--
-- Results are not correctly rounded, even if 'setAllCorrectRound'
-- is True.  The error of the function is less than @1ULP + t@,
-- where @t@ has a maximum of @0.1ULP@, but is almost always less
-- than @0.001ULP@.

power :: Dec -> Dec -> Ctx Dec
power = binary c'mpd_pow

-- | @quantize a b@ returns the number that is equal in value to
-- @a@, but has the exponent of @b@.
quantize :: Dec -> Dec -> Ctx Dec
quantize = binary c'mpd_quantize

-- overflow/underflow checks, returns @a@ in its simplest form with
-- all trailing zeros removed.
reduce :: Dec -> Ctx Dec
reduce = unary c'mpd_reduce

-- | @remainder a b@ returns the remainder of @a / b@.
remainder :: Dec -> Dec -> Ctx Dec
remainder = binary c'mpd_rem

-- | @remainderNear a b@ returns @a - b * n@, where @n@ is the
-- integer nearest the exact value of @a / b@.  If two integers are
-- equally near then the even one is chosen.
remainderNear :: Dec -> Dec -> Ctx Dec
remainderNear = binary c'mpd_rem_near

-- | @rescale a b@ returns the number that is equal in value
-- to @a@, but has the exponent @b@. Special numbers are copied
-- without signaling. This function is not part of the General
-- Decimal Arithmetic Specification. It
-- is also not equivalent to the rescale function that was removed
-- from the specification.

rescale :: Dec -> Signed -> Ctx Dec
rescale a b = Ctx $ \p -> newDec $ \r ->
  withDec a $ \pa ->
  c'mpd_rescale r pa b p

-- | @rotate x y@ returns @x@ rotated by @y@ places. @y@ must be in
-- the range [-'Precision', 'Precision']. A negative @y@ indicates a
-- right rotation, a positive @y@ a left rotation.

rotate :: Dec -> Dec -> Ctx Dec
rotate = binary c'mpd_rotate

-- | @scaleB a b@ - b must be an integer with exponent 0. If @a@ is
-- infinite, returns @a@. Otherwise, returns @a@ with the
-- value of @b@ added to the exponent.

scaleB :: Dec -> Dec -> Ctx Dec
scaleB = binary c'mpd_scaleb

-- | @shift a b@ returns @a@ shifted by @b@ places. @b@ must be in
-- the range [-'Precision', 'Precision']. A negative @b@ indicates a
-- right shift, a positive @b@ a left shift. Digits that do not fit
-- are discarded.

shift :: Dec -> Dec -> Ctx Dec
shift = binary c'mpd_shift

-- | Returns the square root.  This function is always correctly
-- rounded using the 'roundHalfEven' method.

squareRoot :: Dec -> Ctx Dec
squareRoot = unary c'mpd_sqrt

-- | Returns the reciprocal of the square root.  This function
-- always uses 'roundHalfEven'.  Results are not correctly rounded
-- even if 'setAllCorrectRound' is True.

inverseSquareRoot :: Dec -> Ctx Dec
inverseSquareRoot = unary c'mpd_invroot


-- | Subtraction.

subtract :: Dec -> Dec -> Ctx Dec
subtract = binary c'mpd_sub

-- | Round to an integer, using the rounding mode of the context.
-- Only a signaling NaN causes an 'invalidOperation'
-- condition.

toIntegralExact :: Dec -> Ctx Dec
toIntegralExact = unary c'mpd_round_to_intx

-- | Like 'toIntegralExact', but 'inexact' and 'rounded' are never
-- set.
toIntegralValue :: Dec -> Ctx Dec
toIntegralValue = unary c'mpd_round_to_int

floor :: Dec -> Ctx Dec
floor = unary c'mpd_floor

ceiling :: Dec -> Ctx Dec
ceiling = unary c'mpd_ceil

truncate :: Dec -> Ctx Dec
truncate = unary c'mpd_trunc

-- | Digit-wise logical exclusive or.

xor :: Dec -> Dec -> Ctx Dec
xor = binary c'mpd_xor

-- | Returns the closest representable number that is smaller than
-- the operand.
nextMinus :: Dec -> Ctx Dec
nextMinus = unary c'mpd_next_minus

-- | Returns the closest representable number that is larger than
-- the operand.
nextPlus :: Dec -> Ctx Dec
nextPlus = unary c'mpd_next_plus

-- | @nextToward a b@ returns the representable number closest to
-- @a@ in the direction of @b@.

nextToward :: Dec -> Dec -> Ctx Dec
nextToward = binary c'mpd_next_toward

toBool :: Integral a => a -> Bool
toBool i
  | i == 0 = False
  | otherwise = True

-- | False if the decimal is special or zero, or the exponent is
-- less than 'Emin'. True otherwise.

isNormal :: Dec -> Ctx Bool
isNormal d = Ctx $ \p ->
  withDec d $ \pd ->
  c'mpd_isnormal pd p >>= \i ->
  return (toBool i)

-- | False if the decimal is special or zero, or the exponent is
-- greater or equal to 'Emin'. True otherwise.
isSubnormal :: Dec -> Ctx Bool
isSubnormal d = Ctx $ \p ->
  withDec d $ \pd ->
  c'mpd_issubnormal pd p >>= \i ->
  return (toBool i)

data PosNeg = Pos | Neg
  deriving (Eq, Ord, Show)

data Number
  = Infinity
  | Normal
  | Subnormal
  | Zero
  deriving (Eq, Ord, Show)

data Class
  = SNaN
  | NaN
  | Number PosNeg Number
  deriving (Eq, Ord, Show)

strToClass :: IsString a => [(a, Class)]
strToClass =
  [ ("sNaN", SNaN)
  , ("NaN", NaN)
  , ("-Infinity", Number Neg Infinity)
  , ("-Normal", Number Neg Normal)
  , ("-Subnormal", Number Neg Subnormal)
  , ("-Zero", Number Neg Zero)
  , ("+Zero", Number Pos Zero)
  , ("+Subnormal", Number Pos Subnormal)
  , ("+Normal", Number Pos Normal)
  , ("+Infinity", Number Pos Infinity)
  ]

-- | Determines the 'Class' of a 'Dec'.

numClass :: Dec -> Ctx Class
numClass d = Ctx $ \pCtx ->
  withDec d $ \pd ->
  c'mpd_class pd pCtx >>= \chars ->
  BS8.packCString chars >>= \bs ->
  return . maybe (error "numClass: class not found") id
    . lookup bs $ strToClass