-- Copyright (C) 2005 Tomasz Zielonka
--
-- 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 Main (main) where

import Darcs.Arguments ( showFriendly )
import Darcs.Flags ( DarcsFlag(Summary) )
import Darcs.Repository ( read_repo, withRepository, ($-) )
import Darcs.Repository.Prefs ( boring_file_filter )
import Darcs.Patch ( Patchy )
import Darcs.Patch.Match ( match_parser, match_pattern, apply_matcher, Matcher,
                    make_matcher )
import Darcs.Patch.MatchData ( PatchMatch(..) )
import Darcs.Patch.Info ( PatchInfo, just_name, just_author, pi_date, pi_tag )
import Darcs.Ordered ( unsafeUnRL, concatRL )
import Darcs.Hopefully ( info )

import Data.Char ( isSpace )
import Data.Maybe ( catMaybes, mapMaybe, isNothing )
import Data.List ( intersperse, unfoldr )

import Text.ParserCombinators.Parsec
import Printer ( Doc, text, (<+>), (<>), parens, renderString, vcat )
import System (getArgs)
import Control.Monad.Writer
import System.Time ( formatCalendarTime )
import System.Locale ( defaultTimeLocale, rfc822DateFormat )
import System.IO (hPutStr, stderr)

type ChangeLogEntry p = ([Matcher p], Maybe Doc)

main :: IO ()
main = withRepository [] $- \repository -> do
    boring_filter <- boring_file_filter
    entries <- liftM concat $ do
        fnames <- boring_filter `liftM` getArgs
        mapM loadEntryFile fnames

    history <- do
        full_backward_history <- concatRL `liftM` read_repo repository
        return $
            reverse $
            takeWhile (not . (apply_matcher matchTag_oldDarcs)) $
            unsafeUnRL $ full_backward_history

    let (unmatched, docs) = runWriter (foldM processPatch entries history)

    putStr (renderDocs (reverse docs))

    when (not (null unmatched)) $ do
        hPutStr stderr $ concat
            [ "\nunmatched ChangeLog entries (upcoming?):\n\n"
            , renderDocs (mapMaybe snd unmatched)
            ]

    let unlogged = filter (\p -> not $ any (`apply_matcher` p) allpatterns)
                 . filter (isNothing . pi_tag . info) $ history
         where allpatterns = concat $ map fst entries
    when (not (null unlogged)) $ do
        hPutStr stderr $ concat
           [ "\npatches which have not yet been changelogged\n\n"
           , renderDocs $ map (showFriendly [Summary]) unlogged
           ]

  where
    processPatch entries patch = do
        let pinfo = info patch
        entries' <- liftM catMaybes $ (`mapM` entries) $ \(patterns, descr) -> do
            let patterns' = filter (not . (`apply_matcher` patch)) patterns
            if null patterns'
              then do
                case descr of
                 Nothing -> return ()
                 Just d  -> tell [d]
                return Nothing
              else do
                return (Just (patterns', descr))
        when (matchTag `apply_matcher` patch) $ do
            let 'T':'A':'G':' ':tagName = just_name pinfo
            when (isStableTag tagName) $ do
                tell [text (" -- " ++ just_author pinfo ++ "  " ++ show_pi_date pinfo)]
                tell [text "darcs" <+> parens (text tagName)]
        return entries'

show_pi_date :: PatchInfo -> String
show_pi_date pinfo =
    formatCalendarTime defaultTimeLocale rfc822DateFormat (pi_date pinfo)

matchTag :: Patchy p => Matcher p
matchTag = match_pattern (PatternMatch "name \"^TAG \"")

matchTag_oldDarcs :: Patchy p => Matcher p
matchTag_oldDarcs = match_pattern (PatternMatch "exact \"TAG 2.0.2\"")

isStableTag :: String -> Bool
isStableTag tagName = 
    case parse p "" tagName of
        Left _ -> False
        Right _ -> True
  where
    p = do
        many1 digit
        char '.'
        many1 digit
        char '.'
        many1 digit
        many letter
        many digit
        eof

render :: Doc -> String
render = renderString

renderDocs :: [Doc] -> String
renderDocs = unlines . intersperse "" . map render

--------------------------------------------------------------------------------
-- Parsing ChangeLog entries

restOfLine :: CharParser st String
restOfLine = do
    rest <- many (noneOf "\r\n")
    optional (char '\r')
    return rest

formatEntry :: [String] -> Doc
formatEntry descr =
    vcat . indent 2 '*' . map text
         . wrap 75
         . unwords . concatMap words $ descr

wrap :: Int -> String -> [String]
wrap n = unfoldr cut
 where
   cut ""  = Nothing
   cut str =
    let (xy,z)  = splitAt n str
        (_y,_x) = break isSpace (reverse xy)
        x = reverse . drop 1 $ _x
        y = reverse _y
    in Just $ if null z then      (xy, z)
              else if null x then (y ++ z, "")
                   else           (x, y ++ z)

indent :: Int -> Char -> [Doc] -> [Doc]
indent _ _ []      = []
indent n b (x:xs)  = prefixH x : map prefixT xs
 where
   prefixH h = padding n <> text [b] <+> h
   prefixT t = padding (n+1)         <+> t
   padding p = text . take p . repeat $ ' '

entry :: Patchy p => CharParser st (ChangeLogEntry p)
entry =
 do emptyLine
    e <- matchEntry <|> ignoreEntry
    emptyLine
    return e

ignoreEntry :: Patchy p => CharParser st (ChangeLogEntry p)
ignoreEntry = do
    patterns <- many1 $ do
        try (string "ignore:")
        startPos <- getPosition
        -- take the characters to the end of line
        s <- restOfLine
        endState <- getParserState
        -- and parse them again as a match pattern
        setPosition startPos
        setInput s
        m <- match_parser
        -- restore parser state
        setParserState endState
        newline
        return (make_matcher s m)
    return (patterns, Nothing)

matchEntry :: Patchy p => CharParser st (ChangeLogEntry p)
matchEntry = do
    patterns <- many1 $ do
        try (string "match:")
        startPos <- getPosition
        -- take the characters to the end of line
        s <- restOfLine
        endState <- getParserState
        -- and parse them again as a match pattern
        setPosition startPos
        setInput s
        m <- match_parser
        -- restore parser state
        setParserState endState
        newline
        return (make_matcher s m)
    emptyLine
    descr <- many1 $ do
        char '>'
        skipMany (char ' ' <|> char '\t')
        cs <- restOfLine
        newline
        return cs
    return (patterns, Just $ formatEntry descr)

emptyLine :: CharParser st ()
emptyLine = skipMany $ do optional (char '#' >> restOfLine)
                          newline

entryFile :: Patchy p => CharParser st [ChangeLogEntry p]
entryFile = do es <- many entry
               eof
               return es

loadEntryFile :: Patchy p => FilePath -> IO [ChangeLogEntry p]
loadEntryFile fname = do
    cs <- readFile fname -- ratify readFile: not part of darcs executable
    case parse entryFile fname cs of
        Left err -> fail (show err)
        Right x -> return x

