--  Copyright (C) 2003 David Roundy
--
--  This program is free software; you can redistribute it and/or modify
--  it under the terms of the GNU General Public License as published by
--  the Free Software Foundation; either version 2, or (at your option)
--  any later version.
--
--  This program is distributed in the hope that it will be useful,
--  but WITHOUT ANY WARRANTY; without even the implied warranty of
--  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
--  GNU General Public License for more details.
--
--  You should have received a copy of the GNU General Public License
--  along with this program; see the file COPYING.  If not, write to
--  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
--  Boston, MA 02110-1301, USA.

-- |
-- Module      : Darcs.Commands.Dist
-- Copyright   : 2003 David Roundy
-- License     : GPL
-- Maintainer  : darcs-devel@darcs.net
-- Stability   : experimental
-- Portability : portable

module Darcs.Commands.Dist
    (
      dist
    ) where

import Prelude hiding ( writeFile )

import Data.ByteString.Lazy ( writeFile )
import Data.Char ( isAlphaNum )
import Control.Monad ( when )
import System.Directory ( setCurrentDirectory )
import System.Cmd ( system )
import System.Exit ( ExitCode(..), exitWith )
import System.FilePath.Posix ( takeFileName, (</>) )

import Workaround ( getCurrentDirectory )
import Codec.Archive.Tar ( pack, write )
import Codec.Archive.Tar.Entry ( entryPath )
import Codec.Compression.GZip ( compress )

import Darcs.Arguments ( DarcsFlag(Verbose, Quiet, DistName, SetScriptsExecutable), distnameOption,
                         workingRepoDir, matchOne, storeInMemory,
                         setScriptsExecutableOption )
import Darcs.Commands ( DarcsCommand(..), nodefaults )
import Darcs.Lock ( withTempDir )
import Darcs.Match ( getNonrangeMatch, haveNonrangeMatch, firstMatch, getFirstMatch )
import Darcs.Repository ( amInHashedRepository, withRepoReadLock, RepoJob(..),
                          setScriptsExecutable,
                          createPartialsPristineDirectoryTree )
import Darcs.Repository.Prefs ( getPrefval )
import Darcs.RepoPath ( AbsolutePath, toFilePath )
import Darcs.Utils ( withCurrentDirectory )


distDescription :: String
distDescription = "Create a distribution tarball."


distHelp :: String
distHelp =
    "The `darcs dist' command creates a compressed archive (a `tarball') in\n" ++
     "the repository's root directory, containing the recorded state of the\n" ++
     "working tree (unrecorded changes and the _darcs directory are\n" ++
     "excluded).\n" ++
     "\n" ++
     "If a predist command is set (see `darcs setpref'), that command will\n" ++
     "be run on the tarball contents prior to archiving.  For example,\n" ++
     "autotools projects would set it to `autoconf && automake'.\n" ++
     "\n" ++
     "By default, the tarball (and the top-level directory within the\n" ++
     "tarball) has the same name as the repository, but this can be\n" ++
     "overridden with the --dist-name option.\n"

     -- FIXME: this is tedious and ugly.
     {-
     ++ "\n" ++
     "Suppose you use a version numbering scheme `major.minor.patch', and\n" ++
     "you tag each release `major.minor'.  You can then calculate the\n" ++
     "version number by taking the newest tag and appending a dot and the\n" ++
     "number of patches since that tag.  If you use the directory name as\n" ++
     "the project name, you can make tarballs of the form name-version.tgz\n" ++
     "using the following shell script:\n" ++
     "\n" ++
     "  major_minor=$(darcs show tags | head -1) &&\n" ++
     "  patch_level=$(($(darcs changes --count --from-tag .) - 1)) &&\n" ++
     "  version=$major_minor.$patch_level &&\n" ++
     "  project=${PWD##*/} &&\n" ++
     "  darcs dist --dist-name \"$project\"-\"$version\".tar.gz\n"
     -}


dist :: DarcsCommand
dist = DarcsCommand
    {
      commandProgramName = "darcs"
    , commandName = "dist"
    , commandHelp = distHelp
    , commandDescription = distDescription
    , commandExtraArgs = 0
    , commandExtraArgHelp = []
    , commandCommand = distCmd
    , commandPrereq = amInHashedRepository
    , commandGetArgPossibilities = return []
    , commandArgdefaults = nodefaults
    , commandAdvancedOptions = []
    , commandBasicOptions =
        [
          distnameOption
        , workingRepoDir
        , matchOne
        , setScriptsExecutableOption
        , storeInMemory
        ]
    }


distCmd :: [DarcsFlag]
        -> [String]
        -> IO ()
distCmd opts _ = withRepoReadLock opts $ RepoJob $ \repository -> do
  formerdir <- getCurrentDirectory
  let distname = getDistName formerdir [x | DistName x <- opts]
  predist <- getPrefval "predist"
  let resultfile = formerdir </> distname ++ ".tar.gz"
  withTempDir "darcsdist" $ \tempdir -> do
    setCurrentDirectory formerdir
    withTempDir (toFilePath tempdir </> takeFileName distname) $ \ddir -> do
      if haveNonrangeMatch opts
        then
            if firstMatch opts
                then withCurrentDirectory ddir $ getFirstMatch repository opts
                else withCurrentDirectory ddir $ getNonrangeMatch repository opts
        else createPartialsPristineDirectoryTree repository [""] (toFilePath ddir)
      ec <- case predist of Nothing -> return ExitSuccess
                            Just pd -> system pd
      if ec == ExitSuccess
          then
              do
              withCurrentDirectory ddir $
                  when (SetScriptsExecutable `elem` opts) setScriptsExecutable
              doDist opts tempdir ddir resultfile
          else
              do
              putStrLn "Dist aborted due to predist failure"
              exitWith ec


-- | This function performs the actual distribution action itself.
-- NB - it does /not/ perform the pre-dist, that should already
-- have completed successfully before this is invoked.
doDist :: [DarcsFlag] -> AbsolutePath -> AbsolutePath -> FilePath -> IO ()
doDist opts tempdir ddir resultfile = do
    setCurrentDirectory (toFilePath tempdir)
    let safeddir = safename $ takeFileName $ toFilePath ddir
    entries <- pack "." [safeddir]
    when (Verbose `elem` opts) $ putStr $ unlines $ map entryPath entries
    writeFile resultfile $ compress $ write entries
    when (Quiet `notElem` opts) $ putStrLn $ "Created dist as " ++ resultfile
  where
    safename n@(c:_) | isAlphaNum c  = n
    safename n = "./" ++ n


getDistName :: FilePath -> [String] -> FilePath
getDistName _ (dn:_) = dn
getDistName currentDirectory _ = takeFileName currentDirectory