{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}
{-|
Module      : Happstack.Server.Session.Memory
Description : Memory storage backend for Happstack-session
Copyright   : (c) Birk Tjelmeland, 2016
License     : GPL-3
Maintainer  : birktjelmeland@yahoo.no
Stability   : experimental
Portability : POSIX

This module should ONLY be used for testing as it does not save sessions on server restarts and has other security issues.
See "Happstack.Server.Session"
-}
module Happstack.Server.Session.Memory (memoryStartSession) where

import Happstack.Server.Session

import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IM

import Data.Maybe
import Data.Word
import Data.IORef


-- | Constructs value to be used with 'startSession'
memoryStartSession :: IO ((Int -> IO (Maybe (Session Int a))), (a -> Word64 -> IO (Session Int a)), (Int -> a -> IO (Maybe (Session Int a))), (Int -> IO ()))
memoryStartSession = do
    ref <- newIORef (IM.empty, 0)
    return (memorySessionGet ref, memorySessionSet ref, memorySessionUpdate ref, memorySessionDelete ref)


memorySessionGet :: (IORef (IntMap (Word64, a), Int)) -> Int -> IO (Maybe (Session Int a))
memorySessionGet ref i = do
    val <- fmap ((IM.lookup i) . fst) $ readIORef ref
    if isJust val then do
        let val' = fromJust val
        return $ Just $ Session i (fst val') (snd val')
    else
        return Nothing

memorySessionSet :: (IORef (IntMap (Word64, a), Int)) -> a -> Word64 -> IO (Session Int a)
memorySessionSet ref val len = do
    modifyIORef' ref (\(imap, i) -> (IM.insert (i+1) (len, val) imap, i+1))
    i <- fmap snd $ readIORef ref
    return $ Session i len val

memorySessionUpdate :: (IORef (IntMap (Word64, a), Int)) -> Int -> a -> IO (Maybe (Session Int a))
memorySessionUpdate ref i val = do
    (imap, index) <- readIORef ref
    let val' = IM.lookup i imap
    if isJust val' then do
        let val'' = fromJust val'
        writeIORef ref $ (IM.insert i (fst val'', val) imap, index)
        return $ Just $ Session i (fst val'') val
    else
        return Nothing

memorySessionDelete :: (IORef (IntMap (Word64, a), Int)) -> Int -> IO ()
memorySessionDelete ref i = do
    modifyIORef' ref (\(imap, index) -> (IM.delete i imap, index))
    return ()