-- | Module implementing plumbing to get a unified path locator,
-- handling URI & local path. Implement the 'GraphPath' and 'GraphWalker'
-- typeclass with 'HxtNode'
module Text.Webrexp.ResourcePath 
    ( ResourcePath (..)
    , rezPathToString
    , downloadBinary
    , accessResourcePath
    , rawLoadData
    ) where

import Text.Webrexp.GraphWalker
import Text.Webrexp.IOMock
import Control.Applicative
import Network.HTTP
import Network.Browser
import Network.URI( URI
                  , relativeTo
                  , isRelativeReference
                  , parseRelativeReference
                  , uriPath
                  , parseURI )
import System.Directory
import System.FilePath
import qualified Text.Webrexp.ProjectByteString as B

import Text.Webrexp.Remote.MimeTypes

-- | Main data type
data ResourcePath =
    -- | Represent a file stored on the hard-drive of this
    -- machine.
      Local FilePath
    -- | Represent a ressource spread on internet.
    | Remote URI
    deriving (Eq, Show)

instance GraphPath ResourcePath where
    (<//>) = combinePath
    importPath = toRezPath
    dumpDataAtPath = dumpResourcePath 
    localizePath = extractFileName

-- | Given a ressource, transforme it to a string
-- representation. This function should be used instead
-- of the 'Show' instance, which is aimed at debugging
-- only.
rezPathToString :: ResourcePath -> String
rezPathToString (Local p) = p
rezPathToString (Remote uri) = show uri

toRezPath :: String -> Maybe ResourcePath
toRezPath s = case (parseURI s, isValid s, isRelativeReference s) of
        (Just u, _, _) -> Just $ Remote u
        (Nothing, True, _) -> Just . Local $ normalise s
        (Nothing, False, True) -> Remote <$> parseRelativeReference s
        (Nothing, False, False) -> Nothing

-- | Resource path combiner, similar to </> in use,
-- but also handle URI.
combinePath :: ResourcePath -> ResourcePath -> ResourcePath
combinePath (Local a) (Local b) = Local . normalise $ dropFileName a </> b
combinePath (Remote a) (Remote b) = Remote $ b `relativeTo` a
combinePath (Remote a) (Local b) =
    case parseRelativeReference $ substSlash b of
        Just r -> Remote $ r `relativeTo` a
        Nothing -> error "Not possible, checked before"
combinePath (Local _) b@(Remote _) = b

substSlash :: String -> String
substSlash [] = []
substSlash ('\\':xs) = '/' : substSlash xs
substSlash (x:xs) = x : substSlash xs

extractFileName :: ResourcePath -> String
extractFileName (Remote a) = snd . splitFileName $ uriPath a
extractFileName (Local c) = snd $ splitFileName c

dumpResourcePath :: (Monad m, IOMockable m)
                 => Loggers m -> ResourcePath -> m ()
dumpResourcePath _ src@(Local source) = do
    maybeDirectory <- performIO getCurrentDirectory
    case maybeDirectory of
        Nothing -> return ()
        Just cwd -> do
            _ <- performIO . copyFile source $ cwd </> extractFileName src
            return ()

dumpResourcePath loggers@(logger,_,_) p@(Remote a) = do
  (_, rsp) <- downloadBinary loggers a
  let rawFilename = extractFileName p
      filename = case retrieveHeaders HdrContentType rsp of
         [] -> rawFilename 
         (hdr:_) -> addContentTypeExtension
                            (hdrValue hdr) rawFilename
                 

  logger $ "Downloading '" ++ show a ++ "' in '" ++ filename
  _ <- performIO . B.writeFile filename $ rspBody rsp
  return ()

accessResourcePath :: (Monad m, IOMockable m, Functor m)
                   => Loggers m -> ResourcePath -> m (AccessResult a ResourcePath)
accessResourcePath loggers rpath = maybe AccessError (DataBlob rpath)
                                <$> rawLoadData loggers rpath

-- | Extract a blob of data from a resourcepath and return
-- the result.
rawLoadData :: (Monad m, IOMockable m)
            => Loggers m -> ResourcePath -> m (Maybe B.ByteString)
rawLoadData (logger, _errLog, _verbose) (Local s) = do
    logger $ "Opening file : '" ++ s ++ "'"
    realFile <- performIO $ doesFileExist s
    case realFile of
      Just True -> performIO (B.readFile s)
      _         -> return Nothing

rawLoadData loggers@(logger, _, _verbose) (Remote uri) = do
  logger $ "Downloading URL : '" ++ show uri ++ "'"
  (_u, rsp) <- downloadBinary loggers uri
  if rspBody rsp == B.empty 
  	then return Nothing
  	else return . Just $ rspBody rsp


-- | Helper function to grab a resource on internet and returning
-- it's binary representation, and it's real place if any.
downloadBinary :: (Monad m, IOMockable m)
               => Loggers m -> URI -> m (URI, Response B.ByteString)
downloadBinary (_, _errLog, _verbose) url = do
    rez <- performIO . browse $ do
        setAllowRedirects True
        -- setErrHandler errLog
        -- TODO find a way to use that
        -- setOutHandler verbose
        setOutHandler . const $ return ()
        setErrHandler . const $ return ()
        request $ defaultGETRequest_ url
    case rez of
        Nothing -> return (url, Response { rspCode = (4, 0, 4)
                                         , rspReason = "Not allowed" 
                                         , rspHeaders = []
                                         , rspBody = B.empty })
        Just r -> return r