{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}
module Shpadoinkle.Html.LocalStorage where
import           Control.Monad             (void)
import           Control.Monad.Trans.Maybe (MaybeT (MaybeT, runMaybeT))
import           Data.Maybe                (fromMaybe)
import           Data.String               (IsString)
import           Data.Text                 (Text)
import           GHC.Generics              (Generic)
import           GHCJS.DOM                 (currentWindow)
import           GHCJS.DOM.Storage         (getItem, setItem)
import           GHCJS.DOM.Types           (MonadJSM, liftJSM)
import           GHCJS.DOM.Window          (getLocalStorage)
import           Text.Read                 (readMaybe)
import           UnliftIO                  (MonadIO (liftIO), MonadUnliftIO,
                                            TVar, newTVarIO)
import           UnliftIO.Concurrent       (forkIO)
import           Shpadoinkle               (shouldUpdate)
newtype LocalStorageKey a = LocalStorageKey { unLocalStorageKey :: Text }
  deriving (Semigroup, Monoid, IsString, Eq, Ord, Show, Read, Generic)
setStorage :: MonadJSM m => Show a => LocalStorageKey a -> a -> m ()
setStorage (LocalStorageKey k) m = do
  w <- currentWindow
  case w of
    Just w' -> do
      s <- getLocalStorage w'
      setItem s k $ show m
      return ()
    Nothing -> return ()
getStorage :: MonadJSM m => Read a => LocalStorageKey a -> m (Maybe a)
getStorage (LocalStorageKey k) = runMaybeT $ do
  w <- MaybeT currentWindow
  s <- MaybeT $ Just <$> getLocalStorage w
  MaybeT $ (>>= readMaybe) <$> getItem s k
saveOnChange :: MonadJSM m => Show a => Eq a
             => LocalStorageKey a -> TVar a -> m ()
saveOnChange k = liftJSM . shouldUpdate (const $ setStorage k) ()
manageLocalStorage
  :: MonadUnliftIO m
#ifndef ghcjs_HOST_OS
  => MonadJSM m
#endif
  => Show a
  => Read a
  => Eq a
  => LocalStorageKey a -> a -> m (TVar a)
manageLocalStorage k initial = do
  model <- liftIO . newTVarIO . fromMaybe initial =<< getStorage k
  void . forkIO $ saveOnChange k model
  return model