content: -- |
  -- Module      : Data.Git.Object
  -- License     : BSD-style
  -- Maintainer  : Vincent Hanquez <vincent@snarc.org>
  -- Stability   : experimental
  -- Portability : unix
  --
  {-# LANGUAGE DeriveDataTypeable #-}
  module Data.Git.Types
      (
      -- * Type of types
        ObjectType(..)
      -- * Main git types
      , Tree(..)
      , Commit(..)
      , CommitExtra(..)
      , Blob(..)
      , Tag(..)
      , Person(..)
      , EntName
      , entName
      , getEntNameBytes
      , EntPath
      , entPathAppend
      -- * modeperm type
      , ModePerm(..)
      , FilePermissions(..)
      , ObjectFileType(..)
      , getPermission
      , getFiletype
      -- * time type
      , GitTime(..)
      , gitTime
      , gitTimeToLocal
      -- * Pack delta types
      , DeltaOfs(..)
      , DeltaRef(..)
      -- * Basic types part of other bigger types
      , TreeEnt
      ) where
  
  import Data.Word
  import Data.Bits
  import Data.String
  import Data.ByteString (ByteString)
  import qualified Data.ByteString as B
  import qualified Data.ByteString.Lazy as L
  
  import Data.Git.Ref
  import Data.Git.Delta
  import Data.Git.Imports
  import Data.Hourglass (Elapsed, TimezoneOffset(..)
                        , timePrint, timeConvert
                        , Time(..), Timeable(..)
                        , LocalTime, localTimeSetTimezone, localTimeFromGlobal)
  import Data.Data
  import qualified Data.ByteString.UTF8 as UTF8
  
  -- | type of a git object.
  data ObjectType =
        TypeTree
      | TypeBlob
      | TypeCommit
      | TypeTag
      | TypeDeltaOff
      | TypeDeltaRef
      deriving (Show,Eq,Data,Typeable)
  
  -- | Git time is number of seconds since unix epoch in the UTC zone with
  -- the current timezone associated
  data GitTime = GitTime
      { gitTimeUTC      :: !Elapsed
      , gitTimeTimezone :: !TimezoneOffset
      } deriving (Eq)
  
  instance Timeable GitTime where
      timeGetNanoSeconds _ = 0
      timeGetElapsedP (GitTime t _) = timeConvert t
      timeGetElapsed  (GitTime t _) = t
  instance Time GitTime where
      timeFromElapsedP e = GitTime (timeGetElapsed e) (TimezoneOffset 0)
      timeFromElapsed e = GitTime e (TimezoneOffset 0)
  
  instance Show GitTime where
      show (GitTime t tz) =
          timePrint "EPOCH" t ++ " " ++ show tz
  
  gitTime :: Integer -> Int -> GitTime
  gitTime seconds tzMins =
      GitTime (fromIntegral seconds) (TimezoneOffset tzMins)
  
  gitTimeToLocal :: GitTime -> LocalTime Elapsed
  gitTimeToLocal (GitTime t tz) =
      localTimeSetTimezone tz (localTimeFromGlobal t)
  
  -- | the enum instance is useful when marshalling to pack file.
  instance Enum ObjectType where
      fromEnum TypeCommit   = 0x1
      fromEnum TypeTree     = 0x2
      fromEnum TypeBlob     = 0x3
      fromEnum TypeTag      = 0x4
      fromEnum TypeDeltaOff = 0x6
      fromEnum TypeDeltaRef = 0x7
  
      toEnum 0x1 = TypeCommit
      toEnum 0x2 = TypeTree
      toEnum 0x3 = TypeBlob
      toEnum 0x4 = TypeTag
      toEnum 0x6 = TypeDeltaOff
      toEnum 0x7 = TypeDeltaRef
      toEnum n   = error ("not a valid object: " ++ show n)
  
  newtype ModePerm = ModePerm Word32
      deriving (Show,Eq)
  
  getPermission :: ModePerm -> FilePermissions
  getPermission (ModePerm modeperm) =
      let owner = (modeperm .&. 0x700) `shiftR` 6
          group = (modeperm .&. 0x70) `shiftR` 3
          other = modeperm .&. 0x7
       in FilePermissions (fromIntegral owner) (fromIntegral group) (fromIntegral other)
  
  getFiletype :: ModePerm -> ObjectFileType
  getFiletype (ModePerm modeperm) =
      case modeperm `shiftR` 12 of
          _ -> error "filetype unknown"
  
  -- | Git object file type
  data ObjectFileType =
        FileTypeDirectory
      | FileTypeRegularFile
      | FileTypeSymbolicLink
      | FileTypeGitLink
      deriving (Show,Eq)
  
  -- | traditional unix permission for owner, group and permissions
  data FilePermissions = FilePermissions
      { getOwnerPerm :: {-# UNPACK #-} !Perm
      , getGroupPerm :: {-# UNPACK #-} !Perm
      , getOtherPerm :: {-# UNPACK #-} !Perm
      } deriving (Show,Eq)
  
  -- | a bitfield representing a typical unix permission:
  -- * bit 0 represents the read permission
  -- * bit 1 represents the write permission
  -- * bit 2 represents the execute permission
  type Perm = Word8
  
  -- | Entity name
  newtype EntName = EntName { getEntNameBytes :: ByteString }
      deriving (Eq,Ord)
  instance Show EntName where
      show (EntName e) = UTF8.toString e
  instance IsString EntName where
      fromString s = entName $ UTF8.fromString s
  
  entName :: ByteString -> EntName
  entName bs
      | B.elem slash bs = error ("entity name " ++ show bs ++ " contains an invalid '/' character")
      | otherwise       = EntName bs
    where slash = 47
  
  entPathAppend :: EntPath -> EntName -> EntPath
  entPathAppend l e = l ++ [e]
  
  type EntPath = [EntName]
  
  -- | represent one entry in the tree
  -- (permission,file or directory name,blob or tree ref)
  -- name should maybe a filepath, but not sure about the encoding.
  type TreeEnt hash = (ModePerm,EntName,Ref hash)
  
  -- | an author or committer line
  -- has the format: name <email> time timezone
  -- FIXME: should be a string, but I don't know if the data is stored
  -- consistantly in one encoding (UTF8)
  data Person = Person
      { personName  :: !ByteString
      , personEmail :: !ByteString
      , personTime  :: !GitTime
      } deriving (Show,Eq)
  
  -- | Represent a root tree with zero to many tree entries.
  newtype Tree hash = Tree { treeGetEnts :: [TreeEnt hash] } deriving (Show,Eq)
  
  -- | Represent a binary blob.
  newtype Blob hash = Blob { blobGetContent :: L.ByteString } deriving (Show,Eq)
  
  -- | Represent a commit object.
  data Commit hash = Commit
      { commitTreeish   :: !(Ref hash)
      , commitParents   :: [Ref hash]
      , commitAuthor    :: !Person
      , commitCommitter :: !Person
      , commitEncoding  :: Maybe ByteString
      , commitExtras    :: [CommitExtra]
      , commitMessage   :: !ByteString
      } deriving (Show,Eq)
  
  data CommitExtra = CommitExtra
      { commitExtraKey   :: !ByteString
      , commitExtraValue :: !ByteString
      } deriving (Show,Eq)
  
  -- | Represent a signed tag.
  data Tag hash = Tag
      { tagRef        :: !(Ref hash)
      , tagObjectType :: !ObjectType
      , tagBlob       :: !ByteString
      , tagName       :: !Person
      , tagS          :: !ByteString
      } deriving (Show,Eq)
  
  -- | Delta pointing to an offset.
  data DeltaOfs hash = DeltaOfs !Word64 !Delta
      deriving (Show,Eq)
  
  -- | Delta pointing to a ref.
  data DeltaRef hash = DeltaRef !(Ref hash) !Delta
      deriving (Show,Eq)
  