module EZID.ANVL
  ( ANVL
  , encode
  , parse
  ) where

import Control.Applicative ((<|>))
import qualified Data.Attoparsec.ByteString.Char8 as P
import Data.Bits (shiftL, (.|.))
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Builder.Prim as BP
import Data.ByteString.Internal (c2w)
import Data.Char (isHexDigit, digitToInt)
import Data.Monoid ((<>))
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Data.Word (Word8)

type ANVL = [(T.Text, T.Text)]

charEscaped :: Bool -> BP.BoundedPrim Word8
charEscaped colon =
  BP.condB (\c -> c == c2w '%' || (colon && c == c2w ':') || c < c2w ' ')
    (BP.liftFixedToBounded $ (,) '%' BP.>$< BP.char8 BP.>*< BP.word8HexFixed)
    (BP.liftFixedToBounded BP.word8)

encode :: ANVL -> B.Builder
encode = foldMap $ \(n,v) ->
  TE.encodeUtf8BuilderEscaped (charEscaped True) n <> B.char8 ':' <> B.char8 ' ' <> TE.encodeUtf8BuilderEscaped (charEscaped False) v <> B.char8 '\n'

parse :: P.Parser ANVL
parse = P.sepBy nv P.endOfLine <* P.skipSpace
  where
  hd = digitToInt <$> P.satisfy isHexDigit
  pe = P.char '%' >> (.|.) . (`shiftL` 4) <$> hd <*> hd
  textWhile1 p = either (fail . show) return . TE.decodeUtf8' =<< P.takeWhile1 p
  tx d = mconcat <$> P.many' (textWhile1 (`notElem` '%':d) <|> (T.singleton . toEnum <$> pe))
  nv = do
    n <- tx ":\n" P.<?> "name"
    _ <- P.char ':'
    P.skipMany (P.char ' ')
    v <- tx "\n" P.<?> "value"
    return (n, v)