{- | Formal inverses for patches that aren't really invertible. Note that
most the mixed {'Fwd','Rev'} cases for 'Commute' and 'Eq2' are just errors.
-}
module Darcs.Patch.Invertible
    ( Invertible
    , mkInvertible
    , fromPositiveInvertible
    , withInvertible
    ) where

import Darcs.Prelude

import Darcs.Patch.CommuteFn ( invertCommuter )
import Darcs.Patch.Ident
  ( Ident(..), PatchId, SignedId(..) )
import Darcs.Patch.Invert ( Invert(..) )
import Darcs.Patch.RepoPatch
    ( Apply(..)
    , Commute(..)
    , Eq2(..)
    , PrimPatchBase(..)
    , PatchInspect(..)
    , PatchListFormat(..)
    , ShowContextPatch(..)
    , ShowPatch(..)
    , ShowPatchBasic(..)
    )
import Darcs.Patch.Show ( ShowPatchFor(..) )
import Darcs.Patch.Witnesses.Ordered ((:>)(..))

-- | Wrapper type to allow formal inversion of patches which aren't really
-- invertible.
data Invertible p wX wY where
   Fwd :: p wX wY -> Invertible p wX wY
   Rev :: p wX wY -> Invertible p wY wX

-- | Wrap a patch to make it (formally) 'Invertible'. The result is initially
-- positive i.e. 'Fwd'.
mkInvertible :: p wX wY -> Invertible p wX wY
mkInvertible = Fwd

-- | Get the underlying patch from an 'Invertible', assuming (as a precondition)
-- that it is positive i.e. 'Fwd'.
fromPositiveInvertible :: Invertible p wX wY -> p wX wY
fromPositiveInvertible (Fwd p) = p
fromPositiveInvertible (Rev _) = error "precondition of fromPositiveInvertible"

-- | Run a function on the patch inside an 'Invertible'. The function has to be
-- parametric in the witnesses, so we can run it with both a 'Fwd' and a 'Rev'
-- patch.
withInvertible :: (forall wA wB. p wA wB -> r) -> Invertible p wX wY -> r
withInvertible f (Fwd p) = f p
withInvertible f (Rev p) = f p

instance Invert (Invertible p) where
  invert (Fwd p) = Rev p
  invert (Rev p) = Fwd p

instance Commute p => Commute (Invertible p) where
  commute (Fwd p :> Fwd q) = do
    q' :> p' <- commute (p :> q)
    return (Fwd q' :> Fwd p')
  commute pair@(Rev _ :> Rev _) = invertCommuter commute pair
  commute _ = error "cannote commute mixed Fwd/Rev"

instance Eq2 p => Eq2 (Invertible p) where
  Fwd p =\/= Fwd q = p =\/= q
  Rev p =\/= Rev q = p =/\= q
  _ =\/= _ = error "cannot compare mixed Fwd/Rev"

instance Apply p => Apply (Invertible p) where
  type ApplyState (Invertible p) = ApplyState p
  apply (Fwd p) = apply p
  apply (Rev p) = unapply p
  unapply (Fwd p) = unapply p
  unapply (Rev p) = apply p

data InvertibleId ident = InvertibleId Bool ident
  deriving (Eq, Ord)

instance Ord ident => SignedId (InvertibleId ident) where
  positiveId (InvertibleId inverted _) = inverted
  invertId (InvertibleId inverted theid) =
     InvertibleId (not inverted) theid

type instance PatchId (Invertible p) = InvertibleId (PatchId p)

instance Ident p => Ident (Invertible p) where
  ident (Fwd p) = InvertibleId False (ident p)
  ident (Rev p) = InvertibleId True  (ident p)

instance PatchInspect p => PatchInspect (Invertible p) where
  listTouchedFiles (Fwd p) = listTouchedFiles p
  listTouchedFiles (Rev p) = listTouchedFiles p
  hunkMatches f (Fwd p) = hunkMatches f p
  hunkMatches f (Rev p) = hunkMatches f p

instance PrimPatchBase p => PrimPatchBase (Invertible p) where
  type PrimOf (Invertible p) = PrimOf p

instance ShowPatchBasic p => ShowPatchBasic (Invertible p) where
  showPatch ForStorage = error "Invertible patches must not be stored"
  showPatch ForDisplay = withInvertible (showPatch ForDisplay)

instance ShowPatch p => ShowPatch (Invertible p) where
  -- note these are only used for display
  description = withInvertible description
  summary = withInvertible summary
  content = withInvertible content

instance ShowContextPatch p => ShowContextPatch (Invertible p) where
  showPatchWithContextAndApply ForStorage = error "Invertible patches must not be stored"
  showPatchWithContextAndApply ForDisplay = withInvertible (showPatchWithContextAndApply ForDisplay)

instance PatchListFormat p => PatchListFormat (Invertible p)
