{-# LANGUAGE UndecidableInstances, GeneralizedNewtypeDeriving, ScopedTypeVariables, TypeFamilies #-}
{-# LANGUAGE CPP #-}
#if __GLASGOW_HASKELL__ >= 710
#else
{-# LANGUAGE OverlappingInstances #-}
#endif

-- | Abstractions for looking up (type) variables in structures

module CHR.Data.VarLookup
    ( VarLookupKey
    , VarLookupVal
    , VarLookup(..)

    -- , varlookupResolveVarWithMetaLev
    -- , varlookupResolveVar
    -- , varlookupResolveValWithMetaLev
    -- , varlookupResolveVal
    -- 
    -- -- , VarCompareHow(..)
    -- 
    -- , varlookupMap
    -- 
    -- , varlookupResolveAndContinueM
    -- 
    -- , VarLookupFix, varlookupFix
    -- , varlookupFixDel
    -- 
    -- , VarLookupCmb (..)     -- 20170107 TBD: should not be both, VarLookupCmb to be replaced by LookupApply
    , LookupApply(..)
    -- 
    -- -- , VarLookupBase (..)
    -- 
    -- , VarLookupCmbFix, varlookupcmbFix
    -- 
    , MetaLev
    , metaLevVal
    -- 
    , StackedVarLookup -- (..)

    )
  where

-- import           Control.Applicative
-- import           Data.Maybe
-- import           CHR.Pretty
import           CHR.Data.Lookup.Stacked
import           CHR.Data.Lookup.Types      as Lk
import qualified Data.Set as Set

-------------------------------------------------------------------------------------------
--- Level of lookup
-------------------------------------------------------------------------------------------

-- | Level to lookup into
type MetaLev = Int

-- | Base level (of values, usually)
metaLevVal :: MetaLev
metaLevVal = 0

-------------------------------------------------------------------------------------------
--- VarLookup: something which can lookup a value 'v' given a key 'k'.
-------------------------------------------------------------------------------------------


-- | Type family for key of a VarLookup
type family VarLookupKey m :: *
-- | Type family for value of a VarLookup
type family VarLookupVal m :: *

{- |
VarLookup abstracts from a Map.
The purpose is to be able to combine maps only for the purpose of searching without actually merging the maps.
This then avoids the later need to unmerge such mergings.
The class interface serves to hide this.
-}

class VarLookup m where
  -- | Lookup a key at a level
  varlookupWithMetaLev :: MetaLev -> VarLookupKey m -> m -> Maybe (VarLookupVal m)

  -- | Lookup a key
  varlookup :: VarLookupKey m -> m -> Maybe (VarLookupVal m)
  varlookup = varlookupWithMetaLev metaLevVal
  {-# INLINE varlookup #-}

  -- | Keys at a level
  varlookupKeysSetWithMetaLev :: (Ord (VarLookupKey m)) => MetaLev -> m -> Set.Set (VarLookupKey m)

  -- | Keys as Set
  varlookupKeysSet :: (Ord (VarLookupKey m)) => m -> Set.Set (VarLookupKey m)
  varlookupKeysSet = varlookupKeysSetWithMetaLev metaLevVal
  {-# INLINE varlookupKeysSet #-}

  -- | Make an empty VarLookup
  varlookupEmpty :: m
  -- | Make a singleton VarLookup at a level
  varlookupSingletonWithMetaLev :: MetaLev -> VarLookupKey m -> VarLookupVal m -> m

  -- | Make a singleton VarLookup
  varlookupSingleton :: VarLookupKey m -> VarLookupVal m -> m
  varlookupSingleton = varlookupSingletonWithMetaLev metaLevVal
  {-# INLINE varlookupSingleton #-}

-------------------------------------------------------------------------------------------
--- Util/convenience
-------------------------------------------------------------------------------------------

{-
-- | Combine lookup with map; should be obsolete...
varlookupMap :: VarLookup m => (VarLookupVal m -> Maybe res) -> VarLookupKey m -> m -> Maybe res
varlookupMap get k m = varlookup k m >>= get
{-# INLINE varlookupMap #-}
-}

-------------------------------------------------------------------------------------------
--- Lookup and resolution
-------------------------------------------------------------------------------------------

{-
-- | Fully resolve lookup
varlookupResolveVarWithMetaLev :: VarLookup m => MetaLev -> (VarLookupVal m -> Maybe (VarLookupKey m)) -> VarLookupKey m -> m -> Maybe (VarLookupVal m)
varlookupResolveVarWithMetaLev l isVar k m =
  varlookupWithMetaLev l k m >>= \v -> varlookupResolveValWithMetaLev l isVar v m <|> return v

-- | Fully resolve lookup
varlookupResolveVar :: VarLookup m => (VarLookupVal m -> Maybe (VarLookupKey m)) -> VarLookupKey m -> m -> Maybe (VarLookupVal m)
varlookupResolveVar = varlookupResolveVarWithMetaLev metaLevVal
{-# INLINE varlookupResolveVar #-}

varlookupResolveValWithMetaLev :: VarLookup m => MetaLev -> (VarLookupVal m -> Maybe (VarLookupKey m)) -> VarLookupVal m -> m -> Maybe (VarLookupVal m)
varlookupResolveValWithMetaLev l isVar v m = isVar v >>= \k -> varlookupResolveVarWithMetaLev l isVar k m <|> return v

-- | Fully resolve lookup
varlookupResolveVal :: VarLookup m => (VarLookupVal m -> Maybe (VarLookupKey m)) -> VarLookupVal m -> m -> Maybe (VarLookupVal m)
varlookupResolveVal = varlookupResolveValWithMetaLev metaLevVal
{-# INLINE varlookupResolveVal #-}

-- | Monadically lookup a variable, resolve it, continue with either a fail or success monad continuation
varlookupResolveAndContinueM :: (Monad m, VarLookup s) => (VarLookupVal s -> Maybe (VarLookupKey s)) -> (m s) -> (m a) -> (VarLookupVal s -> m a) -> VarLookupKey s -> m a
varlookupResolveAndContinueM tmIsVar gets failFind okFind k = gets >>= \s -> maybe failFind okFind $ varlookupResolveVar tmIsVar k s
-}

-------------------------------------------------------------------------------------------
--- VarLookupFix
-------------------------------------------------------------------------------------------

{-
-- (not yet, still in use in UHC) {-# DEPRECATED VarLookupFix, varlookupFix, varlookupFixDel "As of 20160331: don't use these anymore" #-}

type VarLookupFix k v = k -> Maybe v

-- | fix looking up to be for a certain var mapping
varlookupFix :: VarLookup m => m -> VarLookupFix (VarLookupKey m) (VarLookupVal m)
varlookupFix m = \k -> varlookup k m

-- | simulate deletion
varlookupFixDel :: Ord k => [k] -> VarLookupFix k v -> VarLookupFix k v
varlookupFixDel ks f = \k -> if k `elem` ks then Nothing else f k
-}

-------------------------------------------------------------------------------------------
--- VarLookupCmb: combine VarLookups
-------------------------------------------------------------------------------------------

{- |
VarLookupCmb abstracts the 'combining' of/from a substitution.
The interface goes along with VarLookup but is split off to avoid functional dependency restrictions.
The purpose is to be able to combine maps only for the purpose of searching without actually merging the maps.
This then avoids the later need to unmerge such mergings.
-}

{-
class VarLookupCmb m1 m2 where
  (|+>) :: m1 -> m2 -> m2

infixr 7 |+>

-- build on LookupApply, if available
instance {-# OVERLAPPABLE #-} Lk.LookupApply m1 m2 => VarLookupCmb m1 m2 where
  (|+>) = Lk.apply
-}

{-
#if __GLASGOW_HASKELL__ >= 710
instance {-# OVERLAPPING #-}
#else
instance
#endif
  VarLookupCmb m1 m2 => VarLookupCmb m1 [m2] where
    m1 |+> (m2:m2s) = (m1 |+> m2) : m2s
-}

{-
#if __GLASGOW_HASKELL__ >= 710
instance {-# OVERLAPPING #-}
#else
instance
#endif
  (VarLookupCmb m1 m1, VarLookupCmb m1 m2) => VarLookupCmb [m1] [m2] where
    m1 |+> (m2:m2s) = (foldr1 (|+>) m1 |+> m2) : m2s
-}

{-
instance
  (VarLookupCmb m1 m1, VarLookupCmb m1 m2) => VarLookupCmb (StackedVarLookup m1) (StackedVarLookup m2) where
    m1 |+> StackedVarLookup (m2:m2s) = StackedVarLookup $ (foldr1 (|+>) m1 |+> m2) : m2s
-}

-------------------------------------------------------------------------------------------
--- How to do the VarLookup part of matching/unification/comparing
-------------------------------------------------------------------------------------------

{-
-- | How to match, increasingly more binding is allowed
data VarCompareHow
  = VarCompareHow_Check               -- ^ equality check only
  | VarCompareHow_Match               -- ^ also allow one-directional (left to right) matching/binding of (meta)vars
  | VarCompareHow_MatchAndWait        -- ^ also allow giving back of global vars on which we wait
  | VarCompareHow_Unify               -- ^ also allow bi-directional matching, i.e. unification
  deriving (Ord, Eq)
-}

-------------------------------------------------------------------------------------------
--- VarLookupCmbFix
-------------------------------------------------------------------------------------------

{-
{-# DEPRECATED VarLookupCmbFix, varlookupcmbFix "As of 20160331: don't use these anymore" #-}
type VarLookupCmbFix m1 m2 = m1 -> m2 -> m2

-- | fix combining up to be for a certain var mapping
varlookupcmbFix :: VarLookupCmb m1 m2 => VarLookupCmbFix m1 m2
varlookupcmbFix m1 m2 = m1 |+> m2
-}

-------------------------------------------------------------------------------------------
--- Stack of things in which we can lookup, but which is updated only at the top
-------------------------------------------------------------------------------------------

-- | Stacked VarLookup derived from a base one, to allow a use of multiple lookups but update on top only
type StackedVarLookup s = Stacks s

type instance VarLookupKey (StackedVarLookup s) = VarLookupKey s
type instance VarLookupVal (StackedVarLookup s) = VarLookupVal s
type instance StackedElt   (StackedVarLookup s) = s