{-# Language BangPatterns #-}
{-# Language BlockArguments #-}
{-# Language Trustworthy #-}
-- | Computation with high-precision floats.
module LibBF
  (
    -- * Constants
    BigFloat
  , bfPosZero, bfNegZero
  , bfPosInf, bfNegInf
  , bfNaN

    -- * Conversions
  , bfFromWord
  , bfFromInt
  , bfFromDouble
  , bfFromInteger
  , bfFromString
  , bfToDouble
  , bfToString
  , bfToRep
  , BFRep(..)
  , BFNum(..)
  , bfFromBits
  , bfToBits

    -- * Predicates
  , bfIsFinite
  , bfIsInf
  , bfIsZero
  , bfIsNaN
  , bfIsNormal
  , bfIsSubnormal
  , bfCompare
  , bfSign
  , bfExponent
  , bfIsPos
  , bfIsNeg
  , Sign(..)

    -- * Arithmetic
  , bfNeg, bfAbs
  , bfAdd, bfSub, bfMul, bfDiv, bfRem
  , bfFMA, bfMulWord, bfMulInt, bfMul2Exp
  , bfSqrt
  , bfPow

    -- * Rounding
  , bfRoundFloat, bfRoundInt

    -- * Mutability
  , bfUnsafeThaw
  , bfUnsafeFreeze

    -- * Limits


    -- * Configuration
  , module LibBF.Opts
  ) where


import Data.Bits
import Data.Data (Data(..))
import Data.Hashable
import Data.Word
import Data.Int
import System.IO.Unsafe

import LibBF.Mutable as M
import LibBF.Opts
import Control.DeepSeq

-- | Arbitrary precision floating point numbers.
newtype BigFloat = BigFloat BF

instance Data BigFloat where
  -- BigFloat is exported as an abstract data type, so we intentionally define
  -- the Data instance in a simplistic way so as to avoid leaking the BF
  -- internals.
  gfoldl _ z = z
  gunfold _ _ = error "Data.Data.gunfold(BigFloat)"
  toConstr _ = error "Data.Data.toConstr(BigFloat)"
  dataTypeOf _ = error "Data.Data.dataTypeOf(BigFloat)"

instance NFData BigFloat where
  rnf x = x `seq` ()


instance Show BigFloat where
  show = bfToString 16 (showFreeMin Nothing <> addPrefix)

--------------------------------------------------------------------------------
{-# NOINLINE ctxt #-}
{-# OPTIONS_GHC -fno-cse #-}
ctxt :: BFContext
ctxt = unsafePerformIO newContext

newBigFloat :: (BF -> IO ()) -> BigFloat
newBigFloat f = unsafe $
  do bf <- new ctxt
     f bf
     pure (BigFloat bf)

newBigFloat' :: (BF -> IO a) -> (BigFloat,a)
newBigFloat' f = unsafe $
  do bf <- new ctxt
     a <- f bf
     pure (BigFloat bf, a)

unsafe :: IO a -> a
unsafe = unsafePerformIO

--------------------------------------------------------------------------------
-- Constants

-- | Positive zero.
bfPosZero :: BigFloat
bfPosZero = newBigFloat (setZero Pos)

-- | Negative zero.
bfNegZero :: BigFloat
bfNegZero = newBigFloat (setZero Neg)

-- | Positive infinity.
bfPosInf :: BigFloat
bfPosInf = newBigFloat (setInf Pos)

-- | Negative infinity.
bfNegInf :: BigFloat
bfNegInf = newBigFloat (setInf Neg)

-- | Not-a-number.
bfNaN :: BigFloat
bfNaN = newBigFloat setNaN

-- | A floating point number corresponding to the given word.
bfFromWord :: Word64 -> BigFloat
bfFromWord = newBigFloat . setWord

-- | A floating point number corresponding to the given int.
bfFromInt :: Int64 -> BigFloat
bfFromInt = newBigFloat . setInt

-- | A floating point number corresponding to the given double.
bfFromDouble :: Double -> BigFloat
bfFromDouble = newBigFloat . setDouble

-- | A floating point number corresponding to the given integer.
bfFromInteger :: Integer -> BigFloat
bfFromInteger = newBigFloat . setInteger

-- | IEEE 754 equality
instance Eq BigFloat where
  BigFloat x == BigFloat y = unsafe (cmpEq x y)

-- | IEEE 754 comparisons
instance Ord BigFloat where
  BigFloat x < BigFloat y  = unsafe (cmpLT x y)
  BigFloat x <= BigFloat y = unsafe (cmpLEQ x y)


instance Hashable BigFloat where
  hashWithSalt s x = hashWithSalt s (bfToRep x)


{-| Compare the two numbers.  The special values are ordered like this:

      * -0 < 0
      * NaN == NaN
      * NaN is larger than all other numbers

Note that this differs from `(<=)`
-}
bfCompare :: BigFloat -> BigFloat -> Ordering
bfCompare (BigFloat x) (BigFloat y) = unsafe (cmp x y)


-- | Is this a finite (i.e., non-infinite, non NaN) number.
bfIsFinite :: BigFloat -> Bool
bfIsFinite (BigFloat x) = unsafe (isFinite x)

-- | Is this value NaN.
bfIsNaN :: BigFloat -> Bool
bfIsNaN (BigFloat x) = unsafe (M.isNaN x)

-- | Is this value infinite
bfIsInf :: BigFloat -> Bool
bfIsInf (BigFloat x) = unsafe (isInf x)

-- | This is a "normal" number, which means it is not
--   a NaN, not a zero, not infinite, and not subnormal.
bfIsNormal :: BFOpts -> BigFloat -> Bool
bfIsNormal opts bf =
  case bfToRep bf of
    rep@(BFRep _sgn (Num _ _)) -> not (repIsSubnormal opts rep)
    _ -> False

-- | This number is "subnormal", which means it is among the smallest
--   representable numbers for the given precision and exponent bits.
--   These numbers differ from "normal" numbers in that they do not use
--   an implicit leading 1 bit in the binary representation.
bfIsSubnormal :: BFOpts -> BigFloat -> Bool
bfIsSubnormal opts bf = repIsSubnormal opts (bfToRep bf)

-- | Get the sign of a number.  Returns 'Nothing' if the number is `NaN`.
bfSign :: BigFloat -> Maybe Sign
bfSign (BigFloat x) = unsafe (getSign x)

-- | Compute the absolute value of a number.
bfAbs :: BigFloat -> BigFloat
bfAbs bf =
  case bfSign bf of
    Just Neg -> bfNeg bf
    _        -> bf

-- | Is this value positive
bfIsPos :: BigFloat -> Bool
bfIsPos bf =
  case bfSign bf of
    Just Pos -> True
    _ -> False

-- | Is this value negative
bfIsNeg :: BigFloat -> Bool
bfIsNeg bf =
  case bfSign bf of
    Just Neg -> True
    _ -> False

-- | Get the exponent for the given number.
-- Infinity, zero and NaN do not have an exponent.
bfExponent :: BigFloat -> Maybe Int64
bfExponent (BigFloat x) = unsafe (getExp x)

-- | Is this value a zero.
bfIsZero :: BigFloat -> Bool
bfIsZero (BigFloat x) = unsafe (isZero x)

-- | Negate a floating point number.
bfNeg :: BigFloat -> BigFloat
bfNeg (BigFloat x) = newBigFloat (\bf -> setBF x bf >> fneg bf)

-- | Add two numbers useing the given options.
bfAdd :: BFOpts -> BigFloat -> BigFloat -> (BigFloat,Status)
bfAdd opt (BigFloat x) (BigFloat y) = newBigFloat' (fadd opt x y)

-- | Subtract two numbers useing the given options.
bfSub :: BFOpts -> BigFloat -> BigFloat -> (BigFloat,Status)
bfSub opt (BigFloat x) (BigFloat y) = newBigFloat' (fsub opt x y)

-- | Multiply two numbers using the given options.
bfMul :: BFOpts -> BigFloat -> BigFloat -> (BigFloat,Status)
bfMul opt (BigFloat x) (BigFloat y) = newBigFloat' (fmul opt x y)

-- | Multiply a number and a word, using the given options.
bfMulWord :: BFOpts -> BigFloat -> Word64 -> (BigFloat,Status)
bfMulWord opt (BigFloat x) y = newBigFloat' (fmulWord opt x y)

-- | Multiply a number and an int, using the given options.
bfMulInt :: BFOpts -> BigFloat -> Int64 -> (BigFloat,Status)
bfMulInt opt (BigFloat x) y = newBigFloat' (fmulInt opt x y)

-- | Multiply a number by @2^e@.
bfMul2Exp :: BFOpts -> BigFloat -> Int -> (BigFloat,Status)
bfMul2Exp opt (BigFloat x) e = newBigFloat' (\p ->
  do setBF x p
     fmul2Exp opt e p)

-- | Divide two numbers useing the given options.
bfDiv :: BFOpts -> BigFloat -> BigFloat -> (BigFloat,Status)
bfDiv opt (BigFloat x) (BigFloat y) = newBigFloat' (fdiv opt x y)

-- | Compute the remainder @x - y * n@ where @n@ is the integer
--   nearest to @x/y@ (with ties broken to even values of @n@).
bfRem :: BFOpts -> BigFloat -> BigFloat -> (BigFloat, Status)
bfRem opt (BigFloat x) (BigFloat y) = newBigFloat' (frem opt x y)

-- | Compute the fused-multiply-add @(x*y)+z@
bfFMA :: BFOpts -> BigFloat -> BigFloat -> BigFloat -> (BigFloat, Status)
bfFMA opt (BigFloat x) (BigFloat y) (BigFloat z) = newBigFloat' (ffma opt x y z)

-- | Square root of two numbers useing the given options.
bfSqrt :: BFOpts -> BigFloat -> (BigFloat,Status)
bfSqrt opt (BigFloat x) = newBigFloat' (fsqrt opt x)

-- | Round to a float matching the input parameters.
bfRoundFloat :: BFOpts -> BigFloat -> (BigFloat,Status)
bfRoundFloat opt (BigFloat x) = newBigFloat' (\bf ->
  do setBF x bf
     fround opt bf
  )

-- | Round to an integer using the given rounding mode.
bfRoundInt :: RoundMode -> BigFloat -> (BigFloat,Status)
bfRoundInt r (BigFloat x) = newBigFloat' (\bf ->
  do setBF x bf
     frint r bf
  )

-- | Exponentiate a word to a positive integer power.
bfPow :: BFOpts -> BigFloat -> BigFloat -> (BigFloat, Status)
bfPow opts (BigFloat x) (BigFloat y) = newBigFloat' (fpow opts x y)

-- | Constant to a 'Double'
bfToDouble :: RoundMode -> BigFloat -> (Double, Status)
bfToDouble r (BigFloat x) = unsafe (toDouble r x)

-- | Render as a 'String', using the given settings.
bfToString :: Int {- ^ Base -} -> ShowFmt -> BigFloat -> String
bfToString radix opts (BigFloat x) =
  unsafe (toString radix opts x)

-- | Parse a number from the given string.
-- Returns @NaN` if the string does not correspond to a number we recognize.
bfFromString :: Int {- ^ Base -} -> BFOpts -> String -> (BigFloat,Status)
bfFromString radix opts str =
  newBigFloat' \bf ->
  do (status,_,usedAll) <- setString radix opts str bf
     if usedAll
        then pure status
        else do setNaN bf
                pure Ok

-- | The float as an exponentiated 'Integer'.
bfToRep :: BigFloat -> BFRep
bfToRep (BigFloat x) = unsafe (toRep x)

-- | Make a number mutable.
-- WARNING: This does not copy the number,
-- so it could break referential transperancy.
bfUnsafeThaw :: BigFloat -> BF
bfUnsafeThaw (BigFloat x) = x

-- | Make a number immutable.
-- WARNING: This does not copy the number,
-- so it could break referential transperancy.
bfUnsafeFreeze :: BF -> BigFloat
bfUnsafeFreeze = BigFloat

--------------------------------------------------------------------------------

-- | Make a float using "raw" bits representing the bitvector
--   representation of a floating-point value with the
--   exponent and precision bits given by the options.
bfFromBits ::
  BFOpts ->
  Integer {- ^ Raw bits -} ->
  BigFloat

bfFromBits opts bits
  | expoBiased == 0 && mant == 0 =            -- zero
    if isNeg then bfNegZero else bfPosZero

  | expoBiased == eMask && mant ==  0 =       -- infinity
    if isNeg then bfNegInf else bfPosInf

  | expoBiased == eMask = bfNaN               -- NaN

  | expoBiased == 0 =                         -- Subnormal
    case bfMul2Exp opts' (bfFromInteger mant) (expoVal + 1) of
      (num,Ok) -> if isNeg then bfNeg num else num
      (_,s)    -> error $ unwords ["bfFromBits", "subnormal case", "Unexpected status:", show s, show bits, show mant, show expoVal, show e, show p ]

  | otherwise =                               -- Normal
    case bfMul2Exp opts' (bfFromInteger mantVal) expoVal of
      (num,Ok) -> if isNeg then bfNeg num else num
      (_,s)    -> error $ unwords ["bfFromBits", "normal case", "Unexpected status:", show s, show bits, show mantVal, show expoVal, show e, show p ]

  where
  e = getExpBits opts
  p = getPrecBits opts

  opts' = opts <> allowSubnormal

  p'         = fromIntegral p - 1                          :: Int
  eMask      = (1 `shiftL` e) - 1                          :: Int
  pMask      = (1 `shiftL` p') - 1                         :: Integer

  isNeg      = testBit bits (e + p')

  mant       = pMask .&. bits                              :: Integer
  mantVal    = mant `setBit` p'                            :: Integer
  -- accounts for the implicit 1 bit

  expoBiased = eMask .&. fromInteger (bits `shiftR` p')    :: Int
  bias       = eMask `shiftR` 1                            :: Int
  expoVal    = expoBiased - bias - fromIntegral p'         :: Int


-- | Turn a float into raw bits.
-- @NaN@ is represented as a positive "quiet" @NaN@
-- (most significant bit in the significand is set, the rest of it is 0).
bfToBits :: BFOpts -> BigFloat -> Integer
bfToBits opts bf = res
  where
  res =     (isNeg      `shiftL` (e+p'))
        .|. (expBiased  `shiftL` p')
        .|. (mant       `shiftL` 0)

  e = getExpBits opts
  p = getPrecBits opts

  p' = fromIntegral p - 1 :: Int

  eMask = (1 `shiftL` e) - 1   :: Integer
  pMask = (1 `shiftL` p') - 1   :: Integer

  (isNeg, expBiased, mant) =
    case bfToRep bf of
      BFNaN       -> (0,  eMask, 1 `shiftL` (p' - 1))
      BFRep s num -> (sign, be, ma)
        where
        sign = case s of
                Neg -> 1
                Pos -> 0

        (be,ma) =
          case num of
            Zero     -> (0,0)
            Num i ev
              | ex <= 0 ->
                  (0, i `shiftL` (p'-m-1+fromInteger ex)) -- subnormal case
              | otherwise ->
                  (ex, (i `shiftL` (p' - m)) .&. pMask) -- normal case
              where
              m    = msb 0 i - 1
              bias = eMask `shiftR` 1
              ex   = toInteger ev + bias + toInteger m

            Inf -> (eMask,0)

  msb !n j = if j == 0 then n else msb (n+1) (j `shiftR` 1)

-- | test if a given big float representation is subnormal
repIsSubnormal :: BFOpts -> BFRep -> Bool
repIsSubnormal opts (BFRep _s (Num i ev)) = ex <= 0
  where
  e = getExpBits opts
  eMask = (1 `shiftL` e) - 1   :: Integer
  bias = eMask `shiftR` 1

  m    = msb (0 :: Int) i - 1
  ex   = toInteger ev + bias + toInteger m

  msb !n j = if j == 0 then n else msb (n+1) (j `shiftR` 1)

repIsSubnormal _opts _rep = False
