module Data.Bencodex.Reader
    ( Result (..)
    , bValue
    , byteString
    , decodeLazy
    , decodeStrict
    , false
    , integer
    , list
    , map'
    , null'
    , text
    , true
    ) where
import Control.Monad
import Data.Functor
import Data.Word
import Data.Attoparsec.ByteString hiding (Result)
import Data.Attoparsec.ByteString.Lazy as LP
import Data.ByteString
import qualified Data.ByteString.Lazy as LB
import Data.HashMap.Strict
import Data.Text
import Data.Text.Encoding
import Data.Text.Encoding.Error
import Data.Bencodex.Types
decodeLazy :: LB.ByteString -> Result BValue
decodeLazy = LP.parse bValue
decodeStrict :: ByteString -> Either String BValue
decodeStrict = parseOnly bValue
bValue :: Parser BValue
bValue = choice
    [ (null' $> BNull) <?> "null"
    , (BBool <$> true) <?> "true"
    , (BBool <$> false) <?> "false"
    , (BInteger <$> integer) <?> "integer"
    , (BByteString <$> byteString) <?> "byte string"
    , (BText <$> text) <?> "unicode text"
    , (BList <$> list) <?> "list"
    , (BMap <$> map') <?> "dictionary"
    ]
skipWord8 :: Word8 -> Parser ()
skipWord8 = void . word8
null' :: Parser ()
null' = skipWord8 0x6e  
true :: Parser Bool
true = do
    skipWord8 0x74  
    return True
false :: Parser Bool
false = do
    skipWord8 0x66  
    return False
digits :: Num a => Parser a
digits = do
    digits' <- takeWhile1 (\ w -> 0x30 <= w && w <= 0x39) 
    return $ Data.ByteString.foldl'
        (\ i b -> i * 10 + fromIntegral (b - 0x30))
        0
        digits'
integer :: Parser Integer
integer = do
    skipWord8 0x69  
    sign <- option 1 (word8 0x2d $> -1)  
    digits' <- digits
    skipWord8 0x65  
    return $ sign * digits'
byteString :: Parser ByteString
byteString = do
    size' <- digits
    skipWord8 0x3a  
    Data.Attoparsec.ByteString.take size'
text :: Parser Text
text = do
    skipWord8 0x75  
    size' <- digits
    skipWord8 0x3a  
    bytes <- Data.Attoparsec.ByteString.take size'
    case decodeUtf8' bytes of
        Right decoded -> return decoded
        Left (DecodeError msg _) -> fail msg
        Left e -> fail ("unexpected error: " ++ show e)
list :: Parser [BValue]
list = do
    skipWord8 0x6c  
    elements <- many' bValue
    skipWord8 0x65  
    return elements
map' :: Parser (HashMap BKey BValue)
map' = do
    skipWord8 0x64  
    pairs <- many' $ do
        key <- bKey
        value <- bValue
        return (key, value)
    skipWord8 0x65  
    return $ Data.HashMap.Strict.fromList pairs
bKey :: Parser BKey
bKey = choice
    [ BByteStringKey <$> byteString
    , BTextKey <$> text
    ]