%  Copyright (C) 2007 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}
{-# OPTIONS -fglasgow-exts #-}
#include "gadts.h"
module Darcs.Patch.Patchy ( Patchy,
                            Apply, apply,
                            Commute(..), commuteFL, commuteRL, commuteRLFL,
                            mergeFL,
                            ShowPatch(..),
                            ReadPatch, readPatch', bracketedFL, peekfor,
                            Invert(..), invertFL, invertRL ) where

import Control.Monad ( liftM )
import Data.Maybe ( fromJust )
import Data.Word ( Word8 )
import Data.List ( nub )

import Darcs.SlurpDirectory ( Slurpy )
import Darcs.Sealed ( Sealed(..) )
import Darcs.Patch.ReadMonads ( ParserM, lex_eof, peek_input, my_lex, work, alter_input )
import Darcs.Patch.Ordered
import Printer ( Doc, (<>), text )
import Darcs.Lock ( writeDocBinFile, gzWriteDocFile )
import Darcs.IO ( WriteableDirectory )
import Darcs.Flags ( DarcsFlag )
import FastPackedString ( PackedString, ifHeadThenTail, packString, dropWhitePS )
--import Darcs.ColourPrinter ( traceDoc )
--import Printer ( greenText, ($$) )

class (Apply p, Commute p, ShowPatch p, ReadPatch p, Invert p) => Patchy p where
-- instance (ShowPatch p, Invert p) => Patchy p where

class Apply p where
    apply :: WriteableDirectory m => [DarcsFlag] -> p C(x y) -> m ()

class Commute p where
    commute :: (p :> p) C(x y) -> Maybe ((p :> p) C(x y))
    commutex :: (p :< p) C(x y) -> Maybe ((p :< p) C(x y))
    commute (x :> y) = do x' :< y' <- commutex (y :< x)
                          return (y' :> x')
    commutex (x :< y) = do x' :> y' <- commute (y :> x)
                           return (y' :< x')
    merge :: (p :\/: p) C(x y) -> (p :/\: p) C(x y)
    list_touched_files :: p C(x y) -> [FilePath]

class Commute p => ShowPatch p where
    showPatch :: p C(x y) -> Doc
    showNicely :: p C(x y) -> Doc
    showNicely = showPatch
    showContextPatch :: Slurpy -> p C(x y) -> Doc
    showContextPatch _ p = showPatch p
    description :: p C(x y) -> Doc
    description = showPatch
    summary :: p C(x y) -> Doc
    summary = showPatch
    writePatch :: FilePath -> p C(x y) -> IO ()
    writePatch f p = writeDocBinFile f $ showPatch p <> text "\n"
    gzWritePatch :: FilePath -> p C(x y) -> IO ()
    gzWritePatch f p = gzWriteDocFile f $ showPatch p <> text "\n"
    thing :: p C(x y) -> String
    thing _ = "patch"
    things :: p C(x y) -> String
    things x = thing x ++ "s"

class ReadPatch p where
    readPatch' :: (ParserM m, Monad (m PackedString))
                  => Bool -> m PackedString (Maybe (Sealed (p C(x ))))

class MyEq p => Invert p where
    invert :: p C(x y) -> p C(y x)
    identity :: p C(x x)
    sloppyIdentity :: p C(x y) -> EqCheck C(x y)
    sloppyIdentity p = identity =\/= p

instance Apply p => Apply (FL p) where
    apply _ NilFL = return ()
    apply opts (p:>:ps) = apply opts p >> apply opts ps
instance Commute p => Commute (FL p) where
    commute (NilFL :> x) = Just (x :> NilFL)
    commute (x :> NilFL) = Just (NilFL :> x)
    commute (xs :> ys) = do ys' :> rxs' <- commuteRLFL (reverseFL xs :> ys)
                            return $ ys' :> reverseRL rxs'
    merge (NilFL :\/: x) = x :/\: NilFL
    merge (x :\/: NilFL) = NilFL :/\: x
    merge ((x:>:xs) :\/: ys) = fromJust $ do ys' :/\: x' <- return $ mergeFL (x :\/: ys)
                                             xs' :/\: ys'' <- return $ merge (ys' :\/: xs)
                                             return (ys'' :/\: (x' :>: xs'))
    list_touched_files xs = nub $ concat $ mapFL list_touched_files xs

mergeFL :: Commute p => (p :\/: FL p) C(x y) -> (FL p :/\: p) C(x y)
mergeFL (p :\/: NilFL) = NilFL :/\: p
mergeFL (p :\/: (x :>: xs)) = fromJust $ do x' :/\: p' <- return $ merge (p :\/: x)
                                            xs' :/\: p'' <- return $ mergeFL (p' :\/: xs)
                                            return ((x' :>: xs') :/\: p'')

commuteRLFL :: Commute p => (RL p :> FL p) C(x y) -> Maybe ((FL p :> RL p) C(x y))
commuteRLFL (NilRL :> ys) = Just (ys :> NilRL)
commuteRLFL (xs :> NilFL) = Just (NilFL :> xs)
commuteRLFL (xs :> y :>: ys) = do y' :> xs' <- commuteRL (xs :> y)
                                  ys' :> xs'' <- commuteRLFL (xs' :> ys)
                                  return (y' :>: ys' :> xs'')

commuteRL :: Commute p => (RL p :> p) C(x y) -> Maybe ((p :> RL p) C(x y))
commuteRL (z :<: zs :> w) = do w' :> z' <- commute (z :> w)
                               w'' :> zs' <- commuteRL (zs :> w')
                               return (w'' :> z' :<: zs')
commuteRL (NilRL :> w) = Just (w :> NilRL)

commuteFL :: Commute p => (p :> FL p) C(x y) -> Maybe ((FL p :> p) C(x y))
commuteFL (p :> NilFL) = Just (NilFL :> p)
commuteFL (q :> p :>: ps) = do p' :> q' <- commute (q :> p)
                               ps' :> q'' <- commuteFL (q' :> ps)
                               return (p' :>: ps' :> q'')

instance ReadPatch p => ReadPatch (FL p) where
    readPatch' want_eof = Just `liftM` read_patches
     where read_patches :: (ParserM m, Monad (m PackedString))
                           => m PackedString (Sealed (FL p C(x )))
           read_patches = do --tracePeek "starting FL read"
                             mp <- readPatch' False
                             case mp of
                               Just (Sealed p) -> do --tracePeek "found one patch"
                                                     Sealed ps <- read_patches
                                                     return $ Sealed (p:>:ps)
                               Nothing -> if want_eof
                                          then do --tracePeek "no more patches"
                                                  unit' <- lex_eof
                                                  case unit' of
                                                    () -> return $ Sealed NilFL
                                          else do --tracePeek "no more patches"
                                                  return $ Sealed NilFL
--           tracePeek x = do y <- peek_input
--                            traceDoc (greenText x $$ greenText (show $ sal_to_string y)) return ()

{-# INLINE bracketedFL #-}
bracketedFL :: (ReadPatch p, ParserM m, Monad (m PackedString)) => 
               Word8 -> Word8 -> m PackedString (Maybe (Sealed (FL p C(x))))
bracketedFL pre post =
    peekforw pre bfl (return Nothing)
        where bfl :: (ReadPatch p, ParserM m, Monad (m PackedString)) =>
                     m PackedString (Maybe (Sealed (FL p C(x))))
              bfl = peekforw post (return $ Just $ Sealed NilFL)
                                  (do Just (Sealed p) <- readPatch' False
                                      Just (Sealed ps) <- bfl
                                      return $ Just $ Sealed (p:>:ps))

{-# INLINE peekforw #-}
peekforw :: (ParserM m, Monad (m PackedString)) => Word8
         -> m PackedString a -> m PackedString a -> m PackedString a
peekforw w ifstr ifnot = do s <- peek_input
                            case ifHeadThenTail w $ dropWhitePS s of
                              Just s' -> alter_input (const s') >> ifstr
                              Nothing -> ifnot

peekforPS :: (ParserM m, Monad (m PackedString)) => PackedString
          -> m PackedString a -> m PackedString a -> m PackedString a
peekforPS ps ifstr ifnot = do s <- peek_input
                              case ((ps ==) . fst) `fmap` my_lex s of
                                Just True -> work my_lex >> ifstr
                                _ -> ifnot

{-# INLINE peekfor #-}
peekfor :: (ParserM m, Monad (m PackedString)) => String
        -> m PackedString a -> m PackedString a -> m PackedString a
peekfor str = peekforPS (packString str)

instance Apply p => Apply (RL p) where
    apply _ NilRL = return ()
    apply opts (p:<:ps) = apply opts ps >> apply opts p
instance Commute p => Commute (RL p) where
    commute (xs :> ys) = do fys' :> xs' <- commuteRLFL (xs :> reverseRL ys)
                            return (reverseFL fys' :> xs')
    merge (x :\/: y) = case merge (reverseRL x :\/: reverseRL y) of
                       (ry' :/\: rx') -> reverseFL ry' :/\: reverseFL rx'
    list_touched_files = list_touched_files . reverseRL
instance ReadPatch p => ReadPatch (RL p) where
    readPatch' want_eof = do Just (Sealed fl) <- readPatch' want_eof
                             return $ Just $ Sealed $ reverseFL fl

invertFL :: Invert p => FL p C(x y) -> RL p C(y x)
invertFL NilFL = NilRL
invertFL (x:>:xs) = invert x :<: invertFL xs

invertRL :: Invert p => RL p C(x y) -> FL p C(y x)
invertRL NilRL = NilFL
invertRL (x:<:xs) = invert x :>: invertRL xs

\end{code}

