%  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}
module PatchChoices ( 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, tp_patch,
                    ) where

import Monad ( liftM )

import System.IO.Unsafe ( unsafePerformIO )
import Data.IORef ( newIORef, writeIORef, readIORef )
import Patch
#include "impossible.h"
\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}
type Tag = Integer
data TaggedPatch = TP Tag Patch
data PatchChoice = PC TaggedPatch (Maybe Bool)
newtype PatchChoices = PCs EasyPC
type EasyPC = [PatchChoice]

instance Eq TaggedPatch where
    TP t1 _ == TP t2 _ = t1 == t2

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

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

invertTP :: TaggedPatch -> TaggedPatch
invertTP = liftTP invert

patch_choices :: [Patch] -> PatchChoices
patch_choices = fst . patch_choices_tps

patch_choices_tps :: [Patch] -> (PatchChoices, [TaggedPatch])
patch_choices_tps ps = let tps = zipWith TP [1..] ps
                       in (PCs $ zipWith PC tps (repeat Nothing), tps)

get_first_choice :: PatchChoices -> [TaggedPatch]
get_last_choice :: PatchChoices -> [TaggedPatch]
get_middle_choice :: PatchChoices -> [TaggedPatch]

force_matching_first :: (TaggedPatch -> Bool) -> PatchChoices -> PatchChoices
force_matching_last :: (TaggedPatch -> Bool) -> PatchChoices -> PatchChoices
force_firsts :: [TaggedPatch] -> PatchChoices -> PatchChoices
force_first :: TaggedPatch -> PatchChoices -> PatchChoices
force_lasts :: [TaggedPatch] -> PatchChoices -> PatchChoices
force_last :: TaggedPatch -> PatchChoices -> PatchChoices
make_uncertain :: TaggedPatch -> PatchChoices -> PatchChoices
make_everything_later :: PatchChoices -> PatchChoices

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

\begin{code}
reverse_easy :: EasyPC -> EasyPC
reverse_easy
 = reverse . map (\(PC tp mf) -> PC (invertTP tp) (liftM not mf))

reverse_tagged_patches :: [TaggedPatch] -> [TaggedPatch]
reverse_tagged_patches tps = reverse $ map invertTP tps

separate_first_from_middle_last :: PatchChoices
                                -> ([TaggedPatch], [TaggedPatch])
separate_first_from_middle_last (PCs e) = pull_only_firsts e

separate_first_middle_from_last :: PatchChoices
                                -> ([TaggedPatch], [TaggedPatch])
separate_first_middle_from_last (PCs e) = pull_firsts_middles e

separate_last_from_first_middle :: PatchChoices
                                -> ([TaggedPatch], [TaggedPatch])
separate_last_from_first_middle pc =
    (get_last_choice pc, get_first_choice pc ++ get_middle_choice pc)

separate_middle_last_from_first :: PatchChoices
                                -> ([TaggedPatch], [TaggedPatch])
separate_middle_last_from_first pc =
    (get_middle_choice pc ++ get_last_choice pc, get_first_choice pc)

get_first_choice (PCs e) = fst $ pull_firsts e
get_last_choice (PCs e)
 = reverse_tagged_patches $ fst $ pull_firsts $ reverse_easy e
get_middle_choice (PCs e) =
    [ tp | PC tp _ <- reverse_easy $ snd $ pull_firsts $ reverse_easy
                                   $ snd $ pull_firsts e ]

pull_firsts_middles :: EasyPC -> ([TaggedPatch], [TaggedPatch])
pull_firsts_middles easyPC =
    let r = unsafePerformIO
          $ newIORef (error "pull_firsts_middles called badly")
        f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` []
        f acc (PC tp (Just False):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_firsts :: EasyPC -> ([TaggedPatch], [TaggedPatch])
pull_only_firsts easyPC =
    let r = unsafePerformIO
          $ newIORef (error "pull_only_firsts called badly")
        f acc [] = unsafePerformIO (writeIORef r (reverse acc)) `seq` []
        f acc (PC (TP t p) (Just True):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_middles_lasts :: EasyPC -> ([TaggedPatch], [TaggedPatch])
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 -> ([TaggedPatch], [TaggedPatch])
--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))

commute_up_list :: Patch -> [TaggedPatch] -> ([TaggedPatch], Patch)
commute_up_list p [] = ([], p)
commute_up_list pat (TP t p:tps)
 = case commute (pat, p) of
       Just (p', pat') -> let (tps', pat'') = commute_up_list pat' tps
                          in (TP t p':tps', pat'')
       Nothing -> impossible

pull_firsts :: EasyPC -> ([TaggedPatch], EasyPC)
pull_firsts e = case pull_first e of
                Nothing -> ([],e)
                Just (p,e') -> case pull_firsts e' of
                               (ps,e'') -> (p:ps,e'')

pull_lasts :: EasyPC -> ([TaggedPatch], EasyPC)
pull_lasts e = case pull_firsts $ reverse_easy e of
               (ps,e') -> (reverse_tagged_patches ps, reverse_easy e')

pull_first :: EasyPC -> Maybe (TaggedPatch, EasyPC)
pull_first [] = 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 (p2,p) of
        Just (p',p2') -> 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 (p2,p) of
        Just (p',p2') -> 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'
        ipf [] = error "Aaack in ipf! Please report this bug."

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

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

force_matching_first m (PCs e) =
    let thd (PC (TP t _) _) = t
        xs = m2ids m e
        not_needed = map thd $ snd $ pull_firsts $ set_simplys xs True e
        ch pc@(PC tp@(TP t _) _)
         | t `elem` not_needed = pc
         | otherwise = PC tp (Just True)
    in PCs $ map ch e
force_firsts ps pc = force_matching_first (`elem` ps) pc
force_first p pc = force_matching_first (== p) pc

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

reverse_pc :: PatchChoices -> PatchChoices
reverse_pc (PCs e) = PCs $ reverse_easy e

force_matching_last m (PCs e) =
    let thd (PC (TP t _) _) = t
        xs = m2ids m e
        not_needed = map thd $ snd $ pull_lasts $ set_simplys xs False e
        ch pc@(PC tp@(TP t _) _)
         | t `elem` not_needed = pc
         | otherwise = PC tp (Just False)
    in PCs $ map ch e

force_last p pc = reverse_pc $ force_first (invertTP p) $ reverse_pc pc
force_lasts ps pc = reverse_pc $ force_firsts (map invertTP ps) $ reverse_pc pc

make_uncertain tp (PCs e) = PCs $ map ch e
    where ch pc@(PC x _) = if tp == x then PC tp Nothing else pc

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

