-- | Querying a darcs repository
--

module BuildBox.Command.Darcs (

  EmailAddress, DarcsPath, DarcsPatch(..),
  changes, changesN, changesAfter

) where

-- standard libraries
import Data.Time
import Data.Maybe
import System.Locale
import qualified Data.Sequence          as Seq
import qualified Data.ByteString.Char8  as B

-- friends
import BuildBox.Build
import BuildBox.Command.System
import qualified BuildBox.Data.Log      as Log


type DarcsPath    = String
type EmailAddress = String

data DarcsPatch = DarcsPatch
  {
    darcsTimestamp :: LocalTime         -- TLM: use UTC instead?
  , darcsAuthor    :: EmailAddress
  , darcsComment   :: Log.Log
  }

instance Show DarcsPatch where
  show (DarcsPatch time author desc) =
    formatTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y" time
    ++ "  " ++ author
    ++ "\n" ++ Log.toString desc


-- | List all patches in the given repository. If no repository is given, the
--   current working directory is used.
--
changes :: Maybe DarcsPath -> Build [DarcsPatch]
changes = darcs . ("darcs changes --repo=" ++) . fromMaybe "."


-- The following are more specific invocations of the "darcs changes" command,
-- which may be faster when interacting with very large repositories or over a
-- slow network.
--

-- | Retrieve the last N changes from the repository
--
changesN :: Maybe DarcsPath -> Int -> Build [DarcsPatch]
changesN repo n =
  darcs $ "darcs changes --last=" ++ show n
                    ++ " --repo=" ++ fromMaybe "." repo

-- | Retrieve all patches submitted to the repository after the given time
--
changesAfter :: Maybe DarcsPath -> LocalTime -> Build [DarcsPatch]
changesAfter repo time =
  darcs $ "darcs changes --matches='date \"after " ++ show time ++ "\"'"
                    ++ " --repo=" ++ fromMaybe "." repo


-- Execute the given darcs command string and split the stdout into a series of
-- patches
--
darcs :: String -> Build [DarcsPatch]
darcs cmd = do
  (status, logOut, logErr) <- systemTeeLog False cmd Log.empty
  case status of
    ExitSuccess -> return $ splitPatches logOut
    _           -> throw  $ ErrorSystemCmdFailed cmd status logOut logErr

splitPatches :: Log.Log -> [DarcsPatch]
splitPatches l
  | Seq.null l = []
  | otherwise  = let (h,t) = Seq.breakl B.null l
                 in  patch h : splitPatches (Seq.dropWhileL B.null t)
  where
    patch p =
      let toks          = words . B.unpack $ Seq.index p 0
          (time,author) = splitAt 6 toks
      in
      DarcsPatch
        {
          darcsTimestamp = readTime defaultTimeLocale "%a %b %e %H:%M:%S %Z %Y" (unwords time)
        , darcsAuthor    = unwords author
        , darcsComment   = Seq.drop 1 p
        }