{-# LANGUAGE StrictData #-}
module GitHub.Types.PayloadParser where

import           Data.Aeson          (FromJSON (..), ToJSON (..))
import           Data.Aeson.Types    (Parser, Value (..))
import qualified Data.List           as List
import           Data.Text           (Text)
import qualified Data.Text           as Text

import           GitHub.Types.Event
import           GitHub.Types.Events


data Payload
    = CheckRunEventPayload                 CheckRunEvent
    | CheckSuiteEventPayload               CheckSuiteEvent
    | CommitCommentEventPayload            CommitCommentEvent
    | CreateEventPayload                   CreateEvent
    | DeleteEventPayload                   DeleteEvent
    | DeploymentEventPayload               DeploymentEvent
    | DeploymentStatusEventPayload         DeploymentStatusEvent
    | ForkEventPayload                     ForkEvent
    | GollumEventPayload                   GollumEvent
    | IssueCommentEventPayload             IssueCommentEvent
    | IssuesEventPayload                   IssuesEvent
    | LabelEventPayload                    LabelEvent
    | MemberEventPayload                   MemberEvent
    | MembershipEventPayload               MembershipEvent
    | MilestoneEventPayload                MilestoneEvent
    | OrganizationEventPayload             OrganizationEvent
    | PageBuildEventPayload                PageBuildEvent
    | PingEventPayload                     PingEvent
    | PullRequestEventPayload              PullRequestEvent
    | PullRequestReviewCommentEventPayload PullRequestReviewCommentEvent
    | PullRequestReviewEventPayload        PullRequestReviewEvent
    | PushEventPayload                     PushEvent
    | ReleaseEventPayload                  ReleaseEvent
    | RepositoryEventPayload               RepositoryEvent
    | StarEventPayload                     StarEvent
    | StatusEventPayload                   StatusEvent
    | WatchEventPayload                    WatchEvent
    | WorkflowJobEventPayload              WorkflowJobEvent
    | WorkflowRunEventPayload              WorkflowRunEvent
    deriving (Eq, Show, Read)


instance ToJSON Payload where
    toJSON (CheckRunEventPayload                 x) = toJSON x
    toJSON (CheckSuiteEventPayload               x) = toJSON x
    toJSON (CommitCommentEventPayload            x) = toJSON x
    toJSON (CreateEventPayload                   x) = toJSON x
    toJSON (DeleteEventPayload                   x) = toJSON x
    toJSON (DeploymentEventPayload               x) = toJSON x
    toJSON (DeploymentStatusEventPayload         x) = toJSON x
    toJSON (ForkEventPayload                     x) = toJSON x
    toJSON (GollumEventPayload                   x) = toJSON x
    toJSON (IssueCommentEventPayload             x) = toJSON x
    toJSON (IssuesEventPayload                   x) = toJSON x
    toJSON (LabelEventPayload                    x) = toJSON x
    toJSON (MemberEventPayload                   x) = toJSON x
    toJSON (MembershipEventPayload               x) = toJSON x
    toJSON (MilestoneEventPayload                x) = toJSON x
    toJSON (OrganizationEventPayload             x) = toJSON x
    toJSON (PageBuildEventPayload                x) = toJSON x
    toJSON (PingEventPayload                     x) = toJSON x
    toJSON (PullRequestEventPayload              x) = toJSON x
    toJSON (PullRequestReviewCommentEventPayload x) = toJSON x
    toJSON (PullRequestReviewEventPayload        x) = toJSON x
    toJSON (PushEventPayload                     x) = toJSON x
    toJSON (ReleaseEventPayload                  x) = toJSON x
    toJSON (RepositoryEventPayload               x) = toJSON x
    toJSON (StatusEventPayload                   x) = toJSON x
    toJSON (StarEventPayload                     x) = toJSON x
    toJSON (WatchEventPayload                    x) = toJSON x
    toJSON (WorkflowJobEventPayload              x) = toJSON x
    toJSON (WorkflowRunEventPayload              x) = toJSON x


data PayloadParser = PayloadParser
    { payloadParserTypeName    :: Text
    , payloadParserWebhookName :: Text
    , payloadParser            :: Value -> Parser Payload
    }


payloadParsers :: [PayloadParser]
payloadParsers =
    [ eventParser CheckRunEventPayload
    , eventParser CheckSuiteEventPayload
    , eventParser CommitCommentEventPayload
    , eventParser CreateEventPayload
    , eventParser DeleteEventPayload
    , eventParser DeploymentEventPayload
    , eventParser DeploymentStatusEventPayload
    , eventParser ForkEventPayload
    , eventParser GollumEventPayload
    , eventParser IssueCommentEventPayload
    , eventParser IssuesEventPayload
    , eventParser LabelEventPayload
    , eventParser MemberEventPayload
    , eventParser MembershipEventPayload
    , eventParser MilestoneEventPayload
    , eventParser OrganizationEventPayload
    , eventParser PageBuildEventPayload
    , eventParser PingEventPayload
    , eventParser PullRequestEventPayload
    , eventParser PullRequestReviewCommentEventPayload
    , eventParser PullRequestReviewEventPayload
    , eventParser PushEventPayload
    , eventParser ReleaseEventPayload
    , eventParser RepositoryEventPayload
    , eventParser StatusEventPayload
    , eventParser StarEventPayload
    , eventParser WatchEventPayload
    , eventParser WorkflowJobEventPayload
    , eventParser WorkflowRunEventPayload
    ]
  where
    eventParser' :: FromJSON a => TypeName a -> EventName a -> (a -> Payload) -> PayloadParser
    eventParser' (TypeName ty) (EventName ev) tycon =
        PayloadParser ty ev (fmap tycon . parseJSON)

    eventParser :: (FromJSON a, Event a) => (a -> Payload) -> PayloadParser
    eventParser = eventParser' typeName eventName



eventPayloadParser :: Text -> Value -> Parser Payload
eventPayloadParser eventType x =
    case List.find ((== eventType) . payloadParserTypeName) payloadParsers of
        Nothing -> fail $ "eventPayloadParser: unknown event type: " ++ Text.unpack eventType
        Just p  -> payloadParser p x

-- | Since the event type is included through different means (X-GitHub-Event
-- header, or inline in the JSON object), it's not possible to make 'Event'
-- an instance of 'FromJSON'. But if you know the type, you can use this
-- parser.
webhookPayloadParser :: Text -> Value -> Parser Payload
webhookPayloadParser eventType x =
    case List.find ((== eventType) . payloadParserWebhookName) payloadParsers of
        Nothing -> fail $ "webhookPayloadParser: unknown event type: " ++ Text.unpack eventType
        Just p  -> payloadParser p x
