module Heist.Splices.Markdown
  (
  
    PandocMissingException
  , MarkdownException
  , NoMarkdownFileException
  
  , markdownTag
  , markdownSplice
  
  , pandocSplice
  
  , PandocOptions
  , defaultPandocOptions
  , setPandocExecutable
  , setPandocArgs
  , setPandocBaseDir
  , setPandocWrapDiv
  
  , pandocExecutable
  , pandocArgs
  , pandocBaseDir
  , pandocWrapDiv
  
  , pandoc
  , pandocBS
  , readProcessWithExitCode'
  ) where
import           Control.Concurrent
import           Control.Exception.Lifted
import           Control.Monad
import           Control.Monad.Trans
import           Data.ByteString                 (ByteString)
import qualified Data.ByteString                 as B
import qualified Data.ByteString.Char8           as BC
import           Data.Maybe                      (fromMaybe)
import           Data.Text                       (Text)
import qualified Data.Text                       as T
import qualified Data.Text.Encoding              as T
import           Data.Typeable
import           System.Directory
import           System.Exit
import           System.FilePath.Posix
import           System.IO
import           System.Process
import           Text.XmlHtml
#if !MIN_VERSION_base(4,8,0)
import           Control.Applicative             ((<$>))
#endif
import           Heist.Common
import           Heist.Internal.Types.HeistState
import           Heist.Interpreted.Internal
data PandocMissingException = PandocMissingException
   deriving (Typeable)
instance Show PandocMissingException where
    show PandocMissingException =
        "Cannot find the \"pandoc\" executable.  If you have Haskell, then install it with \"cabal install\".  Otherwise you can download it from http://johnmacfarlane.net/pandoc/installing.html.  Then make sure it is in your $PATH."
instance Exception PandocMissingException
data MarkdownException = MarkdownException ByteString
   deriving (Typeable)
instance Show MarkdownException where
    show (MarkdownException e) =
        "Markdown error: pandoc replied:\n\n" ++ BC.unpack e
instance Exception MarkdownException
data NoMarkdownFileException = NoMarkdownFileException
    deriving (Typeable)
instance Show NoMarkdownFileException where
    show NoMarkdownFileException =
        "Markdown error: no file or template in context" ++
        " during processing of markdown tag"
instance Exception NoMarkdownFileException where
data PandocOptions = PandocOptions
     { _pandocExecutable :: FilePath
     , _pandocArgs       :: [String]         
     , _pandocBaseDir    :: Maybe FilePath   
                                             
     , _pandocWrapDiv    :: Maybe Text       
     } deriving (Eq, Ord, Show)
defaultPandocOptions :: PandocOptions
defaultPandocOptions = PandocOptions "pandoc"
                                     ["-S", "--no-wrap"]
                                     Nothing
                                     (Just "markdown")
setPandocExecutable :: FilePath -> PandocOptions -> PandocOptions
setPandocExecutable e opt = opt { _pandocExecutable = e }
setPandocArgs :: [String] -> PandocOptions -> PandocOptions
setPandocArgs args opt = opt { _pandocArgs = args }
setPandocBaseDir :: Maybe FilePath -> PandocOptions -> PandocOptions
setPandocBaseDir bd opt = opt { _pandocBaseDir = bd }
setPandocWrapDiv :: Maybe Text -> PandocOptions -> PandocOptions
setPandocWrapDiv wd opt = opt { _pandocWrapDiv = wd }
pandocExecutable :: Functor f =>
     (FilePath -> f FilePath) -> PandocOptions -> f PandocOptions
pandocExecutable f po = (\e -> po { _pandocExecutable = e})
                       <$> f (_pandocExecutable po)
pandocArgs :: Functor f =>
     ([String] -> f [String]) -> PandocOptions -> f PandocOptions
pandocArgs f po = (\a -> po { _pandocArgs = a}) <$> f (_pandocArgs po)
pandocBaseDir :: Functor f =>
     (Maybe FilePath -> f (Maybe FilePath)) -> PandocOptions -> f PandocOptions
pandocBaseDir f po = (\b -> po {_pandocBaseDir = b }) <$> f (_pandocBaseDir po)
pandocWrapDiv :: Functor f =>
     (Maybe Text -> f (Maybe Text)) -> PandocOptions -> f PandocOptions
pandocWrapDiv f po = (\w -> po {_pandocWrapDiv = w}) <$> f (_pandocWrapDiv po)
markdownTag :: Text
markdownTag = "markdown"
markdownSplice :: MonadIO m => Splice m
markdownSplice= pandocSplice defaultPandocOptions
pandocSplice :: MonadIO m => PandocOptions -> Splice m
pandocSplice PandocOptions{..} = do
    templateDir <- liftM (fmap takeDirectory) getTemplateFilePath
    pdMD <- liftIO $ findExecutable _pandocExecutable
    pandocExe <- case pdMD of
       Nothing -> liftIO $ throwIO PandocMissingException
       Just pd -> return pd
    let withDir tp = fromMaybe tp _pandocBaseDir
        pandocFile f tp = pandocWith pandocExe _pandocArgs (withDir tp) f
    tree <- getParamNode
    (source,markup) <- liftIO $
        case getAttribute "file" tree of
            Just f  -> do
                m <- maybe (liftIO $ throwIO NoMarkdownFileException )
                           (pandocFile (T.unpack f))
                           templateDir
                return (T.unpack f,m)
            Nothing -> do
                m <- pandocWithBS pandocExe _pandocArgs $ T.encodeUtf8 $ nodeText tree
                return ("inline_splice",m)
    let ee = parseHTML source markup
        nodeAttrs = case tree of
          Element _ a _ -> a
          _ -> []
        nodeClass = lookup "class" nodeAttrs
        attrs = filter (\(name, _) -> name /= "class" && name /= "file") nodeAttrs
    case ee of
      Left e  -> throw $ MarkdownException
                       $ BC.pack ("Error parsing markdown output: " ++ e)
      Right d -> return $ wrapResult nodeClass attrs (docContent d)
  where
    wrapResult nodeClass attrs body = case _pandocWrapDiv of
        Nothing -> body
        Just cls -> let finalAttrs = ("class", appendClass nodeClass cls):attrs
                    in [Element "div" finalAttrs  body]
    appendClass Nothing cls = cls
    appendClass (Just orig) cls = T.concat [orig, " ", cls]
pandoc :: FilePath -> FilePath -> FilePath -> IO ByteString
pandoc pandocPath templateDir inputFile = do
    sout <- pandocWith pandocPath args templateDir inputFile
    return $ BC.concat [ "<div class=\"markdown\">\n"
                         , sout
                         , "\n</div>" ]
  where
    args = [ "-S", "--no-wrap"]
pandocBS :: FilePath -> ByteString -> IO ByteString
pandocBS pandocPath s = do
    sout <- pandocWithBS pandocPath args s
    return $ BC.concat [ "<div class=\"markdown\">\n"
                       , sout
                       , "\n</div>" ]
  where
    args = [ "-S", "--no-wrap" ]
pandocWith :: FilePath -> [String] -> FilePath -> FilePath -> IO ByteString
pandocWith path args templateDir inputFile = do
    (ex, sout, serr) <- readProcessWithExitCode' path args' ""
    when (isFail ex) $ throw $ MarkdownException serr
    return sout
  where
    isFail ExitSuccess = False
    isFail _           = True
    args' = args ++ [templateDir </> inputFile ]
pandocWithBS :: FilePath -> [String] -> ByteString -> IO ByteString
pandocWithBS pandocPath args s = do
    
    (ex, sout, serr) <- readProcessWithExitCode' pandocPath args s
    when (isFail ex) $ throw $ MarkdownException serr
    return sout
  where
    isFail ExitSuccess = False
    isFail _           = True
readProcessWithExitCode'
    :: FilePath                 
    -> [String]                 
    -> ByteString               
    -> IO (ExitCode,ByteString,ByteString) 
readProcessWithExitCode' cmd args input = do
    (Just inh, Just outh, Just errh, pid) <-
        createProcess (proc cmd args){ std_in  = CreatePipe,
                                       std_out = CreatePipe,
                                       std_err = CreatePipe }
    outMVar <- newEmptyMVar
    outM <- newEmptyMVar
    errM <- newEmptyMVar
    
    _ <- forkIO $ do
        out <- B.hGetContents outh
        putMVar outM out
        putMVar outMVar ()
    
    _ <- forkIO $ do
        err  <- B.hGetContents errh
        putMVar errM err
        putMVar outMVar ()
    
    when (not (B.null input)) $ do B.hPutStr inh input; hFlush inh
    hClose inh 
    
    takeMVar outMVar
    takeMVar outMVar
    hClose outh
    
    ex <- waitForProcess pid
    out <- readMVar outM
    err <- readMVar errM
    return (ex, out, err)