module Encode (CodeEvent(..), encode, maxBits)
where

--import GlaExts

data PrefixTrie a b = PTNil |
                      PT a b (PrefixTrie a b) (PrefixTrie a b) (PrefixTrie a b)

type CodeTable = PrefixTrie Char Int

data CodeEvent =
                 Code Int |
                 NewWordSize |
                 Clear deriving Show{-was:Text-}

data CodeState = CS
                 !Int
                 !Int
                 !Int
                 !Int
                 !Int
                 !Int

firstEnt    = 257       :: Int
maxBits     = 16        :: Int
checkGap    = 10000     :: Int
firstCheck  = 10000     :: Int
firstChange = (2^9) + 1 :: Int

maxmaxCode = 2^maxBits + 1 :: Int

encode :: [Int] -> String -> [CodeEvent]
encode = encode' (CS 3 1 firstCheck 0 firstEnt firstChange) initial_table

encode' :: CodeState -> CodeTable -> [Int] -> String -> [CodeEvent]
encode' _ _ _ [] = []
encode' c@(CS bo ci cp ra nx cg) t sizes input
  = if nx == cg then
    NewWordSize : encode' (CS (bo+s) ci cp ra nx cg') t ss input
  else
    if nx == maxmaxCode then
      if ci >= cp then
        let ra' = (ci * 256) `div` bo in
          if ra' > ra then
            encode' (CS bo ci (ci+checkGap) ra' nx cg) t sizes input
          else
            Clear :
            encode' (CS (bo+s) ci (ci+checkGap) 0 firstEnt firstChange)
                    initial_table ss input
       else
         let (input', n, i) = code_string_r (input, 0, 0) nx t
         in  Code n :
             encode' (CS (bo+s) (ci+i) cp ra nx cg) t ss input'
     else
       (\ ((input', n, i), t') ->
       Code n :
       encode' (CS (bo+s) (ci+i) cp ra (nx+1) cg) t' ss input')
       (code_string_rw (input, 0, 0) nx t)
  where
  (s:ss) = sizes
  cg' = let val = ((cg - 1) * 2) + 1 in
             if val == maxmaxCode then 0 else val

csForced (CS a b c d e f) = (a==a) && (b==b) && (c==c) && (d==d)
                                   && (e==e) && (f==f)

code_string_r :: (String, Int, Int) -> Int -> CodeTable -> (String, Int, Int)
code_string_r s@([], _, _) _ _
     = s
code_string_r s _ PTNil
     = s
code_string_r s@(c:cs, old_code, n) next_code (PT k v k_pt l r)
     = if c == k then
            code_string_r (cs, v, (n+1)) next_code k_pt
       else
            code_string_r s next_code (if c < k then l else r)

code_string_rw :: (String, Int, Int) -> Int -> CodeTable
                       -> ((String, Int, Int), CodeTable)
code_string_rw s@([], _, _) _ _
     = (s, PTNil)
code_string_rw s@(c:_,_,_) next_code PTNil
     = (s, PT c next_code PTNil PTNil PTNil)
code_string_rw s@(c:cs, old_code, n) next_code (PT k k_code k_pt l r)
     | c < k     = (\ (s', l') -> (s', PT k k_code k_pt l' r))
                         (code_string_rw s next_code l)
     | c > k     = (\ (s', r') -> (s', PT k k_code k_pt l r'))
                         (code_string_rw s next_code r)
     | otherwise = (\ (s', t') -> (s', PT k k_code t' l r))
                         (code_string_rw (cs, k_code, n+1) next_code k_pt)

initial_table :: CodeTable
initial_table = build_table 0 255

build_table :: Int -> Int -> CodeTable
build_table lo hi
     = if lo > hi then
           PTNil
       else let mid = (lo + hi) `div` 2 in
              --trace (show (lo,hi,mid))
              PT (toEnum mid) mid PTNil
                   (build_table lo (mid - 1))
                   (build_table (mid + 1) hi)
