{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections     #-}
{-# LANGUAGE ViewPatterns      #-}
module Data.Git.Ref
    (
      Ref(..)
    , RefFile(..)
    , mkRef
    , readRefFile
    , readPackedRefsFile
    ) where
import           Control.Applicative
import           Control.Monad.State
import           Data.Attoparsec.ByteString        as A
import           Data.Attoparsec.ByteString.Char8  (isSpace_w8)
import           Data.ByteString                   (ByteString)
import           Data.String
import           System.Posix.FilePath (RawFilePath, (</>))
import Data.Git.Internal.FileUtil
import Data.Git.Hash
import Data.Git.Internal.Parsers
import Data.Git.Paths
import Data.Git.RefName
data Ref = HEAD
         | Branch RefName              
         | TagRef RefName (Maybe Sha1) 
         | RemRef RemoteName RefName   
         | ExpRef RefName              
           deriving (Eq, Ord, Show)
instance IsString Ref where
    fromString s = maybe (error $ "can't parse ref: " ++ s) id $ mkRef (fromString s)
instance InRepo Ref where
    inRepo HEAD         = "HEAD"
    inRepo (Branch b)   = "refs/heads"   </> getRefName b
    inRepo (TagRef b _) = "refs/tags"    </> getRefName b
    inRepo (RemRef r b) = "refs/remotes" </> getRemoteName r </> getRefName b
    inRepo (ExpRef p)   = getRefName p
parseRef :: Parser Ref
parseRef = "HEAD" *> pure HEAD
           <|> Branch <$> ("refs/heads/"   *> parseRefName)
           <|> TagRef <$> ("refs/tags/"    *> parseRefName) <*> pure Nothing
           <|> RemRef <$> ("refs/remotes/" *> parseRemoteName <* "/") <*> parseRefName
           <|> ExpRef <$> parseRefName
data RefFile = ShaRef Sha1
             | SymRef Ref
               deriving (Eq, Ord, Show)
parseRefFile :: Parser RefFile
parseRefFile = SymRef <$> (string "ref: " *> parseRef <* eol)
           <|> ShaRef <$> (parseSha1Hex               <* eol)
parseRefName :: Parser RefName
parseRefName = do
  rn <- takeTill isSpace_w8
  maybe empty return $ refName rn
parseRemoteName :: Parser RemoteName
parseRemoteName = do
  rn <- A.takeWhile (/= 0o57)
  maybe empty return $ remoteName rn
mkRef :: ByteString -> Maybe Ref
mkRef = either (const Nothing) Just . parseOnly parseRef
maybeReadFile :: RawFilePath -> IO (Maybe ByteString)
maybeReadFile fp = mwhenFileExists fp (liftIO . readRawFileS $ fp)
parseFile :: Parser a -> RawFilePath -> IO (Maybe a)
parseFile p fp = do file <- maybeReadFile fp
                    return $ case file of
                               Nothing -> Nothing
                               Just f  -> either (const Nothing) Just (parseOnly p f)
readRefFile :: RawFilePath -> IO (Maybe RefFile)
readRefFile = parseFile parseRefFile
parsePackedRef :: Parser (Ref, Sha1)
parsePackedRef = do
  sha <- parseSha1Hex
  void space
  ref <- parseRef
  eol
  case ref of
    TagRef t _ -> (,sha) . TagRef t <$> optional ("^" *> parseSha1Hex <* eol)
    _          -> return (ref, sha)
parsePackedRefs :: Parser [(Ref, Sha1)]
parsePackedRefs = endOfInput *> pure []
                  <|> (:) <$> parsePackedRef <*> parsePackedRefs
                  <|> skipLine *> parsePackedRefs
readPackedRefsFile :: RawFilePath -> IO (Maybe [(Ref, Sha1)])
readPackedRefsFile = parseFile parsePackedRefs