{-# LANGUAGE CPP, OverloadedStrings #-}

module HsDev.Tools.HDocs (
	hdocsy, hdocs, hdocsPackage, hdocsCabal,
	setSymbolDocs, setDocs, setModuleDocs,

	hdocsProcess,

	readDocs, readModuleDocs, readProjectTargetDocs,

	hdocsSupported,

	module Control.Monad.Except
	) where

import Control.Lens
import Control.Monad ()
import Control.Monad.Except

import Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import Data.Text (Text)
#ifdef NODOCS
import qualified System.Log.Simple as Log
#endif

#ifndef NODOCS
import Control.DeepSeq
import Data.Aeson (decode)
import qualified Data.ByteString.Lazy.Char8 as L (pack)
import Data.String (fromString)
import qualified Data.Text as T

import qualified HDocs.Module as HDocs
import qualified HDocs.Haddock as HDocs

import qualified GHC
#endif

import qualified PackageConfig as P

import Data.LookupTable
#ifndef NODOCS
import HsDev.Error
import HsDev.Scan.Browse (packageConfigs, readPackage)
import HsDev.Tools.Base
#endif
import HsDev.Symbols
import HsDev.Tools.Ghc.Worker
import System.Directory.Paths

-- | Get docs for modules
hdocsy :: PackageDbStack -> [ModuleLocation] -> [String] -> GhcM [Map String String]
#ifndef NODOCS
hdocsy pdbs mlocs opts = (map $ force . HDocs.formatDocs) <$> docs' mlocs where
	docs' :: [ModuleLocation] -> GhcM [HDocs.ModuleDocMap]
	docs' ms = do
		haddockSession pdbs opts
		liftGhc $ hsdevLiftWith (ToolError "hdocs") $
			liftM (map snd) $ HDocs.readSourcesGhc opts $ map (view (moduleFile . path)) ms
#else
hdocsy _ _ _ = notSupported >> return mempty
#endif

-- | Get docs for module
hdocs :: PackageDbStack -> ModuleLocation -> [String] -> GhcM (Map String String)
#ifndef NODOCS
hdocs pdbs mloc opts = (force . HDocs.formatDocs) <$> docs' mloc where
	docs' :: ModuleLocation -> GhcM HDocs.ModuleDocMap
	docs' mloc' = do
		haddockSession pdbs opts
		liftGhc $ case mloc' of
			(FileModule fpath _) -> hsdevLiftWith (ToolError "hdocs") $ liftM snd $ HDocs.readSourceGhc opts (view path fpath)
			(InstalledModule _ _ mname _) -> do
				df <- GHC.getSessionDynFlags
				liftIO $ hsdevLiftWith (ToolError "hdocs") $ HDocs.moduleDocsF df (T.unpack mname)
			_ -> hsdevError $ ToolError "hdocs" $ "Can't get docs for: " ++ show mloc'
#else
hdocs _ _ _ = notSupported >> return mempty
#endif

-- | Get docs for package
hdocsPackage :: P.PackageConfig -> GhcM (Map Text (Map Text Text))
#ifndef NODOCS
hdocsPackage p = do
	ifaces <-
		liftIO . hsdevLiftWith (ToolError "hdocs") .
		liftM concat . mapM ((`mplus` return []) . HDocs.readInstalledInterfaces) $
		P.haddockInterfaces p
	let
		idocs = HDocs.installedInterfacesDocs ifaces
		iexports = M.fromList $ map (HDocs.exportsDocs idocs) ifaces
		docs = M.map HDocs.formatDocs iexports
		tdocs = M.map (M.map fromString . M.mapKeys fromString) . M.mapKeys fromString $ docs
	return $!! tdocs
#else
hdocsPackage _ = notSupported >> return mempty
#endif

-- | Get all docs
hdocsCabal :: PackageDbStack -> [String] -> GhcM [(ModulePackage, (Map Text (Map Text Text)))]
#ifndef NODOCS
hdocsCabal pdbs opts = do
	haddockSession pdbs opts
	pkgs <- packageConfigs
	forM pkgs $ \pkg -> do
		pkgDocs' <- hdocsPackage pkg
		return (readPackage pkg, pkgDocs')
#else
hdocsCabal _ _ = notSupported >> return mempty
#endif

-- | Set docs for module
setSymbolDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text Text -> Symbol -> m Symbol
setSymbolDocs tbl d sym = do
	symDocs <- cacheInTableM tbl (symName, symMod) (return $ M.lookup symName d)
	return $ set symbolDocs symDocs sym
	where
		symName = view (symbolId . symbolName) sym
		symMod = view (symbolId . symbolModule . moduleName) sym

-- | Set docs for module symbols
setDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text Text -> Module -> m Module
setDocs tbl d = mapMOf (moduleExports . each) setDoc >=> mapMOf (moduleScope . each . each) setDoc where
	setDoc = setSymbolDocs tbl d

-- | Set docs for modules
setModuleDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text (Map Text Text) -> Module -> m Module
setModuleDocs tbl docs m = maybe return (setDocs tbl) (M.lookup (view (moduleId . moduleName) m) docs) $ m

hdocsProcess :: String -> [String] -> IO (Maybe (Map String String))
#ifndef NODOCS
hdocsProcess mname opts = liftM (decode . L.pack . last . lines) $ runTool_ "hdocs" opts' where
	opts' = mname : concat [["-g", opt] | opt <- opts]
#else
hdocsProcess _ _ = return mempty
#endif

-- | Read docs for one module
readDocs :: Text -> [String] -> Path -> GhcM (Maybe (Map String String))
#ifndef NODOCS
readDocs mname opts fpath = do
	docs <- liftGhc $ hsdevLift $ HDocs.readSourcesGhc opts [view path fpath]
	return $ fmap HDocs.formatDocs $ lookup (T.unpack mname) docs
#else
readDocs _ _ _ = notSupported >> return mempty
#endif

-- | Read docs for one module
readModuleDocs :: [String] -> Module -> GhcM (Maybe (Map String String))
#ifndef NODOCS
readModuleDocs opts m = case view (moduleId . moduleLocation) m of
	FileModule fpath _ -> withCurrentDirectory (sourceRoot_ (m ^. moduleId) ^. path) $ do
		readDocs (m ^. moduleId . moduleName) opts fpath
	_ -> hsdevError $ ModuleNotSource (view (moduleId . moduleLocation) m)
#else
readModuleDocs _ _ = notSupported >> return mempty
#endif

readProjectTargetDocs :: [String] -> Project -> [Path] -> GhcM (Map String (Map String String))
#ifndef NODOCS
readProjectTargetDocs opts proj fpaths = withCurrentDirectory (proj ^. projectPath . path) $ do
	docs <- liftGhc $ hsdevLift $ HDocs.readSourcesGhc opts (fpaths ^.. each . path)
	return $ M.map HDocs.formatDocs $ M.fromList docs
#else
readProjectTargetDocs _ _ _ = notSupported >> return mempty
#endif

#ifdef NODOCS
notSupported :: Log.MonadLog m => m ()
notSupported = Log.sendLog Log.Warning "compiled without hdocs support"
#endif

hdocsSupported :: Bool
#ifndef NODOCS
hdocsSupported = True
#else
hdocsSupported = False
#endif
