module Network.HPACK.Huffman.Tree (
    -- * Huffman decoding
    HTree (..),
    eosInfo,
    toHTree,
    showTree,
    printTree,
    flatten,
) where

import Control.Arrow (second)

import Imports
import Network.HPACK.Huffman.Bit
import Network.HPACK.Huffman.Params

----------------------------------------------------------------

type EOSInfo = Maybe Int

-- | Type for Huffman decoding.
data HTree
    = Tip
        EOSInfo -- EOS info from 1
        {-# UNPACK #-} Int -- Decoded value. Essentially Word8
    | Bin
        EOSInfo -- EOS info from 1
        {-# UNPACK #-} Int -- Sequence no from 0
        HTree -- Left
        HTree -- Right
    deriving (Show)

eosInfo :: HTree -> EOSInfo
eosInfo (Tip mx _) = mx
eosInfo (Bin mx _ _ _) = mx

----------------------------------------------------------------

showTree :: HTree -> String
showTree = showTree' ""

showTree' :: String -> HTree -> String
showTree' _ (Tip _ i) = show i ++ "\n"
showTree' pref (Bin _ n l r) =
    "No "
        ++ show n
        ++ "\n"
        ++ pref
        ++ "+ "
        ++ showTree' pref' l
        ++ pref
        ++ "+ "
        ++ showTree' pref' r
  where
    pref' = "  " ++ pref

printTree :: HTree -> IO ()
printTree = putStr . showTree

----------------------------------------------------------------

-- | Creating 'HTree'.
toHTree :: [Bits] -> HTree
toHTree bs = mark 1 eos $ snd $ build 0 $ zip [0 .. idxEos] bs
  where
    eos = bs !! idxEos

build :: Int -> [(Int, Bits)] -> (Int, HTree)
build cnt0 [(v, [])] = (cnt0, Tip Nothing v)
build cnt0 xs =
    let (cnt1, l) = build (cnt0 + 1) fs
        (cnt2, r) = build cnt1 ts
     in (cnt2, Bin Nothing cnt0 l r)
  where
    (fs', ts') = partition ((==) F . head . snd) xs
    fs = map (second tail) fs'
    ts = map (second tail) ts'

-- | Marking the EOS path
mark :: Int -> Bits -> HTree -> HTree
mark i [] (Tip Nothing v) = Tip (Just i) v
mark i (F : bs) (Bin Nothing n l r) = Bin (Just i) n (mark (i + 1) bs l) r
mark i (T : bs) (Bin Nothing n l r) = Bin (Just i) n l (mark (i + 1) bs r)
mark _ _ _ = error "mark"

----------------------------------------------------------------

flatten :: HTree -> [HTree]
flatten (Tip _ _) = []
flatten t@(Bin _ _ l r) = t : (flatten l ++ flatten r)
