{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE RankNTypes, FlexibleContexts, FlexibleInstances, ScopedTypeVariables #-}

-- | References for standard containers
module Control.Reference.Predefined.Containers where

import Control.Reference.Representation
import Control.Reference.Types
import Control.Reference.Generators
import Control.Reference.Operators
                 
import Control.Instances.Morph
import Data.Map as Map
import qualified Data.Array as Arr
import qualified Data.Set as Set
import qualified Data.IntSet as IS
import qualified Data.IntMap as IM
import qualified Data.Sequence as Seq
import qualified Data.Text as Text
                 
-- | Lenses for given values in a data structure that is indexed by keys.
class Association e where
  type AssocIndex e :: *
  type AssocElem e :: *
  
  element :: AssocIndex e -> Simple Partial e (AssocElem e)
     
instance Association [a] where          
  type AssocIndex [a] = Int
  type AssocElem [a] = a
  element i = reference (morph . at i) (\v -> upd (const (return v))) upd
    where at :: Int -> [a] -> Maybe a
          at n _ | n < 0  = Nothing
          at _ []         = Nothing
          at 0 (x:_)      = Just x
          at n (_:xs)     = at (n-1) xs
          
          upd :: Monad w => (a -> w a) -> [a] -> w [a]
          upd f ls = let (before,rest) = Prelude.splitAt i ls
                      in case rest of [] -> return before
                                      (x:xs) -> f x >>= \fx -> return $ before ++ fx : xs
   
instance Arr.Ix i => Association (Arr.Array i a) where          
  type AssocIndex (Arr.Array i a) = i
  type AssocElem (Arr.Array i a) = a
  element i = reference (morph . at) (\v -> upd (const (return v))) upd
    where at :: (Arr.Array i a) -> Maybe a
          at arr | Arr.inRange (Arr.bounds arr) i
                 = Just (arr Arr.! i)
             | otherwise = Nothing
          upd :: Monad w => (a -> w a) -> Arr.Array i a -> w (Arr.Array i a)
          upd f arr | Arr.inRange (Arr.bounds arr) i
                    = f (arr Arr.! i) >>= \v -> return (arr Arr.// [(i,v)])
             | otherwise = return arr

instance Association (Seq.Seq a) where          
  type AssocIndex (Seq.Seq a) = Int
  type AssocElem (Seq.Seq a) = a
  element i = reference (morph . at i) (\v -> upd (const (return v)))
                        upd
    where at :: Int -> Seq.Seq a -> Maybe a
          at n s = case Seq.viewl (snd (Seq.splitAt i s)) of 
                     Seq.EmptyL -> Nothing
                     v Seq.:< _ -> Just v
          
          upd :: Monad w => (a -> w a) -> Seq.Seq a -> w (Seq.Seq a)
          upd f s = let (before,rest) = Seq.splitAt i s
                     in case Seq.viewl rest of 
                          Seq.EmptyL -> return before
                          x Seq.:< xs -> f x >>= \fx -> return $ before Seq.>< (fx Seq.<| xs)
  
instance Association Text.Text where
  type AssocIndex Text.Text = Int
  type AssocElem Text.Text = Char
  element i = reference (morph . at) (\v -> upd (const (return v)))
                        upd
    where at :: Text.Text -> Maybe Char
          at s | Text.length s > i  = Just (Text.index s i)
               | otherwise          = Nothing
          
          upd :: Monad w => (Char -> w Char) -> Text.Text -> w Text.Text
          upd f s = let (before,rest) = Text.splitAt i s
                     in case Text.uncons rest of 
                          Nothing -> return before
                          Just (x,xs) -> f x >>= \fx -> return $ Text.append before (Text.cons fx xs)
  
class Association e => Mapping e where
  at :: AssocIndex e -> Simple Lens e (Maybe (AssocElem e))
    
instance Eq a => Association (a -> Maybe b) where          
  type AssocIndex (a -> Maybe b) = a
  type AssocElem (a -> Maybe b) = b
  element i = simplePartial (\f -> case f i of Just x -> Just (x, \b k -> if i == k then Just b else f k)
                                               Nothing -> Nothing) 
                                               
instance Eq a => Mapping (a -> Maybe b) where
  at i = lens ($ i) (\b f k -> if i == k then b else f k)
    
instance Ord k => Association (Map k v) where
  type AssocIndex (Map k v) = k
  type AssocElem (Map k v) = v
  element k = reference (morph . Map.lookup k)
                        (\v -> return . Map.insert k v) 
                        (\trf m -> case Map.lookup k m of Just x -> trf x >>= \x' -> return (Map.insert k x' m)
                                                          Nothing -> return m)

instance Ord k => Mapping (Map k v) where
  at k = reference (return . (^? element k))
                   (\v -> return . Map.alter (const v) k) 
                   (\f m -> f (Map.lookup k m) >>=
                              return . maybe (Map.delete k m) 
                                             (\fx -> Map.insert k fx m))                   
                                                          
instance Association (IM.IntMap v) where
  type AssocIndex (IM.IntMap v) = Int
  type AssocElem (IM.IntMap v) = v
  element k = reference (morph . IM.lookup k)
                        (\v -> return . IM.insert k v) 
                        (\trf m -> case IM.lookup k m of 
                                     Just x -> trf x >>= \x' -> return (IM.insert k x' m)
                                     Nothing -> return m)

instance Mapping (IM.IntMap v) where
  at k = reference (return . (^? element k))
                   (\v -> return . IM.alter (const v) k) 
                   (\f m -> f (IM.lookup k m) >>=
                              return . maybe (IM.delete k m) 
                                             (\fx -> IM.insert k fx m))   
                                                         
-- | Containers that can be used as a set, inserting and removing elements
class SetLike e where
  type SetElem e :: *
  contains :: (SetElem e) -> Simple Lens e Bool
                
instance Ord v => SetLike (Set.Set v) where
  type SetElem (Set.Set v) = v
  contains e 
    = reference 
        (return . Set.member e)
        (\v -> return . if v then Set.insert e
                             else Set.delete e)
        (\trf s -> trf (Set.member e s) >>= return . \case  
                     True -> Set.insert e s
                     False -> Set.delete e s)
                     
instance SetLike IS.IntSet where
  type SetElem IS.IntSet = Int
  contains e 
    = reference 
        (return . IS.member e)
        (\v -> return . if v then IS.insert e
                             else IS.delete e)
        (\trf s -> trf (IS.member e s) >>= return . \case  
                     True -> IS.insert e s
                     False -> IS.delete e s)