packages feed

natskell-0.0.0.1: internal/Types/Nuid.hs

module Nuid
  ( Nuid (..)
  , newNuid
  , newNuidIO
  , nextNuid
  ) where

import qualified Data.ByteString       as BS
import qualified Data.ByteString.Char8 as BC
import           Data.Word             (Word64, Word8)
import           System.Random         (StdGen, newStdGen, randomR)

data Nuid = Nuid
              { nuidPrefix :: BS.ByteString
              , nuidSeq    :: Word64
              , nuidInc    :: Word64
              , nuidRng    :: StdGen
              }

newNuid :: StdGen -> Nuid
newNuid rng0 = Nuid prefix seqVal incVal rng3
  where
    (prefix, rng1) = randomPrefix rng0
    (seqVal, rng2) = randomSeq rng1
    (incVal, rng3) = randomInc rng2

newNuidIO :: IO Nuid
newNuidIO = newNuid <$> newStdGen

nextNuid :: Nuid -> (BS.ByteString, Nuid)
nextNuid nuid =
  let seqVal = nuidSeq nuid + nuidInc nuid
  in if seqVal >= maxSeq
      then resetAndNext nuid
      else let nuid' = nuid { nuidSeq = seqVal }
           in (renderNuid (nuidPrefix nuid) seqVal, nuid')

resetAndNext :: Nuid -> (BS.ByteString, Nuid)
resetAndNext nuid = (renderNuid prefix seqVal, Nuid prefix seqVal incVal rng3)
  where
    (prefix, rng1) = randomPrefix (nuidRng nuid)
    (seqVal, rng2) = randomSeq rng1
    (incVal, rng3) = randomInc rng2

renderNuid :: BS.ByteString -> Word64 -> BS.ByteString
renderNuid prefix seqVal = BS.append prefix (encodeSeq seqVal)

randomPrefix :: StdGen -> (BS.ByteString, StdGen)
randomPrefix rng0 = go prefixLen rng0 []
  where
    go 0 rng acc = (BS.pack (reverse acc), rng)
    go n rng acc =
      let (idx, rng') = randomR (0, base62Len - 1) rng
      in go (n - 1) rng' (base62Index idx : acc)

encodeSeq :: Word64 -> BS.ByteString
encodeSeq value = BS.pack (map base62Index (buildDigits seqLen value []))
  where
    buildDigits 0 _ acc = acc
    buildDigits n v acc =
      let (q, r) = v `quotRem` base
      in buildDigits (n - 1) q (fromIntegral r : acc)

base62Index :: Int -> Word8
base62Index = BS.index base62Alphabet

base62Alphabet :: BS.ByteString
base62Alphabet = BC.pack "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"

base62Len :: Int
base62Len = BS.length base62Alphabet

prefixLen :: Int
prefixLen = 12

seqLen :: Int
seqLen = 10

base :: Word64
base = 62

maxSeq :: Word64
maxSeq = base ^ seqLen

minInc :: Word64
minInc = 33

maxInc :: Word64
maxInc = 333

randomSeq :: StdGen -> (Word64, StdGen)
randomSeq = randomR (0, maxSeq - 1)

randomInc :: StdGen -> (Word64, StdGen)
randomInc = randomR (minInc, maxInc - 1)