{-# LANGUAGE OverloadedStrings #-}

module Network.Utilities where

import           Control.Lens
import           Data.Aeson
import           Data.Aeson.Lens
import qualified Data.ByteString.Lazy.Char8 as BS
import qualified Data.HashMap.Lazy          as Hash
import qualified Data.Map.Lazy              as Map
import           Data.Maybe
import           Data.Scientific
import qualified Data.Text                  as T
import qualified Data.Vector                as V
import           Data.Word
import           Network.Wreq

type Resp = Response BS.ByteString
type Obj = Map.Map String Value
type HObj = Hash.HashMap T.Text Value
type JSON = Response Obj
type Errors = (Int, String)

-- | Enum type for verifying FilePaths before sending to the API.
data VerificationStatus = Good | Bad | Unknown deriving (Show)

-- Process the results of a request into JSON
-- Returns an IO Tuple of the status and response body.
processRequest :: IO Resp -> IO (Status, Obj)
processRequest response = do  r <- asJSON =<< response :: IO JSON
                              return (r ^. responseStatus, r ^. responseBody)

-- Parses status code errors for api endpoints
apiErr :: Int -> Obj -> String
apiErr code body
  | code == 401 = getString "status_msg" body
  | code == 400 = ret $ Map.lookup "status_msg" body
  where ret val
          | isNothing val = getString "status_msg" body
          | otherwise = value2String $ fromJust val

-- Gets a value that we know exists from a map
definite :: String -> Obj -> Value
definite k m = fromJust $ Map.lookup k m

-- Gets a value that we know exists from a hash map
definite' :: String -> HObj -> Value
definite' k m = fromJust $ Hash.lookup (T.pack k) m

-- Convert an Aeson value into a String
value2String :: Value -> String
value2String (String xs) = T.unpack xs
value2String _ = ""

-- Convert an Aeson value into an Integer
value2Int :: Value -> Integer
value2Int (Number x) = coefficient x
value2Int _ = 0

-- Convert an Aeson value into a Double
value2Double :: Value -> Double
value2Double (Number x) = toRealFloat x :: Double
value2Double _ = 0

-- Convert an Aeson value into a hash map
value2Map :: Value -> HObj
value2Map (Object o) = o
value2Map _ = Hash.empty

-- Convert an Aeson value into a Vector
value2Vector :: Value -> V.Vector Value
value2Vector (Array a) = a
value2Vector _ = V.empty

-- Composes the definite and value2* functions into
-- a single function that gets and converts from a map.
-- Data.Map
getInt      key = value2Int . definite key
getString   key = value2String . definite key
getMap      key = value2Map . definite key
getVec      key = value2Vector . definite key

-- Data.HashMap
getInt'     key = value2Int . definite' key
getString'  key = value2String . definite' key
getMap'     key = value2Map . definite' key
getVec'     key = value2Vector . definite' key

-- custom postWith that overrides default checkStatus functionality.
-- no longer returns an error on non-2** status codes.
postWith' :: (String -> [FormParam] -> IO (Response BS.ByteString) )
postWith' = postWith defaults'

defaults' = set checkStatus (Just $ \_ _ _ -> Nothing) defaults

-- List of extensions for videos. If a file has one of these extensions
-- we use the video Info from the API to verify the file.
videoExtensions = [".mov", ".webm", ".ogv", ".ogg", ".wmv", ".mp4",
                   ".m4p", ".qt", ".mpg", ".mp2", ".mpeg", ".mpe"]

-- List of extensions for images. If a file has one of these extensions
-- we use the image Info from the API to verify the file.
imageExtensions = [".png", ".jpeg", ".jpg", ".gif", ".bmp"]

-- Checks that a given file size is within the bounds specified by
-- the (min, max) tuple.
fileCheck :: Integer -> Word64 -> VerificationStatus
fileCheck maxSize size =
  if conv <= maxSize
  then Good
  else Bad
  where conv = fromIntegral size :: Integer