{-# LANGUAGE
  FlexibleContexts,
  TypeFamilies #-}

-- | Interfaces of structures used to implement 'DiffLoc.ADiff'.
module DiffLoc.Shift
  ( -- * Interfaces
    -- ** Replacement
    Shift(..)
  , BlockOrder(..)

    -- ** Indices and offsets
  , Amor(..)
  , Origin(..)
  , fromOrigin
  , ofOrigin
  ) where

import Data.Kind (Type)
import Data.Maybe (fromMaybe)
import GHC.Stack (HasCallStack)

-- $setup
-- >>> import Control.Monad ((<=<))
-- >>> import Test.QuickCheck
-- >>> import DiffLoc
-- >>> import DiffLoc.Unsafe ((.-.))
-- >>> import DiffLoc.Test
-- >>> type N = Plain Int

-- | Partial ordering of interval-like things.
class BlockOrder b where
  precedes :: b -> b -> Bool

  -- | Precedes but not adjacent, provided you have a notion of adjacence.
  -- Otherwise it's fine to equate this with precedes.
  distantlyPrecedes :: b -> b -> Bool

-- | Shift algebra.
--
-- __Laws:__
--
-- @
-- 'src' \<$>   'shiftR' r s   =     'shiftBlock' r ('src' s)
-- 'tgt' \<$>   'shiftR' r s   =     'shiftBlock' r ('tgt' s)
-- 'src' \<$> 'coshiftR' r s   =   'coshiftBlock' r ('src' s)
-- 'tgt' \<$> 'coshiftR' r s   =   'coshiftBlock' r ('tgt' s)
--
-- 'shiftBlock' r b = Just d   \<==>   'coshiftBlock' r d = Just b
-- 'shiftR'     r s = Just z   \<==>   'coshiftR'     r z = Just s
--
-- 'shiftR' r s = Just z  &&  'shiftR' s r = Just q   ==>
--   z '<>' r  =  q '<>' s
--
-- 'coshiftR' r s = Just z  &&  'coshiftR' s r = Just q   ==>
--   r '<>' z  =  s '<>' q
-- @
--
-- __Duality laws:__
--
-- @
-- src = tgt . 'dual'
-- tgt = src . 'dual'
-- shiftBlock = coshiftBlock . 'dual'
-- coshiftBlock = shiftBlock . 'dual'
-- coshiftR = shiftR . 'dual'
-- shiftR = coshiftR . 'dual'
-- @
class (Semigroup r, BlockOrder (Block r)) => Shift r where
  type Block r :: Type
  src :: r -> Block r
  tgt :: r -> Block r

  shiftBlock   :: r -> Block r -> Maybe (Block r)
  coshiftBlock :: r -> Block r -> Maybe (Block r)

  shiftR :: r -> r -> Maybe r
  coshiftR :: r -> r -> Maybe r

  dual :: r -> r

  src = forall r. Shift r => r -> Block r
tgt forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r. Shift r => r -> r
dual
  tgt = forall r. Shift r => r -> Block r
src forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r. Shift r => r -> r
dual
  shiftBlock = forall r. Shift r => r -> Block r -> Maybe (Block r)
coshiftBlock forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r. Shift r => r -> r
dual
  coshiftBlock = forall r. Shift r => r -> Block r -> Maybe (Block r)
shiftBlock forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r. Shift r => r -> r
dual
  coshiftR = forall r. Shift r => r -> r -> Maybe r
shiftR forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r. Shift r => r -> r
dual
  shiftR = forall r. Shift r => r -> r -> Maybe r
coshiftR forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall r. Shift r => r -> r
dual

-- | /Action d'un Monoïde Ordonné./ Ordered monoid actions.
--
-- - An ordered set of points @Ord p@.
-- - An ordered monoid of translations (or "vectors") @(Ord (Trans p), Monoid ('Trans' p))@.
--
-- In addition to the 'Ord' and 'Monoid' laws, ordered monoids must
-- have a monotone @('<>')@:
--
-- @
-- v \<= v'   ==>    w \<= w'   =>   (v '<>' w) \<= (v' '<>' w')
-- @
--
-- - Points can be translated along vectors using @('.+')@.
-- - Given two ordered points @i <= j@, @j '.-.?' i@ finds a vector @n@
--   such that @i + n = j@.
--
-- In other words, we only require the existence of "positive" translations
-- (this is unlike affine spaces, where translations exist between any two points).
-- This makes it possible to implement this class for line-column locations
-- ("DiffLoc.Colline"), where translations are not invertible.
--
-- @('.-.?')@ is not part of a standard definition of ordered monoid actions.
-- Feel free to suggest a better name for this structure or a way to not
-- depend on this operation.
--
-- __Laws:__
--
-- @
--               (x '.+' v) '.+' w  =  x '.+' (v '<>' w)
-- x '<=' y  ==>  x '.+' (y 'DiffLoc.Unsafe..-.' x)  =  y
--              (x '.+' v) 'DiffLoc.Unsafe..-.' x  =  x
-- @
class (Ord p, Ord (Trans p), Monoid (Trans p)) => Amor p where
  -- | Type of translations between points of @p@.
  type Trans p :: Type

  infixr 6 .+

  -- | Translate a point.
  (.+) :: p -> Trans p -> p

  -- | Translation between two points.
  -- @j .-.? i@ must be defined ('Just') if @i <= j@,
  --
  -- There is an unsafe wrapper @('DiffLoc.Unsafe..-.')@ in "DiffLoc.Unsafe".
  (.-.?) :: p -> p -> Maybe (Trans p)

-- $hidden
-- prop> (x .+ v) .+ w                   ===  (x .+ (v <> w) :: Plain Int)
-- prop> x <= y  ==>  x .+ (y .-. x)     ===  (y :: Plain Int)
-- prop> (x .+ v) .-. (x :: Plain Int)   ===   v

infixl 6 .-.

-- | An unsafe variant of @('.-.?')@. This will be redefined in "DiffLoc.Unsafe".
(.-.) :: HasCallStack => Amor p => p -> p -> Trans p
p
i .-. :: forall p. (HasCallStack, Amor p) => p -> p -> Trans p
.-. p
j = forall a. a -> Maybe a -> a
fromMaybe (forall a. HasCallStack => [Char] -> a
error [Char]
"undefined vector") (p
i forall p. Amor p => p -> p -> Maybe (Trans p)
.-.? p
j)

-- | Extend 'Amor' with an "origin" point from which vectors can be drawn to
-- all points. To make the interface slightly more general, only the partial
-- application @(origin .-.)@ needs to be supplied.
--
-- __Laws:__
--
-- @
-- 'origin' <= x
-- @
class Amor p => Origin p where
  origin :: p

-- | Translate the origin along a vector.
--
-- @
-- x \<= y   <=>   ofOrigin x \<= ofOrigin y
--
-- 'ofOrigin' x '.+' v             =   'ofOrigin' (x '.+' v)
-- 'ofOrigin' x 'DiffLoc.Unsafe..-.' 'ofOrigin' y   =   x 'DiffLoc.Unsafe..-.' y
-- @
ofOrigin :: Origin p => Trans p -> p
ofOrigin :: forall p. Origin p => Trans p -> p
ofOrigin Trans p
v = forall p. Origin p => p
origin forall p. Amor p => p -> Trans p -> p
.+ Trans p
v

-- | Find the vector from the origin to this point.
--
-- @
-- x \<= y   <=>   fromOrigin x \<= fromOrigin y
--
-- 'ofOrigin' ('fromOrigin' x) = x
-- 'fromOrigin' ('ofOrigin' v) = v
--
-- 'fromOrigin' (x .+ v)  =   'fromOrigin' x <> v
-- @
fromOrigin :: Origin p => p -> Trans p
fromOrigin :: forall p. Origin p => p -> Trans p
fromOrigin p
p = p
p forall p. (HasCallStack, Amor p) => p -> p -> Trans p
.-. forall p. Origin p => p
origin