module Data.Bencodex.Writer
    ( encodeLazy
    , encodeStrict
    ) where

import Data.List (sort)

import Data.ByteString hiding (sort)
import Data.ByteString.Builder
import qualified Data.ByteString.Lazy
import Data.HashMap.Strict
import Data.Text.Encoding

import Data.Bencodex.Types

encodeLazy :: BValue -> Data.ByteString.Lazy.ByteString
encodeLazy = toLazyByteString . renderBValue

encodeStrict :: BValue -> ByteString
encodeStrict = Data.ByteString.Lazy.toStrict . encodeLazy

renderBValue :: BValue -> Builder
renderBValue BNull = char7 'n'
renderBValue (BBool True) = char7 't'
renderBValue (BBool False) = char7 'f'
renderBValue (BInteger int) = mconcat
    [ char7 'i'
    , integerDec int
    , char7 'e'
    ]
renderBValue (BByteString bs) = mconcat
    [ intDec (Data.ByteString.length bs)
    , char7 ':'
    , byteString bs
    ]
renderBValue (BText txt) = mconcat
    [ char7 'u'
    , intDec (Data.ByteString.length encoded)
    , char7 ':'
    , byteString encoded
    ]
  where
    encoded :: ByteString
    encoded = encodeUtf8 txt
renderBValue (BList elements) =
    char7 'l' <> mconcat (renderBValue <$> elements) <> char7 'e'
renderBValue (BMap hmap) =
    char7 'd' <> mconcat renderedPairs <> char7 'e'
  where
    pairs :: [(KeyType, ByteString, BValue)]
    pairs = sort
        [ case k of
            BTextKey k' -> (UnicodeKey, encodeUtf8 k', v)
            BByteStringKey k' -> (BytesKey, k', v)
        | (k, v) <- toList hmap
        ]
    renderedPairs :: [Builder]
    renderedPairs =
        [ renderKey kt k <> renderBValue v
        | (kt, k, v) <- pairs
        ]

data KeyType = BytesKey | UnicodeKey deriving (Eq, Ord, Show)

renderKey :: KeyType -> ByteString -> Builder
renderKey kt bs =
    case kt of
        BytesKey -> common
        UnicodeKey -> char7 'u' <> common
  where
    common :: Builder
    common = mconcat
        [ intDec (Data.ByteString.length bs)
        , char7 ':'
        , byteString bs
        ]