module Data.Bencode.Util
  ( readKnownNaturalAsInt
  , readKnownNaturalAsWord
  ) where

import Data.Bits
import qualified Data.ByteString as B

-- | The input string must be an unsigned decimal integer with no extraneous
-- leading zeros. Returns Nothing if the value is outside the bounds of an
-- @Int@.
readKnownNaturalAsInt :: Bool -> B.ByteString -> Maybe Int
readKnownNaturalAsInt :: Bool -> ByteString -> Maybe Int
readKnownNaturalAsInt Bool
neg ByteString
s = case ByteString -> Maybe (Word8, ByteString)
B.uncons ByteString
sr of
  Maybe (Word8, ByteString)
Nothing -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$! if Bool
neg then -Int
n else Int
n
  Just (Word8
d,ByteString
sr')
    | ByteString -> Bool
B.null ByteString
sr'
    , let d' :: Int
d' = forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
d forall a. Num a => a -> a -> a
- Int
48
    , Int
n forall a. Ord a => a -> a -> Bool
< Int
iMaxDiv10 Bool -> Bool -> Bool
|| Int
n forall a. Eq a => a -> a -> Bool
== Int
iMaxDiv10 Bool -> Bool -> Bool
&& Int
d' forall a. Ord a => a -> a -> Bool
<= (Int
7 forall a. Num a => a -> a -> a
+ forall a. Enum a => a -> Int
fromEnum Bool
neg)
                       -- last digit of maxBound = 7, minBound = 8
    -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$! if Bool
neg then -Int
n forall a. Num a => a -> a -> a
* Int
10 forall a. Num a => a -> a -> a
- Int
d' else Int
n forall a. Num a => a -> a -> a
* Int
10 forall a. Num a => a -> a -> a
+ Int
d'
    | Bool
otherwise -> forall a. Maybe a
Nothing
  where
    (ByteString
sl,ByteString
sr) = Int -> ByteString -> (ByteString, ByteString)
B.splitAt (Int
maxIntLen forall a. Num a => a -> a -> a
- Int
1) ByteString
s
    n :: Int
n = forall a. (a -> Word8 -> a) -> a -> ByteString -> a
B.foldl' (\Int
acc Word8
d -> Int
acc forall a. Num a => a -> a -> a
* Int
10 forall a. Num a => a -> a -> a
+ forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
d forall a. Num a => a -> a -> a
- Int
48) Int
0 ByteString
sl

iMaxDiv10 :: Int
iMaxDiv10 :: Int
iMaxDiv10 = forall a. Bounded a => a
maxBound forall a. Integral a => a -> a -> a
`div` Int
10

maxIntLen :: Int
maxIntLen :: Int
maxIntLen = case forall b. FiniteBits b => b -> Int
finiteBitSize (Int
0 :: Int) of
  Int
32 -> Int
10
  Int
64 -> Int
19
  Int
_  -> forall a. HasCallStack => [Char] -> a
error [Char]
"unsupported word size"

-- | The input string must be an unsigned decimal integer with no extraneous
-- leading zeros. Returns Nothing if the value is outside the bounds of a
-- @Word@.
readKnownNaturalAsWord :: B.ByteString -> Maybe Word
readKnownNaturalAsWord :: ByteString -> Maybe Word
readKnownNaturalAsWord ByteString
s = case ByteString -> Maybe (Word8, ByteString)
B.uncons ByteString
sr of
  Maybe (Word8, ByteString)
Nothing -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$! Word
n
  Just (Word8
d,ByteString
sr')
    | ByteString -> Bool
B.null ByteString
sr'
    , let d' :: Word
d' = forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
d forall a. Num a => a -> a -> a
- Word
48
    , Word
n forall a. Ord a => a -> a -> Bool
< Word
wMaxDiv10 Bool -> Bool -> Bool
|| Word
n forall a. Eq a => a -> a -> Bool
== Word
wMaxDiv10 Bool -> Bool -> Bool
&& Word
d' forall a. Ord a => a -> a -> Bool
<= Word
5 -- last digit of maxBound is 5
    -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$! Word
n forall a. Num a => a -> a -> a
* Word
10 forall a. Num a => a -> a -> a
+ Word
d'
    | Bool
otherwise -> forall a. Maybe a
Nothing
  where
    (ByteString
sl,ByteString
sr) = Int -> ByteString -> (ByteString, ByteString)
B.splitAt (Int
maxWordLen forall a. Num a => a -> a -> a
- Int
1) ByteString
s
    n :: Word
n = forall a. (a -> Word8 -> a) -> a -> ByteString -> a
B.foldl' (\Word
acc Word8
d -> Word
acc forall a. Num a => a -> a -> a
* Word
10 forall a. Num a => a -> a -> a
+ forall a b. (Integral a, Num b) => a -> b
fromIntegral Word8
d forall a. Num a => a -> a -> a
- Word
48) Word
0 ByteString
sl

wMaxDiv10 :: Word
wMaxDiv10 :: Word
wMaxDiv10 = forall a. Bounded a => a
maxBound forall a. Integral a => a -> a -> a
`div` Word
10

maxWordLen :: Int
maxWordLen :: Int
maxWordLen = case forall b. FiniteBits b => b -> Int
finiteBitSize (Word
0 :: Word) of
  Int
32 -> Int
10
  Int
64 -> Int
20
  Int
_  -> forall a. HasCallStack => [Char] -> a
error [Char]
"unsupported word size"