-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE LambdaCase          #-}
{-# LANGUAGE NamedFieldPuns      #-}
{-# LANGUAGE OverloadedStrings   #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main(main) where

import           Control.Exception             (displayException)
import           Control.Monad.IO.Class        (liftIO)
import           Data.Bifunctor                (first)
import           Data.Function                 ((&))
import           Data.Functor                  ((<&>))
import           Data.Maybe                    (catMaybes)
import           Data.Text                     (Text)
import           Ide.Logger  (Doc, Priority (Error, Info),
                                                Recorder,
                                                WithPriority (WithPriority, priority),
                                                cfilter, cmapWithPrio,
                                                defaultLayoutOptions,
                                                layoutPretty, logWith,
                                                makeDefaultStderrRecorder,
                                                renderStrict, withFileRecorder)
import qualified Ide.Logger  as Logger
import qualified HlsPlugins                    as Plugins
import           Ide.Arguments                 (Arguments (..),
                                                GhcideArguments (..),
                                                getArguments)
import           Ide.Main                      (defaultMain)
import qualified Ide.Main                      as IdeMain
import           Ide.PluginUtils               (pluginDescToIdePlugins)
import           Ide.Types                     (PluginDescriptor (pluginNotificationHandlers),
                                                defaultPluginDescriptor,
                                                mkPluginNotificationHandler)
import           Language.LSP.Protocol.Message as LSP
import           Language.LSP.Server           as LSP
import           Prettyprinter                 (Pretty (pretty), vcat, vsep)

data Log
  = LogIdeMain IdeMain.Log
  | LogPlugins Plugins.Log

instance Pretty Log where
  pretty log = case log of
    LogIdeMain ideMainLog -> pretty ideMainLog
    LogPlugins pluginsLog -> pretty pluginsLog

main :: IO ()
main = do
    stderrRecorder <- makeDefaultStderrRecorder Nothing
    -- plugin cli commands use stderr logger for now unless we change the args
    -- parser to get logging arguments first or do more complicated things
    let pluginCliRecorder = cmapWithPrio pretty stderrRecorder
    args <- getArguments "haskell-language-server" (Plugins.idePlugins (cmapWithPrio LogPlugins pluginCliRecorder))

    -- Recorder that logs to the LSP client with logMessage
    (lspLogRecorder, cb1) <-
        Logger.withBacklog Logger.lspClientLogRecorder
        <&> first (cmapWithPrio renderDoc)
    -- Recorder that logs to the LSP client with showMessage
    (lspMessageRecorder, cb2) <-
        Logger.withBacklog Logger.lspClientMessageRecorder
        <&> first (cmapWithPrio renderDoc)
    -- Recorder that logs Error severity logs to the client with showMessage and some extra text
    let lspErrorMessageRecorder = lspMessageRecorder
            & cfilter (\WithPriority{ priority } -> priority >= Error)
            & cmapWithPrio (\msg -> vsep
                ["Error condition, please check your setup and/or the [issue tracker](" <> issueTrackerUrl <> "): "
                , msg
                ])
    -- This plugin just installs a handler for the `initialized` notification, which then
    -- picks up the LSP environment and feeds it to our recorders
    let lspRecorderPlugin = (defaultPluginDescriptor "LSPRecorderCallback")
          { pluginNotificationHandlers = mkPluginNotificationHandler LSP.SMethod_Initialized $ \_ _ _ _ -> do
              env <- LSP.getLspEnv
              liftIO $ (cb1 <> cb2) env
          }

    let (minPriority, logFilePath, logStderr, logClient) =
          case args of
            Ghcide GhcideArguments{ argsLogLevel, argsLogFile, argsLogStderr, argsLogClient} ->
              (argsLogLevel, argsLogFile, argsLogStderr, argsLogClient)
            _ -> (Info, Nothing, True, False)

    -- Adapter for withFileRecorder to handle the case where we don't want to log to a file
    let withLogFileRecorder action = case logFilePath of
            Just p -> withFileRecorder p Nothing $ \case
                Left e -> do
                    let exceptionMessage = pretty $ displayException e
                    let message = vcat [exceptionMessage, "Couldn't open log file; not logging to it."]
                    logWith stderrRecorder Error message
                    action Nothing
                Right r -> action (Just r)
            Nothing -> action Nothing

    withLogFileRecorder $ \logFileRecorder -> do
      let
        lfr = logFileRecorder
        ser = if logStderr then Just stderrRecorder else Nothing
        lemr = Just lspErrorMessageRecorder
        llr = if logClient then Just lspLogRecorder else Nothing
        recorder :: Recorder (WithPriority Log) =
            [lfr, ser, lemr, llr]
              & catMaybes
              & mconcat
              & cmapWithPrio pretty
              & cfilter (\WithPriority{ priority } -> priority >= minPriority)
        plugins = Plugins.idePlugins (cmapWithPrio LogPlugins recorder)

      defaultMain
        (cmapWithPrio LogIdeMain recorder)
        args
        (plugins <> pluginDescToIdePlugins [lspRecorderPlugin])

renderDoc :: Doc a -> Text
renderDoc d = renderStrict $ layoutPretty defaultLayoutOptions d

issueTrackerUrl :: Doc a
issueTrackerUrl = "https://github.com/haskell/haskell-language-server/issues"
