content: {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE RecordWildCards #-}
  {-# LANGUAGE DeriveDataTypeable #-}
  
  -- | Structures pertaining to Discord Scheduled Events
  module Discord.Internal.Types.ScheduledEvents where
  
  import           Data.Aeson                     ( (.:)
                                                  , (.:!)
                                                  , (.:?)
                                                  , (.=)
                                                  , FromJSON(parseJSON)
                                                  , ToJSON(toJSON)
                                                  , Value(Null, Number, String)
                                                  , object
                                                  , withObject
                                                  , withText
                                                  )
  import           Data.Aeson.Types               ( Parser )
  import qualified Data.ByteString               as B
  import           Data.Data                      ( Data )
  import           Data.Default                   ( Default(def) )
  import qualified Data.Text                     as T
  import qualified Data.Text.Encoding            as T
  import           Data.Time                      ( UTCTime )
  import           Discord.Internal.Types.Prelude ( ChannelId
                                                  , GuildId
                                                  , InternalDiscordEnum
                                                    ( discordTypeParseJSON
                                                    , discordTypeStartValue
                                                    , fromDiscordType
                                                    )
                                                  , ScheduledEventEntityId
                                                  , ScheduledEventId
                                                  , UserId
                                                  , (.==)
                                                  , (.=?)
                                                  , objectFromMaybes
                                                  )
  import           Discord.Internal.Types.User    ( GuildMember
                                                  , User
                                                  )
  
  
  
  -- | The ScheduledEvent data structure
  data ScheduledEvent
    = ScheduledEventStage
        { scheduledEventStageId :: ScheduledEventId
        , scheduledEventStageGuildId :: GuildId
        , scheduledEventStageChannelId :: ChannelId
        , scheduledEventStageCreatorId :: Maybe UserId
        , scheduledEventStageName :: T.Text
        , scheduledEventStageDescription :: Maybe T.Text
        , scheduledEventStageStartTime :: UTCTime
        , scheduledEventStageEndTime :: Maybe UTCTime
        , scheduledEventStagePrivacyLevel :: ScheduledEventPrivacyLevel
        , scheduledEventStageStatus :: ScheduledEventStatus
        , scheduledEventStageEntityId :: Maybe ScheduledEventEntityId
        , scheduledEventStageCreator :: Maybe User
        , scheduledEventStageUserCount :: Maybe Integer
        , scheduledEventStageImage :: Maybe ScheduledEventImageHash
        }
    | ScheduledEventVoice
        { scheduledEventVoiceId :: ScheduledEventId
        , scheduledEventVoiceGuildId :: GuildId
        , scheduledEventVoiceChannelId :: ChannelId
        , scheduledEventVoiceCreatorId :: Maybe UserId
        , scheduledEventVoiceName :: T.Text
        , scheduledEventVoiceDescription :: Maybe T.Text
        , scheduledEventVoiceStartTime :: UTCTime
        , scheduledEventVoiceEndTime :: Maybe UTCTime
        , scheduledEventVoicePrivacyLevel :: ScheduledEventPrivacyLevel
        , scheduledEventVoiceStatus :: ScheduledEventStatus
        , scheduledEventVoiceEntityId :: Maybe ScheduledEventEntityId
        , scheduledEventVoiceCreator :: Maybe User
        , scheduledEventVoiceUserCount :: Maybe Integer
        , scheduledEventVoiceImage :: Maybe ScheduledEventImageHash
        }
    | ScheduledEventExternal
        { scheduledEventExternalId :: ScheduledEventId
        , scheduledEventExternalGuildId :: GuildId
        , scheduledEventExternalLocation :: T.Text
        , scheduledEventExternalCreatorId :: Maybe UserId
        , scheduledEventExternalName :: T.Text
        , scheduledEventExternalDescription :: Maybe T.Text
        , scheduledEventExternalStartTime :: UTCTime
        , scheduledEventExternalEndTime :: UTCTime
        , scheduledEventExternalPrivacyLevel :: ScheduledEventPrivacyLevel
        , scheduledEventExternalStatus :: ScheduledEventStatus
        , scheduledEventExternalEntityId :: Maybe ScheduledEventEntityId
        , scheduledEventExternalCreator :: Maybe User
        , scheduledEventExternalUserCount :: Maybe Integer
        , scheduledEventExternalImage :: Maybe ScheduledEventImageHash
        }
    deriving (Show, Eq, Read)
  
  instance ToJSON ScheduledEvent where
    toJSON ScheduledEventStage {..} = objectFromMaybes
        [ "id"                   .== scheduledEventStageId
        , "guild_id"             .== scheduledEventStageGuildId
        , "channel_id"           .== scheduledEventStageChannelId
        , "creator_id"           .=? scheduledEventStageCreatorId
        , "name"                 .== scheduledEventStageName
        , "description"          .=? scheduledEventStageDescription
        , "scheduled_start_time" .== scheduledEventStageStartTime
        , "scheduled_end_time"   .=? scheduledEventStageEndTime
        , "privacy_level"        .== scheduledEventStagePrivacyLevel
        , "entity_type"          .== Number 1
        , "entity_id"            .=? scheduledEventStageEntityId
        , "creator"              .=? scheduledEventStageCreator
        , "user_count"           .=? scheduledEventStageUserCount
        , "image"                .=? scheduledEventStageImage
        ]
    toJSON ScheduledEventVoice {..} = objectFromMaybes
        [ "id"                   .== scheduledEventVoiceId
        , "guild_id"             .== scheduledEventVoiceGuildId
        , "channel_id"           .== scheduledEventVoiceChannelId
        , "creator_id"           .=? scheduledEventVoiceCreatorId
        , "name"                 .== scheduledEventVoiceName
        , "description"          .=? scheduledEventVoiceDescription
        , "scheduled_start_time" .== scheduledEventVoiceStartTime
        , "scheduled_end_time"   .=? scheduledEventVoiceEndTime
        , "privacy_level"        .== scheduledEventVoicePrivacyLevel
        , "entity_type"          .== Number 2
        , "entity_id"            .=? scheduledEventVoiceEntityId
        , "creator"              .=? scheduledEventVoiceCreator
        , "user_count"           .=? scheduledEventVoiceUserCount
        , "image"                .=? scheduledEventVoiceImage
        ]
    toJSON ScheduledEventExternal {..} = objectFromMaybes
        [ "id"                   .== scheduledEventExternalId
        , "guild_id"             .== scheduledEventExternalGuildId
        , "creator_id"           .=? scheduledEventExternalCreatorId
        , "name"                 .== scheduledEventExternalName
        , "description"          .=? scheduledEventExternalDescription
        , "scheduled_start_time" .== scheduledEventExternalStartTime
        , "scheduled_end_time"   .== scheduledEventExternalEndTime
        , "privacy_level"        .== scheduledEventExternalPrivacyLevel
        , "entity_type"          .== Number 3
        , "entity_id"            .=? scheduledEventExternalEntityId
        , "creator"              .=? scheduledEventExternalCreator
        , "user_count"           .=? scheduledEventExternalUserCount
        , "image"                .=? scheduledEventExternalImage
        , "entity_metadata"
           .== object ["location" .= toJSON scheduledEventExternalLocation]
        ]
  
  
  instance FromJSON ScheduledEvent where
    parseJSON = withObject
      "ScheduledEvent"
      (\v -> do
        setype <- v .: "entity_type" :: Parser Int
        seid   <- v .: "id"
        segid  <- v .: "guild_id"
        secrid <- v .:? "creator_id"
        sename <- v .: "name"
        sedesc <- v .:? "description"
        sest   <- v .: "scheduled_start_time"
        sepl   <- v .: "privacy_level" :: Parser ScheduledEventPrivacyLevel
        sestat <- v .: "status" :: Parser ScheduledEventStatus
        seeid  <- v .:? "entity_id"
        secrea <- v .:? "creator"
        seuc   <- v .:? "user_count"
        seim   <- v .:? "image"
  
        case setype of
          1 -> do
            sechid <- v .: "channel_id"
            seet   <- v .:? "scheduled_end_time"
            return $ ScheduledEventStage seid
                                         segid
                                         sechid
                                         secrid
                                         sename
                                         sedesc
                                         sest
                                         seet
                                         sepl
                                         sestat
                                         seeid
                                         secrea
                                         seuc
                                         seim
          2 -> do
            sechid <- v .: "channel_id"
            seet   <- v .:? "scheduled_end_time"
            return $ ScheduledEventVoice seid
                                         segid
                                         sechid
                                         secrid
                                         sename
                                         sedesc
                                         sest
                                         seet
                                         sepl
                                         sestat
                                         seeid
                                         secrea
                                         seuc
                                         seim
          3 -> do
            semeta <- v .: "entity_metadata"
            seloc  <- withObject "entity_metadata" (.: "location") semeta
            seet   <- v .: "scheduled_end_time"
            return $ ScheduledEventExternal seid
                                            segid
                                            seloc
                                            secrid
                                            sename
                                            sedesc
                                            sest
                                            seet
                                            sepl
                                            sestat
                                            seeid
                                            secrea
                                            seuc
                                            seim
          _ -> error "unreachable"
      )
  
  -- | The privacy level of a scheduled event
  data ScheduledEventPrivacyLevel = ScheduledEventPrivacyLevelGuildOnly
    deriving (Show, Read, Eq, Ord, Data)
  
  instance InternalDiscordEnum ScheduledEventPrivacyLevel where
    discordTypeStartValue = ScheduledEventPrivacyLevelGuildOnly
    fromDiscordType ScheduledEventPrivacyLevelGuildOnly = 2
  
  instance ToJSON ScheduledEventPrivacyLevel where
    toJSON = toJSON . fromDiscordType
  
  instance FromJSON ScheduledEventPrivacyLevel where
    parseJSON = discordTypeParseJSON "ScheduledEventPrivacyLevel"
  
  -- | The Status of a Scheduled Event
  data ScheduledEventStatus
    = ScheduledEventStatusScheduled
    | ScheduledEventStatusActive
    | ScheduledEventStatusCompleted
    | ScheduledEventStatusCancelled
    deriving (Show, Read, Eq, Ord, Data)
  
  instance InternalDiscordEnum ScheduledEventStatus where
    discordTypeStartValue = ScheduledEventStatusScheduled
    fromDiscordType ScheduledEventStatusScheduled = 1
    fromDiscordType ScheduledEventStatusActive    = 2
    fromDiscordType ScheduledEventStatusCompleted = 3
    fromDiscordType ScheduledEventStatusCancelled = 4
  
  instance ToJSON ScheduledEventStatus where
    toJSON = toJSON . fromDiscordType
  
  instance FromJSON ScheduledEventStatus where
    parseJSON = discordTypeParseJSON "ScheduledEventStatus"
  
  -- | The hash of the cover image of a ScheduledEvent
  type ScheduledEventImageHash = T.Text
  
  -- | The type of images that can be uploaded
  data CreateScheduledEventImageUploadType
    = CreateScheduledEventImageUploadTypeJPG
    | CreateScheduledEventImageUploadTypePNG
    | CreateScheduledEventImageUploadTypeGIF
    deriving (Show, Read, Eq, Ord)
  
  -- | The required information to add a cover image to a Scheduled Event
  data CreateScheduledEventImage
    = CreateScheduledEventImageURL T.Text
    | CreateScheduledEventImageUpload CreateScheduledEventImageUploadType B.ByteString
    deriving (Show, Read, Eq, Ord)
  
  instance ToJSON CreateScheduledEventImage where
    toJSON (CreateScheduledEventImageURL u) = String u
    toJSON (CreateScheduledEventImageUpload typ bs) =
      String
        $  "data:"
        <> (case typ of
             CreateScheduledEventImageUploadTypeJPG -> "image/jpeg"
             CreateScheduledEventImageUploadTypePNG -> "image/png"
             CreateScheduledEventImageUploadTypeGIF -> "image/gif"
           )
        <> ";base64,"
        <> T.decodeUtf8 bs
  
  instance FromJSON CreateScheduledEventImage where
    parseJSON =
      withText "CreateScheduledEventImage" (return . CreateScheduledEventImageURL)
  
  -- | Data required to create a Scheduled Event
  data CreateScheduledEventData
    = CreateScheduledEventDataStage
        { createScheduleEventDataStageChannelId :: ChannelId
        , createScheduleEventDataStageName :: T.Text
        , createScheduleEventDataStagePrivacyLevel :: ScheduledEventPrivacyLevel
        , createScheduleEventDataStageStartTime :: UTCTime
        , createScheduleEventDataStageEndTime :: Maybe UTCTime
        , createScheduleEventDataStageDescription :: Maybe T.Text
        , createScheduleEventDataStageImage :: Maybe CreateScheduledEventImage
        }
    | CreateScheduledEventDataVoice
        { createScheduleEventDataVoiceChannelId :: ChannelId
        , createScheduleEventDataVoiceName :: T.Text
        , createScheduleEventDataVoicePrivacyLevel :: ScheduledEventPrivacyLevel
        , createScheduleEventDataVoiceStartTime :: UTCTime
        , createScheduleEventDataVoiceEndTime :: Maybe UTCTime
        , createScheduleEventDataVoiceDescription :: Maybe T.Text
        , createScheduleEventDataVoiceImage :: Maybe CreateScheduledEventImage
        }
    | CreateScheduledEventDataExternal
        { createScheduleEventDataExternalLocation :: T.Text
        , createScheduleEventDataExternalName :: T.Text
        , createScheduleEventDataExternalPrivacyLevel :: ScheduledEventPrivacyLevel
        , createScheduleEventDataExternalStartTime :: UTCTime
        , createScheduleEventDataExternalEndTime :: UTCTime
        , createScheduleEventDataExternalDescription :: Maybe T.Text
        , createScheduleEventDataExternalImage :: Maybe CreateScheduledEventImage
        }
  
  instance ToJSON CreateScheduledEventData where
    toJSON CreateScheduledEventDataStage {..} = objectFromMaybes
        [ "channel_id"           .== createScheduleEventDataStageChannelId
        , "name"                 .== createScheduleEventDataStageName
        , "privacy_level"        .== createScheduleEventDataStagePrivacyLevel
        , "scheduled_start_time" .== createScheduleEventDataStageStartTime
        , "scheduled_end_time"   .=? createScheduleEventDataStageEndTime
        , "description"          .=? createScheduleEventDataStageDescription
        , "entity_type"          .== Number 1
        , "image"                .=? createScheduleEventDataStageImage
        ]
    toJSON CreateScheduledEventDataVoice {..} = objectFromMaybes
        [ "channel_id"           .== createScheduleEventDataVoiceChannelId
        , "name"                 .== createScheduleEventDataVoiceName
        , "privacy_level"        .== createScheduleEventDataVoicePrivacyLevel
        , "scheduled_start_time" .== createScheduleEventDataVoiceStartTime
        , "scheduled_end_time"   .=? createScheduleEventDataVoiceEndTime
        , "description"          .=? createScheduleEventDataVoiceDescription
        , "entity_type"          .== Number 2
        , "image"                .=? createScheduleEventDataVoiceImage
        ]
    toJSON CreateScheduledEventDataExternal {..} = objectFromMaybes
        [ "entity_metadata"
           .== object ["location" .= createScheduleEventDataExternalLocation]
        , "name"                 .== createScheduleEventDataExternalName
        , "privacy_level"        .== createScheduleEventDataExternalPrivacyLevel
        , "scheduled_start_time" .== createScheduleEventDataExternalStartTime
        , "scheduled_end_time"   .== createScheduleEventDataExternalEndTime
        , "description"          .=? createScheduleEventDataExternalDescription
        , "entity_type"          .== Number 2
        , "image"                .=? createScheduleEventDataExternalImage
        ]
  
  instance FromJSON CreateScheduledEventData where
    parseJSON = withObject
      "CreateScheduledEventData"
      (\v -> do
        t       <- v .: "entity_type" :: Parser Int
        csename <- v .: "name"
        csepl   <- v .: "privacy_level"
        csest   <- v .: "scheduled_start_time"
        csedesc <- v .:? "description"
        cseimg  <- v .:? "image"
  
        case t of
          1 -> do
            csecid <- v .: "channel_id"
            cseet  <- v .:? "scheduled_end_time"
            return $ CreateScheduledEventDataStage csecid
                                                   csename
                                                   csepl
                                                   csest
                                                   cseet
                                                   csedesc
                                                   cseimg
          2 -> do
            csecid <- v .: "channel_id"
            cseet  <- v .:? "scheduled_end_time"
            return $ CreateScheduledEventDataVoice csecid
                                                   csename
                                                   csepl
                                                   csest
                                                   cseet
                                                   csedesc
                                                   cseimg
          3 -> do
            csemeta <- v .: "entity_metadata"
            cseloc  <- withObject "entity_metadata" (.: "location") csemeta
            cseet   <- v .: "scheduled_end_time"
            return $ CreateScheduledEventDataVoice cseloc
                                                   csename
                                                   csepl
                                                   csest
                                                   cseet
                                                   csedesc
                                                   cseimg
          _ -> error "unreachable"
      )
  
  
  -- | The type of ScheduledEvent, used in 'ModifyScheduledEventData'
  data ScheduledEventType
    = ScheduledEventTypeStage
    | ScheduledEventTypeVoice
    | ScheduledEventTypeExternal
    deriving (Show, Read, Ord, Eq, Data)
  
  instance InternalDiscordEnum ScheduledEventType where
    discordTypeStartValue = ScheduledEventTypeStage
    fromDiscordType ScheduledEventTypeStage    = 1
    fromDiscordType ScheduledEventTypeVoice    = 2
    fromDiscordType ScheduledEventTypeExternal = 3
  
  instance FromJSON ScheduledEventType where
    parseJSON = discordTypeParseJSON "ScheduledEventType"
  
  instance ToJSON ScheduledEventType where
    toJSON = toJSON . fromDiscordType
  
  -- | Data required to issue a Modify Scheduled Event request
  -- This isnt fully type-safe, and can allow for boggus requests but I don't
  -- know of any sane solution to this
  data ModifyScheduledEventData = ModifyScheduledEventData
    { modifyScheduledEventDataChannelId    :: Maybe (Maybe ChannelId)
    , modifyScheduledEventDataLocation     :: Maybe (Maybe T.Text)
    , modifyScheduledEventDataName         :: Maybe T.Text
    , modifyScheduledEventDataPrivacyLevel :: Maybe ScheduledEventPrivacyLevel
    , modifyScheduledEventDataStartTime    :: Maybe UTCTime
    , modifyScheduledEventDataEndTime      :: Maybe UTCTime
    , modifyScheduledEventDataDescription  :: Maybe (Maybe T.Text)
    , modifyScheduledEventDataType         :: Maybe ScheduledEventType
    , modifyScheduledEventDataStatus       :: Maybe ScheduledEventStatus
    , modifyScheduledEventDataImage        :: Maybe CreateScheduledEventImage
    }
  
  instance Default ModifyScheduledEventData where
    def = ModifyScheduledEventData Nothing
                                   Nothing
                                   Nothing
                                   Nothing
                                   Nothing
                                   Nothing
                                   Nothing
                                   Nothing
                                   Nothing
                                   Nothing
  
  instance ToJSON ModifyScheduledEventData where
    toJSON ModifyScheduledEventData {..} = objectFromMaybes
        [ "channel_id"           .=? modifyScheduledEventDataChannelId
        , "entity_metadata"      .=? loc
        , "name"                 .=? modifyScheduledEventDataName
        , "scheduled_start_time" .=? modifyScheduledEventDataStartTime
        , "scheduled_end_time"   .=? modifyScheduledEventDataEndTime
        , "description"          .=? modifyScheduledEventDataDescription
        , "entity_type"          .=? modifyScheduledEventDataType
        , "status"               .=? modifyScheduledEventDataStatus
        , "image"                .=? modifyScheduledEventDataImage
        ]
     where
      loc = case modifyScheduledEventDataLocation of
        Nothing      -> Nothing
        Just Nothing -> Just Null
        Just loc'    -> Just $ object [("location", toJSON loc')]
  
  instance FromJSON ModifyScheduledEventData where
    parseJSON = withObject
      "ModifyScheduledEventData"
      (\v -> do
        -- The trivial fields
        msename  <- v .:? "name"
        msest    <- v .:? "scheduled_start_time"
        mseet    <- v .:? "scheduled_end_time"
        msetype  <- v .:? "entity_type"
        msepl    <- v .:? "privacy_level"
        msestat  <- v .:? "status"
        mseimg   <- v .:? "image"
  
        -- The not so trivial ones
        msecid'  <- v .:! "channel_id"
        mseloc'  <- v .:! "entity_metadata"
        msedesc' <- v .:! "description"
  
        -- Extract the values
        msecid   <- case msecid' of
          Nothing   -> return Nothing
          Just Null -> return $ Just Nothing
          Just x    -> do
            x' <- parseJSON x
            return $ Just x'
  
        mseloc <- case mseloc' of
          Nothing   -> return Nothing
          Just Null -> return $ Just Nothing
          Just x    -> do
            x' <- withObject "entity_metadata" (.: "location") x
            return $ Just x'
  
        msedesc <- case msedesc' of
          Nothing   -> return Nothing
          Just Null -> return $ Just Nothing
          Just x    -> do
            x' <- parseJSON x
            return $ Just x'
  
        return $ ModifyScheduledEventData
          { modifyScheduledEventDataChannelId    = msecid
          , modifyScheduledEventDataLocation     = mseloc
          , modifyScheduledEventDataName         = msename
          , modifyScheduledEventDataPrivacyLevel = msepl
          , modifyScheduledEventDataStartTime    = msest
          , modifyScheduledEventDataEndTime      = mseet
          , modifyScheduledEventDataDescription  = msedesc
          , modifyScheduledEventDataType         = msetype
          , modifyScheduledEventDataStatus       = msestat
          , modifyScheduledEventDataImage        = mseimg
          }
      )
  
  -- | An User that subscribed to a Scheduled Event
  data ScheduledEventUser = ScheduledEventUser
    { scheduledEventUserEvent       :: ScheduledEventId
    , scheduledEventUserUser        :: User
    , scheduledEventUserGuildMember :: Maybe GuildMember
    }
  
  instance FromJSON ScheduledEventUser where
    parseJSON = withObject
      "ScheduledEventUser"
      (\v ->
        ScheduledEventUser
          <$> v .:  "guild_scheduled_event_id"
          <*> v .:  "user"
          <*> v .:? "member"
      )
  