module Coda.Server
  ( server
  , logMessage
  , telemetryEvent
  , showMessage
  ) where
import Coda.Server.Options
import Coda.Syntax.Document
import Control.Applicative
import Control.Monad.IO.Class
import Control.Monad.State
import Control.Monad.Reader
import Control.Lens hiding ((.=))
import Data.Aeson
import Data.ByteString.Lazy as Lazy
import Data.Text as Text
import Language.Server.Builder
import Language.Server.Protocol
import Language.Server.Parser
import System.Exit
import System.IO
putError :: MonadIO m => Maybe Id -> ErrorCode -> Text -> m ()
putError i c t = 
  putMessage $ Response i Nothing (Just (ResponseError c t Nothing))
data ServerState = ServerState
  { _shutdownRequested :: Bool
  , _documents :: Documents
  } deriving Show
makeFieldsNoPrefix ''ServerState
class (HasShutdownRequested t Bool, HasDocuments t Documents) => HasServerState t
instance (HasShutdownRequested t Bool, HasDocuments t Documents) => HasServerState t
eitherDecodeRequest :: Lazy.ByteString -> Either String (Either [Request] Request)
eitherDecodeRequest bs
    = Left  <$> eitherDecode' bs
  <|> Right <$> eitherDecode' bs
listen :: (MonadIO m, MonadState s m, HasServerState s) => m (Either [Request] Request)
listen = liftIO (parse parseMessage stdin) >>= \case
  Left e -> do
    putError Nothing InvalidRequest (Text.pack e)
    liftIO $ do
      hFlush stdout
      hFlush stderr
      exitWith $ ExitFailure 1
  Right v -> case eitherDecodeRequest v of
    Left s -> do
      putError Nothing ParseError (Text.pack s)
      listen
    Right e -> do
      liftIO $ do
        Lazy.hPutStr stderr $ encode e
        hPutChar stderr '\n'
        hFlush stderr
      return e
logMessage :: MonadIO m => Severity -> Text -> m ()
logMessage s t = putMessage $ LogMessage s t
showMessage :: MonadIO m => Severity -> Text -> m ()
showMessage s t = putMessage $ ShowMessage s t
telemetryEvent :: MonadIO m => Value -> m ()
telemetryEvent v = putMessage $ TelemetryEvent v
server :: ServerOptions -> IO ()
server opts = do
  hSetBuffering stdin NoBuffering
  hSetEncoding stdin char8
  hSetBuffering stdout NoBuffering
  hSetEncoding stdout char8
  hFlush stdout
  runReaderT ?? opts $ evalStateT ?? ServerState False mempty $ do
    initializeServer
    loop
ok :: (MonadIO m, ToJSON a) => Id -> a -> m ()
ok i p = liftIO $ putMessage $ Response (Just i) (Just (toJSON p)) Nothing
initializeServer :: (MonadState s m, HasServerState s, MonadReader e m, HasServerOptions e, MonadIO m) => m ()
initializeServer = listen >>= \case
  Right (Initialize i _ip) ->
    ok i $ object
      [ "capabilities" .= object
        [ "textDocumentSync" .= object
          [ "openClose" .= toJSON True
          , "change" .= toJSON (2 :: Int) 
          , "save" .= object [ "includeText" .= toJSON False ]
          ]
        ]
      ]
  Right Shutdown -> do
    assign shutdownRequested True
    initializeServer
  Right Exit ->
    use shutdownRequested >>= \b -> liftIO $ do
      hFlush stdout
      hFlush stderr
      exitWith $ if b then ExitSuccess else ExitFailure 1
  Right (Request _ m _)
    | Text.isPrefixOf "$/" m -> initializeServer 
  Right (Request Nothing _ _) -> initializeServer               
  Right (Request (Just i) _ _) -> do
    putError (Just i) ServerNotInitialized "waiting for initialization"
    initializeServer
  Left _ -> do
    putError Nothing InternalError "batch commands not yet implemented"
    initializeServer
loop :: (MonadState s m, HasServerState s, MonadReader e m, HasServerOptions e, MonadIO m) => m ()
loop = listen >>= \case
  Right (DidClose tdi) -> didClose tdi >> loop
  Right (DidChange ps) -> didChange ps >> loop
  Right (DidChangeConfiguration _) -> loop
  Right (DidOpen tdi) -> didOpen tdi >> loop
  Right (DidSave ps) -> didSave ps >> loop
  Right Exit ->
    use shutdownRequested >>= \b -> liftIO $ do
      hFlush stdout
      hFlush stderr
      exitWith $ if b then ExitSuccess else ExitFailure 1
  Right Initialized -> loop 
  Right Shutdown -> assign shutdownRequested True >> loop
  Right (Request _ m _) | Text.isPrefixOf "$/" m -> loop 
  Right (Request (Just i) _ _) -> do
    putError (Just i) InvalidRequest "unsupported request"
    loop
  Right (Request _ m _) -> do
    liftIO $ hPrint stderr m
    logMessage Information m
    loop
  Left _ -> do
    putError Nothing InternalError "batch commands not yet implemented"
    loop