{-# LANGUAGE UndecidableInstances #-}
module Darcs.Patch.Named.Wrapped
  ( WrappedNamed(..)
  , fromRebasing
  ) where

import Darcs.Prelude

import Control.Applicative ( (<|>) )
import Data.Coerce ( coerce )

import Darcs.Patch.Apply ( Apply(..) )
import Darcs.Patch.Commute ( Commute(..) )
import Darcs.Patch.Effect ( Effect(..) )
import Darcs.Patch.Ident ( Ident(..), PatchId )
import Darcs.Patch.Format ( PatchListFormat(..), ListFormat )
import Darcs.Patch.Info ( PatchInfo, showPatchInfo )
import Darcs.Patch.FromPrim ( FromPrim, PrimPatchBase(..) )
import Darcs.Patch.Named ( Named(..), patch2patchinfo )
import Darcs.Patch.Read ( ReadPatch(..) )
import Darcs.Patch.Rebase.Suspended
  ( Suspended(..)
  , addFixupsToSuspended
  , removeFixupsFromSuspended
import Darcs.Patch.RepoPatch ( RepoPatch )
import Darcs.Patch.RepoType
  ( RepoType(..), IsRepoType(..), SRepoType(..)
  , RebaseType(..), SRebaseType(..)
import Darcs.Patch.Show ( ShowPatchBasic(..) )

import Darcs.Patch.Witnesses.Sealed ( mapSeal )
import Darcs.Patch.Witnesses.Show ( Show1, Show2 )
import Darcs.Patch.Witnesses.Ordered
  ( FL(..), mapFL_FL, (:>)(..)

-- |A patch that lives in a repository where an old-style rebase is in
-- progress. Such a repository will consist of @Normal@ patches
-- along with exactly one @Suspended@ patch.
-- It is here only so that we can upgrade an old-style rebase.
-- @NormalP@ represents a normal patch within a respository where a
-- rebase is in progress. @NormalP p@ is given the same on-disk
-- representation as @p@, so a repository can be switched into
-- and out of rebasing mode simply by adding or removing a
-- @RebaseP@ patch and setting the appropriate format flag.
-- Note that the witnesses are such that the @RebaseP@
-- patch has no effect on the context of the rest of the
-- repository; in a sense the patches within it are
-- dangling off to one side from the main repository.
data WrappedNamed (rt :: RepoType) p wX wY where
  NormalP :: !(Named p wX wY) -> WrappedNamed rt p wX wY
    :: (PrimPatchBase p, FromPrim p, Effect p)
    => !PatchInfo
    -> !(Suspended p wX wX)
    -> WrappedNamed ('RepoType 'IsRebase) p wX wX

deriving instance Show2 p => Show (WrappedNamed rt p wX wY)

instance Show2 p => Show1 (WrappedNamed rt p wX)

instance Show2 p => Show2 (WrappedNamed rt p)

fromRebasing :: WrappedNamed rt p wX wY -> Named p wX wY
fromRebasing (NormalP n) = n
fromRebasing (RebaseP {}) = error "internal error: found rebasing internal patch"

instance PrimPatchBase p => PrimPatchBase (WrappedNamed rt p) where
  type PrimOf (WrappedNamed rt p) = PrimOf p

type instance PatchId (WrappedNamed rt p) = PatchInfo

instance Ident (WrappedNamed rt p) where
  ident (NormalP p) = patch2patchinfo p
  ident (RebaseP name _) = name

instance PatchListFormat (WrappedNamed rt p)

instance (ShowPatchBasic p, PatchListFormat p)
  => ShowPatchBasic (WrappedNamed rt p) where

  showPatch f (NormalP n) = showPatch f n
  showPatch f (RebaseP i s) = showPatchInfo f i <> showPatch f s

-- This is a local hack to maintain backwards compatibility with
-- the on-disk format for rebases. Previously the rebase container
-- was internally represented via a 'Rebasing' type that sat *inside*
-- a 'Named', and so the rebase container patch had the structure
-- 'NamedP i [] (Suspendended s :>: NilFL)'. This structure was reflected
-- in the way it was saved on disk.
-- The easiest to read this structure is to use an intermediate type
-- that reflects the old structure.
-- TODO: switch to a more natural on-disk structure that directly
-- saves/reads 'RebaseP'.
data ReadRebasing p wX wY where
  ReadNormal    :: p wX wY -> ReadRebasing p wX wY
  ReadSuspended :: Suspended p wX wX -> ReadRebasing p wX wX

instance ( ReadPatch p, PrimPatchBase p, FromPrim p, Effect p, PatchListFormat p
         , RepoPatch p, IsRepoType rt
         ) => ReadPatch (WrappedNamed rt p) where
  readPatch' =
    case singletonRepoType :: SRepoType rt of
      SRepoType SIsRebase ->
        let wrapNamed :: Named (ReadRebasing p) wX wY -> WrappedNamed rt p wX wY
            wrapNamed (NamedP i [] (ReadSuspended s :>: NilFL))
               = RebaseP i s
            wrapNamed (NamedP i deps ps) = NormalP (NamedP i deps (mapFL_FL unRead ps))

            unRead (ReadNormal p) = p
            unRead (ReadSuspended _) = error "unexpected suspended patch"

        in fmap (mapSeal wrapNamed) readPatch'

      _ -> fmap (mapSeal NormalP) readPatch'

instance PatchListFormat p => PatchListFormat (ReadRebasing p) where
  patchListFormat = coerce (patchListFormat :: ListFormat p)

instance (ReadPatch p, PatchListFormat p, PrimPatchBase p, RepoPatch p) => ReadPatch (ReadRebasing p) where
  readPatch' =
       mapSeal toSuspended <$> readPatch'
    <|> mapSeal ReadNormal <$> readPatch'
      where -- needed to get a suitably polymorphic type
            toSuspended :: Suspended p wX wY -> ReadRebasing p wX wY
            toSuspended (Items ps) = ReadSuspended (Items ps)

instance Apply p => Apply (WrappedNamed rt p) where
  type ApplyState (WrappedNamed rt p) = ApplyState p
  apply (NormalP n) = apply n
  -- the data type definition claims that a 'RebaseP' has no effect,
  -- so make sure it really doesn't have any
  apply (RebaseP _ _) = return ()
  unapply (NormalP n) = unapply n
  unapply (RebaseP _ _) = return ()

instance Commute p => Commute (WrappedNamed rt p) where
  commute (NormalP n1 :> NormalP n2) = do
    n2' :> n1' <- commute (n1 :> n2)
    return (NormalP n2' :> NormalP n1')

  commute (RebaseP i1 s1 :> RebaseP i2 s2) =
    -- Two rebases in sequence must have the same starting context,
    -- so they should trivially commute.
    -- This case shouldn't actually happen since each repo only has
    -- a single Suspended patch.
    return (RebaseP i2 s2 :> RebaseP i1 s1)

  commute (NormalP n1 :> RebaseP i2 s2) =
    return (RebaseP i2 (addFixupsToSuspended n1 s2) :> NormalP n1)

  commute (RebaseP i1 s1 :> NormalP n2) =
    return (NormalP n2 :> RebaseP i1 (removeFixupsFromSuspended n2 s1))