%  Copyright (C) 2002-2004 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{darcs record}
\begin{code}
{-# OPTIONS -fglasgow-exts #-}
#include "gadts.h"
module Darcs.Patch.Choices ( PatchChoices, patch_choices, patch_choices_tps,
                      is_patch_first,
                      get_first_choice, get_middle_choice, get_last_choice,
                      separate_first_middle_from_last,
                      separate_first_from_middle_last,
                      separate_middle_last_from_first,
                      separate_last_from_first_middle,
                      force_first, force_firsts, force_last, force_lasts,
                      force_matching_first, force_matching_last,
                      select_all_middles,
                      make_uncertain, make_everything_later,
                      TaggedPatch, Tag, tag, tp_patch,
                    ) where

import System.IO.Unsafe ( unsafePerformIO )
import Data.IORef ( newIORef, writeIORef, readIORef )
import Darcs.Patch
import Darcs.Patch.Permutations ( commuteWhatWeCanRL )
import Darcs.Patch.Patchy ( Invert, Commute )
import Darcs.Patch.Ordered ( FL(..), RL(..), MyEq, unsafeCompare,
                             (:>)(..), (:\/:)(..), (:/\:)(..),
                             zipWithFL, mapFL_FL, mapFL,
                             (+>+), reverseRL )
\end{code}

PatchChoices divides a sequence of patches into three sets: ``first'',
``middle'' and ``last'', such that all patches can be applied, if you first
apply the first ones then the middle ones and then the last ones.
Obviously if there are dependencies between the patches that will put a
constraint on how you can choose to divide them up.  The PatchChoices data
type and associated functions are here to deal with many of the common
cases that come up when choosing a subset of a group of patches.

\verb!force_last! tells PatchChoices that a particular patch is required to be in
the ``last'' group, which also means that any patches that depend on it
must be in the ``last'' group.

Internally, a PatchChoices doesn't actually reorder the patches until it is
asked for the final output (e.g.\ by \verb!get_first_choice!).  Instead, each
patch is placed in a state of definitely first, definitely last and
undecided---undecided leans towards ``middle''.  In case you're wondering
about the first-middle-last language, it's because in some cases the
``yes'' answers will be last (as is the case for the revert command), and
in others first (as in record, pull and push).

\begin{code}
newtype Tag = TG Integer deriving ( Num, Show, Eq, Ord, Enum )
data TaggedPatch p C(x y) = TP Tag (p C(x y))
data PatchChoice p C(x y) = PC (TaggedPatch p C(x y)) (Maybe Bool)
newtype PatchChoices p = PCs (EasyPC p)
type EasyPC p = FL (PatchChoice p)

instance Eq (TaggedPatch p) where
    TP t1 _ == TP t2 _ = t1 == t2

tag :: TaggedPatch p -> Tag
tag (TP (TG t) _) = TG t

tp_patch :: TaggedPatch p -> p
tp_patch (TP _ p) = p

liftTP :: (p -> p) -> (TaggedPatch p -> TaggedPatch p)
liftTP f (TP t p) = TP t (f p)

instance MyEq p => MyEq (TaggedPatch p) where
    unsafeCompare (TP t1 p1) (TP t2 p2) = t1 == t2 && unsafeCompare p1 p2

instance Invert p => Invert (TaggedPatch p) where
    invert = liftTP invert
    identity = TP (-1) identity

instance Commute p => Commute (TaggedPatch p) where
    commute (TP t1 p1 :> TP t2 p2) = do p2' :> p1' <- commute (p1 :> p2)
                                        return (TP t2 p2' :> TP t1 p1')
    list_touched_files (TP _ p) = list_touched_files p
    merge (TP t1 p1 :\/: TP t2 p2) = case merge (p1 :\/: p2) of
                                     p2' :/\: p1' -> TP t2 p2' :/\: TP t1 p1'

patch_choices :: Patchy p => FL p C(x y) -> PatchChoices p C(x y)
patch_choices = fst . patch_choices_tps

patch_choices_tps :: Patchy p => FL p C(x y) -> (PatchChoices p C(x y), FL (TaggedPatch p) C(x y))
patch_choices_tps ps = let tps = zipWithFL TP [1..] ps
                       in (PCs $ zipWithFL (flip PC) (repeat Nothing) tps, tps)

force_matching_first :: Patchy p => (TaggedPatch p -> Bool) -> PatchChoices p -> PatchChoices p
make_everything_later :: Patchy p => PatchChoices p -> PatchChoices p

is_patch_first :: TaggedPatch p -> PatchChoices p -> Maybe Bool
\end{code}

\begin{code}
instance MyEq p => MyEq (PatchChoice p) where
    unsafeCompare (PC tp1 _) (PC tp2 _) = unsafeCompare tp1 tp2

instance Invert p => Invert (PatchChoice p) where
    invert (PC tp mf) = PC (invert tp) (fmap not mf)
    identity = PC identity Nothing

instance Commute p => Commute (PatchChoice p) where
    commute (PC t1 x1 :> PC t2 x2)
        = do t2' :> t1' <- commute (t1 :> t2)
             return (PC t2' x2 :> PC t1' x1)
    merge (PC t1 x1 :\/: PC t2 x2)
        = case merge (t1 :\/: t2) of
          t2' :/\: t1' -> PC t2' x2 :/\: PC t1' x1
    list_touched_files (PC t _) = list_touched_files t

invertSeq :: (Invert p, Invert q) => (p :> q) C(x y) -> (q :> p) C(y x)
invertSeq (x :> y) = (invert y :> invert x)

separate_first_from_middle_last :: Patchy p => PatchChoices p
                                -> (FL (TaggedPatch p) :> FL (TaggedPatch p))
separate_first_from_middle_last (PCs e) = pull_only_firsts e

separate_first_middle_from_last :: Patchy p => PatchChoices p
                                -> (FL (TaggedPatch p) :> FL (TaggedPatch p))
separate_first_middle_from_last (PCs e) = pull_firsts_middles e

separate_last_from_first_middle :: Patchy p => PatchChoices p
                                -> (FL (TaggedPatch p) :> FL (TaggedPatch p))
separate_last_from_first_middle pc =
    (get_first_choice pc +>+ get_middle_choice pc :> get_last_choice pc)

separate_middle_last_from_first :: Patchy p => PatchChoices p
                                -> (FL (TaggedPatch p) :> FL (TaggedPatch p))
separate_middle_last_from_first pc =
    (get_first_choice pc :> get_middle_choice pc +>+ get_last_choice pc)

get_first_choice :: Patchy p => PatchChoices p -> FL (TaggedPatch p)
get_first_choice (PCs e) = fst' $ pull_firsts e

get_last_choice :: Patchy p => PatchChoices p -> FL (TaggedPatch p)
get_last_choice (PCs e)
 = invert $ fst' $ pull_firsts $ invert e

get_middle_choice :: Patchy p => PatchChoices p -> FL (TaggedPatch p)
get_middle_choice (PCs e) = mapFL_FL pc2tp $
                            invert $ snd' $ pull_firsts $ invert
                                   $ snd' $ pull_firsts e
    where pc2tp (PC tp _) = tp

fst' :: (a :> b) -> a
fst' (x:>_) = x

snd' :: (a :> b) -> b
snd' (_:>x) = x

pull_firsts_middles :: Patchy p => EasyPC p -> (FL (TaggedPatch p) :> FL (TaggedPatch p))
pull_firsts_middles easyPC =
    let r = unsafePerformIO
          $ newIORef (error "pull_firsts_middles called badly")
        f :: Patchy p => RL (TaggedPatch p) C(x y) -> EasyPC p C(y z) -> FL (TaggedPatch p)
        f acc NilFL = unsafePerformIO (writeIORef r (reverseRL acc)) `seq` NilFL
        f acc (PC tp (Just False):>:e) = f (tp:<:acc) e
        f acc (PC tp _:>:e) = case commuteWhatWeCanRL (acc :> tp) of
                              more :> tp' :> acc' -> reverseRL more+>+tp':>:f acc' e
        xs = f NilRL easyPC
    in (xs :> unsafePerformIO (readIORef r))

pull_only_firsts :: Patchy p => EasyPC p -> (FL (TaggedPatch p) :> FL (TaggedPatch p))
pull_only_firsts easyPC =
    let r = unsafePerformIO
          $ newIORef (error "pull_only_firsts called badly")
        f :: Patchy p => RL (TaggedPatch p) C(x y) -> EasyPC p C(y z) -> FL (TaggedPatch p)
        f acc NilFL = unsafePerformIO (writeIORef r (reverseRL acc)) `seq` NilFL
        f acc (PC tp (Just True):>:e) = case commuteWhatWeCanRL (acc :> tp) of
                                        more :> tp' :> acc' -> reverseRL more+>+tp':>:f acc' e
        f acc (PC tp _:>:e) = f (tp:<:acc) e
        xs = f NilRL easyPC
    in (xs :> unsafePerformIO (readIORef r))

{-
pull_middles_lasts :: EasyPC p -> ([TaggedPatch p], [TaggedPatch p])
pull_middles_lasts easyPC =
    let r = unsafePerformIO
          $ newIORef (error "pull_middles_lasts called badly")
        f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` []
        f acc (PC tp (Just True):e) = f (tp:acc) e
        f acc (PC (TP t p) _:e) = case commute_up_list p acc of
                                  (acc', p') -> TP t p':f acc' e
        xs = f [] easyPC
    in (xs, unsafePerformIO (readIORef r))
-}

--pull_only_lasts :: EasyPC p -> ([TaggedPatch p], [TaggedPatch p])
--pull_only_lasts easyPC =
--    let r = unsafePerformIO
--          $ newIORef (error "pull_only_lasts called badly")
--        f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` []
--        f acc (PC (TP t p) (Just False):e) = case commute_up_list p acc of
--                                             (acc', p') -> TP t p':f acc' e
--        f acc (PC tp _:e) = f (tp:acc) e
--        xs = f [] easyPC
--    in (xs, unsafePerformIO (readIORef r))

pull_firsts :: Patchy p => EasyPC p -> (FL (TaggedPatch p) :> EasyPC p)
pull_firsts e = case pull_first e of
                Nothing -> (NilFL :> e)
                Just (p:>e') -> case pull_firsts e' of
                                (ps:>e'') -> (p:>:ps :> e'')

pull_lasts :: Patchy p => EasyPC p -> (EasyPC p :> FL (TaggedPatch p))
pull_lasts e = invertSeq $ pull_firsts $ invert e

pull_first :: Patchy p => EasyPC p -> Maybe (TaggedPatch p :> EasyPC p)
pull_first NilFL = Nothing
pull_first (PC tp (Just True):>:e) = Just (tp :> e)
pull_first (PC (TP t p) (Just False):>:e) =
    case pull_first e of
    Just (TP t2 p2 :> e') ->
        case commute (p:>p2) of
        Just (p2':>p') -> Just (TP t2 p2' :> PC (TP t p') (Just False):>:e')
        Nothing -> error "Aaack fixme!"
    Nothing -> Nothing
pull_first (PC tp@(TP t p) Nothing:>:e) =
    case pull_first e of
    Just (TP t2 p2 :> e') ->
        case commute (p:>p2) of
        Just (p2':>p') -> Just (TP t2 p2' :> (PC (TP t p') Nothing:>:e'))
        Nothing -> Just (tp :> PC (TP (-t2) p2) (Just True):>:e')
    Nothing -> Nothing
\end{code}

\begin{code}
is_patch_first tp (PCs e) = ipf e
  where ipf (PC a mb:>:e') | a == tp   = mb
                           | otherwise = ipf e'
        -- actually, the following should be impossible, but this is a reasonable answer
        ipf NilFL = Just False

set_simplys :: [Tag] -> Bool -> EasyPC p -> EasyPC p
set_simplys ts b e = mapFL_FL ch e
    where ch (PC tp@(TP t _) _)
           | t `elem` ts = PC tp (Just b)
           | otherwise   = PC tp Nothing

m2ids :: (TaggedPatch p -> Bool) -> EasyPC p -> [Tag]
m2ids m (PC tp@(TP t _) _:>:e)
 | m tp = t:m2ids m e
 | otherwise = m2ids m e
m2ids _ NilFL = []

force_matching_first m (PCs e) =
    let thd (PC (TP t _) _) = t
        xs = m2ids m e
        not_needed = case pull_firsts $ set_simplys xs True e of
                     _ :> rest -> mapFL thd rest
        ch pc@(PC tp@(TP t _) _)
         | t `elem` not_needed = pc
         | otherwise = PC tp (Just True)
    in PCs $ mapFL_FL ch e

force_firsts :: Patchy p => [Tag] -> PatchChoices p -> PatchChoices p
force_firsts ps pc = force_matching_first ((`elem` ps) . tag) pc

force_first :: Patchy p => Tag -> PatchChoices p -> PatchChoices p
force_first p pc = force_matching_first ((== p) . tag) pc

select_all_middles :: Patchy p => Bool -> PatchChoices p -> PatchChoices p
select_all_middles b (PCs e) = PCs (mapFL_FL f e)
    where f (PC tp Nothing) = PC tp (Just b')
          f pc = pc
          b' = not b

reverse_pc :: Patchy p => PatchChoices p -> PatchChoices p
reverse_pc (PCs e) = PCs $ invert e

force_matching_last :: Patchy p => (TaggedPatch p -> Bool) -> PatchChoices p -> PatchChoices p
force_matching_last m (PCs e) =
    let thd (PC (TP t _) _) = t
        xs = m2ids m e
        not_needed = case pull_lasts $ set_simplys xs False e of
                     rest :> _ -> mapFL thd rest
        ch pc@(PC tp@(TP t _) _)
         | t `elem` not_needed = pc
         | otherwise = PC tp (Just False)
    in PCs $ mapFL_FL ch e

force_last :: Patchy p => Tag -> PatchChoices p -> PatchChoices p
force_last p pc = reverse_pc $ force_first p $ reverse_pc pc

force_lasts :: Patchy p => [Tag] -> PatchChoices p -> PatchChoices p
force_lasts ps pc = reverse_pc $ force_firsts ps $ reverse_pc pc

make_uncertain :: Patchy p => Tag -> PatchChoices p -> PatchChoices p
make_uncertain t (PCs e) = PCs $ mapFL_FL ch e
    where ch pc@(PC x _) = if t == tag x then PC x Nothing else pc

make_everything_later (PCs e) = PCs $ mapFL_FL ch e
                               where ch (PC tp Nothing) = PC tp (Just False)
                                     ch (PC tp (Just True)) = PC tp Nothing
                                     ch x = x
\end{code}

