    {-# LANGUAGE Rank2Types, TypeSynonymInstances, DeriveDataTypeable, FlexibleInstances #-}
    {- |
       Module      : Data.FileStore.Types
       Copyright   : Copyright (C) 2009 John MacFarlane
       License     : BSD 3

       Maintainer  : John MacFarlane <jgm@berkeley.edu>
       Stability   : alpha
       Portability : GHC 6.10 required

       Type definitions for "Data.FileStore".
    -}

    module Data.FileStore.Types
               ( RevisionId
               , Resource(..)
               , Author(..)
               , Change(..)
               , Description
               , Revision(..)
               , Contents(..)
               , TimeRange(..)
               , MergeInfo(..)
               , FileStoreError(..)
               , SearchMatch(..)
               , SearchQuery(..)
               , defaultSearchQuery
               , UTCTime
               , FileStore (..) )

    where
    import Data.ByteString.Lazy (ByteString)
    import Data.Typeable
    import Data.ByteString.Lazy.UTF8 (toString, fromString)
    import Data.Time (UTCTime)
    import Control.Exception (Exception)

    type RevisionId   = String

    data Resource = FSFile FilePath
                  | FSDirectory FilePath
                  deriving (Show, Read, Eq, Typeable, Ord)

    data Author =
      Author {
        authorName  :: String
      , authorEmail :: String
      } deriving (Show, Read, Eq, Typeable)

    data Change =
        Added FilePath
      | Deleted FilePath
      | Modified FilePath
      deriving (Show, Read, Eq, Typeable)

    type Description = String

    data Revision =
      Revision {
        revId          :: RevisionId
      , revDateTime    :: UTCTime
      , revAuthor      :: Author
      , revDescription :: Description
      , revChanges     :: [Change]
      } deriving (Show, Read, Eq, Typeable)

    class Contents a where
      fromByteString :: ByteString -> a
      toByteString   :: a -> ByteString

    instance Contents ByteString where
      toByteString = id
      fromByteString = id

    instance Contents String where
      toByteString   = fromString
      fromByteString = toString

    data TimeRange =
      TimeRange {
        timeFrom :: Maybe UTCTime  -- ^ @Nothing@ means no lower bound
      , timeTo   :: Maybe UTCTime  -- ^ @Nothing@ means no upper bound
      } deriving (Show, Read, Eq, Typeable)

    data MergeInfo =
      MergeInfo {
        mergeRevision  :: Revision   -- ^ The revision w/ which changes were merged
      , mergeConflicts :: Bool       -- ^ @True@ if there were merge conflicts
      , mergeText      :: String     -- ^ The merged text, w/ conflict markers
      } deriving (Show, Read, Eq, Typeable)

    data FileStoreError =
        RepositoryExists             -- ^ Tried to initialize a repo that exists
      | ResourceExists               -- ^ Tried to create a resource that exists
      | NotFound                     -- ^ Requested resource was not found
      | IllegalResourceName          -- ^ The specified resource name is illegal
      | Unchanged                    -- ^ The resource was not modified,
                                     --   because the contents were unchanged
      | UnsupportedOperation
      | NoMaxCount                   -- ^ The darcs version used does not support
                                     --   --max-count
      | UnknownError String
      deriving (Read, Eq, Typeable)

    instance Show FileStoreError where
      show RepositoryExists      = "RepositoryExists"
      show ResourceExists        = "ResourceExists"
      show NotFound              = "NotFound"
      show IllegalResourceName   = "IllegalResourceName"
      show Unchanged             = "Unchanged"
      show UnsupportedOperation  = "UnsupportedOperation"
      show NoMaxCount            = "NoMaxCount:\n"
        ++ "filestore was compiled with the maxcount flag, but your version of\n"
        ++ "darcs does not support the --max-count option.  You should either\n"
        ++ "upgrade to darcs >= 2.3.0 (recommended) or compile filestore without\n"
        ++ "the maxcount flag (cabal install filestore -f-maxcount)."
      show (UnknownError s)      = "UnknownError: " ++ s

    instance Exception FileStoreError

    data SearchQuery =
      SearchQuery {
        queryPatterns    :: [String] -- ^ Patterns to match
      , queryWholeWords  :: Bool     -- ^ Match patterns only with whole words?
      , queryMatchAll    :: Bool     -- ^ Return matches only from files in which
                                     --   all patterns match?
      , queryIgnoreCase  :: Bool     -- ^ Make matches case-insensitive?
      } deriving (Show, Read, Eq, Typeable)

    defaultSearchQuery :: SearchQuery
    defaultSearchQuery = SearchQuery {
         queryPatterns   = []
       , queryWholeWords = True
       , queryMatchAll   = True
       , queryIgnoreCase = True
       }

    data SearchMatch =
      SearchMatch {
        matchResourceName :: FilePath
      , matchLineNumber   :: Integer
      , matchLine         :: String
      } deriving (Show, Read, Eq, Typeable)

    -- | A versioning filestore, which can be implemented using the
    -- file system, a database, or revision-control software.
    data FileStore = FileStore {

        -- | Initialize a new filestore.
        initialize     :: IO ()

        -- | Save contents in the filestore.
      , save           :: forall a . Contents a
                       => FilePath          -- Resource to save.
                       -> Author            --  Author of change.
                       -> Description       --  Description of change.
                       -> a                 --  New contents of resource.
                       -> IO ()

        -- | Retrieve the contents of the named resource.
      , retrieve       :: forall a . Contents a
                       => FilePath          -- Resource to retrieve.
                       -> Maybe RevisionId  -- @Just@ a particular revision ID,
                                            -- or @Nothing@ for latest
                       -> IO a

        -- | Delete a named resource, providing author and log message.
      , delete         :: FilePath          -- Resource to delete.
                       -> Author            -- Author of change.
                       -> Description       -- Description of change.
                       -> IO ()

        -- | Rename a resource, providing author and log message.
      , rename         :: FilePath          -- Resource original name.
                       -> FilePath          -- Resource new name.
                       -> Author            -- Author of change.
                       -> Description       -- Description of change.
                       -> IO ()

        -- | Get history for a list of named resources in a (possibly openended)
        -- time range. If the list is empty, history for all resources will
        -- be returned. If the TimeRange is 2 Nothings, history for all dates will be returned.
      , history        :: [FilePath]        -- List of resources to get history for
                                            -- or @[]@ for all.
                       -> TimeRange         -- Time range in which to get history.
                       -> Maybe Int         -- Maybe max number of entries.
                       -> IO [Revision]

        -- | Return the revision ID of the latest change for a resource.
        -- Raises 'NotFound' if the resource is not found.
      , latest         :: FilePath          -- Resource to get revision ID for.
                       -> IO RevisionId

        -- | Return information about a revision, given the ID.
        -- Raises 'NotFound' if there is no such revision.
      , revision       :: RevisionId        -- Revision ID to get information for.
                       -> IO Revision

        -- | Return a list of resources in the filestore.
      , index          :: IO [FilePath]

      -- | Return a list of resources in a directory of the filestore.
      , directory      :: FilePath          -- Directory to list (empty for root)
                       -> IO [Resource]

        -- | @True@ if the revision IDs match, in the sense that the
        -- can be treated as specifying the same revision.
      , idsMatch       :: RevisionId
                       -> RevisionId
                       -> Bool

      -- | Search the filestore for patterns.
      , search         :: SearchQuery
                       -> IO [SearchMatch]

      }