    -- |
    -- Module      : Data.Git.Revision
    -- License     : BSD-style
    -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
    -- Stability   : experimental
    -- Portability : unix
    --
    {-# LANGUAGE DeriveDataTypeable #-}
    module Data.Git.Revision
        ( Revision(..)
        , RevModifier(..)
        , RevisionNotFound(..)
        , fromString
        ) where

    import Text.Parsec
    import Data.String
    import Data.Data

    -- | A modifier to a revision, which is
    -- a function apply of a revision
    data RevModifier =
          RevModParent Int       -- ^ parent accessor ^<n> and ^
        | RevModParentFirstN Int -- ^ parent accessor ~<n>
        | RevModAtType String    -- ^ @{type} accessor
        | RevModAtDate String    -- ^ @{date} accessor
        | RevModAtN Int          -- ^ @{n} accessor
        deriving (Eq,Data,Typeable)

    instance Show RevModifier where
        show (RevModParent 1)       = "^"
        show (RevModParent n)       = "^" ++ show n
        show (RevModParentFirstN n) = "~" ++ show n
        show (RevModAtType s)       = "@{" ++ s ++ "}"
        show (RevModAtDate s)       = "@{" ++ s ++ "}"
        show (RevModAtN s)          = "@{" ++ show s ++ "}"

    -- | A git revision. this can be many things:
    --    * a shorten ref
    --    * a ref
    --    * a named branch or tag
    --  followed by optional modifiers 'RevModifier' that can represent:
    --    * parenting
    --    * type
    --    * date
    data Revision = Revision String [RevModifier]
        deriving (Eq,Data,Typeable)

    -- | Exception when a revision cannot be resolved to a reference
    data RevisionNotFound = RevisionNotFound Revision
        deriving (Show,Eq,Data,Typeable)

    instance Show Revision where
        show (Revision s ms) = s ++ concatMap show ms

    instance IsString Revision where
        fromString = revFromString

    revFromString :: String -> Revision
    revFromString s = either (error.show) id $ parse parser "" s
      where parser = do
                    p    <- many (noneOf "^~@")
                    mods <- many (choice [parseParent, parseFirstParent, parseAt])
                    return $ Revision p mods
            parseParent = try $ do
                    _ <- char '^'
                    n <- optionMaybe (many1 digit)
                    case n of
                            Nothing -> return $ RevModParent 1
                            Just d  -> return $ RevModParent (read d)
            parseFirstParent = try $
                    char '~' >> many1 digit >>= return . RevModParentFirstN . read
            parseAt = try $ do
                    _  <- char '@' >> char '{'
                    at <- choice [ parseAtType, parseAtDate, parseAtN ]
                    _  <- char '}'
                    return at
            parseAtType = try $ do
                    ty <- choice $ map string ["tree","commit","blob","tag"]
                    return $ RevModAtType ty
            parseAtN = try $ do
                    many1 digit >>= return . RevModAtN . read
            parseAtDate = try $ do
                    many (noneOf "}") >>= return . RevModAtDate