{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
module Codec.Compression.Zlib.OutputWindow(
         OutputWindow
       , emptyWindow
       , addByte
       , addChunk
       , addOldChunk
       , outByteString
       )
 where

import Data.ByteString.Builder
import Data.ByteString.Lazy(ByteString)
import qualified Data.ByteString as SBS
import qualified Data.ByteString.Lazy as BS
import Data.Int
import Data.FingerTree
import Data.Foldable(foldMap)
import Data.Monoid
import Data.Word

data OutputWindow = OutputWindow {
       owCommitted :: !(FingerTree Int SBS.ByteString)
     , owRecent    :: !Builder
     }

instance Monoid Int where
  mempty  = 0
  mappend = (+)

instance Measured Int SBS.ByteString where
  measure = SBS.length

emptyWindow :: OutputWindow
emptyWindow = OutputWindow empty mempty

addByte :: OutputWindow -> Word8 -> OutputWindow
addByte !ow !b = ow{ owRecent = owRecent ow <> word8 b }

addChunk :: OutputWindow -> ByteString -> OutputWindow
addChunk !ow !bs = ow{ owRecent = owRecent ow <> lazyByteString bs }

addOldChunk :: OutputWindow -> Int -> Int64 -> (OutputWindow, ByteString)
addOldChunk !ow !dist !len = (OutputWindow output (lazyByteString chunk), chunk)
 where
  output      = owCommitted ow |> BS.toStrict (toLazyByteString (owRecent ow))
  dropAmt     = measure output - dist
  (prev, sme) = split (> dropAmt) output
  s :< rest   = viewl sme
  start       = SBS.take (fromIntegral len) (SBS.drop (dropAmt-measure prev) s)
  len'        = fromIntegral len - SBS.length start
  (m, rest')  = split (> len') rest
  middle      = BS.toStrict (toLazyByteString (outFinger m))
  end         = case viewl rest' of
                  EmptyL -> SBS.empty
                  bs2 :< _ -> SBS.take (len' - measure m) bs2
  chunkInf    = BS.fromChunks [start, middle, end] `BS.append` chunk
  chunk       = BS.take len chunkInf

outFinger :: FingerTree Int SBS.ByteString -> Builder
outFinger = foldMap byteString

outByteString :: OutputWindow -> ByteString
outByteString ow = 
  toLazyByteString (outFinger (owCommitted ow) <> owRecent ow)