{-# LANGUAGE DeriveFunctor, GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts, FlexibleInstances, UndecidableInstances #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TemplateHaskell #-}

-------------------------------------------------------------------------------------------
-- | Lookups combined into stack of lookups, allowing combined lookup coupled with updates on top of stack only
-------------------------------------------------------------------------------------------

module CHR.Data.Lookup.Stacked
  ( Stacked(..)
  , StackedElt

  , Stacks(..)
  )
  where

-------------------------------------------------------------------------------------------
import           Control.Applicative
import           Control.Arrow
import           Control.Monad.State
import           CHR.Data.Lookup.Types
import           CHR.Pretty
import           CHR.Data.Lens               as L
import           Prelude                     hiding (lookup, null, map)
import           Data.Maybe
import qualified Data.List                   as List
import qualified Data.Map                    as Map
import qualified Data.Set                    as Set
import qualified Data.Vector.Unboxed         as UV
import qualified Data.Vector.Unboxed.Mutable as MV
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
-- Types
-------------------------------------------------------------------------------------------

-- | Stacked Lookup derived from a base one, to allow a use of multiple lookups but update on top only
newtype Stacks l = Stacks {unStacks :: [l]}
  deriving (Functor, Applicative)

-------------------------------------------------------------------------------------------
-- Lenses
-------------------------------------------------------------------------------------------

-------------------------------------------------------------------------------------------
-- Stacked API
-------------------------------------------------------------------------------------------

type family StackedElt stk :: *

-- | Functionality on top of 'Lookup' for awareness of a scope.
-- Minimal definition 'lifts', 'unlifts,', 'top'/'topM', 'pop'/'popM', 'push'/'pushM'
class Stacked stk where
  -- lifting in/out
  lifts         :: StackedElt stk -> stk
  unlifts       :: stk -> [StackedElt stk]

  -- basic api
  top           :: stk -> StackedElt stk
  pop           :: stk -> (StackedElt stk,stk)
  push          :: StackedElt stk -> stk -> stk

  -- monadic api
  topM          :: (MonadState stk m) => m (StackedElt stk)
  popM          :: (MonadState stk m) => m (StackedElt stk)
  pushM         :: (MonadState stk m) => StackedElt stk -> m ()

  -- lifted variations
  tops          :: stk -> stk
  pops          :: stk -> (stk,stk)
  pushs         :: stk -> stk -> stk        -- ^ push, but only top of first arg

  -- lifted monadic variations
  topsM         :: (MonadState stk m) => m stk
  popsM         :: (MonadState stk m) => m stk
  pushsM        :: (MonadState stk m) => stk -> m ()

  -- defaults one way
  tops      = lifts . top
  pops      = first lifts . pop
  pushs     = push . top
  topsM     = gets tops
  popsM     = state pops
  pushsM    = modify . pushs

  -- defaults both ways
  topM = gets top
  top  = evalState topM

  popM = state pop
  pop  = runState popM

  pushM = modify . push
  push  = execState . pushM

-------------------------------------------------------------------------------------------
-- Default impl
-------------------------------------------------------------------------------------------

type instance StackedElt (Stacks e) = e

instance Stacked (Stacks lkup) where
  lifts e = Stacks [e]
  unlifts = unStacks
  top = List.head . unStacks
  pop (Stacks (h:t)) = (h, Stacks t)
  push h (Stacks t) = Stacks (h:t)

instance (Lookup lkup k v) => Lookup (Stacks lkup) k v where
  lookup  k  = listToMaybe . catMaybes . List.map (lookup k) . unStacks
  alter f k  = Stacks . List.map (alter f k) . unStacks
  null       = all null . unStacks
  size       = sum . List.map size . unStacks
  toList     = concatMap toList . unStacks
  fromList   = lifts . fromList

  -- for performance reasons
  keysSet = Set.unions . List.map keysSet . unStacks

-- modifications only for top level, otherwise use <$>
instance LookupApply l1 l2 => LookupApply l1 (Stacks l2) where
  l1 `apply` Stacks (h:t) = Stacks $ apply l1 h : t

instance Show (Stacks s) where
  show _ = "Stacks"

instance PP s => PP (Stacks s) where
  pp (Stacks xs) = ppCurlysCommas $ List.map pp xs