packages feed

moffy-samples-events-0.2.1.0: src/Control/Moffy/Samples/Followbox/Handle.hs

{-# LANGUAGE ImportQualifiedPost #-}
{-# LANGUAGE LambdaCase, TupleSections, OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE PatternSynonyms #-}
{-# OPTIONS_GHC -Wall -fno-warn-tabs #-}

module Control.Moffy.Samples.Followbox.Handle (
	-- * Handle
	HandleF, HandleF', handleFollowboxWith, GuiEv,
	-- * State
	FollowboxState(..), initialFollowboxState,
	) where

import Control.Moffy.Samples.Event.Delete
import Control.Moffy.Samples.Event.Mouse qualified as Mouse
import Control.Moffy.Samples.Event.CalcTextExtents (CalcTextExtents)
import Control.Moffy.Handle (
	Handle, Handle', HandleSt, HandleSt', HandleIo', liftHandle',
	retrySt, beforeSt, mergeSt )
import Control.Moffy.Handle.ThreadId (handleGetThreadId)
import Control.Moffy.Handle.Lock (LockState(..), LockId, handleLock)
import Control.Moffy.Samples.Handle.Random (RandomState(..), handleRandom)
import Data.Type.Set (Singleton, (:-), (:+:), pattern Nil)
import Data.OneOrMore as Oom (pattern Singleton)
import Data.Bool (bool)
import Data.List (delete)
import Data.String (fromString)
import Data.Aeson (Object)
import Data.Time (UTCTime, getCurrentTime, getCurrentTimeZone, diffUTCTime, DiffTime)
import System.Random (StdGen)
import System.Process (spawnProcess)

import qualified Data.Text as T
import qualified Network.HTTP.Simple as H

import Control.Moffy.Samples.Followbox.Event (
	FollowboxEv, StoreJsons(..), pattern OccStoreJsons,
	LoadJsons, pattern OccLoadJsons, HttpGet(..), pattern OccHttpGet,
	GetTimeZone, pattern OccGetTimeZone, Browse(..), pattern OccBrowse,
	BeginSleep(..), pattern OccBeginSleep, EndSleep, pattern OccEndSleep,
	RaiseError(..), pattern OccRaiseError, Error(..), ErrorResult(..) )
import Control.Moffy.Samples.Followbox.TypeSynonym (Browser, GithubNameToken)

import Data.OneOrMoreApp as Ooma

import Control.Concurrent.STM
import Control.Moffy.Samples.Handle.Area qualified as A
import Control.Moffy.Samples.Event.Area qualified as A
import Data.Map (Map)

---------------------------------------------------------------------------

-- * STATE
-- 	+ FOLLOWBOX STATE
-- 	+ PUT AND GET EACH STATE
-- * HANDLE
--	+ FOLLOWBOX
--	+ MOUSE
--	+ STORE AND LOAD JSONS
--	+ REQUEST DATA
--	+ BROWSE
--	+ BEGIN AND END SLEEP
--	+ RAISE ERROR
-- * HELPER FUNCTION

---------------------------------------------------------------------------
-- STATE
---------------------------------------------------------------------------

type GuiEv = DeleteEvent :- MouseEv

type MouseEv = Mouse.Move :- Mouse.Down :- Mouse.Up :- 'Nil

-- FOLLOWOBOX STATE

data FollowboxState = FollowboxState {
	fsNextLockId :: Int, fsLockState :: [LockId], fsObjects :: [Object],
	fsSleepUntil :: Maybe UTCTime, fsRandomGen :: StdGen
	} deriving Show

initialFollowboxState :: StdGen -> FollowboxState
initialFollowboxState g = FollowboxState {
	fsNextLockId = 0, fsLockState = [], fsObjects = [],
	fsSleepUntil = Nothing, fsRandomGen = g }

type HandleF m es = HandleSt FollowboxState m es
type HandleF' m es = HandleIo' FollowboxState FollowboxState m es

-- PUT AND GET EACH STATE

instance LockState FollowboxState where
	getNextLockId = fsNextLockId; putNextLockId s l = s { fsNextLockId = l }
	isLocked s l = l `elem` fsLockState s
	lockIt s l = s { fsLockState = l : fsLockState s }
	unlockIt s l = s { fsLockState = delete l $ fsLockState s }

instance RandomState FollowboxState where
	getRandomGen = fsRandomGen; putRandomGen s g = s { fsRandomGen = g }

---------------------------------------------------------------------------
-- HANDLE
---------------------------------------------------------------------------

-- FOLLOWBOX

handleFollowboxWith ::
	(Maybe DiffTime -> f -> Handle' IO (CalcTextExtents :- GuiEv)) ->
	f -> TVar (Map Int (A.Point, A.Point)) -> Browser -> Maybe GithubNameToken ->
	HandleF IO (GuiEv :+: FollowboxEv)
handleFollowboxWith h f va brws mba = retrySt $
	liftHandle' (A.handle va) `mergeSt`
	liftHandle' handleGetThreadId `mergeSt` handleLock `mergeSt`
	handleRandom `mergeSt`
	handleStoreJsons `mergeSt` handleLoadJsons `mergeSt`
	liftOnJust (handleHttpGet mba) `mergeSt`
	liftOnJust handleGetTimeZone `mergeSt`
	liftOnJust (handleBrowse brws) `mergeSt`
	handleBeginSleep `mergeSt` handleEndSleep `mergeSt`
	liftHandle' handleRaiseError `beforeSt` handleMouseWithSleep h f

-- MOUSE

handleMouseWithSleep ::
	(Maybe DiffTime -> f -> Handle' IO (CalcTextExtents :- GuiEv)) ->
	f -> HandleF' IO (CalcTextExtents :- GuiEv)
handleMouseWithSleep h f rqs s = (, s) <$> case fsSleepUntil s of
	Nothing -> h Nothing f rqs
	Just t -> getCurrentTime >>= \now ->
		h (Just . realToFrac $ t `diffUTCTime` now) f rqs

-- STORE AND LOAD JSONS

handleStoreJsons :: Monad m => HandleF' m (Singleton StoreJsons)
handleStoreJsons (Oom.Singleton (StoreJsonsReq os)) s =
	pure (Just . Ooma.Singleton $ OccStoreJsons os, s { fsObjects = os })

handleLoadJsons :: Monad m => HandleF' m (Singleton LoadJsons)
handleLoadJsons _rqs s = pure (Just . Ooma.Singleton . OccLoadJsons $ fsObjects s, s)

-- REQUEST DATA

handleHttpGet :: Maybe GithubNameToken -> Handle IO (Singleton HttpGet)
handleHttpGet mgnt (Oom.Singleton (HttpGetReq u)) = do
	r <- H.httpLBS . maybe id (uncurry H.setRequestBasicAuth) mgnt
		. H.setRequestHeader "User-Agent" ["Yoshio"]
		. fromString $ T.unpack u
	print $ H.getResponseHeader "X-RateLimit-Remaining" r
	pure . Ooma.Singleton
		$ OccHttpGet u (H.getResponseHeaders r) (H.getResponseBody r)

handleGetTimeZone :: Handle IO (Singleton GetTimeZone)
handleGetTimeZone _reqs = Ooma.Singleton . OccGetTimeZone <$> getCurrentTimeZone

-- BROWSE

handleBrowse :: Browser -> Handle IO (Singleton Browse)
handleBrowse brws (Oom.Singleton (Browse u)) =
	Ooma.Singleton OccBrowse <$ spawnProcess brws [T.unpack u]

-- BEGIN AND END SLEEP

handleBeginSleep :: Monad m => HandleF' m (Singleton BeginSleep)
handleBeginSleep (Oom.Singleton bs) s = case bs of
	BeginSleep t -> case fsSleepUntil s of
		Just t' -> pure (Just . Ooma.Singleton $ OccBeginSleep t', s)
		Nothing -> pure (
			Just . Ooma.Singleton $ OccBeginSleep t,
			s { fsSleepUntil = Just t } )
	CheckBeginSleep -> pure (Nothing, s)

handleEndSleep :: HandleF' IO (Singleton EndSleep)
handleEndSleep _rqs s = case fsSleepUntil s of
	Just t -> getCurrentTime >>= bool
		(pure (Nothing, s))
		(pure (Just $ Ooma.Singleton OccEndSleep,
			s { fsSleepUntil = Nothing })) . (t <=)
	Nothing -> pure (Just $ Ooma.Singleton OccEndSleep, s)

-- RAISE ERROR

handleRaiseError :: Handle' IO (Singleton RaiseError)
handleRaiseError (Oom.Singleton (RaiseError e em)) = case er e of
	Nothing -> pure Nothing
	Just r -> Just (Ooma.Singleton $ OccRaiseError e r) <$ putStrLn emsg
	where
	emsg = "ERROR: " <> em
	er = \case
		NoRateLimitRemaining -> Just Terminate
		NoRateLimitReset -> Just Terminate
		NotJson -> Just Terminate
		EmptyJson -> Just Continue
		NoLoginName -> Just Terminate
		NoAvatarAddress -> Just Terminate
		NoAvatar -> Just Terminate
		NoHtmlUrl -> Just Terminate
		Trace -> Just Continue
		CatchError -> Nothing

---------------------------------------------------------------------------
-- HELPER FUNCTION
---------------------------------------------------------------------------

liftOnJust :: Functor f => Handle f es -> HandleSt' st f es
liftOnJust = liftHandle' . ((Just <$>) .)