content: {-# OPTIONS_HADDOCK ignore-exports #-}
  module Buchhaltung.Zipper
    (module Buchhaltung.Zipper
    , E.NonEmpty(..)
    , E.nonEmpty
    ) where
  
  import           Data.List.NonEmpty (NonEmpty(..))
  import qualified Data.List.NonEmpty as E
  import           Data.Monoid
  
  -- | Nonemtpy zipper
  data Zipper a = LZ { past    :: E.NonEmpty a
                      , future  :: [a]
                      }
  
  present :: Zipper a -> a
  present = E.head . past
  
  instance Functor Zipper where
    fmap f (LZ ps fs) = LZ (E.map f ps) (map f fs)
  
  -- | Re-constitute a list from a zipper context.
  integrate' :: Zipper a -> E.NonEmpty a
  integrate' (LZ p f) = E.fromList $ reverse (E.toList p) <> f
  
  integrate :: Zipper a -> [a]
  integrate = E.toList . integrate'
  
  -- | Turn a list into a context with the focus on the first element.
  differentiate :: E.NonEmpty a -> Zipper a
  differentiate (x :| xs) = LZ (pure x) xs
  
  -- | Move the focus to the previous element. Do nothing if the focus
  -- | is already on the first element.
  back :: Zipper a -> Zipper a
  back z@(LZ (_ :| []) _) = z
  back (LZ (pr :| (np:ps)) fs) = LZ (np :| ps) (pr:fs)
  
  -- | Move the focus to the next element.  Do nothing if the focus is
  -- | already on the last element.
  fwd :: Zipper a -> Zipper a
  fwd z@(LZ _ []) = z
  fwd (LZ ps (f:fs)) = LZ (f E.<| ps) fs
  
  -- | Apply the given function to the currently focused element to
  -- | produce a new currently focused element.
  modifyPresent :: (a -> a) -> Zipper a -> Zipper a
  modifyPresent f z@LZ{past=(present :| past)} = z { past = (f present :| past) }
  
  -- | Apply the given function to all elements preceding the focus.
  modifyBack :: ([a] -> [a]) -> Zipper a -> Zipper a
  modifyBack f z@LZ{ past=(pr :| ps) } = z { past = (pr :| f ps) }
  
  -- | Apply the given function to all elements after the focus.
  modifyFwd :: ([a] -> [a]) -> Zipper a -> Zipper a
  modifyFwd f z = z { future = f (future z) }
  
  -- | Delete the currently focused element.  If there are no future
  -- elements move the focus to the next last element.
  delete :: Zipper a -> Zipper a
  delete (LZ (_ :| []) [] ) = error "empty zipper not allowed"
  delete (LZ (_ :| (np:past)) [] ) = LZ (np :| past) []
  delete (LZ (_ :| past) (np:f) ) = LZ (np :| past) f
  
  -- | Insert a new element just before the current focus, then move the
  -- | focus to the newly inserted element.
  insback :: a -> Zipper a -> Zipper a
  insback x (LZ (pr :| ps) fs) = LZ (x :| ps) (pr:fs)
  
  -- | Insert a new element just after the current focus, then move the
  -- | focus to the newly inserted element.
  insfwd :: a -> Zipper a -> Zipper a
  insfwd x (LZ ps fs) = LZ (x E.<| ps) fs
  