{-# LANGUAGE DefaultSignatures #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module MiniLight.Event (
  Event (..),
  EventType (..),
  EventData (..),
  signal,
  asSignal,
  asRawEvent,
  asNotifyEvent,
  asEventData,
) where

import qualified SDL
import Data.Aeson
import qualified Data.HashMap.Strict as HM
import qualified Data.Text as T
import Data.Type.Equality
import Type.Reflection
import qualified System.FSNotify as Notify

-- | EventType says some type can be used as an event type.
class Typeable e => EventType e where
  getEventType :: e -> T.Text
  default getEventType :: Show e => e -> T.Text
  getEventType = T.pack . show

  getEventProperties :: e -> Object
  getEventProperties _ = HM.empty

-- | This type is same as 'Dynamic' from @Data.Dynamic@, but it requires 'EventType' contraint.
data Dynamic where
  Dynamic :: forall a. EventType a => TypeRep a -> a -> Dynamic

toDyn :: EventType a => a -> Dynamic
toDyn v = Dynamic typeRep v

fromDynamic :: forall a . (EventType a) => Dynamic -> Maybe a
fromDynamic (Dynamic t v) | Just HRefl <- t `eqTypeRep` rep = Just v
                          | otherwise                       = Nothing
  where rep = typeRep :: TypeRep a

-- | Event type representation
data Event
  = Signal T.Text (Maybe T.Text) Dynamic
  | RawEvent SDL.Event
  | NotifyEvent Notify.Event

-- | Create a signal event.
signal
  :: EventType a
  => T.Text  -- ^ source component ID
  -> Maybe T.Text  -- ^ target component ID, leave Nothing if this is a global event
  -> a
  -> Event
signal s t v = Signal s t (toDyn v)

-- | Cast a signal event to some 'EventType'.
asSignal :: EventType a => Event -> Maybe a
asSignal (Signal _ _ v) = fromDynamic v
asSignal _              = Nothing

-- | Cast an event to some 'SDL.Event'
asRawEvent :: Event -> Maybe SDL.Event
asRawEvent (RawEvent e) = Just e
asRawEvent _            = Nothing

-- | Cast an event to some 'Notify.Event'
asNotifyEvent :: Event -> Maybe Notify.Event
asNotifyEvent (NotifyEvent e) = Just e
asNotifyEvent _               = Nothing

-- | Canonical datatype of 'Event'. It consists of event name and event data itself.
-- This type is usually used for global events.
data EventData = EventData T.Text Value
  deriving (Show, Typeable)

instance EventType EventData where
  getEventType (EventData t _) = t
  getEventProperties (EventData _ o) = HM.singleton "data" o

-- | Cast a signal event to 'EventData'
asEventData :: Event -> Maybe EventData
asEventData (Signal _ Nothing v) = fromDynamic v
asEventData _                    = Nothing