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


\section{Patch relationships}

\begin{code}
module Darcs.Patch.Core
       ( Patch(..), DirPatchType(..), FilePatchType(..),
         nubAdjBy, invert, null_patch, is_null_patch, infopatch,
         flatten, flatten_to_primitives, merger_undo, n_fn, fn2d,
         adddeps, adddir, addfile, binary, changepref,
         hunk, move, namepatch, rmdir, rmfile, tokreplace,
         join_patches, unjoin_patches, getdeps,
         is_addfile, is_hunk, is_binary, is_merger, is_setpref,
         is_similar, is_adddir, patch2patchinfo, patchname,
         filenames,
       )
       where

import Prelude hiding ( pi )
import FastPackedString ( PackedString, nullPS )
import FileName ( FileName, fn2ps, fn2fp, fp2fn, norm_path )
import Darcs.Patch.Info ( PatchInfo, patchinfo, make_filename, invert_name )
import Printer ( Doc, packedString )
#include "impossible.h"

data Patch = NamedP !PatchInfo ![PatchInfo] !Patch
           | Move !FileName !FileName
           | DP !FileName !DirPatchType
           | FP !FileName !FilePatchType
           | Split [Patch]
           | ComP [Patch]
           | Merger !Bool !String Patch [Patch] Patch Patch
           | ChangePref !String !String !String

data FilePatchType = RmFile | AddFile
                   | Hunk !Int [PackedString] [PackedString]
                   | TokReplace !String !String !String
                   | Binary PackedString PackedString
                     deriving (Eq,Ord)

data DirPatchType = RmDir | AddDir
                    deriving (Eq,Ord)

nubAdjBy :: (a -> a -> Bool) -> [a] -> [a]
nubAdjBy _ [] = []
nubAdjBy _ [x] = [x]
nubAdjBy f (x:y:xs) = if f x y then nubAdjBy f (x:xs)
                               else x:nubAdjBy f (y:xs)

fn2d :: FileName -> Doc
fn2d f = packedString $ fn2ps f

null_patch :: Patch
null_patch = join_patches []

is_null_patch :: Patch -> Bool
is_null_patch (ComP ps) = all is_null_patch ps
is_null_patch (FP _ (Binary x y)) = nullPS x && nullPS y
is_null_patch (FP _ (Hunk _ [] [])) = True
is_null_patch _ = False

is_merger :: Patch -> Bool
is_merger (Merger _ _ _ _ _ _) = True
is_merger _ = False

merger_undo :: Patch -> Patch
merger_undo (Merger _ _ undo _ _ _) = undo
merger_undo _ = impossible
\end{code}

%FIXME: The following code needs to be moved.  It is a function
%``is\_similar'' which tells you if two patches are in the same category
%human-wise.  Currently it just returns true if they are filepatches on the
%same file.

\begin{code}
is_similar :: Patch -> Patch -> Bool
is_similar (FP f _) (FP f' _) = f == f'
is_similar (DP f _) (DP f' _) = f == f'
is_similar _ _ = False

is_addfile :: Patch -> Bool
is_addfile (FP _ AddFile) = True
is_addfile _ = False

is_adddir :: Patch -> Bool
is_adddir (DP _ AddDir) = True
is_adddir _ = False

is_hunk :: Patch -> Bool
is_hunk (FP _ (Hunk _ _ _)) = True
is_hunk _ = False

is_binary :: Patch -> Bool
is_binary (FP _ (Binary _ _)) = True
is_binary _ = False

is_setpref :: Patch -> Bool
is_setpref (ChangePref _ _ _) = True
is_setpref _ = False
\end{code}

%Another nice thing to be able to do with composite patches is to `flatten'
%them, that is, turn them into a simple list of patches (appropriately
%ordered, of course), with all nested compositeness unnested.

\begin{code}
{- INLINE flatten -}
flatten :: Patch -> [Patch]

flatten (ComP ps) = concatMap flatten ps
flatten p = [p]

{- INLINE flatten_to_primitives -}
flatten_to_primitives :: Patch -> [Patch]

flatten_to_primitives (ComP ps) = concatMap flatten_to_primitives ps
flatten_to_primitives (NamedP _ _ p) = flatten_to_primitives p
flatten_to_primitives p = [p]

\end{code}

\begin{code}
addfile :: FilePath -> Patch
rmfile :: FilePath -> Patch
adddir :: FilePath -> Patch
rmdir :: FilePath -> Patch
move :: FilePath -> FilePath -> Patch
changepref :: String -> String -> String -> Patch
hunk :: FilePath -> Int -> [PackedString] -> [PackedString] -> Patch
tokreplace :: FilePath -> String -> String -> String -> Patch
binary :: FilePath -> PackedString -> PackedString -> Patch
join_patches :: [Patch] -> Patch
unjoin_patches :: Patch -> Maybe [Patch]
namepatch :: String -> String -> String -> [String] -> Patch -> Patch
infopatch :: PatchInfo -> Patch -> Patch
adddeps :: Patch -> [PatchInfo] -> Patch
getdeps :: Patch -> [PatchInfo]

evalargs :: (a -> b -> c) -> a -> b -> c
evalargs f x y = (f $! x) $! y

addfile f = FP (fp2fn $ n_fn f) AddFile
rmfile f = FP (fp2fn $ n_fn f) RmFile
adddir d = DP (fp2fn $ n_fn d) AddDir
rmdir d = DP (fp2fn $ n_fn d) RmDir
move f f' = Move (fp2fn $ n_fn f) (fp2fn $ n_fn f')
changepref p f t = ChangePref p f t
hunk f line old new = evalargs FP (fp2fn $ n_fn f) (Hunk line old new)
tokreplace f tokchars old new =
    evalargs FP (fp2fn $ n_fn f) (TokReplace tokchars old new)
binary f old new = FP (fp2fn $! n_fn f) $ Binary old new
join_patches ps = ComP $! ps
unjoin_patches (ComP ps) = Just ps
unjoin_patches _ = Nothing
namepatch date name author desc p
    | '\n' `elem` name = error "Patch names cannot contain newlines."
    | otherwise = NamedP (patchinfo date name author desc) [] p
infopatch pi p = NamedP pi [] p
adddeps (NamedP pi ds p) ds' = NamedP pi (ds++ds') p
adddeps _ _ = bug "can't adddeps to anything but named patch"
getdeps (NamedP _ ds _) = ds
getdeps _ = bug "can't getdeps on anything but named patch"

patch2patchinfo :: Patch -> Maybe PatchInfo
patch2patchinfo (NamedP i _ _) = Just i
patch2patchinfo _ = Nothing

patchname :: Patch -> Maybe String
patchname (NamedP i _ _) = Just $ make_filename i
patchname _ = Nothing
\end{code}

\begin{code}
n_fn :: FilePath -> FilePath
n_fn f = "./"++(fn2fp $ norm_path $ fp2fn f)
\end{code}

The simplest relationship between two patches is that of ``sequential''
patches, which means that the context of the second patch (the one on the
left) consists of the first patch (on the right) plus the context of the
first patch.  The composition of two patches (which is also a patch) refers
to the patch which is formed by first applying one and then the other.  The
composition of two patches, $P_1$ and $P_2$ is represented as $P_2P_1$,
where $P_1$ is to be applied first, then $P_2$\footnote{This notation is
inspired by the notation of matrix multiplication or the application of
operators upon a Hilbert space.  In the algebra of patches, there is
multiplication (i.e.\ composition), which is associative but not
commutative, but no addition or subtraction.}

There is one other very useful relationship that two patches can have,
which is to be parallel patches, which means that the two patches have an
identical context (i.e.\ their representation applies to identical trees).
This is represented by $P_1\parallel P_2$.  Of course, two patches may also
have no simple relationship to one another.  In that case, if you want to
do something with them, you'll have to manipulate them with respect to
other patches until they are either in sequence or in parallel.

The most fundamental and simple property of patches is that they must be
invertible.  The inverse of a patch is described by: $P^{ -1}$.  In the
darcs implementation, the inverse is required to be computable from
knowledge of the patch only, without knowledge of its context, but that
(although convenient) is not required by the theory of patches.
\begin{dfn}
The inverse of patch $P$ is $P^{ -1}$, which is the ``simplest'' patch for
which the composition \( P^{ -1} P \) makes no changes to the tree.
\end{dfn}
Using this definition, it is trivial to prove the following theorem
relating to the inverse of a composition of two patches.
\begin{thm} The inverse of the composition of two patches is
\[ (P_2 P_1)^{ -1} = P_1^{ -1} P_2^{ -1}. \]
\end{thm}
Moreover, it is possible to show that the right inverse of a patch is equal
to its left inverse.  In this respect, patches continue to be analogous to
square matrices, and indeed the proofs relating to these properties of the
inverse are entirely analogous to the proofs in the case of matrix
multiplication.  The compositions proofs can also readily be extended to
the composition of more than two patches.
\begin{code}
invert :: Patch -> Patch
invert (NamedP n d p)  = NamedP (invert_name n) (map invert_name d) (invert p)
invert (Merger b g undo unwindings p1 p2)
    = Merger (not b) g undo unwindings p1 p2
invert (FP f RmFile)  = FP f AddFile
invert (FP f AddFile)  = FP f RmFile
invert (FP f (Hunk line old new))  = FP f $ Hunk line new old
invert (FP f (TokReplace t o n)) = FP f $ TokReplace t n o
invert (FP f (Binary o n)) = FP f $ Binary n o
invert (DP d RmDir) = DP d AddDir
invert (DP d AddDir) = DP d RmDir
invert (Move f f') = Move f' f
invert (ChangePref p f t) = ChangePref p t f
-- I need to see if there is a combined map-reverse, which I think would
-- be more efficient.
invert (ComP ps)  = ComP (map invert (reverse ps))
invert (Split ps) = Split (map invert (reverse ps))
\end{code}

\begin{code}
-- Recurse on everything, these are potentially spoofed patches
filenames :: Patch -> [FileName]
filenames (NamedP _ _ p) = filenames p
filenames (Move f1 f2) = [f1, f2]
filenames (DP f _) = [f]
filenames (FP f _) = [f]
filenames (Split ps) = concatMap filenames ps
filenames (ComP ps) = concatMap filenames ps
filenames (Merger _ _ p ps p1 p2) = concatMap filenames (p:p1:p2:ps)
filenames (ChangePref _ _ _) = []
\end{code}

