{-# LANGUAGE LambdaCase #-}

module Haskell.Debug.Adapter.Request where

import Control.Monad.IO.Class
import Data.Conduit
import Control.Lens
import Text.Parsec
import Data.Aeson
import qualified Data.ByteString.Lazy as B
import Control.Monad.State.Lazy
import qualified System.Log.Logger as L
import Control.Monad.Except

import qualified Haskell.DAP as DAP
import Haskell.Debug.Adapter.Type
import Haskell.Debug.Adapter.Utility
import Haskell.Debug.Adapter.Constant

-- |
--
run :: AppStores -> IO ()
run appData = do
  L.debugM _LOG_REQUEST "start request app"
  _ <- runApp appData app
  L.debugM _LOG_REQUEST "end request app"


-- |
--
app :: AppContext ()
app = flip catchError errHdl $ do
  _ <- runConduit pipeline
  return ()

  where
    pipeline :: ConduitM () Void AppContext ()
    pipeline = src .| lbs2req .| sink

    errHdl msg = do
      criticalEV _LOG_REQUEST msg
      addEvent CriticalExitEvent


---------------------------------------------------------------------------------
-- |
--
src :: ConduitT () B.ByteString AppContext ()
src = do
  liftIO $ L.debugM _LOG_REQUEST $ "src start waiting."
  bs <- lift goApp
  yield bs
  src

  where
    goApp :: AppContext B.ByteString
    goApp = getContentLength >>= getContent


-- |
--
getContent :: Int -> AppContext B.ByteString
getContent l = view inHandleAppStores <$> get
           >>= flip readCharsL l


-- |
--
getContentLength :: AppContext Int
getContentLength = go B.empty
  where
    go :: B.ByteString -> AppContext Int
    go buf = updateBuf buf >>= findLength

    updateBuf :: B.ByteString -> AppContext B.ByteString
    updateBuf buf = do
      hdl <- view inHandleAppStores <$> get
      B.append buf <$> readCharL hdl

    findLength :: B.ByteString -> AppContext Int
    findLength buf = case parse parser "find ContentLength parser" (lbs2str buf) of
      Left _  -> go buf
      Right l -> return l

    parser = do
      string _CONTENT_LENGTH
      len <- manyTill digit (string _TWO_CRLF)
      return . read $ len


---------------------------------------------------------------------------------
-- |
--
lbs2req :: ConduitT B.ByteString WrapRequest AppContext ()
lbs2req = do
  liftIO $ L.debugM _LOG_REQUEST $ "lbs2req start waiting."
  await >>= \case
    Nothing  -> do
      throwError $ "unexpectHed Nothing."
    Just reqBS -> do
      liftIO $ L.debugM _LOG_REQUEST $ "lbs2req get data. " ++ lbs2str reqBS
      lift (goApp reqBS) >>= \case
        Nothing -> return ()
        Just rq -> yield rq
      lbs2req

  where
    goApp :: B.ByteString -> AppContext (Maybe WrapRequest)
    goApp reqBS = flip catchError errHdl $ do
      req <- decodeRequest reqBS
      reqW <- createWrapRequest reqBS req
      return $ Just reqW

    -- |
    --
    errHdl msg = do
      warnEV _LOG_REQUEST msg
      return Nothing

-- |
--
decodeRequest :: B.ByteString -> AppContext DAP.Request
decodeRequest bs = liftEither $ eitherDecode bs

-- |
--
createWrapRequest :: B.ByteString -> DAP.Request -> AppContext WrapRequest
createWrapRequest bs req
  | "initialize" == DAP.commandRequest req = WrapRequest . InitializeRequest <$> (liftEither (eitherDecode bs))
  | "launch"     == DAP.commandRequest req = WrapRequest . LaunchRequest <$> (liftEither (eitherDecode bs))
  | "disconnect" == DAP.commandRequest req = WrapRequest . DisconnectRequest <$> (liftEither (eitherDecode bs))
  | "pause" == DAP.commandRequest req = WrapRequest . PauseRequest <$> (liftEither (eitherDecode bs))
  | "terminate" == DAP.commandRequest req = WrapRequest . TerminateRequest <$> (liftEither (eitherDecode bs))
  | "setBreakpoints" == DAP.commandRequest req = WrapRequest . SetBreakpointsRequest <$> (liftEither (eitherDecode bs))
  | "setFunctionBreakpoints" == DAP.commandRequest req = WrapRequest . SetFunctionBreakpointsRequest <$> (liftEither (eitherDecode bs))
  | "setExceptionBreakpoints" == DAP.commandRequest req = WrapRequest . SetExceptionBreakpointsRequest <$> (liftEither (eitherDecode bs))
  | "configurationDone" == DAP.commandRequest req = WrapRequest . ConfigurationDoneRequest <$> (liftEither (eitherDecode bs))
  | "threads" == DAP.commandRequest req = WrapRequest . ThreadsRequest <$> (liftEither (eitherDecode bs))
  | "stackTrace" == DAP.commandRequest req = WrapRequest . StackTraceRequest <$> (liftEither (eitherDecode bs))
  | "scopes" == DAP.commandRequest req = WrapRequest . ScopesRequest <$> (liftEither (eitherDecode bs))
  | "variables" == DAP.commandRequest req = WrapRequest . VariablesRequest <$> (liftEither (eitherDecode bs))
  | "continue" == DAP.commandRequest req = WrapRequest . ContinueRequest <$> (liftEither (eitherDecode bs))
  | "next" == DAP.commandRequest req = WrapRequest . NextRequest <$> (liftEither (eitherDecode bs))
  | "stepIn" == DAP.commandRequest req = WrapRequest . StepInRequest <$> (liftEither (eitherDecode bs))
  | "evaluate" == DAP.commandRequest req = WrapRequest . EvaluateRequest <$> (liftEither (eitherDecode bs))
  | "completions" == DAP.commandRequest req = WrapRequest . CompletionsRequest <$> (liftEither (eitherDecode bs))
  | otherwise = throwError $ "unsupported request command. " ++ lbs2str bs


---------------------------------------------------------------------------------
-- |
--
sink :: ConduitT WrapRequest Void AppContext ()
sink = do
  liftIO $ L.debugM _LOG_REQUEST $ "sink start waiting."
  await >>= \case
    Nothing  -> do
      throwError $ "unexpected Nothing."
      return ()
    Just req@(WrapRequest DisconnectRequest{}) -> do
      lift $ goApp req
      liftIO $ L.infoM _LOG_REQUEST $ "disconnect. end of request thread."
    Just req -> do
      lift $ goApp req
      sink

  where
    -- |
    --
    goApp :: WrapRequest -> AppContext ()
    goApp = addRequest