{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}

module Data.PrioHeap.Lens
    ( prioHeapOf
    ) where

import Data.Ord (Down(..))

import Control.Lens.Combinators (IndexedGetting, iviews)
import Control.Lens.Each (Each(..))
import Control.Lens.Empty (AsEmpty(..))
import Control.Lens.Indexed
import Control.Lens.Iso (iso)
import Control.Lens.Operators ((<&>))
import Control.Lens.Prism (nearly)
import Control.Lens.Traversal
import Control.Lens.Type (IndexedGetter, IndexedFold, IndexedTraversal, IndexedLens)
import Control.Lens.Wrapped

import qualified Data.PrioHeap as H
import Data.PrioHeap (PrioHeap)

instance (c ~ d) => Each (PrioHeap c a) (PrioHeap d b) a b where
    each = traversed
    {-# INLINE each #-}

instance AsEmpty (PrioHeap k a) where
    _Empty = nearly H.empty null

instance FunctorWithIndex k (PrioHeap k) where
    imap = H.mapWithKey
    {-# INLINE imap #-}

instance FoldableWithIndex k (PrioHeap k) where
    ifoldMap = H.foldMapWithKey
    {-# INLINE ifoldMap #-}

    ifoldr = H.foldrWithKey
    {-# INLINE ifoldr #-}

    ifoldl f = H.foldlWithKey (flip f)
    {-# INLINE ifoldl #-}

    ifoldr' = H.foldrWithKey'
    {-# INLINE ifoldr' #-}

    ifoldl' f = H.foldlWithKey' (flip f)
    {-# INLINE ifoldl' #-}

instance TraversableWithIndex k (PrioHeap k) where
    itraverse = H.traverseWithKey
    {-# INLINE itraverse #-}

instance Ord k => TraverseMin k (PrioHeap k) where
    traverseMin f heap = case H.lookupMin heap of
        Nothing -> pure heap
        Just (key, x) -> indexed f key x <&> \y -> H.adjustMin (const y) heap

-- TODO: TraverseMax instance for PrioHeap (Down k)?
instance Ord k => TraverseMax k (PrioHeap (Down k)) where
    traverseMax f heap = case H.lookupMin heap of
        Nothing -> pure heap
        Just (Down key, x) -> indexed f key x <&> \y -> H.adjustMin (const y) heap

instance Ord k => Wrapped (PrioHeap k a) where
    type Unwrapped (PrioHeap k a) = [(k, a)]

    _Wrapped' = iso H.toList H.fromList
    {-# INLINE _Wrapped' #-}

-- | Use @'wrapping' 'H.fromList'@. Unwrapping returns some permutation of the list.
instance (t ~ PrioHeap k' a', Ord k) => Rewrapped (PrioHeap k a) t

-- | Construct a PrioHeap from a 'IndexedGetter', 'IndexedFold', 'IndexedTraversal' or 'IndexedLens'.
prioHeapOf :: IndexedGetting k (PrioHeap k a) s a -> s -> PrioHeap k a
prioHeapOf l = iviews l H.singleton