%  Copyright (C) 2002-2005 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.


\begin{code}
module PatchShow ( writePatch, gzWritePatch, showPatch, showNamedPrefix, showUnnamed )
             where

import Prelude hiding ( pi )

import PatchInfo ( PatchInfo, showPatchInfo )
import FastPackedString ( PackedString, lengthPS, dropPS,
                          takePS, fromPS2Hex )
import FileName ( FileName )
import Lock ( writeDocBinFile, gzWriteDocFile, )
import Printer ( Doc, renderString, vcat,
                 text, userchunk, invisibleText, invisiblePS, blueText,
                 ($$), (<+>), (<>), prefix, userchunkPS, redText,
               )
import PatchCore ( Patch(..), DirPatchType(..), FilePatchType(..), fn2d, )
\end{code}

\section{Patch string formatting}

Of course, in order to store our patches in a file, we'll have to save them
as some sort of strings.  The convention is that each patch string will end
with a newline, but on parsing we skip any amount of whitespace between
patches.
\begin{code}
instance Show Patch  where
    show p = renderString (showPatch p) ++ "\n"

showPatch :: Patch -> Doc
showPatch (FP f AddFile) = showAddFile f
showPatch (FP f RmFile)  = showRmFile f
showPatch (FP f (Hunk line old new))  = showHunk f line old new
showPatch (FP f (TokReplace t old new))  = showTok f t old new
showPatch (FP f (Binary old new))  = showBinary f old new
showPatch (DP d AddDir) = showAddDir d
showPatch (DP d RmDir)  = showRmDir d
showPatch (Move f f') = showMove f f'
showPatch (ChangePref p f t) = showChangePref p f t
showPatch (ComP ps)  = showComP ps
showPatch (Split ps)  = showSplit ps
showPatch (NamedP n d p) = showNamed n d p
showPatch (Merger b g _ _ p1 p2) = showMerger b g p1 p2
showPatch (Conflictor inv a b) = showConflictor inv a b

writePatch :: FilePath -> Patch -> IO ()
writePatch f p = writeDocBinFile f $ showPatch p <> text "\n"

gzWritePatch :: FilePath -> Patch -> IO ()
gzWritePatch f p = gzWriteDocFile f $ showPatch p <> text "\n"
\end{code}

\paragraph{Composite patch}
A patch made up of a few other patches.
\begin{verbatim}
{
  <put patches here> (indented two)
}
\end{verbatim}
\begin{code}
showComP :: [Patch] -> Doc
showComP ps = blueText "{"
           $$ vcat (map showPatch ps)
           $$ blueText "}"
\end{code}

\paragraph{Split patch}
A split patch is similar to a composite patch (identical in how it's
stored), but rather than being composed of several patches grouped
together, it is created from one patch that has been split apart, typically
through a merge or commutation.
\begin{verbatim}
(
  <put patches here> (indented two)
)
\end{verbatim}
\begin{code}
showSplit :: [Patch] -> Doc
showSplit ps = blueText "("
            $$ vcat (map showPatch ps)
            $$ blueText ")"
\end{code}

\paragraph{Hunk}
Replace a hunk (set of contiguous lines) of text with a new
hunk.
\begin{verbatim}
hunk FILE LINE#
-LINE
...
+LINE
...
\end{verbatim}
\begin{code}
showHunk :: FileName -> Int -> [PackedString] -> [PackedString] -> Doc
showHunk f line old new =
           blueText "hunk" <+> fn2d f <+> text (show line)
        $$ prefix "-" (vcat $ map userchunkPS old)
        $$ prefix "+" (vcat $ map userchunkPS new)
\end{code}

\paragraph{Token replace}

Replace a token with a new token.  Note that this format means that
whitespace must not be allowed within a token.  If you know of a practical
application of whitespace within a token, let me know and I may change
this.
\begin{verbatim}
replace FILENAME [REGEX] OLD NEW
\end{verbatim}
\begin{code}
showTok :: FileName -> String -> String -> String -> Doc
showTok f t o n = blueText "replace" <+> fn2d f
                                     <+> text "[" <> userchunk t <> text "]"
                                     <+> userchunk o
                                     <+> userchunk n
\end{code}

\paragraph{Binary file modification}

Modify a binary file
\begin{verbatim}
binary FILENAME
oldhex
*HEXHEXHEX
...
newhex
*HEXHEXHEX
...
\end{verbatim}
\begin{code}
showBinary :: FileName -> PackedString -> PackedString -> Doc
showBinary f o n =
    blueText "binary" <+> fn2d f
 $$ invisibleText "oldhex"
 $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex o)
 $$ invisibleText "newhex"
 $$ (vcat $ map makeprintable $ break_every 78 $ fromPS2Hex n)
     where makeprintable ps = invisibleText "*" <> invisiblePS ps

break_every :: Int -> PackedString -> [PackedString]
break_every n ps | lengthPS ps < n = [ps]
                 | otherwise = takePS n ps : break_every n (dropPS n ps)
\end{code}

\paragraph{Add file}
Add an empty file to the tree.

\verb!addfile filename!
\begin{code}
showAddFile :: FileName -> Doc
showAddFile f = blueText "addfile" <+> fn2d f
\end{code}

\paragraph{Remove file}
Delete a file from the tree.

\verb!rmfile filename!
\begin{code}
showRmFile :: FileName -> Doc
showRmFile f = blueText "rmfile" <+> fn2d f
\end{code}

\paragraph{Move}
Rename a file or directory.

\verb!move oldname newname!
\begin{code}
showMove :: FileName -> FileName -> Doc
showMove d d' = blueText "move" <+> fn2d d <+> fn2d d'
\end{code}

\paragraph{Change Pref}
Change one of the preference settings.  Darcs stores a number of simple
string settings.  Among these are the name of the test script and the name
of the script that must be called prior to packing in a make dist.
\begin{verbatim}
changepref prefname
oldval
newval
\end{verbatim}
\begin{code}
showChangePref :: String -> String -> String -> Doc
showChangePref p f t = blueText "changepref" <+> text p
                    $$ userchunk f
                    $$ userchunk t
\end{code}

\paragraph{Add dir}
Add an empty directory to the tree.

\verb!adddir filename!
\begin{code}
showAddDir :: FileName -> Doc
showAddDir d = blueText "adddir" <+> fn2d d
\end{code}

\paragraph{Remove dir}
Delete a directory from the tree.

\verb!rmdir filename!
\begin{code}
showRmDir :: FileName -> Doc
showRmDir d = blueText "rmdir" <+> fn2d d
\end{code}

\paragraph{Merger patches}
Merge two patches.  The MERGERVERSION is included to allow some degree of
backwards compatibility if the merger algorithm needs to be changed.
\begin{verbatim}
merger MERGERVERSION
<first patch>
<second patch>
\end{verbatim}
\begin{code}
showMerger :: Bool -> String -> Patch -> Patch -> Doc
showMerger forwards g p1 p2 = blueText merger_name <+> text g <+> blueText "("
                           $$ showPatch p1
                           $$ showPatch p2
                           $$ blueText ")"
    where merger_name = if forwards then "merger" else "regrem"
\end{code}

\paragraph{Conflictor patches}
The conflictor patch type is the replacement for the old merger patch
type.  FIXME: More explanation should be added here.
\begin{verbatim}
conflict
<CONFLICTING PATCH SEQUENCE>
with
<OLDER PATCH SEQUENCE>
tcilfnoc
\end{verbatim}

\begin{code}
showConflictor :: Bool -> [Patch] -> [Patch] -> Doc
showConflictor inv a b = redText startconfl
                         $$ vcat (map showPatch b)
                         $$ redText "with"
                         $$ vcat (map showPatch a)
                         $$ redText endconfl
    where startconfl = if not inv then "conflict" else "tcilfnoc"
          endconfl = if not inv then "done_conflict" else "done_tcilfnoc"
\end{code}

\paragraph{Named patches}

Named patches are displayed as a ``patch id'' which is in square brackets,
followed by a patch.  Optionally, after the patch id (but before the patch
itself) can come a list of dependencies surrounded by angle brackets.  Each
dependency consists of a patch id.

\begin{code}
showNamedPrefix :: PatchInfo -> [PatchInfo] -> Doc
showNamedPrefix n d = showPatchInfo n
                   $$ blueText "<"
                   $$ vcat (map showPatchInfo d)
                   $$ blueText ">"

showNamed :: PatchInfo -> [PatchInfo] -> Patch -> Doc
showNamed n [] p = showPatchInfo n <> showPatch p
showNamed n d p = showNamedPrefix n d <+> showPatch p

showUnnamed :: Patch -> Doc
showUnnamed (NamedP _ _ p) = showUnnamed p
showUnnamed (ComP ps) = vcat (map showPatch ps) -- leave out braces...
showUnnamed p = showPatch p
\end{code}
