{-# LANGUAGE DeriveDataTypeable, OverloadedStrings #-}

module Github.PostReceive.Types
    ( Payload (..)
    , Commit (..)
    , Repository (..)
    , User (..)
      -- Re-exports
    , EmailAddress
    ) where

import Control.Applicative ((<$>), (<*>), pure)
import Data.Aeson (Value (..), FromJSON (..), (.:), (.:?))
import qualified Data.ByteString.Char8 as B
import Data.Text (Text)
import qualified Data.Text as T
import Data.Typeable (Typeable)
import Text.Email.Validate (EmailAddress, emailAddress)

data Payload = Payload
    { payloadRef :: Text
    , payloadAfter :: Text
    , payloadBefore :: Text
    , payloadCreated :: Bool
    , payloadDeleted :: Bool
    , payloadForced :: Bool
    , payloadCompare :: Text
    , payloadCommits :: [Commit]
    , payloadHeadCommit :: Commit
    , payloadRepository :: Repository
    , payloadPusher :: User
    } deriving (Show, Eq, Typeable)

instance FromJSON Payload where
    parseJSON (Object o) = Payload
        <$> o .: "ref"
        <*> o .: "after"
        <*> o .: "before"
        <*> o .: "created"
        <*> o .: "deleted"
        <*> o .: "forced"
        <*> o .: "compare"
        <*> o .: "commits"
        <*> o .: "head_commit"
        <*> o .: "repository"
        <*> o .: "pusher"
    parseJSON _ = fail "Payload must be an object"

data Commit = Commit
    { commitId :: Text
    , commitDistinct :: Bool
    , commitMessage :: Text
    , commitTimestamp :: Text
    , commitUrl :: Text
    , commitAuthor :: User
    , commitCommitter :: User
    , commitAdded :: [FilePath]
    , commitRemoved :: [FilePath]
    , commitModified :: [FilePath]
    } deriving (Show, Eq, Typeable)

instance FromJSON Commit where
    parseJSON (Object o) = Commit
        <$> o .: "id"
        <*> o .: "distinct"
        <*> o .: "message"
        <*> o .: "timestamp"
        <*> o .: "url"
        <*> o .: "author"
        <*> o .: "committer"
        <*> o .: "added"
        <*> o .: "removed"
        <*> o .: "modified"
    parseJSON _ = fail "Commit must be an object"

data Repository = Repository
    { repoId :: Int
    , repoName :: Text
    , repoUrl :: Text
    , repoDescription :: Text
    , repoHomepage :: Maybe Text
    , repoWatchers :: Int
    , repoStargazers :: Int
    , repoForks :: Int
    , repoFork :: Bool
    , repoSize :: Int
    , repoOwner :: User
    , repoPrivate :: Bool
    , repoOpenIssues :: Int
    , repoHasIssues :: Bool
    , repoHasDownloads :: Bool
    , repoHasWiki :: Bool
    , repoLanguage :: Text
    , repoCreatedAt :: Int
    , repoPushedAt :: Int
    , repoMasterBranch :: Text
    } deriving (Show, Eq, Typeable)

instance FromJSON Repository where
    parseJSON (Object o) = Repository
        <$> o .: "id"
        <*> o .: "name"
        <*> o .: "url"
        <*> o .: "description"
        <*> o .:? "homepage"
        <*> o .: "watchers"
        <*> o .: "stargazers"
        <*> o .: "forks"
        <*> o .: "fork"
        <*> o .: "size"
        <*> o .: "owner"
        <*> o .: "private"
        <*> o .: "open_issues"
        <*> o .: "has_issues"
        <*> o .: "has_downloads"
        <*> o .: "has_wiki"
        <*> o .: "language"
        <*> o .: "created_at"
        <*> o .: "pushed_at"
        <*> o .: "master_branch"
    parseJSON _ = fail "Repository must be an object"

data User = User
    { userName :: Text
    , userEmail :: Maybe EmailAddress
    , userUsername :: Maybe Text
    } deriving (Show, Eq, Typeable)

instance FromJSON User where
    parseJSON (Object o) = User
        <$> o .: "name"
        <*> o .:? "email"
        <*> o .:? "username"
    parseJSON _ = fail "User must be an object"

instance FromJSON EmailAddress where
    parseJSON (String t) = case emailAddress $ B.pack . T.unpack $ t of
        Just a -> pure a
        Nothing -> fail "failed to parse EmailAddress"
    parseJSON _ = fail "EmailAddress must be a text"