    {-# LANGUAGE OverloadedStrings #-}
    {-# LANGUAGE CPP #-}
    {-# LANGUAGE RecordWildCards #-}
    {-# LANGUAGE ViewPatterns #-}
    {-# LANGUAGE LambdaCase #-}
    module Hpack.Defaults (
      ensure
    , Defaults(..)
    #ifdef TEST
    , Result(..)
    , ensureFile
    #endif
    ) where

    import           Network.HTTP.Types
    import           Network.HTTP.Client
    import           Network.HTTP.Client.TLS
    import           Data.List
    import qualified Data.ByteString.Lazy as LB
    import qualified Data.ByteString.Char8 as B
    import           System.FilePath
    import           System.Directory

    import           Hpack.Syntax.Defaults

    type URL = String

    defaultsUrl :: Github -> URL
    defaultsUrl Github{..} = "https://raw.githubusercontent.com/" ++ githubOwner ++ "/" ++ githubRepo ++ "/" ++ githubRef ++ "/" ++ intercalate "/" githubPath

    defaultsCachePath :: FilePath -> Github -> FilePath
    defaultsCachePath dir Github{..} = joinPath $
      dir : "defaults" : githubOwner : githubRepo : githubRef : githubPath

    data Result = Found | NotFound | Failed String
      deriving (Eq, Show)

    get :: URL -> FilePath -> IO Result
    get url file = do
      manager <- newManager tlsManagerSettings
      request <- parseRequest url
      response <- httpLbs request manager
      case responseStatus response of
        Status 200 _ -> do
          createDirectoryIfMissing True (takeDirectory file)
          LB.writeFile file (responseBody response)
          return Found
        Status 404 _ -> return NotFound
        status -> return (Failed $ "Error while downloading " ++ url ++ " (" ++ formatStatus status ++ ")")

    formatStatus :: Status -> String
    formatStatus (Status code message) = show code ++ " " ++ B.unpack message

    ensure :: FilePath -> FilePath -> Defaults -> IO (Either String FilePath)
    ensure userDataDir dir = \ case
      DefaultsGithub defaults -> do
        let
          url = defaultsUrl defaults
          file = defaultsCachePath userDataDir defaults
        ensureFile file url >>= \ case
          Found -> return (Right file)
          NotFound -> return (Left $ notFound url)
          Failed err -> return (Left err)
      DefaultsLocal (Local ((dir </>) -> file)) -> do
        doesFileExist file >>= \ case
          True -> return (Right file)
          False -> return (Left $ notFound file)
      where
        notFound file = "Invalid value for \"defaults\"! File " ++ file ++ " does not exist!"

    ensureFile :: FilePath -> URL -> IO Result
    ensureFile file url = do
      doesFileExist file >>= \ case
        True -> return Found
        False -> get url file