{-# LANGUAGE OverloadedStrings #-}

-- | Module containing the functions directly dealing with twitter's API. Most functions in this module have two versions - one which takes a path to a TOML file containing api keys/secrets and tokens/secrets, the other takes api keys/secrets and tokens/secrets as an argument.
module Web.Tweet.API where

import           Control.Composition
import           Control.Monad
import qualified Data.ByteString.Lazy.Char8 as BSL
import           Data.Maybe                 (isJust)
import           Data.Void
import           Lens.Micro
import           Lens.Micro.Extras
import           Text.Megaparsec.Error
import           Web.Tweet.Types
import           Web.Tweet.Utils
import           Web.Tweet.Utils.API

-- | Get tweets (text only) for some user
getMarkov :: String -> Maybe Int -> FilePath -> IO [String]
getMarkov = fmap (map (view text)) .** getAll

-- | Get all tweets by some user
getAll :: String -> Maybe Int -> FilePath -> IO Timeline
getAll sn maxId filepath = do
    tweets <- either (error "Parse tweets failed") id <$> getProfileMax sn 200 filepath maxId
    let lastId = _tweetId <$> tweets^?_last
    if lastId == maxId then
        pure []
    else
        do
            if isJust lastId
                then putStrLn $ "fetching tweets since " ++ show (lastId^?!_Just) ++ "..."
                else pure ()
            next <- getAll sn lastId filepath
            pure (tweets ++ next)

-- | tweet, given a `Tweet` and a `Config` containing necessary data to sign the request.
tweetDataMem :: Tweet -> Config -> IO Int
tweetDataMem tweet config = do
    let requestString = urlString tweet
    bytes <- postRequestMem ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString) config
    putStrLn $ displayTimelineColor . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
    pure . view tweetId . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes

-- | tweet, given a `Tweet` and path to credentials. Return id of posted tweet.
tweetData :: Tweet -> FilePath -> IO Int
tweetData tweet filepath = do
    let requestString = urlString tweet
    bytes <- postRequest ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString) filepath -- FIXME fix the coloration
    putStrLn $ displayTimelineColor . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
    pure . view tweetId . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes

-- | Gets user profile with max_id set.
getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseErrorBundle String Void) Timeline)
getProfileMax = fmap (getTweets . BSL.toStrict) .*** getProfileRaw

-- | Gets user profile with max_id set.
getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseErrorBundle String Void) Timeline)
getProfileMaxMem = fmap (getTweets . BSL.toStrict) .*** getProfileRawMem

-- | Gets user profile with max_id set.
getProfileRaw :: String -> Int -> FilePath -> Maybe Int -> IO BSL.ByteString
getProfileRaw sn count filepath maxId = getRequest ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) filepath
    where requestString = case maxId of {
        (Just i) -> "?screen_name=" ++ sn ++ "&count=" ++ show count ++ "&max_id=" ++ show i ;
        Nothing -> "?screen_name=" ++ sn ++ "&count=" ++ show count }

-- | Gets user profile with max_id set
getProfileRawMem :: String -> Int -> Config -> Maybe Int -> IO BSL.ByteString
getProfileRawMem sn count config maxId = getRequestMem ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) config
    where requestString = case maxId of {
        (Just i) -> "?screen_name=" ++ sn ++ "&count=" ++ show count ++ "&max_id=" ++ show i ;
        Nothing -> "?screen_name=" ++ sn ++ "&count=" ++ show count }

-- | Get mentions and parse response as a list of tweets
mentions :: Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
mentions = fmap (getTweets . BSL.toStrict) .* mentionsRaw

-- | Get mentions and parse response as a list of tweets
mentionsMem :: Int -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
mentionsMem = fmap (getTweets . BSL.toStrict) .* mentionsRawMem

-- | Gets mentions
mentionsRaw :: Int -> FilePath -> IO BSL.ByteString
mentionsRaw count = getRequest ("https://api.twitter.com/1.1/statuses/mentions_timeline.json" ++ requestString)
    where requestString = "?count=" ++ show count

-- | Gets mentions
mentionsRawMem :: Int -> Config -> IO BSL.ByteString
mentionsRawMem count = getRequestMem ("https://api.twitter.com/1.1/statuses/mentions_timeline.json" ++ requestString)
    where requestString = "?count=" ++ show count

-- | Get user profile given screen name and how many tweets to return
getProfile :: String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
getProfile sn count filepath = getProfileMax sn count filepath Nothing

-- | Mute a user given their screen name
mute :: String -> FilePath -> IO ()
mute = fmap void . muteUserRaw

-- | Mute a user given their screen name
muteMem :: String -> Config -> IO ()
muteMem = fmap void . muteUserRawMem

-- | Unmute a user given their screen name
unmute :: String -> FilePath -> IO ()
unmute = fmap void . unmuteUserRaw

-- | Unmute a user given their screen name
unmuteMem :: String -> Config -> IO ()
unmuteMem = fmap void . unmuteUserRawMem

-- | Mute a user given their screen name
muteUserRaw :: String -> FilePath -> IO BSL.ByteString
muteUserRaw sn = postRequest ("https://api.twitter.com/1.1/mutes/users/create.json?screen_name=" ++ sn)

-- | Mute a user given their screen name
muteUserRawMem :: String -> Config -> IO BSL.ByteString
muteUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/mutes/users/create.json?screen_name=" ++ sn)

-- | Unmute a user given their screen name
unmuteUserRaw :: String -> FilePath -> IO BSL.ByteString
unmuteUserRaw sn = postRequest ("https://api.twitter.com/1.1/mutes/users/destroy.json?screen_name=" ++ sn)

-- | Unmute a user given their screen name
unmuteUserRawMem :: String -> Config -> IO BSL.ByteString
unmuteUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/mutes/users/destroy.json?screen_name=" ++ sn)

-- | Get user's DMs.
getDMsRaw :: Show p => p -> FilePath -> IO BSL.ByteString
getDMsRaw count = getRequest ("https://api.twitter.com/1.1/direct_messages.json" ++ requestString)
    where requestString = "?count=" ++ show count

-- | Get a user's favorites
getFavorites :: Int -> String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
getFavorites count = fmap (fmap (take count) . getTweets . BSL.toStrict) .* favoriteTweetListRaw

-- | Get a timeline
getTimeline :: Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
getTimeline = fmap (getTweets . BSL.toStrict) .* getTimelineRaw

-- | Get a timeline
getTimelineMem :: Int -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
getTimelineMem = fmap (getTweets . BSL.toStrict) .* getTimelineRawMem

-- | Get a user's timeline and return response as a bytestring
getTimelineRaw :: Int -> FilePath -> IO BSL.ByteString
getTimelineRaw count = getRequest ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString)
    where requestString = "?count=" ++ show count

-- | Get a user's timeline and return response as a bytestring
getTimelineRawMem :: Int -> Config -> IO BSL.ByteString
getTimelineRawMem count = getRequestMem ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString)
    where requestString = "?count=" ++ show count

-- | Delete a tweet given its id
deleteTweet :: Integer -> FilePath -> IO ()
deleteTweet = fmap void . deleteTweetRaw

-- | Delete a tweet given its id
deleteTweetMem :: Integer -> Config -> IO ()
deleteTweetMem = fmap void . deleteTweetRawMem

-- | Get response, i.e. the tweet deleted
deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
deleteTweetResponse = fmap (getTweets . BSL.toStrict) .* deleteTweetRaw

-- | Get response, i.e. the tweet deleted
deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
deleteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* deleteTweetRawMem

-- | Favorite a tweet given its id
favoriteTweet :: Integer -> FilePath -> IO ()
favoriteTweet = fmap void . favoriteTweetRaw

-- | Favorite a tweet given its id
favoriteTweetMem :: Integer -> Config -> IO ()
favoriteTweetMem = fmap void . favoriteTweetRawMem

-- | Favorite a tweet and returned the (parsed) response
favoriteTweetList :: String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
favoriteTweetList = fmap (getTweets . BSL.toStrict) .* favoriteTweetListRaw

-- | Favorite a tweet and returned the (parsed) response
favoriteTweetListMem :: String -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
favoriteTweetListMem = fmap (getTweets . BSL.toStrict) .* favoriteTweetListRawMem

-- | Favorite a tweet and returned the (parsed) response
favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
favoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* favoriteTweetRaw

-- | Unfavorite a tweet given its id
unfavoriteTweet :: Integer -> FilePath -> IO ()
unfavoriteTweet = fmap void . unfavoriteTweetRaw

-- | Unfavorite a tweet given its id
unfavoriteTweetMem :: Integer -> Config -> IO ()
unfavoriteTweetMem = fmap void . unfavoriteTweetRawMem

-- | Unfavorite a tweet and returned the (parsed) response
unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
unfavoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRaw

-- | Unfavorite a tweet and returned the (parsed) response
unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
unfavoriteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRawMem

-- | Unretweet a tweet given its id
unretweetTweet :: Integer -> FilePath -> IO ()
unretweetTweet = fmap void . unretweetTweetRaw

-- | Unretweet a tweet given its id
unretweetTweetMem :: Integer -> Config -> IO ()
unretweetTweetMem = fmap void . unretweetTweetRawMem

-- | Unretweet a tweet and returned the (parsed) response
unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
unretweetTweetResponse = fmap (getTweets . BSL.toStrict) .* unretweetTweetRaw

-- | Unretweet a tweet and returned the (parsed) response
unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
unretweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unretweetTweetRawMem

-- | Unfollow a user given their screen name
unfollow :: String -> FilePath -> IO ()
unfollow = fmap void . unfollowUserRaw

-- | Unfollow a user given their screen name
unfollowMem :: String -> Config -> IO ()
unfollowMem = fmap void . unfollowUserRawMem

-- | Follow a user given their screen name
follow :: String -> FilePath -> IO ()
follow = fmap void . followUserRaw

-- | Follow a user given their screen name
followMem :: String -> Config -> IO ()
followMem = fmap void . followUserRawMem

-- | Block a user given their screen name
block :: String -> FilePath -> IO ()
block = fmap void . blockUserRaw

-- | Block a user given their screen name
blockMem :: String -> Config -> IO ()
blockMem = fmap void . blockUserRawMem

-- | Unblock a user given their screen name
unblock :: String -> FilePath -> IO ()
unblock = fmap void . unblockUserRaw

-- | Unblock a user given their screen name
unblockMem :: String -> Config -> IO ()
unblockMem = fmap void . unblockUserRawMem

-- | Retweet a tweet given its id
retweetTweet :: Integer -> FilePath -> IO ()
retweetTweet = fmap void . retweetTweetRaw

-- | Retweet a tweet given its id
retweetTweetMem :: Integer -> Config -> IO ()
retweetTweetMem = fmap void . retweetTweetRawMem

-- | Retweet a tweet and returned the (parsed) response
retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
retweetTweetResponse = fmap (getTweets . BSL.toStrict) .* retweetTweetRaw

-- | Retweet a tweet and returned the (parsed) response
retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
retweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* retweetTweetRawMem

-- | Get a lisr of favorited tweets by screen name; return bytestring response
favoriteTweetListRaw :: String -> FilePath -> IO BSL.ByteString
favoriteTweetListRaw sn = getRequest ("https://api.twitter.com/1.1/favorites/list.json?screen_name=" ++ sn)

-- | Get a lisr of favorited tweets by screen name; return bytestring response
favoriteTweetListRawMem :: String -> Config -> IO BSL.ByteString
favoriteTweetListRawMem sn = getRequestMem ("https://api.twitter.com/1.1/favorites/list.json?screen_name=" ++ sn)

-- | Favorite a tweet given its id; return bytestring response
favoriteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
favoriteTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ show idNum)

-- | Favorite a tweet given its idNum; return bytestring response
favoriteTweetRawMem :: Integer -> Config -> IO BSL.ByteString
favoriteTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ show idNum)

-- | Retweet a tweet given its idNum; return bytestring response
retweetTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
retweetTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/statuses/retweet/" ++ show idNum ++ ".json")

-- | Retweet a tweet given its idNum; return bytestring response
retweetTweetRawMem :: Integer -> Config -> IO BSL.ByteString
retweetTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/statuses/retweet/" ++ show idNum ++ ".json")

-- | Send a DM given text, screen name of recipient.
sendDMRaw :: String -> String -> FilePath -> IO BSL.ByteString
sendDMRaw txt sn = postRequest ("https://api.twitter.com/1.1/direct_messages/new.json?text=" ++ encoded ++ "&screen_name" ++ sn ++ ".json")
    where encoded = strEncode txt

-- | Get DMs, return bytestring of response
getDMs :: Int -> FilePath -> IO BSL.ByteString
getDMs count = getRequest ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ show count)

-- | Get DMs, return bytestring of response
getDMMem :: Int -> Config -> IO BSL.ByteString
getDMMem count = getRequestMem ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ show count)

-- | Follow a user given their screen name
followUserRaw :: String -> FilePath -> IO BSL.ByteString
followUserRaw sn = postRequest ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ sn)

-- | Follow a user given their screen name
followUserRawMem :: String -> Config -> IO BSL.ByteString
followUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ sn)

-- | Block a user given their screen name
blockUserRaw :: String -> FilePath -> IO BSL.ByteString
blockUserRaw sn = postRequest ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ sn)

-- | Block a user given their screen name
blockUserRawMem :: String -> Config -> IO BSL.ByteString
blockUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ sn)

-- | Unblock a user given their screen name
unblockUserRaw :: String -> FilePath -> IO BSL.ByteString
unblockUserRaw sn = postRequest ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ sn)

-- | Unblock a user given their screen name
unblockUserRawMem :: String -> Config -> IO BSL.ByteString
unblockUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ sn)

-- | Follow a user given their screen name
unfollowUserRaw :: String -> FilePath -> IO BSL.ByteString
unfollowUserRaw sn = postRequest ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ sn)

-- | Follow a user given their screen name
unfollowUserRawMem :: String -> Config -> IO BSL.ByteString
unfollowUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ sn)

-- | Unretweet a tweet given its id; return bytestring response
unretweetTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
unretweetTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/statuses/unretweet/" ++ show idNum ++ ".json")

-- | Unretweet a tweet given its idNum; return bytestring response
unretweetTweetRawMem :: Integer -> Config -> IO BSL.ByteString
unretweetTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/statuses/unretweet/" ++ show idNum ++ ".json")

-- | Unfavorite a tweet given its idNum; return bytestring response
unfavoriteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
unfavoriteTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ show idNum)

-- | Unfavorite a tweet given its idNum; return bytestring response
unfavoriteTweetRawMem :: Integer -> Config -> IO BSL.ByteString
unfavoriteTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ show idNum)

-- | Delete a tweet given its idNum; return bytestring response
deleteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
deleteTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/statuses/destroy/" ++ show idNum ++ ".json")

-- | Delete a tweet given its idNum; return bytestring response
deleteTweetRawMem :: Integer -> Config -> IO BSL.ByteString
deleteTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/statuses/destroy/" ++ show idNum ++ ".json")