{-# LANGUAGE DataKinds        #-}
{-# LANGUAGE TypeApplications #-}

module Arbor.File.Format.Asif.Segment
  ( Z.Segment(..)
  , mkDefaultSegment
  , extractSegments
  , extractNamedSegments
  , extractHeader
  , extractSegmentByteStringsWithHeader
  , segmentNamed
  ) where

import Arbor.File.Format.Asif.ByIndex
import Arbor.File.Format.Asif.Get
import Arbor.File.Format.Asif.Lookup
import Control.Lens
import Control.Monad
import Data.Binary.Get
import Data.Generics.Product.Any
import Data.Maybe
import Data.Monoid                    ((<>))
import Data.Text                      (Text, pack)

import qualified Arbor.File.Format.Asif.Extract as E
import qualified Arbor.File.Format.Asif.Get     as G
import qualified Arbor.File.Format.Asif.Type    as Z
import qualified Data.Attoparsec.ByteString     as AP
import qualified Data.ByteString                as BS
import qualified Data.ByteString.Lazy           as LBS
import qualified Data.ByteString.Lazy.Char8     as LC8
import qualified Data.Map.Strict                as M

mkDefaultSegment :: LBS.ByteString -> Z.Segment LBS.ByteString
mkDefaultSegment bs = Z.segment bs mempty

extractSegments :: AP.Parser BS.ByteString -> LBS.ByteString -> Either String [Z.Segment LBS.ByteString]
extractSegments magicParser bs = do
  header <- extractHeader magicParser bs
  bss <- extractSegmentByteStringsWithHeader bs header
  case bss of
    (as:_) -> if ".asif/filenames\0" `LBS.isPrefixOf` as
      then do
        let filenames     = E.list G.getTextUtf8Z as
        let namedSegments = M.fromList (zip filenames bss)

        let metas = mempty
              <> ByIndex (replicate (length bss) mempty)
              <> ByIndex (Z.metaFilename    <$> filenames)
              <> ByIndex (Z.metaCreateTime  <$> lookupSegment ".asif/createtimes" namedSegments (E.list G.getTimeMicro64))
              <> ByIndex (Z.metaMaybeFormat <$> lookupSegment ".asif/formats"     namedSegments E.formats)

        return $ uncurry Z.segment <$> zip bss (unByIndex metas)
      else return (mkDefaultSegment <$> bss)
    _      -> return (mkDefaultSegment <$> bss)

extractNamedSegments :: AP.Parser BS.ByteString -> LBS.ByteString -> Either String (M.Map Text (Z.Segment LBS.ByteString))
extractNamedSegments magicParser bs = do
  segments <- extractSegments magicParser bs
  let filenames = fromMaybe "" . (^. the @"meta" . the @"filename") <$> segments
  return $ M.fromList $ zip filenames segments

extractHeader :: AP.Parser BS.ByteString -> LBS.ByteString -> Either String [(Int, Int)]
extractHeader magicParser bs = case runGetOrFail (getHeader magicParser) bs of
  Left (_, _, err)     -> Left err
  Right (_, _, header) -> Right header

extractSegmentByteStrings :: AP.Parser BS.ByteString -> LBS.ByteString -> Either String [LBS.ByteString]
extractSegmentByteStrings magicParser bs = extractHeader magicParser bs >>= extractSegmentByteStringsWithHeader bs

extractSegmentByteStringsWithHeader :: LBS.ByteString -> [(Int, Int)] -> Either String [LBS.ByteString]
extractSegmentByteStringsWithHeader bs header = do
    let segs = fmap (\(o, l) -> LBS.take (fromIntegral l) $ LBS.drop (fromIntegral o) bs) header
    forM_ (zip segs header) $ \(seg, (_, len)) ->
      when (LC8.length seg /= fromIntegral len) $
        fail "XXX segments not read correctly"
    return segs

segmentNamed :: String -> M.Map Text (Z.Segment LC8.ByteString) -> Either String LC8.ByteString
segmentNamed name segments = do
  let seg = M.lookup (pack name) segments >>= (\s -> Just (s ^. the @"payload"))
  seg & maybe (Left ("Missing segment: " ++ name)) Right
