{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
module SketeServer where

import Servant

import Data.Maybe
import Control.Monad
import Control.Monad.Trans
import qualified Network.HTTP.Types as HTTP
import qualified Network.Wai as WAI
import qualified Network.Wai.Middleware.RequestLogger as WAI
import qualified Network.Wai.Handler.Warp as Warp
import System.FilePath (joinPath)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import Control.DeepSeq
import Text.Read
import Web.Routes
import Web.Routes.Wai
import qualified Text.Parsec as P
import qualified Codec.Compression.GZip as GZ
import Data.Default.Class

import Distribution.Skete.Storage.Interface (PackageVersion(..))
import qualified Distribution.Skete.Storage.Interface as Storage
import qualified Distribution.Skete.Storage.GitFat as GitFat
import Distribution.Skete.TarUtils
import Distribution.Skete.Haskell.Package

-- | Don't mind me. I'm just a lowly orphan
instance FromHttpApiData PackageVersion where
  parseUrlPiece token =
    maybe
      (Left "Unable to parse package version")
      Right $ T.stripSuffix ".tar.gz" token >>= (readMaybe . T.unpack)

type API =
       "packages" :> "archive" :> "00-index.tar.gz" :> Get '[OctetStream] BSL.ByteString
  :<|> "packages" :> "archive" :> Capture "PackageVersion" PackageVersion :> Get '[OctetStream] BSL.ByteString

server' :: GitFat.GitFatConfig -> Server API
server' ss = serveIndexH ss :<|> serveTarH ss

serveTarH :: GitFat.GitFatConfig -> PackageVersion -> Handler BSL.ByteString 
serveTarH ss pid = do
  mResult <- (Storage.storage ss :: GitFat.GitFat p a -> Handler a) $ do
    mr <- Storage.lookup pid
    case mr of
      Nothing -> return Nothing
      Just pr -> do
        dt <- Storage.versionFiles pr
        return $ Just $
          GZ.compressWith (GZ.defaultCompressParams { GZ.compressLevel = GZ.bestSpeed }) .
            implodeTar $ dt
  maybe
    (throwError $ err404 { errBody = "I will not give you what you want" })
    return $ mResult

serveIndexH :: GitFat.GitFatConfig -> Handler BSL.ByteString
serveIndexH ss =
  (Storage.storage ss :: GitFat.GitFat p a -> Handler a) $ do
    let ps = "all"
    pis <- Storage.list ps
    (snd . createIndexTar . catMaybes) <$> (forM pis $ \pid -> do
      mc <- Storage.labelDataLookup ps pid "cabal"
      case mc of
        Nothing -> return Nothing
        Just r -> r `deepseq` do
          return $ Just (joinPath [ T.unpack . pvName $ pid
                                  , show . pvVersion $ pid
                                  , (T.unpack . pvName $ pid) ++ ".cabal"], BSL.fromStrict r))

data SiteMap =
    PackageIndex
  | Package PackageVersion
  deriving (Show, Eq, Ord)

type UrlMaker = SiteMap -> [(T.Text, Maybe T.Text)] -> T.Text

genURL :: SiteMap -> [T.Text]
genURL PackageIndex = ["packages", "archive", "00-index.tar.gz"]
genURL (Package a) = ["packages", "archive", "package", (T.pack $ show a) `T.append` ".tar.gz"]

parseURL :: URLParser SiteMap
parseURL =
    P.choice $ map P.try [
        pPackageIndex
      , pPackageTar
      ]
  where
    pPackageIndex = do
      void $ segment "packages"
      void $ segment "archive"
      void $ segment "00-index.tar.gz"
      P.eof
      return PackageIndex
    pPackageTar = do
      void $ segment "packages"
      void $ segment "archive"
      void $ segment "package"
      pv <- pToken ("bad package-version"::String) (\t -> T.stripSuffix ".tar.gz" t >>= (readMaybe . T.unpack))
      return $ Package pv

instance PathInfo SiteMap where
  toPathSegments = genURL
  fromPathSegments = parseURL

server :: IO ()
server = do
  let ss = GitFat.GFC. TE.encodeUtf8 $ ".git"
  logger <- WAI.mkRequestLogger def
  Warp.run 8080 $ logger (handleWai (TE.encodeUtf8 "") $
    \_ sm ->
    case sm of
      PackageIndex -> serveIndex ss
      Package pv -> serveTar ss pv)

serveTar :: GitFat.GitFatConfig -> PackageVersion -> WAI.Application
serveTar ss pid _ respond = (Storage.storage ss::GitFat.GitFat p a -> IO a) $ do
  mr <- Storage.lookup pid
  case mr of
    Nothing -> do
      liftIO . respond . WAI.responseLBS HTTP.status404 [] $ "I will not give you what you want"
    Just pr -> do
      dt <- Storage.versionFiles pr
      liftIO . respond . WAI.responseLBS HTTP.status200 [] .
        GZ.compressWith (GZ.defaultCompressParams { GZ.compressLevel = GZ.bestSpeed }) .
        implodeTar $ dt

serveIndex :: GitFat.GitFatConfig -> WAI.Application
serveIndex ss _ respond = (Storage.storage ss::GitFat.GitFat p a -> IO a) $ do
  let ps = "all"
  pis <- Storage.list ps
  index <- (snd . createIndexTar . catMaybes) <$> (forM pis $ \pid -> do
    mc <- Storage.labelDataLookup ps pid "cabal"
    case mc of
      Nothing -> return Nothing
      Just r -> r `deepseq` do
        return $ Just (joinPath [ T.unpack . pvName $ pid
                                , show . pvVersion $ pid
                                , (T.unpack . pvName $ pid) ++ ".cabal"], BSL.fromStrict r))
  liftIO . respond . WAI.responseLBS HTTP.status200 [] $ index
