module Hack.Frontend.Happstack where

import Data.Foldable (toList)
import Data.Maybe
import Hack (http, pathInfo, scriptName, queryString, hackInput, serverName, serverPort, Application)
import Happstack.Server
import Happstack.Server.HTTP.Types (Request (..), Version (Version))
import Happstack.Server.SURI (parse)
import Network.URI (unEscapeString)
import qualified Data.ByteString.Char8 as S
import qualified Hack
import qualified Hack as Hack
import qualified Happstack.Server as H


serverPartToApp :: (ToMessage b) => ServerPartT IO b -> Application
serverPartToApp = convert . processRequest

convert :: (Request -> IO Response) -> Application
convert f = \env -> do
  let rq = toHappstackRequest env
  rs <- f rq
  let r = toHackResponse rs
  return r

toHackResponse :: Response -> Hack.Response
toHackResponse r = Hack.Response
  {
    Hack.body = rsBody r
  , Hack.status = rsCode r
  , Hack.headers = map convertHeader $ toList (rsHeaders r)
  }

-- | Sets all the headers coming from Happstack
convertHeader :: HeaderPair -> (String, String)
convertHeader (HeaderPair k v) = (S.unpack k, S.unpack (last v))

-- | Converts one request into another
toHappstackRequest :: Hack.Env -> Request
toHappstackRequest env =
  tmpRequest
    { rqInputs = queryInput uri ++ bodyInput tmpRequest }
  where
    uri = fromJust $ parse $ concat $ map (\f -> f env) 
      [ serverName
      , const ":"
      , show . serverPort
      , scriptName
      , pathInfo
      , add_q . queryString
      ]
    tmpRequest = 
      Request { rqMethod  = convertRequestMethod $ Hack.requestMethod env
              , rqPaths   = split '/' $ unescape $ pathInfo env
              , rqUri	    = unescape $ scriptName env ++ pathInfo env
              , rqQuery   = unescape $ add_q $ queryString env
              , rqInputs  = []
              , rqCookies = readCookies $ http env
              , rqVersion = Version 1 1
              , rqHeaders = mkHeaders $ http env
              , rqBody    = Body $ hackInput env
              , rqPeer    = (serverName env, serverPort env)
              }
    unescape = unEscapeString
  
    convertRequestMethod Hack.OPTIONS     =     OPTIONS
    convertRequestMethod Hack.GET         =     GET
    convertRequestMethod Hack.HEAD        =     HEAD
    convertRequestMethod Hack.POST        =     POST
    convertRequestMethod Hack.PUT         =     PUT
    convertRequestMethod Hack.DELETE      =     DELETE
    convertRequestMethod Hack.TRACE       =     TRACE
    convertRequestMethod Hack.CONNECT     =     CONNECT
    
    
    readCookies = map cookieWithName 
      . either (const []) id . parseCookies . fromMaybe "" . lookup "Cookie"
      
    add_q [] = []
    add_q x = '?' : x

cookieWithName :: H.Cookie -> (String, H.Cookie)
cookieWithName x = (H.cookieName x, x)


-- | Transforms a ServerPartT into a function. This is a copy of simpleHTTP'
processRequest :: (ToMessage b, Monad m, Functor m) => ServerPartT m b -> Request -> m Response
processRequest hs req =  (runWebT $ runServerPartT hs req) >>= (return . (maybe standardNotFound id))
    where
        standardNotFound = H.setHeader "Content-Type" "text/html" $ toResponse "Not found"


-- | Splits a list by character, the resulting lists don't have the character in them.
split :: Char -> String -> [String]
split c cs = filter (not.null) $ worker [] cs
    where worker acc [] = [reverse acc]
          worker acc (c':cs') | c==c' = reverse acc:worker [] cs'
          worker acc (c':cs') = worker (c':acc) cs'