module Darcs.Patch.Prim.Class
    ( PrimConstruct(..)
    , PrimCoalesce(..)
    , PrimDetails(..)
    , PrimSift(..)
    , PrimShow(..)
    , PrimRead(..)
    , PrimApply(..)
    , PrimPatch
    , PrimMangleUnravelled(..)
    , Mangled
    , Unravelled
    , primCleanMerge
    )
    where

import Darcs.Prelude

import Darcs.Patch.Annotate.Class ( Annotate )
import Darcs.Patch.ApplyMonad ( ApplyMonad )
import Darcs.Patch.FileHunk ( FileHunk, IsHunk )
import Darcs.Patch.Format ( FileNameFormat, PatchListFormat )
import Darcs.Patch.Inspect ( PatchInspect )
import Darcs.Patch.Apply ( Apply(..), ObjectIdOfPatch )
import Darcs.Patch.Commute ( Commute(..) )
import Darcs.Patch.CommuteFn ( PartialMergeFn )
import Darcs.Patch.Invert ( Invert(..) )
import Darcs.Patch.Merge ( CleanMerge(..) )
import Darcs.Patch.Read ( ReadPatch )
import Darcs.Patch.Repair ( RepairToFL )
import Darcs.Patch.Show ( ShowPatch, ShowContextPatch )
import Darcs.Patch.SummaryData ( SummDetail )
import Darcs.Patch.Witnesses.Eq ( Eq2(..), EqCheck )
import Darcs.Patch.Witnesses.Ordered ( (:/\:)(..), (:>)(..), (:\/:)(..), FL )
import Darcs.Patch.Witnesses.Show ( Show2 )
import Darcs.Patch.Witnesses.Sealed ( Sealed )

import Darcs.Util.Parser ( Parser )
import Darcs.Util.Path ( AnchoredPath )
import Darcs.Util.Printer ( Doc )

import qualified Data.ByteString as B ( ByteString )


type PrimPatch prim =
    ( Annotate prim
    , Apply prim
    , CleanMerge prim
    , Commute prim
    , Invert prim
    , Eq2 prim
    , IsHunk prim
    , PatchInspect prim
    , RepairToFL prim
    , Show2 prim
    , PrimConstruct prim
    , PrimCoalesce prim
    , PrimDetails prim
    , PrimApply prim
    , PrimSift prim
    , PrimMangleUnravelled prim
    , ReadPatch prim
    , ShowPatch prim
    , ShowContextPatch prim
    , PatchListFormat prim
    )

class PrimConstruct prim where
   addfile :: AnchoredPath -> prim wX wY
   rmfile :: AnchoredPath -> prim wX wY
   adddir :: AnchoredPath -> prim wX wY
   rmdir :: AnchoredPath -> prim wX wY
   move :: AnchoredPath -> AnchoredPath -> prim wX wY
   changepref :: String -> String -> String -> prim wX wY
   hunk :: AnchoredPath -> Int -> [B.ByteString] -> [B.ByteString] -> prim wX wY
   tokreplace :: AnchoredPath -> String -> String -> String -> prim wX wY
   binary :: AnchoredPath -> B.ByteString -> B.ByteString -> prim wX wY
   primFromHunk :: FileHunk (ObjectIdOfPatch prim) wX wY -> prim wX wY

class (Commute prim, Eq2 prim, Invert prim) => PrimCoalesce prim where
   -- | Try to shrink the input sequence by getting rid of self-cancellations
   -- and identity patches or by coalescing patches. Also sort patches
   -- according to some internally defined order (specific to the patch type)
   -- as far as possible while respecting dependencies.
   -- A result of 'Nothing' means that we could not shrink the input.
   --
   -- This method is included in the class for optimization. Instances are free
   -- to use 'Darcs.Patch.Prim.Coalesce.defaultTryToShrink'.
   tryToShrink :: FL prim wX wY -> Maybe (FL prim wX wY)

   -- | This is similar to 'tryToShrink' but always gives back a result: if the
   -- sequence could not be shrunk we merely give back a sorted version.
   --
   -- This method is included in the class for optimization. Instances are free
   -- to use 'Darcs.Patch.Prim.Coalesce.defaultSortCoalesceFL'.
   sortCoalesceFL :: FL prim wX wY -> FL prim wX wY

   -- | Coalesce adjacent patches to one with the same effect.
   --
   -- prop> apply (primCoalesce p q) == apply p >> apply q
   primCoalesce :: prim wX wY -> prim wY wZ -> Maybe (prim wX wZ)

   -- | Whether prim patch has no effect at all and thus can be eliminated
   -- as far as coalescing is concerned.
   isIdentity :: prim wX wY -> EqCheck wX wY

   -- | Provide a total order between arbitrary patches that is consistent
   -- with 'Eq2':
   --
   -- prop> unsafeCompare p q == IsEq  <=>  comparePrim p q == EQ
   comparePrim :: prim wA wB -> prim wC wD -> Ordering

-- | Prim patches that support "sifting". This is the process of eliminating
-- changes from a sequence of prims that can be recovered by comparing states
-- (normally the pristine and working states), except those that other changes
-- depend on. In other words, changes to the content of (tracked) files. The
-- implementation is allowed and expected to shrink and coalesce changes in the
-- process.
class PrimSift prim where
  -- | Whether a prim is a candidate for sifting
  primIsSiftable :: prim wX wY -> Bool

class PrimDetails prim where
   summarizePrim :: prim wX wY -> [SummDetail]

class PrimShow prim where
   showPrim :: FileNameFormat -> prim wA wB -> Doc
   showPrimWithContextAndApply :: ApplyMonad  (ApplyState prim) m => FileNameFormat -> prim wA wB -> m Doc

class PrimRead prim where
   readPrim :: FileNameFormat -> Parser (Sealed (prim wX))

class PrimApply prim where
   applyPrimFL :: ApplyMonad (ApplyState prim) m => FL prim wX wY -> m ()

-- | A list of conflicting alternatives. They form a connected
-- component of the conflict graph i.e. one transitive conflict.
type Unravelled prim wX = [Sealed (FL prim wX)]

-- | Result of mangling a single Unravelled.
type Mangled prim wX = Sealed (FL prim wX)

class PrimMangleUnravelled prim where
  -- | Mangle conflicting alternatives if possible.
  mangleUnravelled :: Unravelled prim wX -> Maybe (Mangled prim wX)

primCleanMerge :: (Commute prim, Invert prim) => PartialMergeFn prim prim
primCleanMerge (p :\/: q) = do
  q' :> ip' <- commute (invert p :> q)
  return $ q' :/\: invert ip'
