module Git.Blob where

import           Conduit
import           Control.Applicative
import           Control.Monad
import           Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import           Data.HashSet (HashSet)
import qualified Data.HashSet as HashSet
import           Data.Tagged
import           Data.Text as T
import           Data.Text.Encoding as T
import           Git.Types

createBlobUtf8 :: MonadGit r m => Text -> m (BlobOid r)
createBlobUtf8 = createBlob . BlobString . T.encodeUtf8

catBlob :: MonadGit r m => BlobOid r -> m ByteString
catBlob = lookupBlob >=> blobToByteString

catBlobLazy :: MonadGit r m => BlobOid r -> m BL.ByteString
catBlobLazy = lookupBlob >=> blobToLazyByteString

catBlobUtf8 :: MonadGit r m => BlobOid r -> m Text
catBlobUtf8 = catBlob >=> return . T.decodeUtf8

blobContentsToByteString :: MonadGit r m => BlobContents m -> m ByteString
blobContentsToByteString (BlobString bs) = return bs
blobContentsToByteString (BlobStringLazy bs) =
    return $ B.concat (BL.toChunks bs)
blobContentsToByteString (BlobStream bs) =
    B.concat . BL.toChunks <$> (runConduit $ bs .| sinkLazy)
blobContentsToByteString (BlobSizedStream bs _) =
    runConduit $ B.concat . BL.toChunks <$> (bs .| sinkLazy)

blobToByteString :: MonadGit r m => Blob r m -> m ByteString
blobToByteString (Blob _ contents) = blobContentsToByteString contents

blobContentsToLazyByteString :: MonadGit r m
                             => BlobContents m -> m BL.ByteString
blobContentsToLazyByteString (BlobString bs) = return $ BL.fromChunks [bs]
blobContentsToLazyByteString (BlobStringLazy bs) = return bs
blobContentsToLazyByteString (BlobStream bs) = runConduit $ bs .| sinkLazy
blobContentsToLazyByteString (BlobSizedStream bs _) = runConduit $ bs .| sinkLazy

blobToLazyByteString :: MonadGit r m => Blob r m -> m BL.ByteString
blobToLazyByteString (Blob _ contents) = blobContentsToLazyByteString contents

writeBlob :: (MonadGit r m, MonadIO m, MonadResource m)
          => FilePath -> BlobContents m -> m ()
writeBlob path (BlobString bs)         = liftIO $ B.writeFile path bs
writeBlob path (BlobStringLazy bs)     = runConduit $ sourceLazy bs .| sinkFile path
writeBlob path (BlobStream str)        = runConduit $ str .| sinkFile path
writeBlob path (BlobSizedStream str _) = runConduit $ str .| sinkFile path

treeBlobEntries :: MonadGit r m
                => Tree r -> m [(TreeFilePath, BlobOid r, BlobKind)]
treeBlobEntries tree = runConduit $ sourceTreeBlobEntries tree .| sinkList

sourceTreeBlobEntries
    :: MonadGit r m
    => Tree r -> ConduitT i (TreeFilePath, BlobOid r, BlobKind) m ()
sourceTreeBlobEntries tree =
    sourceTreeEntries tree .| awaitForever go
  where
    go (fp ,BlobEntry oid k) = yield (fp, oid, k)
    go _ = return ()

copyBlob :: (MonadGit r m, MonadGit s (t m), MonadTrans t)
         => BlobOid r -> HashSet Text -> t m (BlobOid s, HashSet Text)
copyBlob blobr needed = do
    let oid = untag blobr
        sha = renderOid oid
    oid2 <- parseOid (renderOid oid)
    if HashSet.member sha needed
        then do
        bs <- lift $ blobToByteString =<< lookupBlob (Tagged oid)
        boid <- createBlob (BlobString bs)

        let x = HashSet.delete sha needed
        return $ boid `seq` x `seq` (boid, x)

        else return (Tagged oid2, needed)