{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TemplateHaskell #-}

module Main where

import Filesystem
import Control.Applicative
import Control.Monad
import Stackage.CLI
import Options.Applicative (Parser)
import Options.Applicative.Builder (strArgument, metavar, value)
import Data.Monoid
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Typeable (Typeable)
import Network.HTTP.Client
import Network.HTTP.Client.TLS (tlsManagerSettings)
import Network.HTTP.Types.Status (statusCode)
import Network.HTTP.Types.Header (hUserAgent)
import qualified Data.ByteString.Lazy as LBS
import System.Exit (exitFailure)
import System.Environment (getArgs)
import System.IO (hPutStrLn, stderr)
import Control.Exception
import qualified Paths_stackage_cabal as CabalInfo

type Snapshot = String

data InitException
  = InvalidSnapshot
  | SnapshotNotFound
  | UnexpectedHttpException HttpException
  | CabalConfigExists
  deriving (Show, Typeable)
instance Exception InitException

version :: String
version = $(simpleVersion CabalInfo.version)

header :: String
header = "Initializes cabal.config"

progDesc :: String
progDesc = header

userAgent :: Text
userAgent = "stackage-init/" <> T.pack version

snapshotParser :: Parser Snapshot
snapshotParser = strArgument mods where
  mods = (metavar "SNAPSHOT" <> value "lts")

toUrl :: Snapshot -> String
toUrl t = "https://www.stackage.org/" <> t <> "/cabal.config"

snapshotReq :: Snapshot -> IO Request
snapshotReq snapshot = case parseUrl (toUrl snapshot) of
  Left _ -> throwIO $ InvalidSnapshot
  Right req -> return req
    { requestHeaders = [(hUserAgent, T.encodeUtf8 userAgent)]
    }

downloadSnapshot :: Snapshot -> IO LBS.ByteString
downloadSnapshot snapshot = withManager tlsManagerSettings $ \manager -> do
  let getResponseLbs req = do
        response <- httpLbs req manager
        return $ responseBody response
  let handle404 firstTry (StatusCodeException s _ _)
        | statusCode s == 404 = if firstTry
          then do
            req <- snapshotReq $ "snapshot/" <> snapshot
            getResponseLbs req `catch` handle404 False
          else do
            throwIO $ SnapshotNotFound
      handle404 _ e = throwIO $ UnexpectedHttpException e
  req <- snapshotReq snapshot
  getResponseLbs req `catch` handle404 True

initSnapshot :: Snapshot -> IO ()
initSnapshot snapshot = do
  configExists <- isFile "cabal.config"
  when configExists $ throwIO $ CabalConfigExists
  downloadSnapshot snapshot >>= LBS.writeFile "cabal.config"

handleInitExceptions :: Snapshot -> InitException -> IO ()
handleInitExceptions snapshot e = hPutStrLn stderr (err e) >> exitFailure where
  err InvalidSnapshot
    = "Invalid snapshot: " <> snapshot
  err SnapshotNotFound
    = "Snapshot not found: " <> snapshot
  err CabalConfigExists
    = "Warning: Cabal config already exists.\n"
   <> "No action taken."
  err (UnexpectedHttpException e)
    = "Unexpected http exception:\n"
   <> show e

main = do
  (snapshot, ()) <- simpleOptions
    version
    header
    progDesc
    snapshotParser -- global parser
    empty          -- subcommands
  initSnapshot snapshot `catch` handleInitExceptions snapshot
