-- 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.Repository ( read_repo, identifyRepository )
import Darcs.Repository.Prefs ( boring_file_filter )
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 )
import Data.Maybe ( catMaybes )
import Data.List ( intersperse )

import Text.ParserCombinators.Parsec
import Text.PrettyPrint ( Doc, text, (<+>), nest, parens, fsep,
                          renderStyle, Style(..), Mode(PageMode) )
import System (getArgs)
import Control.Monad.Writer
import System.Time ( formatCalendarTime )
import System.Locale ( defaultTimeLocale, rfc822DateFormat )
import System.IO (hPutStr, stderr)

type ChangeLogEntry = ([Matcher], Doc)

main :: IO ()
main = do
    boring_filter <- boring_file_filter
    entries <- liftM concat $ do
        fnames <- boring_filter `liftM` getArgs
        mapM loadEntryFile fnames

    history <- do
        repository <- identifyRepository [] "."
        full_backward_history <- liftM concat (read_repo repository)
        return $
            reverse $
            takeWhile (not . (apply_matcher matchTag_1_0_2)) $
            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 (map snd unmatched)
            ]
  where
    processPatch entries patch@(pinfo, _) = do
        entries' <- liftM catMaybes $ (`mapM` entries) $ \(patterns, descr) -> do
            let patterns' = filter (not . (`apply_matcher` patch)) patterns
            if null patterns'
              then do
                tell [descr]
                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 :: Matcher
matchTag = match_pattern (PatternMatch "name \"^TAG \"")

matchTag_1_0_2 :: Matcher
matchTag_1_0_2 = match_pattern (PatternMatch "exact \"TAG 1.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 doc = renderStyle style doc
  where
    style = Style { mode = PageMode,
                    lineLength = 78,
                    ribbonsPerLine = 1 }

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 =
    nest 2 (text "*") <+> fsep (map text (concatMap words descr))

entry :: CharParser st ChangeLogEntry
entry = do
    emptyLine
    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
    emptyLine
    return (patterns, formatEntry descr)

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

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

loadEntryFile :: FilePath -> IO [ChangeLogEntry]
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

