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  -- 'n'

true :: Parser Bool
true = do
    skipWord8 0x74  -- 't'
    return True

false :: Parser Bool
false = do
    skipWord8 0x66  -- 'f'
    return False

digits :: Num a => Parser a
digits = do
    digits' <- takeWhile1 (\ w -> 0x30 <= w && w <= 0x39) -- '0' - '9'
    return $ Data.ByteString.foldl'
        (\ i b -> i * 10 + fromIntegral (b - 0x30))
        0
        digits'

integer :: Parser Integer
integer = do
    skipWord8 0x69  -- 'i'
    sign <- option 1 (word8 0x2d $> -1)  -- 0x2d: '-'
    digits' <- digits
    skipWord8 0x65  -- 'e'
    return $ sign * digits'

byteString :: Parser ByteString
byteString = do
    size' <- digits
    skipWord8 0x3a  -- ':'
    Data.Attoparsec.ByteString.take size'

text :: Parser Text
text = do
    skipWord8 0x75  -- 'u'
    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  -- 'l'
    elements <- many' bValue
    skipWord8 0x65  -- 'e'
    return elements

map' :: Parser (HashMap BKey BValue)
map' = do
    skipWord8 0x64  -- 'd'
    pairs <- many' $ do
        key <- bKey
        value <- bValue
        return (key, value)
    skipWord8 0x65  -- 'e'
    return $ Data.HashMap.Strict.fromList pairs

bKey :: Parser BKey
bKey = choice
    [ BByteStringKey <$> byteString
    , BTextKey <$> text
    ]