{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE UnboxedTuples #-}
module Main (main) where

import Data.Int
import Data.Word
import Foreign.Ptr (plusPtr)
import Foreign.Storable (Storable(..))
import GHC.Base (realWorld#)
import GHC.IO (IO(IO))
import GHC.Ptr (Ptr(..))
import System.IO.Unsafe (unsafeDupablePerformIO)

type Accum = Int64
type Result = Word64

runIO :: IO a -> a
runIO (IO m) = case m realWorld# of (# _, r #) -> r

q :: Accum
q = 0x1999_9999_9999_9999

_digits :: Ptr Word8 -> Int -> Maybe Accum
{-# INLINE _digits #-}
_digits ptr len = runIO $ 
    let end = ptr `plusPtr` len
     in go ptr end ptr 0
  where
    go :: Ptr Word8 -> Ptr Word8 -> Ptr Word8 -> Accum -> IO (Maybe Accum)
    go start end = loop
      where
        loop :: Ptr Word8 -> Accum -> IO (Maybe Accum)
        loop !ptr !acc = getDigit >>= \ !d ->
            if | d > 9
                -> return $! if ptr /= start then Just acc else Nothing
               | !i <- acc - q
               , i < 0 || i == 0 && d <= 5
                 -> loop (ptr `plusPtr` 1) (acc * 10 + fromIntegral d)
               | otherwise -> return Nothing
          where
            fromDigit = \w -> fromIntegral @Word8 @Word w - 0x30
            getDigit | ptr /= end = fromDigit <$> peek ptr
                     | otherwise  = pure 10  -- End of input

main :: IO ()
main =
    let !s = "12345678901234567890 junk"#
        !w = fromIntegral @Accum @Result <$> _digits (Ptr s) 25
     in print w
