-- | Based on «Scrap Your Zippers: A Generic Zipper for Heterogeneous Types.
-- Michael D. Adams.  WGP '10: Proceedings of the 2010 ACM SIGPLAN
-- workshop on Generic programming, 2010»
-- (<http://michaeldadams.org/papers/scrap_your_zippers/>).
--
-- Compared to the original @syz@ package, this implementation (based on
-- 'GTraversable') gives more flexibility as to where a zipper may point
-- to and what is considered as siblings.
--
-- Specifically, a zipper may point to any element which `gtraverse`
-- applies its function to.
--
-- == Example
-- === syz
-- Consider the classical example: lists. With syz, a list is interpreted as a right-balanced
-- tree.
--
-- >>> let z = fromJust . down' $ toZipper ['a'..'d']
-- >>> getHole z :: Maybe Char
-- Just 'a'
--
-- The zipper @z@ points to the first element of the list. Now let's
-- move to the right:
--
-- >>> let z' = fromJust . right $ z
-- >>> getHole z' :: Maybe Char
-- Nothing
-- >>> getHole z' :: Maybe [Char]
-- Just "bcd"
--
-- Instead of pointing to the second element of the list, as one might
-- expect, the zipper @z\'@ points to the tail of the list. In order to
-- actually move to the second element, we need another 'down'':
--
-- >>> let z'' = fromJust . down' $ z'
-- >>> getHole z'' :: Maybe Char
-- Just 'b'
--
-- === traverse-with-class
-- 'GTraversable'-based zippers behave more intuitively in this regard,
-- thanks to the uniform instance for lists.
--
-- >>> let z = fromJust . down' $ toZipper ['a'..'d'] :: Zipper Typeable [Char]
-- >>> getHole z :: Maybe Char
-- Just 'a'
--
-- So far it's more or less the same as with syz. We needed to add a type
-- annotation for the zipper itself to clarify the context which should
-- be available at each hole ('Typeable' in this case). Now let's see
-- what's to the right of us:
--
-- >>> let z' = fromJust . right $ z
-- >>> getHole z' :: Maybe Char
-- Just 'b'
--
-- That is, we jumped right to the second element of the list. Likewise,
--
-- >>> let z'' = rightmost z
-- >>> getHole z'' :: Maybe Char
-- Just 'd'
--
-- So, unlike in @syz@, all of the list elements are siblings.
{-# LANGUAGE Rank2Types, GADTs, KindSignatures, ImplicitParams,
             ScopedTypeVariables, ConstraintKinds,
             MultiParamTypeClasses, UndecidableInstances #-}

module Data.Generics.Traversable.Zipper (
  -- * Core types
  Zipper(),

  -- * Core interface
  -- ** Injection and projection
  toZipper, fromZipper,
  -- ** Basic movement
  left, right, down, down', up, leftmost, rightmost,

  -- ** Basic hole manipulation
  query,
  trans,
  transM,

  -- * Convenience hole manipulation interface
  -- | It does not appear easy to make these functions polymorphic over the constraint @c@.
  --
  -- If you want these functions for your own constraint (which entails
  -- 'Typeable'), you need to copy their definitions and change 'Typeable'
  -- to your constraint in the 'Zipper' \'s argument.
  getHole,
  setHole,
  setHole'
  ) where

import Control.Monad ((<=<), MonadPlus, mzero, mplus, liftM)
import Data.Maybe (fromJust)
import Data.Generics.Traversable
import Data.Typeable (Typeable, cast)
import Data.Proxy.Fork
import GHC.Exts (Constraint)
import Control.Applicative

-- Core types

-- | A generic zipper with a root object of type @root@.
data Zipper (c :: * -> Constraint) root =
  forall hole. (Rec c hole) =>
    Zipper hole (Context c hole root)

---- Internal types and functions
data Context c hole root where
    CtxtNull :: Context c a a
    CtxtCons ::
      forall hole root rights parent c. (Rec c parent) =>
        Left c (hole -> rights)
        -> Right c rights parent
        -> Context c parent root
        -> Context c hole root

combine :: Left c (hole -> rights)
         -> hole
         -> Right c rights parent
         -> parent
combine lefts hole rights =
  fromRight ((fromLeft lefts) hole) rights

-- Left is essentially Ørjan Johansen’s free applicative functor.
-- (see http://ro-che.info/articles/2013-03-31-flavours-of-free-applicative-functors.html)
--
-- This allows us to convert any GTraversable value to a zipper.
data Left c expects
  = LeftUnit expects
  | forall b. (Rec c b) => LeftCons (Left c (b -> expects)) b

instance Functor (Left c) where
  fmap f (LeftUnit x) = LeftUnit $ f x
  fmap f (LeftCons lft x) = LeftCons (fmap (f .) lft) x

instance Applicative (Left c) where
  pure = LeftUnit
  tx <*> LeftUnit e = fmap ($ e) tx
  tx <*> LeftCons ty az = LeftCons ((.) <$> tx <*> ty) az

unit :: Rec c b => b -> Left c b
unit = LeftCons (LeftUnit id)

toLeft :: forall a c . (Rec c a) => a -> Left c a
toLeft =
  let ?c = Proxy :: Proxy (Rec c) in
    gtraverse unit

fromLeft :: Left c r -> r
fromLeft (LeftUnit a)   = a
fromLeft (LeftCons f b) = fromLeft f b

data Right c provides parent where
  RightNull :: Right c parent parent
  RightCons ::
    (Rec c b) => b -> Right c a t -> Right c (b -> a) t

fromRight :: r -> Right c r parent -> parent
fromRight f (RightNull)     = f
fromRight f (RightCons b r) = fromRight (f b) r

-- Core interface

---- Injection and projection

-- | Move up a zipper to the root and return the root object.
fromZipper :: Zipper c a -> a
fromZipper (Zipper hole CtxtNull) = hole
fromZipper (Zipper hole (CtxtCons l r ctxt)) =
  fromZipper (Zipper (combine l hole r) ctxt)

-- | Create a zipper.  The focus starts at the root of the object.
toZipper :: Rec c a => a -> Zipper c a
toZipper x = Zipper x CtxtNull

---- Basic movement

-- | Move left.  Returns 'Nothing' iff already at leftmost sibling.
left  :: Zipper c a -> Maybe (Zipper c a)
left (Zipper _ CtxtNull) = Nothing
left (Zipper _ (CtxtCons (LeftUnit _) _ _)) = Nothing
left (Zipper h (CtxtCons (LeftCons l h') r c)) =
  Just (Zipper h' (CtxtCons l (RightCons h r) c))

-- | Move right.  Returns 'Nothing' iff already at rightmost sibling.
right :: Zipper c a -> Maybe (Zipper c a)
right (Zipper _ CtxtNull) = Nothing
right (Zipper _ (CtxtCons _ RightNull _)) = Nothing
right (Zipper h (CtxtCons l (RightCons h' r) c)) =
  Just (Zipper h' (CtxtCons (LeftCons l h) r c))

-- | Move down.  Moves to rightmost immediate child.  Returns 'Nothing' iff at a leaf and thus no children exist.
down  :: forall a c . Zipper c a -> Maybe (Zipper c a)
down (Zipper (hole :: holeT) ctxt) =
  case toLeft hole :: Left c holeT of
    LeftUnit _ -> Nothing
    LeftCons l hole' ->
      Just (Zipper hole' (CtxtCons l RightNull ctxt))

-- | Move down. Move to the leftmost immediate child.  Returns 'Nothing' iff at a leaf and thus no children exist.
down' :: Zipper c a -> Maybe (Zipper c a)
down' z = liftM leftmost (down z)

-- | Move up.  Returns 'Nothing' iff already at root and thus no parent exists.
up    :: Zipper c a -> Maybe (Zipper c a)
up (Zipper _ CtxtNull) = Nothing
up (Zipper hole (CtxtCons l r ctxt)) =
  Just (Zipper (combine l hole r) ctxt)

---- Basic hole manipulation

-- | Apply a generic query to the hole.
query
  :: (forall a . Rec c a => a -> b)
  -> Zipper c a -> b
query f (Zipper hole _ctxt) = f hole

-- | Apply a generic transformation to the hole.
trans
  :: (forall a . Rec c a => a -> a)
  -> Zipper c a -> Zipper c a
trans f (Zipper hole ctxt) = Zipper (f hole) ctxt

-- | Apply a generic monadic transformation to the hole
transM
  :: Monad m
  => (forall a . Rec c a => a -> m a)
  -> Zipper c a -> m (Zipper c a)
transM f (Zipper hole ctxt) = do
  hole' <- f hole
  return (Zipper hole' ctxt)

-- Convenience hole manipulation interface

-- | Get the value in the hole.  Returns 'Nothing' iff @a@ is not the type of the value in the hole.
getHole :: (Typeable b) => Zipper Typeable a -> Maybe b
getHole = query cast

-- | Set the value in the hole.  Does nothing iff @a@ is not the type of the value in the hole.
setHole :: (Typeable a) => a -> Zipper Typeable b -> Zipper Typeable b
setHole h z = trans (maybe id const $ cast h) z

-- | Set the value in the hole.  Returns 'Nothing' iff @a@ is not the type of the value in the hole.
setHole' :: (Typeable a) => a -> Zipper Typeable b -> Maybe (Zipper Typeable b)
setHole' h z = transM (const (cast h)) z
-- Generic zipper traversals
---- Traversal helpers

-- | A movement operation such as 'left', 'right', 'up', or 'down'.
type Move c a = Zipper c a -> Maybe (Zipper c a)

-- | Apply a generic query using the specified movement operation.
moveQ :: Move c a -- ^ Move operation
      -> b -- ^ Default if can't move
      -> (Zipper c a -> b) -- ^ Query if can move
      -> Zipper c a -- ^ Zipper
      -> b
moveQ move b f z = case move z of
                     Nothing -> b
                     Just z' -> f z'

------ Query
-- | Apply a generic query to the left sibling if one exists.
leftQ :: b -- ^ Value to return of no left sibling exists.
      -> (Zipper c a -> b) -> Zipper c a -> b
leftQ b f z = moveQ left b f z

-- | Apply a generic query to the right sibling if one exists.
rightQ :: b -- ^ Value to return if no right sibling exists.
       -> (Zipper c a -> b) -> Zipper c a -> b
rightQ b f z = moveQ right b f z

-- | Move to the leftmost sibling.
leftmost :: Zipper c a -> Zipper c a
leftmost z = leftQ z leftmost z

-- | Move to the rightmost sibling.
rightmost :: Zipper c a -> Zipper c a
rightmost z = rightQ z rightmost z