{-# LANGUAGE OverloadedStrings #-}
-- | Types relating to Discord Guilds (servers)
module Network.Discord.Types.Guild where
  import Data.Time.Clock

  import Data.Aeson
  import Control.Applicative ((<|>))
  import Control.Monad (mzero)

  import Network.Discord.Types.Channel
  import Network.Discord.Types.Prelude

  -- |Representation of a guild member.
  data Member = GuildMember {-# UNPACK #-} !Snowflake User
            | MemberShort User (Maybe String) ![Snowflake]
            deriving Show
  instance FromJSON Member where
    parseJSON (Object o) =
      GuildMember <$> o .: "guild_id" <*> o .: "user"
    parseJSON _ = mzero

  -- | Guilds in Discord represent a collection of users and channels into an isolated
  --   "Server"
  data Guild
    = Guild
        { guildId           :: {-# UNPACK #-} !Snowflake -- ^ Gulid id
        , guildName         ::                 String    -- ^ Guild name (2 - 100 chars)
        , guildIcon         ::                 String    -- ^ Icon hash
        , guildSplash       ::                 String    -- ^ Splash hash
        , guildOwner        ::                !Snowflake -- ^ Guild owner id
        , guildRegion       ::                 String    -- ^ Guild voice region
        , guildAfkId        ::                !Snowflake -- ^ Id of afk channel
        , guildAfkTimeout   ::                !Integer   -- ^ Afk timeout in seconds
        , guildEmbedEnabled ::                !Bool      -- ^ Is this guild embeddable?
        , guildEmbedChannel ::                !Snowflake -- ^ Id of embedded channel
        , guildVerification ::                !Integer   -- ^ Level of verification
        , guildNotification ::                !Integer   -- ^ Level of default notifications
        , guildRoles        ::                [Role]     -- ^ Array of 'Role' objects
        , guildEmojis       ::                [Emoji]    -- ^ Array of 'Emoji' objects
        }
    | Unavailable
        { guildId :: {-# UNPACK #-} !Snowflake
        } deriving Show

  instance FromJSON Guild where
    parseJSON (Object o) = do
      short <- o .:? "unavailable" .!= False
      if short
        then Unavailable <$> o .: "id"
        else
          Guild <$> o .:  "id"
                <*> o .:  "name"
                <*> o .:? "icon" .!= ""
                <*> o .:? "hash" .!= ""
                <*> o .:  "owner_id"
                <*> o .:  "region"
                <*> o .:? "afk_channel_id"   .!= 0
                <*> o .:? "afk_timeout"      .!= 0
                <*> o .:? "embed_enabled"    .!= False
                <*> o .:? "embed_channel_id" .!= 0
                <*> o .:  "verification_level"
                <*> o .:  "default_message_notifications"
                <*> o .:  "roles"
                <*> o .:  "emojis"
    parseJSON _          = mzero

  -- | Represents an emoticon (emoji)
  data Emoji = Emoji
    { emojiId      :: {-# UNPACK #-} !Snowflake   -- ^ The emoji id
    , emojiName    ::                 String      -- ^ The emoji name
    , emojiRoles   ::                ![Snowflake] -- ^ Roles the emoji is active for
    , emojiManaged ::                !Bool        -- ^ Whether this emoji is managed
    } deriving (Show)

  instance FromJSON Emoji where
    parseJSON (Object o) =
      Emoji <$> o .: "id"
            <*> o .: "name"
            <*> o .: "roles"
            <*> o .: "managed"
    parseJSON _ = mzero

  -- | Roles represent a set of permissions attached to a group of users. Roles have unique
  --   names, colors, and can be "pinned" to the side bar, causing their members to be listed separately.
  --   Roles are unique per guild, and can have separate permission profiles for the global context
  --   (guild) and channel context.
  data Role =
      Role {
          roleID      :: {-# UNPACK #-} !Snowflake -- ^ The role id
        , roleName    :: String                    -- ^ The role name
        , roleColor   :: Integer                   -- ^ Integer representation of color code
        , roleHoist   :: Bool                      -- ^ If the role is pinned in the user listing
        , rolePos     :: Integer                   -- ^ Position of this role
        , rolePerms   :: Integer                   -- ^ Permission bit set
        , roleManaged :: Bool                      -- ^ Whether this role is managed by an integration
        , roleMention :: Bool                      -- ^ Whether this role is mentionable
      } deriving (Show, Eq)

  instance FromJSON Role where
    parseJSON (Object o) = Role
      <$> o .: "id"
      <*> o .: "name"
      <*> o .: "color"
      <*> o .: "hoist"
      <*> o .: "position"
      <*> o .: "permissions"
      <*> o .: "managed"
      <*> o .: "mentionable"
    parseJSON _ = mzero

  -- | VoiceRegion is only refrenced in Guild endpoints, will be moved when voice support is added
  data VoiceRegion =
      VoiceRegion
        { regionId          :: {-# UNPACK #-} !Snowflake -- ^ Unique id of the region
        , regionName        :: String                    -- ^ Name of the region
        , regionHostname    :: String                    -- ^ Example hostname for the region
        , regionPort        :: Int                       -- ^ Example port for the region
        , regionVip         :: Bool                      -- ^ True if this is a VIP only server
        , regionOptimal     :: Bool                      -- ^ True for the closest server to a client
        , regionDepreciated :: Bool                      -- ^ Whether this is a deprecated region
        , regionCustom      :: Bool                      -- ^ Whether this is a custom region
        } deriving (Show)

  instance FromJSON VoiceRegion where
    parseJSON (Object o) = VoiceRegion
      <$> o .: "id"
      <*> o .: "name"
      <*> o .: "sample_hostname"
      <*> o .: "sample_port"
      <*> o .: "vip"
      <*> o .: "optimal"
      <*> o .: "deprecated"
      <*> o .: "custom"
    parseJSON _ = mzero

  -- | Represents a code to add a user to a guild
  data Invite =
      Invite {
          inviteCode  ::  String    -- ^ The invite code
        , inviteGuild :: !Snowflake -- ^ The guild the code will invite to
        , inviteChan  :: !Snowflake -- ^ The channel the code will invite to
      }
    -- | Invite code with additional metadata
    | InviteLong Invite InviteMeta

  instance FromJSON Invite where
    parseJSON ob@(Object o) =
          InviteLong <$> parseJSON ob <*> parseJSON ob
      <|> Invite
          <$>  o .: "code"
          <*> ((o .: "guild")   >>= (.: "id"))
          <*> ((o .: "channel") >>= (.: "id"))
    parseJSON _ = mzero

  -- | Additional metadata about an invite.
  data InviteMeta =
    InviteMeta {
        inviteCreator :: User    -- ^ The user that created the invite
      , inviteUses    :: Integer -- ^ Number of times the invite has been used
      , inviteMax     :: Integer -- ^ Max number of times the invite can be used
      , inviteAge     :: Integer -- ^ The duration (in seconds) after which the invite expires
      , inviteTemp    :: Bool    -- ^ Whether this invite only grants temporary membership
      , inviteCreated :: UTCTime -- ^ When the invite was created
      , inviteRevoked :: Bool    -- ^ If the invite is revoked
    }

  instance FromJSON InviteMeta where
    parseJSON (Object o) = InviteMeta
      <$> o .: "inviter"
      <*> o .: "uses"
      <*> o .: "max_uses"
      <*> o .: "max_age"
      <*> o .: "temporary"
      <*> o .: "created_at"
      <*> o .: "revoked"
    parseJSON _ = mzero

  -- | Represents the behavior of a third party account link.
  data Integration =
      Integration
        { integrationId       :: {-# UNPACK #-} !Snowflake -- ^ Integration id
        , integrationName     :: String                    -- ^ Integration name
        , integrationType     :: String                    -- ^ Integration type (Twitch, Youtube, ect.)
        , integrationEnabled  :: Bool                      -- ^ Is the integration enabled
        , integrationSyncing  :: Bool                      -- ^ Is the integration syncing
        , integrationRole     :: Snowflake                 -- ^ Id the integration uses for "subscribers"
        , integrationBehavior :: Integer                   -- ^ The behavior of expiring subscribers
        , integrationGrace    :: Integer                   -- ^ The grace period before expiring subscribers
        , integrationOwner    :: User                      -- ^ The user of the integration
        , integrationAccount  :: IntegrationAccount        -- ^ The account the integration links to
        , integrationSync     :: UTCTime                   -- ^ When the integration was last synced
        } deriving (Show)

  instance FromJSON Integration where
    parseJSON (Object o) = Integration
      <$> o .: "id"
      <*> o .: "name"
      <*> o .: "type"
      <*> o .: "enabled"
      <*> o .: "syncing"
      <*> o .: "role_id"
      <*> o .: "expire_behavior"
      <*> o .: "expire_grace_period"
      <*> o .: "user"
      <*> o .: "account"
      <*> o .: "synced_at"
    parseJSON _ = mzero

  -- | Represents a third party account link.
  data IntegrationAccount =
    Account
      { accountId   :: String -- ^ The id of the account.
      , accountName :: String -- ^ The name of the account.
      } deriving (Show)

  instance FromJSON IntegrationAccount where
    parseJSON (Object o) = Account
      <$> o .: "id"
      <*> o .: "name"
    parseJSON _ = mzero

  -- | Represents an image to be used in third party sites to link to a discord channel
  data GuildEmbed =
      GuildEmbed
        { embedEnabled :: !Bool                     -- ^ Whether the embed is enabled
        , embedChannel :: {-# UNPACK #-} !Snowflake -- ^ The embed channel id
        }
  instance FromJSON GuildEmbed where
    parseJSON (Object o) = GuildEmbed
      <$> o .: "enabled"
      <*> o .: "snowflake"
    parseJSON _ = mzero

  instance ToJSON GuildEmbed where
    toJSON (GuildEmbed enabled snowflake) = object
      [ "enabled"   .= enabled
      , "snowflake" .= snowflake
      ]
