{-|
Module: BattlePlace.WebApi.Types
Description: Web API types.
License: MIT
-}

{-# LANGUAGE DeriveGeneric, GeneralizedNewtypeDeriving, LambdaCase, OverloadedStrings, StandaloneDeriving, TemplateHaskell, ViewPatterns #-}

module BattlePlace.WebApi.Types
  ( ProjectId(..)
  , Auth(..)
  , AuthType(..)
  , authTypeOf
  , Client(..)
  , ClientType(..)
  , clientTypeOf
  , DeveloperId(..)
  , ProjectServerId(..)
  , ProjectServerToken(..)
  , ProjectServerName(..)
  , MatchTeamSize
  , MatchTag(..)
  , ServerTag(..)
  , MatchPlayerInfo(..)
  , MatchServerInfo(..)
  , MatchToken(..)
  , MatchFailureReason(..)
  , SessionToken(..)
  , SessionId(..)
  , ServerSessionToken(..)
  , ExternalSessionId(..)
  , MatchSession(..)
  , MatchServerSession(..)
  , MatchTeam(..)
  , MatchPlayer(..)
  , MatchServer(..)
  , UserStats(..)
  , Identified(..)
  , Base64ByteString(..)
  , Base64Word64(..)
  , StrWord64(..)
  ) where

import qualified Data.Aeson as J
import Data.Hashable
import Data.Proxy
import qualified Data.Swagger as SW
import qualified Data.Text as T
import qualified Data.Vector as V
import GHC.Generics(Generic)
import Servant.API

import BattlePlace.Rating
import BattlePlace.Token.Types
import BattlePlace.Util
import BattlePlace.WebApi.Types.Util

-- | Project id.
newtype ProjectId = ProjectId Base64Word64 deriving (Eq, Generic, Hashable, J.FromJSON, J.ToJSON, FromHttpApiData)
instance SW.ToSchema ProjectId where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

data Auth
  = Auth_itchJwtToken
    { auth_itchJwtToken :: !T.Text
    }
  | Auth_itchApiKey
    { auth_itchApiKey :: !T.Text
    }
  | Auth_steamEncryptedTicket
    { auth_steamEncryptedTicket :: !T.Text
    }
  | Auth_testKey
    { auth_testKey :: !T.Text
    , auth_testId :: !StrWord64
    }
  deriving Generic
instance J.FromJSON Auth where
  parseJSON = J.genericParseJSON jsonOptions
    { J.sumEncoding = J.UntaggedValue
    }
instance J.ToJSON Auth where
  toJSON = J.genericToJSON jsonOptions
    { J.sumEncoding = J.UntaggedValue
    }
  toEncoding = J.genericToEncoding jsonOptions
    { J.sumEncoding = J.UntaggedValue
    }
instance SW.ToSchema Auth where
  declareNamedSchema = SW.genericDeclareNamedSchemaUnrestricted swaggerSchemaOptions

-- | Auth type (for logging).
data AuthType
  = AuthType_itchJwtToken
  | AuthType_itchApiKey
  | AuthType_steamEncryptedTicket
  | AuthType_testKey
  deriving Eq

authTypeOf :: Auth -> AuthType
authTypeOf = \case
  Auth_itchJwtToken {} -> AuthType_itchJwtToken
  Auth_itchApiKey {} -> AuthType_itchApiKey
  Auth_steamEncryptedTicket {} -> AuthType_steamEncryptedTicket
  Auth_testKey {} -> AuthType_testKey

data Client
  = Client_itch
    { client_itchUserId :: {-# UNPACK #-} !StrWord64
    }
  | Client_steam
    { client_steamId :: {-# UNPACK #-} !StrWord64
    }
  | Client_test
    { client_testId :: {-# UNPACK #-} !StrWord64
    }
  deriving (Eq, Generic)
instance Hashable Client
instance J.FromJSON Client where
  parseJSON = J.genericParseJSON $ jsonOptionsWithTag "type"
instance J.ToJSON Client where
  toJSON = J.genericToJSON $ jsonOptionsWithTag "type"
  toEncoding = J.genericToEncoding $ jsonOptionsWithTag "type"

-- | Type of the client. Must correspont to JSON field "type" of 'Client'.
data ClientType
  = ClientType_itch
  | ClientType_steam
  | ClientType_test
  deriving Eq
instance Hashable ClientType

clientTypeOf :: Client -> ClientType
clientTypeOf = \case
  Client_itch {} -> ClientType_itch
  Client_steam {} -> ClientType_steam
  Client_test {} -> ClientType_test

-- | Developer id.
-- At the moment it's just itch user id, but that may change.
newtype DeveloperId = DeveloperId Base64Word64 deriving (J.FromJSON, J.ToJSON, FromHttpApiData)

-- | Project's server id.
newtype ProjectServerId = ProjectServerId Base64Word64 deriving (Eq, Hashable, J.FromJSON, J.FromJSONKey, J.ToJSON, J.ToJSONKey)

-- | Project's secret server token.
newtype ProjectServerToken = ProjectServerToken T.Text deriving (Eq, Generic, Hashable, J.FromJSON, J.ToJSON)
instance SW.ToSchema ProjectServerToken where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | Project's server name.
newtype ProjectServerName = ProjectServerName T.Text deriving (Eq, Generic, Hashable, J.FromJSON, J.ToJSON)
instance SW.ToSchema ProjectServerName where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | Size of a team in match request.
type MatchTeamSize = Int

-- | Match tag in match request.
newtype MatchTag = MatchTag T.Text deriving (Eq, Generic, Hashable, Semigroup, Monoid, J.FromJSON, J.ToJSON)
instance SW.ToSchema MatchTag where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | Server tag in match request.
newtype ServerTag = ServerTag T.Text deriving (Eq, Generic, Hashable, Semigroup, Monoid, J.FromJSON, J.ToJSON)
instance SW.ToSchema ServerTag where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | Opaque player info.
newtype MatchPlayerInfo = MatchPlayerInfo J.Value deriving (Generic, J.FromJSON, J.ToJSON)
instance SW.ToSchema MatchPlayerInfo where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions $ \_ -> SW.declareSchema (Proxy :: Proxy J.Object)

-- | Opaque server info.
newtype MatchServerInfo = MatchServerInfo J.Value deriving (Generic, J.FromJSON, J.ToJSON)
instance SW.ToSchema MatchServerInfo where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions $ \_ -> SW.declareSchema (Proxy :: Proxy J.Object)

-- | Match token.
data MatchToken = MatchToken
  {
  }

-- | Reason of match failure.
data MatchFailureReason
  -- | Failed to make a match in a specified time.
  = MatchFailureReason_timedOut
  -- | Match was made, but no server is available (and use of server is mandatory).
  | MatchFailureReason_noServer
  -- | Matching was explicitly cancelled by user.
  | MatchFailureReason_cancelled

-- | Session token.
data SessionToken = SessionToken
  { sessionToken_sessionId :: !SessionId
  , sessionToken_teamIndex :: {-# UNPACK #-} !Int
  , sessionToken_mateIndex :: {-# UNPACK #-} !Int
  }

-- | Session id.
newtype SessionId = SessionId Base64ByteString deriving (Eq, Generic, Hashable, J.FromJSON, J.ToJSON)
instance SW.ToSchema SessionId where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | Server session token.
newtype ServerSessionToken = ServerSessionToken
  { serverSessionToken_sessionId :: SessionId
  }

-- | External session token for exposure to clients and servers.
newtype ExternalSessionId = ExternalSessionId T.Text deriving (Eq, Generic, Hashable, J.FromJSON, J.ToJSON)
instance SW.ToSchema ExternalSessionId where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | Match player.
data MatchPlayer = MatchPlayer
  { matchPlayer_info :: !MatchPlayerInfo
  , matchPlayer_ourTicket :: !(Maybe Ticket)
  , matchPlayer_theirTicket :: !(Maybe Ticket)
  }

-- | Match server.
data MatchServer = MatchServer
  { matchServer_info :: !MatchServerInfo
  , matchServer_ourTicket :: !Ticket
  , matchServer_theirTicket :: !Ticket
  }

declareStruct
  [ ''AuthType
  , ''ClientType
  , ''MatchToken
  , ''MatchFailureReason
  , ''SessionToken
  , ''ServerSessionToken
  , ''MatchPlayer
  , ''MatchServer
  ]

instance Hashable AuthType

-- | Match session.
data MatchSession = MatchSession
  { matchSession_externalSessionId :: !ExternalSessionId
  , matchSession_sessionToken :: !(InternalToken SessionToken)
  , matchSession_teams :: !(V.Vector MatchTeam)
  , matchSession_teamIndex :: {-# UNPACK #-} !Int
  , matchSession_mateIndex :: {-# UNPACK #-} !Int
  , matchSession_server :: !(Maybe MatchServer)
  }

-- | Match server session.
data MatchServerSession = MatchServerSession
  { matchServerSession_externalSessionId :: !ExternalSessionId
  , matchServerSession_serverSessionToken :: !(InternalToken ServerSessionToken)
  , matchServerSession_teams :: !(V.Vector MatchTeam)
  , matchServerSession_matchTag :: !MatchTag
  , matchServerSession_serverTag :: !ServerTag
  }

-- | Match team.
newtype MatchTeam = MatchTeam (V.Vector MatchPlayer) deriving (Generic, J.FromJSON, J.ToJSON)
instance SW.ToSchema MatchTeam where
  declareNamedSchema = SW.genericDeclareNamedSchemaNewtype swaggerSchemaOptions SW.declareSchema

-- | User stats.
data UserStats = UserStats
  { userStats_rank :: {-# UNPACK #-} !Int
  , userStats_rating :: {-# UNPACK #-} !Rating
  }

-- | Generic data type for id + object.
data Identified i a = Identified
  { identified_id :: !i
  , identified_info :: !a
  } deriving Generic
instance (J.FromJSON i, J.FromJSON a) => J.FromJSON (Identified i a) where
  parseJSON = J.genericParseJSON jsonOptions
instance (J.ToJSON i, J.ToJSON a) => J.ToJSON (Identified i a) where
  toJSON = J.genericToJSON jsonOptions
  toEncoding = J.genericToEncoding jsonOptions
instance (SW.ToSchema i, SW.ToSchema a) => SW.ToSchema (Identified i a)

declareStruct
  [ ''MatchSession
  , ''MatchServerSession
  , ''UserStats
  ]