{-# LANGUAGE CPP                        #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE RankNTypes                 #-}
{-# LANGUAGE ScopedTypeVariables        #-}


-- | Local storage IO operations
-- Get and set localstorage values from some 'LocalStorageKey'


module Shpadoinkle.Html.LocalStorage where


import           Control.Monad
import           Data.Maybe
import           Data.String
import           Data.Text
import           GHC.Generics
import           GHCJS.DOM
import           GHCJS.DOM.Storage
import           GHCJS.DOM.Window
import           Text.Read
import           UnliftIO
import           UnliftIO.Concurrent (forkIO)

import           Shpadoinkle         (MonadJSM, Territory (..), liftJSM)


-- | The key for a specific state kept in local storage
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
  s <- getLocalStorage =<< currentWindowUnchecked
  setItem s k $ show m


getStorage :: MonadJSM m => Read a => LocalStorageKey a -> m (Maybe a)
getStorage (LocalStorageKey k) = do
  s <- getLocalStorage =<< currentWindowUnchecked
  (>>= readMaybe) <$> getItem s k


-- Whe we should update we save
saveOnChange :: MonadJSM m => Territory t => Show a => Eq a
             => LocalStorageKey a -> t 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