{-# 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.Lens
import           Control.Monad
import qualified Data.ByteString.Lazy.Char8 as BSL
import           Data.Void
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 . last $ tweets
    if Just lastId == maxId then
        pure []
    else
        do
            putStrLn $ "fetching tweets since " ++ show lastId ++ "..."
            next <- getAll sn (Just 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 (ParseError Char Void) Timeline)
getProfileMax = fmap (getTweets . BSL.toStrict) .*** getProfileRaw

-- | Gets user profile with max_id set.
getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseError Char 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 (ParseError Char Void) Timeline)
mentions = fmap (getTweets . BSL.toStrict) .* mentionsRaw

-- | Get mentions and parse response as a list of tweets
mentionsMem :: Int -> Config -> IO (Either (ParseError Char 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 (ParseError Char 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 (ParseError Char Void) Timeline)
getFavorites count = fmap (fmap (take count) . getTweets . BSL.toStrict) .* favoriteTweetListRaw

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

-- | Get a timeline
getTimelineMem :: Int -> Config -> IO (Either (ParseError Char 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 (ParseError Char Void) Timeline)
deleteTweetResponse = fmap (getTweets . BSL.toStrict) .* deleteTweetRaw

-- | Get response, i.e. the tweet deleted
deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char 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 (ParseError Char Void) Timeline)
favoriteTweetList = fmap (getTweets . BSL.toStrict) .* favoriteTweetListRaw

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

-- | Favorite a tweet and returned the (parsed) response
favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char 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 (ParseError Char Void) Timeline)
unfavoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRaw

-- | Unfavorite a tweet and returned the (parsed) response
unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char 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 (ParseError Char Void) Timeline)
unretweetTweetResponse = fmap (getTweets . BSL.toStrict) .* unretweetTweetRaw

-- | Unretweet a tweet and returned the (parsed) response
unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char 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 (ParseError Char Void) Timeline)
retweetTweetResponse = fmap (getTweets . BSL.toStrict) .* retweetTweetRaw

-- | Retweet a tweet and returned the (parsed) response
retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseError Char 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")