{-# LANGUAGE CPP                #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE OverloadedStrings  #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

-- | This module re-exports the @Github.Data.Definitions@ module, adding
-- instances of @FromJSON@ to it. If you wish to use the data without the
-- instances, use the @Github.Data.Definitions@ module instead.

module Github.Data (
    -- * Module re-exports
    module Github.Auth,
    module Github.Data.Definitions,
    module Github.Data.Gists,
    module Github.Data.GitData,
    module Github.Data.Issues,
    module Github.Data.PullRequests,
    module Github.Data.Repos,
    module Github.Data.Request,
    module Github.Data.Search,
    module Github.Data.Teams,
    module Github.Data.Webhooks,

    -- * Tagged types
    -- ** Name
    Name,
    mkName,
    untagName,
    mkOwnerName,
    mkTeamName,
    -- ** Id
    Id,
    mkId,
    untagId,
    mkOwnerId,
    mkTeamId,
    ) where

import Prelude        ()
import Prelude.Compat

import Data.Aeson.Compat
import Data.Aeson.Types  (Parser)
import Data.Hashable     (Hashable)

import qualified Data.HashMap.Lazy as Map
import qualified Data.Text         as T
import qualified Data.Vector       as V

import Github.Auth
import Github.Data.Definitions
import Github.Data.Gists
import Github.Data.GitData
import Github.Data.Id
import Github.Data.Issues
import Github.Data.Name
import Github.Data.PullRequests
import Github.Data.Repos
import Github.Data.Request
import Github.Data.Search
import Github.Data.Teams
import Github.Data.Webhooks

mkOwnerId :: Int -> Id GithubOwner
mkOwnerId = Id

mkOwnerName :: T.Text -> Name GithubOwner
mkOwnerName = N

mkTeamId :: Int -> Id Team
mkTeamId = Id

mkTeamName :: T.Text -> Name Team
mkTeamName = N

instance FromJSON Commit where
  parseJSON (Object o) =
    Commit <$> o .: "sha"
           <*> o .: "parents"
           <*> o .: "url"
           <*> o .: "commit"
           <*> o .:? "committer"
           <*> o .:? "author"
           <*> o .:< "files"
           <*> o .:? "stats"
  parseJSON _          = fail "Could not build a Commit"

instance FromJSON Tree where
  parseJSON (Object o) =
    Tree <$> o .: "sha"
         <*> o .: "url"
         <*> o .:< "tree"
  parseJSON _          = fail "Could not build a Tree"

instance FromJSON GitTree where
  parseJSON (Object o) =
    GitTree <$> o .: "type"
         <*> o .: "sha"
         <*> o .:? "url"
         <*> o .:? "size"
         <*> o .: "path"
         <*> o .: "mode"
  parseJSON _          = fail "Could not build a GitTree"

instance FromJSON GitCommit where
  parseJSON (Object o) =
    GitCommit <$> o .: "message"
              <*> o .: "url"
              <*> o .: "committer"
              <*> o .: "author"
              <*> o .: "tree"
              <*> o .:? "sha"
              <*> o .:< "parents"
  parseJSON _          = fail "Could not build a GitCommit"

instance FromJSON SimpleOwner where
  parseJSON (Object o)
    | o `at` "gravatar_id" == Nothing =
      SimpleOrganizationOwner
          <$> o .: "avatar_url"
          <*> o .: "login"
          <*> o .: "url"
          <*> o .: "id"
    | otherwise =
      SimpleUserOwner
          <$> o .: "avatar_url"
          <*> o .: "login"
          <*> o .: "url"
          <*> o .: "id"
          <*> o .: "gravatar_id"
  parseJSON v          = fail $ "Could not build a SimpleGithubOwner out of " ++ (show v)

instance FromJSON GitUser where
  parseJSON (Object o) =
    GitUser <$> o .: "name"
            <*> o .: "email"
            <*> o .: "date"
  parseJSON _          = fail "Could not build a GitUser"

instance FromJSON File where
  parseJSON (Object o) =
    File <$> o .: "blob_url"
         <*> o .: "status"
         <*> o .: "raw_url"
         <*> o .: "additions"
         <*> o .: "sha"
         <*> o .: "changes"
         <*> o .:? "patch"
         <*> o .: "filename"
         <*> o .: "deletions"
  parseJSON _          = fail "Could not build a File"

instance FromJSON Stats where
  parseJSON (Object o) =
    Stats <$> o .: "additions"
          <*> o .: "total"
          <*> o .: "deletions"
  parseJSON _          = fail "Could not build a Stats"

instance FromJSON Comment where
  parseJSON (Object o) =
    Comment <$> o .:? "position"
            <*> o .:? "line"
            <*> o .: "body"
            <*> o .:? "commit_id"
            <*> o .: "updated_at"
            <*> o .:? "html_url"
            <*> o .: "url"
            <*> o .: "created_at"
            <*> o .:? "path"
            <*> o .: "user"
            <*> o .: "id"
  parseJSON _          = fail "Could not build a Comment"

instance ToJSON NewComment where
  toJSON (NewComment b) = object [ "body" .= b ]

instance ToJSON EditComment where
  toJSON (EditComment b) = object [ "body" .= b ]

instance FromJSON Diff where
  parseJSON (Object o) =
    Diff <$> o .: "status"
         <*> o .: "behind_by"
         <*> o .: "patch_url"
         <*> o .: "url"
         <*> o .: "base_commit"
         <*> o .:< "commits"
         <*> o .: "total_commits"
         <*> o .: "html_url"
         <*> o .:< "files"
         <*> o .: "ahead_by"
         <*> o .: "diff_url"
         <*> o .: "permalink_url"
  parseJSON _          = fail "Could not build a Diff"

instance FromJSON Gist where
  parseJSON (Object o) =
    Gist <$> o .: "owner"
         <*> o .: "git_push_url"
         <*> o .: "url"
         <*> o .:? "description"
         <*> o .: "created_at"
         <*> o .: "public"
         <*> o .: "comments"
         <*> o .: "updated_at"
         <*> o .: "html_url"
         <*> o .: "id"
         <*> o `values` "files"
         <*> o .: "git_push_url"
  parseJSON _          = fail "Could not build a Gist"

instance FromJSON GistFile where
  parseJSON (Object o) =
    GistFile <$> o .: "type"
             <*> o .: "raw_url"
             <*> o .: "size"
             <*> o .:? "language"
             <*> o .: "filename"
             <*> o .:? "content"
  parseJSON _          = fail "Could not build a GistFile"

instance FromJSON GistComment where
  parseJSON (Object o) =
    GistComment <$> o .: "user"
                <*> o .: "url"
                <*> o .: "created_at"
                <*> o .: "body"
                <*> o .: "updated_at"
                <*> o .: "id"
  parseJSON _          = fail "Could not build a GistComment"

instance FromJSON Blob where
  parseJSON (Object o) =
    Blob <$> o .: "url"
         <*> o .: "encoding"
         <*> o .: "content"
         <*> o .: "sha"
         <*> o .: "size"
  parseJSON _          = fail "Could not build a Blob"

instance ToJSON NewGitReference where
  toJSON (NewGitReference r s) = object [ "ref" .= r, "sha" .= s  ]

instance FromJSON GitReference where
  parseJSON (Object o) =
    GitReference <$> o .: "object"
                 <*> o .: "url"
                 <*> o .: "ref"
  parseJSON _          = fail "Could not build a GitReference"

instance FromJSON GitObject where
  parseJSON (Object o) =
    GitObject <$> o .: "type"
           <*> o .: "sha"
           <*> o .: "url"
  parseJSON _          = fail "Could not build a GitObject"

instance FromJSON Issue where
  parseJSON (Object o) =
    Issue <$> o .:? "closed_at"
          <*> o .: "updated_at"
          <*> o .: "events_url"
          <*> o .: "html_url"
          <*> o .:? "closed_by"
          <*> o .: "labels"
          <*> o .: "number"
          <*> o .:? "assignee"
          <*> o .: "user"
          <*> o .: "title"
          <*> o .:? "pull_request"
          <*> o .: "url"
          <*> o .: "created_at"
          <*> o .: "body"
          <*> o .: "state"
          <*> o .: "id"
          <*> o .: "comments"
          <*> o .:? "milestone"
  parseJSON _          = fail "Could not build an Issue"

instance ToJSON NewIssue where
  toJSON (NewIssue t b a m ls) =
    object
    [ "title"     .= t
    , "body"      .= b
    , "assignee"  .= a
    , "milestone" .= m
    , "labels"    .= ls ]

instance ToJSON EditIssue where
  toJSON (EditIssue t b a s m ls) =
    object $ filter notNull $ [ "title" .= t
                              , "body" .= b
                              , "assignee" .= a
                              , "state" .= s
                              , "milestone" .= m
                              , "labels" .= ls ]
    where notNull (_, Null) = False
          notNull (_, _)    = True

instance FromJSON Milestone where
  parseJSON (Object o) =
    Milestone <$> o .: "creator"
              <*> o .: "due_on"
              <*> o .: "open_issues"
              <*> o .: "number"
              <*> o .: "closed_issues"
              <*> o .: "description"
              <*> o .: "title"
              <*> o .: "url"
              <*> o .: "created_at"
              <*> o .: "state"
  parseJSON _          = fail "Could not build a Milestone"

instance FromJSON IssueLabel where
  parseJSON (Object o) =
    IssueLabel <$> o .: "color"
               <*> o .: "url"
               <*> o .: "name"
  parseJSON _          = fail "Could not build a Milestone"

instance FromJSON PullRequestReference where
  parseJSON (Object o) =
    PullRequestReference <$> o .:? "html_url"
                         <*> o .:? "patch_url"
                         <*> o .:? "diff_url"
  parseJSON _          = fail "Could not build a PullRequest"

instance FromJSON IssueComment where
  parseJSON (Object o) =
    IssueComment <$> o .: "updated_at"
                 <*> o .: "user"
                 <*> o .: "url"
                 <*> o .: "html_url"
                 <*> o .: "created_at"
                 <*> o .: "body"
                 <*> o .: "id"
  parseJSON _          = fail "Could not build an IssueComment"

instance FromJSON Event where
  parseJSON (Object o) =
    Event <$> o .: "actor"
          <*> o .: "event"
          <*> o .:? "commit_id"
          <*> o .: "url"
          <*> o .: "created_at"
          <*> o .: "id"
          <*> o .:? "issue"
  parseJSON _          = fail "Could not build an Event"

instance FromJSON EventType where
  parseJSON (String "closed") = pure Closed
  parseJSON (String "reopened") = pure Reopened
  parseJSON (String "subscribed") = pure Subscribed
  parseJSON (String "merged") = pure Merged
  parseJSON (String "referenced") = pure Referenced
  parseJSON (String "mentioned") = pure Mentioned
  parseJSON (String "assigned") = pure Assigned
  parseJSON (String "unsubscribed") = pure Unsubscribed
  parseJSON (String "unassigned") = pure ActorUnassigned
  parseJSON (String "labeled") = pure Labeled
  parseJSON (String "unlabeled") = pure Unlabeled
  parseJSON (String "milestoned") = pure Milestoned
  parseJSON (String "demilestoned") = pure Demilestoned
  parseJSON (String "renamed") = pure Renamed
  parseJSON (String "locked") = pure Locked
  parseJSON (String "unlocked") = pure Unlocked
  parseJSON (String "head_ref_deleted") = pure HeadRefDeleted
  parseJSON (String "head_ref_restored") = pure HeadRefRestored
  parseJSON _ = fail "Could not build an EventType"

instance FromJSON SimpleOrganization where
  parseJSON (Object o) =
    SimpleOrganization <$> o .: "url"
                       <*> o .: "avatar_url"
                       <*> o .: "id"
                       <*> o .: "login"
  parseJSON _ = fail "Could not build a SimpleOrganization"

instance FromJSON Organization where
  parseJSON (Object o) =
    Organization <$> o .: "type"
                 <*> o .:? "blog"
                 <*> o .:? "location"
                 <*> o .: "login"
                 <*> o .: "followers"
                 <*> o .:? "company"
                 <*> o .: "avatar_url"
                 <*> o .: "public_gists"
                 <*> o .: "html_url"
                 <*> o .:? "email"
                 <*> o .: "following"
                 <*> o .: "public_repos"
                 <*> o .: "url"
                 <*> o .: "created_at"
                 <*> o .:? "name"
                 <*> o .: "id"
  parseJSON _ = fail "Could not build an Organization"

instance FromJSON SimplePullRequest where
  parseJSON (Object o) =
      SimplePullRequest
        <$> o .:? "closed_at"
        <*> o .: "created_at"
        <*> o .: "user"
        <*> o .: "patch_url"
        <*> o .: "state"
        <*> o .: "number"
        <*> o .: "html_url"
        <*> o .: "updated_at"
        <*> o .: "body"
        <*> o .: "issue_url"
        <*> o .: "diff_url"
        <*> o .: "url"
        <*> o .: "_links"
        <*> o .:? "merged_at"
        <*> o .: "title"
        <*> o .: "id"
  parseJSON _ = fail "Could not build a SimplePullRequest"

instance ToJSON EditPullRequestState where
  toJSON (EditPullRequestStateOpen) = String "open"
  toJSON (EditPullRequestStateClosed) = String "closed"

instance ToJSON EditPullRequest where
  toJSON (EditPullRequest t b s) =
    object $ filter notNull [ "title" .= t, "body" .= b, "state" .= s ]
    where notNull (_, Null) = False
          notNull (_, _) = True

instance ToJSON CreatePullRequest where
  toJSON (CreatePullRequest t b headPR basePR) =
    object [ "title" .= t, "body" .= b, "head" .= headPR, "base" .= basePR ]
  toJSON (CreatePullRequestIssue issueNum headPR basePR) =
    object [ "issue" .= issueNum, "head" .= headPR, "base" .= basePR]

instance FromJSON PullRequest where
  parseJSON (Object o) =
      PullRequest
        <$> o .:? "closed_at"
        <*> o .: "created_at"
        <*> o .: "user"
        <*> o .: "patch_url"
        <*> o .: "state"
        <*> o .: "number"
        <*> o .: "html_url"
        <*> o .: "updated_at"
        <*> o .: "body"
        <*> o .: "issue_url"
        <*> o .: "diff_url"
        <*> o .: "url"
        <*> o .: "_links"
        <*> o .:? "merged_at"
        <*> o .: "title"
        <*> o .: "id"
        <*> o .:? "merged_by"
        <*> o .: "changed_files"
        <*> o .: "head"
        <*> o .: "comments"
        <*> o .: "deletions"
        <*> o .: "additions"
        <*> o .: "review_comments"
        <*> o .: "base"
        <*> o .: "commits"
        <*> o .: "merged"
        <*> o .:? "mergeable"
  parseJSON _ = fail "Could not build a PullRequest"

instance FromJSON PullRequestLinks where
  parseJSON (Object o) =
    PullRequestLinks <$> o <.:> ["review_comments", "href"]
                     <*> o <.:> ["comments", "href"]
                     <*> o <.:> ["html", "href"]
                     <*> o <.:> ["self", "href"]
  parseJSON _ = fail "Could not build a PullRequestLinks"

instance FromJSON PullRequestCommit where
  parseJSON (Object o) =
    PullRequestCommit <$> o .: "label"
                      <*> o .: "ref"
                      <*> o .: "sha"
                      <*> o .: "user"
                      <*> o .: "repo"
  parseJSON _ = fail "Could not build a PullRequestCommit"

instance FromJSON PullRequestEvent where
  parseJSON (Object o) =
    PullRequestEvent <$> o .: "action"
                     <*> o .: "number"
                     <*> o .: "pull_request"
                     <*> o .: "repository"
                     <*> o .: "sender"
  parseJSON _ = fail "Could not build a PullRequestEvent"

instance FromJSON PullRequestEventType where
  parseJSON (String "opened") = pure PullRequestOpened
  parseJSON (String "closed") = pure PullRequestClosed
  parseJSON (String "synchronize") = pure PullRequestSynchronized
  parseJSON (String "reopened") = pure PullRequestReopened
  parseJSON (String "assigned") = pure PullRequestAssigned
  parseJSON (String "unassigned") = pure PullRequestUnassigned
  parseJSON (String "labeled") = pure PullRequestLabeled
  parseJSON (String "unlabeled") = pure PullRequestUnlabeled
  parseJSON _ = fail "Could not build a PullRequestEventType"

instance FromJSON RepoWebhookEvent where
  parseJSON (String "*") = pure WebhookWildcardEvent
  parseJSON (String "commit_comment") = pure WebhookCommitCommentEvent
  parseJSON (String "create") = pure WebhookCreateEvent
  parseJSON (String "delete") = pure WebhookDeleteEvent
  parseJSON (String "deployment") = pure WebhookDeploymentEvent
  parseJSON (String "deployment_status") = pure WebhookDeploymentStatusEvent
  parseJSON (String "fork") = pure WebhookForkEvent
  parseJSON (String "gollum") = pure WebhookGollumEvent
  parseJSON (String "issue_comment") = pure WebhookIssueCommentEvent
  parseJSON (String "issues") = pure WebhookIssuesEvent
  parseJSON (String "member") = pure WebhookMemberEvent
  parseJSON (String "page_build") = pure WebhookPageBuildEvent
  parseJSON (String "public") = pure WebhookPublicEvent
  parseJSON (String "pull_request_review_comment") = pure WebhookPullRequestReviewCommentEvent
  parseJSON (String "pull_request") = pure WebhookPullRequestEvent
  parseJSON (String "push") = pure WebhookPushEvent
  parseJSON (String "release") = pure WebhookReleaseEvent
  parseJSON (String "status") = pure WebhookStatusEvent
  parseJSON (String "team_add") = pure WebhookTeamAddEvent
  parseJSON (String "watch") = pure WebhookWatchEvent
  parseJSON _ = fail "Could not build a Webhook event"

instance ToJSON RepoWebhookEvent where
  toJSON (WebhookWildcardEvent) = String "*"
  toJSON (WebhookCommitCommentEvent) = String "commit_comment"
  toJSON (WebhookCreateEvent) = String "create"
  toJSON (WebhookDeleteEvent) = String "delete"
  toJSON (WebhookDeploymentEvent) = String "deployment"
  toJSON (WebhookDeploymentStatusEvent) = String "deployment_status"
  toJSON (WebhookForkEvent) = String "fork"
  toJSON (WebhookGollumEvent) = String "gollum"
  toJSON (WebhookIssueCommentEvent) = String "issue_comment"
  toJSON (WebhookIssuesEvent) = String "issues"
  toJSON (WebhookMemberEvent) = String "member"
  toJSON (WebhookPageBuildEvent) = String "page_build"
  toJSON (WebhookPublicEvent) = String "public"
  toJSON (WebhookPullRequestReviewCommentEvent) = String "pull_request_review_comment"
  toJSON (WebhookPullRequestEvent) = String "pull_request"
  toJSON (WebhookPushEvent) = String "push"
  toJSON (WebhookReleaseEvent) = String "release"
  toJSON (WebhookStatusEvent) = String "status"
  toJSON (WebhookTeamAddEvent) = String "team_add"
  toJSON (WebhookWatchEvent) = String "watch"

instance FromJSON PingEvent where
  parseJSON (Object o) =
    PingEvent <$> o .: "zen"
              <*> o .: "hook"
              <*> o .: "hook_id"
  parseJSON _ = fail "Could not build a PingEvent"

instance FromJSON SearchReposResult where
  parseJSON (Object o) =
    SearchReposResult <$> o .: "total_count"
                      <*> o .:< "items"
  parseJSON _ = fail "Could not build a SearchReposResult"

instance FromJSON SearchIssuesResult where
  parseJSON (Object o) =
    SearchIssuesResult <$> o .: "total_count"
                       <*> o .:< "items"
  parseJSON _ = fail "Could not build a SearchIssuesResult"

instance FromJSON Repo where
  parseJSON (Object o) =
    Repo <$> o .:? "ssh_url"
         <*> o .: "description"
         <*> o .:? "created_at"
         <*> o .: "html_url"
         <*> o .:? "svn_url"
         <*> o .:? "forks"
         <*> o .:? "homepage"
         <*> o .: "fork"
         <*> o .:? "git_url"
         <*> o .: "private"
         <*> o .:? "clone_url"
         <*> o .:? "size"
         <*> o .:? "updated_at"
         <*> o .:? "watchers"
         <*> o .: "owner"
         <*> o .: "name"
         <*> o .:? "language"
         <*> o .:? "master_branch"
         <*> o .:? "pushed_at"
         <*> o .: "id"
         <*> o .: "url"
         <*> o .:? "open_issues"
         <*> o .:? "has_wiki"
         <*> o .:? "has_issues"
         <*> o .:? "has_downloads"
         <*> o .:? "parent"
         <*> o .:? "source"
         <*> o .: "hooks_url"
         <*> o .: "stargazers_count"
  parseJSON _ = fail "Could not build a Repo"

instance ToJSON NewRepo where
  toJSON (NewRepo { newRepoName         = name
                  , newRepoDescription  = description
                  , newRepoHomepage     = homepage
                  , newRepoPrivate      = private
                  , newRepoHasIssues    = hasIssues
                  , newRepoHasWiki      = hasWiki
                  , newRepoAutoInit     = autoInit
                  }) = object
                  [ "name"                .= name
                  , "description"         .= description
                  , "homepage"            .= homepage
                  , "private"             .= private
                  , "has_issues"          .= hasIssues
                  , "has_wiki"            .= hasWiki
                  , "auto_init"           .= autoInit
                  ]

instance ToJSON EditRepo where
  toJSON (EditRepo { editName         = name
                   , editDescription  = description
                   , editHomepage     = homepage
                   , editPublic       = public
                   , editHasIssues    = hasIssues
                   , editHasWiki      = hasWiki
                   , editHasDownloads = hasDownloads
                   }) = object
                   [ "name"          .= name
                   , "description"   .= description
                   , "homepage"      .= homepage
                   , "public"        .= public
                   , "has_issues"    .= hasIssues
                   , "has_wiki"      .= hasWiki
                   , "has_downloads" .= hasDownloads
                   ]

instance FromJSON SearchCodeResult where
  parseJSON (Object o) =
    SearchCodeResult <$> o .: "total_count"
                     <*> o .:< "items"
  parseJSON _ = fail "Could not build a SearchCodeResult"

instance FromJSON Code where
  parseJSON (Object o ) =
    Code <$> o .: "name"
         <*> o .: "path"
         <*> o .: "sha"
         <*> o .: "url"
         <*> o .: "git_url"
         <*> o .: "html_url"
         <*> o .: "repository"
  parseJSON _ = fail "Could not build a Code"

instance FromJSON RepoRef where
  parseJSON (Object o) =
    RepoRef <$> o .: "owner"
            <*> o .: "name"
  parseJSON _ = fail "Could not build a RepoRef"

instance FromJSON Contributor where
  parseJSON (Object o)
    | o `at` "type" == (Just "Anonymous") =
      AnonymousContributor <$> o .: "contributions"
                           <*> o .: "name"
    | otherwise =
      KnownContributor <$> o .: "contributions"
                       <*> o .: "avatar_url"
                       <*> o .: "login"
                       <*> o .: "url"
                       <*> o .: "id"
                       <*> o .: "gravatar_id"
  parseJSON _ = fail "Could not build a Contributor"

instance FromJSON Languages where
  parseJSON (Object o) =
    Languages . V.fromList <$>
      mapM (\name -> Language name <$> o .: name)
           (Map.keys o)
  parseJSON _ = fail "Could not build Languages"

instance FromJSON Tag where
  parseJSON (Object o) =
    Tag <$> o .: "name"
        <*> o .: "zipball_url"
        <*> o .: "tarball_url"
        <*> o .: "commit"
  parseJSON _ = fail "Could not build a Tag"

instance FromJSON Branch where
  parseJSON (Object o) = Branch <$> o .: "name" <*> o .: "commit"
  parseJSON _ = fail "Could not build a Branch"

instance FromJSON BranchCommit where
  parseJSON (Object o) = BranchCommit <$> o .: "sha" <*> o .: "url"
  parseJSON _ = fail "Could not build a BranchCommit"

instance FromJSON GithubOwner where
  parseJSON (Object o)
    | o `at` "gravatar_id" == Nothing =
      GithubOrganization <$> o .: "created_at"
                   <*> o .: "type"
                   <*> o .: "public_gists"
                   <*> o .: "avatar_url"
                   <*> o .: "followers"
                   <*> o .: "following"
                   <*> o .:? "blog"
                   <*> o .:? "bio"
                   <*> o .: "public_repos"
                   <*> o .:? "name"
                   <*> o .:? "location"
                   <*> o .:? "company"
                   <*> o .: "url"
                   <*> o .: "id"
                   <*> o .: "html_url"
                   <*> o .: "login"
    | otherwise =
      GithubUser <$> o .: "created_at"
                   <*> o .: "type"
                   <*> o .: "public_gists"
                   <*> o .: "avatar_url"
                   <*> o .: "followers"
                   <*> o .: "following"
                   <*> o .:? "hireable"
                   <*> o .: "gravatar_id"
                   <*> o .:? "blog"
                   <*> o .:? "bio"
                   <*> o .: "public_repos"
                   <*> o .:? "name"
                   <*> o .:? "location"
                   <*> o .:? "company"
                   <*> o .:? "email"
                   <*> o .: "url"
                   <*> o .: "id"
                   <*> o .: "html_url"
                   <*> o .: "login"
  parseJSON _ = fail "Could not build a GithubOwner"

instance FromJSON Privacy where
  parseJSON (String attr) =
    case attr of
      "secret" -> return PrivacySecret
      "closed" -> return PrivacyClosed
      _        -> fail "Unknown Privacy Attribute"
  parseJSON _ = fail "Could not build Privacy"

instance ToJSON Privacy where
  toJSON attr =
    String $
      case attr of
        PrivacySecret -> "secret"
        PrivacyClosed -> "closed"

instance FromJSON Permission where
  parseJSON (String attr) =
    case attr of
      "pull"  -> return PermissionPull
      "push"  -> return PermissionPush
      "admin" -> return PermissionAdmin
      _       -> fail "Unknown Permission Attribute"
  parseJSON _ = fail "Could not build Permission"

instance ToJSON Permission where
  toJSON attr =
    String $
      case attr of
        PermissionPull  -> "pull"
        PermissionPush  -> "push"
        PermissionAdmin -> "admin"

instance FromJSON SimpleTeam where
  parseJSON (Object o) =
    SimpleTeam <$> o .: "id"
               <*> o .: "url"
               <*> o .: "name"
               <*> o .: "slug"
               <*> o .:?"description" .!= Nothing
               <*> o .:?"privacy" .!= Nothing
               <*> o .: "permission"
               <*> o .: "members_url"
               <*> o .: "repositories_url"
  parseJSON _ = fail "Could not build SimpleTeam"

instance FromJSON Team where
  parseJSON (Object o) =
    Team <$> o .: "id"
         <*> o .: "url"
         <*> o .: "name"
         <*> o .: "slug"
         <*> o .:?"description" .!= Nothing
         <*> o .:?"privacy" .!= Nothing
         <*> o .: "permission"
         <*> o .: "members_url"
         <*> o .: "repositories_url"
         <*> o .: "members_count"
         <*> o .: "repos_count"
         <*> o .: "organization"
  parseJSON _ = fail "Could not build a Team"

instance ToJSON CreateTeam where
  toJSON (CreateTeam name desc repo_names {-privacy-} permissions) =
    object [ "name"        .= name
           , "description" .= desc
           , "repo_names"  .= repo_names
           {-, "privacy" .= privacy-}
           , "permissions" .= permissions ]

instance ToJSON EditTeam where
  toJSON (EditTeam name desc {-privacy-} permissions) =
    object [ "name"        .= name
           , "description" .= desc
           {-, "privacy" .= privacy-}
           , "permissions" .= permissions ]

instance FromJSON Role where
  parseJSON (String attr) =
    case attr of
      "maintainer" -> return RoleMaintainer
      "member"     -> return RoleMember
      _            -> fail "Unknown Role"
  parseJSON _ = fail "Could not build Role"

instance ToJSON Role where
  toJSON RoleMaintainer = String "maintainer"
  toJSON RoleMember     = String "member"

instance FromJSON ReqState where
  parseJSON (String attr) =
    case attr of
      "active"  -> return StateActive
      "pending" -> return StatePending
      _         -> fail "Unknown ReqState"
  parseJSON _ = fail "Could not build ReqState"

instance ToJSON ReqState where
  toJSON StateActive  = String "active"
  toJSON StatePending = String "pending"

instance FromJSON TeamMembership where
  parseJSON (Object o) =
    TeamMembership <$> o .: "url"
                   <*> o .: "role"
                   <*> o .: "state"
  parseJSON _ = fail "Could not build TeamMembership"

instance FromJSON CreateTeamMembership where
  parseJSON (Object o) =
    CreateTeamMembership <$> o .: "role"
  parseJSON _ = fail "Could not build CreateTeamMembership"

instance ToJSON CreateTeamMembership where
  toJSON (CreateTeamMembership { createTeamMembershipRole = role }) =
    object [ "role" .= role ]

instance FromJSON RepoWebhook where
  parseJSON (Object o) =
    RepoWebhook <$> o .: "url"
                <*> o .: "test_url"
                <*> o .: "id"
                <*> o .: "name"
                <*> o .: "active"
                <*> o .: "events"
                <*> o .: "config"
                <*> o .: "last_response"
                <*> o .: "updated_at"
                <*> o .: "created_at"
  parseJSON _          = fail "Could not build a RepoWebhook"

instance FromJSON RepoWebhookResponse where
  parseJSON (Object o) =
    RepoWebhookResponse <$> o .: "code"
                        <*> o .: "status"
                        <*> o .: "message"
  parseJSON _          = fail "Could not build a RepoWebhookResponse"

instance ToJSON NewRepoWebhook where
  toJSON (NewRepoWebhook { newRepoWebhookName = name
                         , newRepoWebhookConfig = config
                         , newRepoWebhookEvents = events
                         , newRepoWebhookActive = active

             }) = object
             [ "name" .= name
             , "config" .= config
             , "events" .= events
             , "active" .= active
             ]

instance ToJSON EditRepoWebhook where
  toJSON (EditRepoWebhook { editRepoWebhookConfig = config
                          , editRepoWebhookEvents = events
                          , editRepoWebhookAddEvents = addEvents
                          , editRepoWebhookRemoveEvents = removeEvents
                          , editRepoWebhookActive = active
             }) = object
             [ "config" .= config
             , "events" .= events
             , "add_events" .= addEvents
             , "remove_events" .= removeEvents
             , "active" .= active
             ]

instance FromJSON Content where
  parseJSON o@(Object _) = ContentFile <$> parseJSON o
  parseJSON (Array os) = ContentDirectory <$> (V.mapM parseJSON os)
  parseJSON _ = fail "Could not build a Content"

instance FromJSON ContentFileData where
  parseJSON (Object o) =
    ContentFileData <$> parseJSON (Object o)
                    <*> o .: "encoding"
                    <*> o .: "size"
                    <*> o .: "content"
  parseJSON _ = fail "Could not build a ContentFileData"

instance FromJSON ContentItem where
  parseJSON (Object o) =
    ContentItem <$> o .: "type"
                <*> parseJSON (Object o)
  parseJSON _ = fail "Could not build a ContentItem"

instance FromJSON ContentItemType where
  parseJSON (String "file") = return ItemFile
  parseJSON (String "dir")  = return ItemDir
  parseJSON _ = fail "Could not build a ContentItemType"

instance FromJSON ContentInfo where
  parseJSON (Object o) =
    ContentInfo <$> o .: "name"
                <*> o .: "path"
                <*> o .: "sha"
                <*> o .: "url"
                <*> o .: "git_url"
                <*> o .: "html_url"
  parseJSON _ = fail "Could not build a ContentInfo"

-- | A slightly less generic version of Aeson's '.:?', using `V.empty' instead
-- of 'Nothing'.
(.:<) :: (FromJSON a) => Object -> T.Text -> Parser (V.Vector a)
obj .:< key = case Map.lookup key obj of
                   Nothing -> pure V.empty
                   Just v  -> parseJSON v

-- | Produce all values for the given key.
values :: (Eq k, Hashable k, FromJSON v) => Map.HashMap k Value -> k -> Parser v
obj `values` key =
  let (Object children) = findWithDefault (Object Map.empty) key obj in
    parseJSON $ Array $ V.fromList $ Map.elems children

-- | Produce the value for the last key by traversing.
(<.:>) :: (FromJSON v) => Object -> [T.Text] -> Parser v
obj <.:> [key] = obj .: key
obj <.:> (key:keys) =
  let (Object nextObj) = findWithDefault (Object Map.empty) key obj in
      nextObj <.:> keys
_ <.:> [] = fail "must have a pair"

-- | Produce the value for the given key, maybe.
at :: Object -> T.Text -> Maybe Value
obj `at` key = Map.lookup key obj

-- Taken from Data.Map:
findWithDefault :: (Eq k, Hashable k) => v -> k -> Map.HashMap k v -> v
findWithDefault def k m =
  case Map.lookup k m of
    Nothing -> def
    Just x  -> x