module System.Linux.Btrfs.UUID
    ( UUID(..)
    , toString
    , fromString
    ) where

import Data.Word (Word64)
import Data.Word.Endian (BE64(..))
import Data.Bits ((.&.), unsafeShiftR)
import Text.Printf (printf)

import Foreign.Storable (Storable(..))
import Foreign.Ptr (castPtr)
import Foreign.C.Types (CInt)

-- | A @UUID@ is stored as two big-endian 'Word64's.
data UUID = UUID Word64 Word64
  deriving (Eq, Ord)

instance Show UUID where
    showsPrec p u =
        showParen (p > 9) $
            showString "fromString " . shows (toString u)

instance Storable UUID where
    sizeOf _ = 16
    alignment _ = alignment (undefined :: CInt)
    peek ptr = do
        BE64 h  <- peek (castPtr ptr)
        BE64 l  <- peekByteOff ptr 8
        return $ UUID h l
    poke ptr (UUID h l) = do
        poke (castPtr ptr) (BE64 h)
        pokeByteOff ptr 8 (BE64 l)

toString :: UUID -> String
toString (UUID h l) = printf "%.8x-%.4x-%.4x-%.4x-%.12x" h1 h2 h3 l1 l2
  where
    h1 = (h .&. 0xffffffff00000000) `unsafeShiftR` 32
    h2 = (h .&.         0xffff0000) `unsafeShiftR` 16
    h3 = (h .&.             0xffff)
    l1 = (l .&. 0xffff000000000000) `unsafeShiftR` 48
    l2 = (l .&.     0xffffffffffff)

fromString :: String -> Maybe UUID
fromString s
    | isValidUUID s = Just $ UUID h l
    | otherwise = Nothing
  where
    h = read $ '0' : 'x' : take 16 s'
    l = read $ '0' : 'x' : drop 16 s'
    s' = filter (/= '-') s

isValidUUID :: String -> Bool
isValidUUID = and . zipWith checkChar [0..]
  where
    checkChar i c =
        if i `elem` hyphenPosns then
            c == '-'
        else
            c `elem` "0123456789abcdefABCDEF"
    hyphenPosns = [8, 13, 18, 23] :: [Int]