content: module Duffer.Plumbing (module Duffer.Plumbing) where
  
  import qualified Data.ByteString as B
  import qualified Data.Set        as S
  
  import Control.Applicative       ((<|>))
  import Control.Monad             (unless)
  import Control.Monad.Trans.Maybe (MaybeT(..), runMaybeT)
  import Data.Foldable             (traverse_)
  import System.Directory          (createDirectoryIfMissing, doesDirectoryExist
                                   ,doesFileExist, listDirectory)
  import System.FilePath           ((</>))
  import Data.Bool                 (bool)
  import Data.ByteString.UTF8      (fromString)
  import Data.List                 (isPrefixOf)
  
  import Duffer.Loose.Objects (GitObjectGeneric(..), GitObject, TreeEntry(..)
                              ,EntryPermission(..), Ref, Blob(..), Tree(..))
  import Duffer.Pack          (getPackObjectRefs)
  import Duffer.Unified       (readRef, readObject, writeObject)
  import Duffer.WithRepo      (WithRepo, liftIO, ask, asks, localObjects)
  
  fuzzyReadObject :: String -> WithRepo (Maybe GitObject)
  fuzzyReadObject search = maybe (return Nothing) readObject =<< runMaybeT (
      MaybeT (readRef                      search) <|>
      MaybeT (readRef $ "refs/heads"   </> search) <|>
      MaybeT (readRef $ "refs/remotes" </> search) <|>
      MaybeT (readRef $ "refs/tags"    </> search) <|>
      MaybeT (resolvePartialRef            search))
  
  resolvePartialRef :: String -> WithRepo (Maybe Ref)
  resolvePartialRef search = runMaybeT
      $   MaybeT (localObjects $ resolvePartialLooseRef' search)
      <|> MaybeT (localObjects $ resolvePartialPackRef'  search)
  
  resolvePartialLooseRef' :: String -> WithRepo (Maybe Ref)
  resolvePartialLooseRef' search = let dir = take 2 search in do
      objectDir <- asks (</> dir)
      liftIO (doesDirectoryExist objectDir) >>= bool (return Nothing)
          (do filtered <- filter (isPrefixOf (drop 2 search)) <$>
                  liftIO (listDirectory objectDir)
              return $ bool
                  Nothing
                  (Just . fromString $ dir ++ head filtered)
                  (length filtered == 1))
  
  resolvePartialPackRef' :: String -> WithRepo (Maybe Ref)
  resolvePartialPackRef' search = do
      matching <- S.filter (B.isPrefixOf (fromString search)) <$>
          getPackObjectRefs
      return $ bool Nothing (Just $ S.elemAt 0 matching) (S.size matching == 1)
  
  initRepo :: WithRepo ()
  initRepo = ask >>= \path -> do
      objectsPresent <- liftIO . doesDirectoryExist $ path </> "objects"
      liftIO . unless objectsPresent $ do
          traverse_ (createDirectoryIfMissing True)
              [ path </> "branches"
              , path </> "hooks"
              , path </> "info"
              , path </> "objects" </> "info"
              , path </> "objects" </> "pack"
              , path </> "refs"    </> "heads"
              , path </> "refs"    </> "tags"
              ]
          traverse_ (uncurry writeFile)
              [ (path </> "HEAD",        "ref: refs/heads/master\n")
              , (path </> "config",      "")
              , (path </> "description", "")
              ]
  
  writeTree :: FilePath -> WithRepo Ref
  writeTree path = writeObject . GitTree . Tree . S.fromList =<< traverse
      (\entry -> do
          fileExists <- liftIO . doesFileExist      $ path </> entry
          dirExists  <- liftIO . doesDirectoryExist $ path </> entry
          case (dirExists, fileExists) of
              (True, _) -> do
                  tRef <- writeTree $ path </> entry
                  return $ TreeEntry Directory (fromString entry) tRef
              (_, True) -> do
                  bContent <- liftIO . B.readFile $ path </> entry
                  bRef     <- writeObject . GitBlob $ Blob bContent
                  return $ TreeEntry Regular (fromString entry) bRef
              (False, False) -> error "what even"
      ) =<< liftIO (listDirectory path)
  