{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

module Haskell.Debug.Adapter.State.Utility where

-- import Control.Monad.IO.Class
import qualified System.Log.Logger as L
import qualified Text.Read as R
import qualified Data.List as L
import Control.Monad.Except
import Control.Concurrent (threadDelay)

import qualified Haskell.DAP as DAP
import Haskell.Debug.Adapter.Type
import Haskell.Debug.Adapter.Constant
import qualified Haskell.Debug.Adapter.Utility as U
import qualified Haskell.Debug.Adapter.GHCi as P


-- | 
--
unsupported :: String -> AppContext WrapStateRequest
unsupported reqStr = do
  let msg = "InitState does not support this request. " ++ reqStr
  throwError msg


-- |
--
setBreakpointsRequest :: DAP.SetBreakpointsRequest -> AppContext (Maybe StateTransit)
setBreakpointsRequest req = flip catchError errHdl $ do
  let args = DAP.argumentsSetBreakpointsRequest req
      dap = ":dap-set-breakpoints "
      cmd = dap ++ U.showDAP args
      dbg = dap ++ show args

  P.cmdAndOut cmd
  U.debugEV _LOG_APP dbg
  P.expectH $ P.funcCallBk lineCallBk

  return Nothing

  where
    lineCallBk :: Bool -> String -> AppContext ()
    lineCallBk True  s = U.sendStdoutEvent s
    lineCallBk False s
      | L.isPrefixOf _DAP_HEADER s = do
        U.debugEV _LOG_APP s
        dapHdl $ drop (length _DAP_HEADER) s
      | otherwise = U.sendStdoutEventLF s

    -- |
    --
    dapHdl :: String -> AppContext ()
    dapHdl str = case R.readEither str of
      Left err -> errHdl err >> return ()
      Right (Left err) -> errHdl err >> return ()
      Right (Right body) -> do
        resSeq <- U.getIncreasedResponseSequence
        let res = DAP.defaultSetBreakpointsResponse {
                  DAP.seqSetBreakpointsResponse = resSeq
                , DAP.request_seqSetBreakpointsResponse = DAP.seqSetBreakpointsRequest req
                , DAP.successSetBreakpointsResponse = True
                , DAP.bodySetBreakpointsResponse = body
                }

        U.addResponse $ SetBreakpointsResponse res

    -- |
    --
    errHdl :: String -> AppContext (Maybe StateTransit)
    errHdl msg = do
      U.errorEV _LOG_APP msg
      resSeq <- U.getIncreasedResponseSequence
      let res = DAP.defaultSetBreakpointsResponse {
                DAP.seqSetBreakpointsResponse = resSeq
              , DAP.request_seqSetBreakpointsResponse = DAP.seqSetBreakpointsRequest req
              , DAP.successSetBreakpointsResponse = False
              , DAP.messageSetBreakpointsResponse = msg
              }

      U.addResponse $ SetBreakpointsResponse res
      return Nothing


-- |
--
setExceptionBreakpointsRequest :: DAP.SetExceptionBreakpointsRequest -> AppContext (Maybe StateTransit)
setExceptionBreakpointsRequest req = do
  let args = DAP.argumentsSetExceptionBreakpointsRequest req
      filters = DAP.filtersSetExceptionBreakpointsRequestArguments args

  mapM_ go $ getOptions filters

  resSeq <- U.getIncreasedResponseSequence
  let res = DAP.defaultSetExceptionBreakpointsResponse {
            DAP.seqSetExceptionBreakpointsResponse = resSeq
          , DAP.request_seqSetExceptionBreakpointsResponse = DAP.seqSetExceptionBreakpointsRequest req
          , DAP.successSetExceptionBreakpointsResponse = True
          }

  U.addResponse $ SetExceptionBreakpointsResponse res

  return Nothing

  where
    getOptions filters
      | null filters                      = ["-fno-break-on-exception", "-fno-break-on-error"]
      | filters == ["break-on-error"]     = ["-fno-break-on-exception", "-fbreak-on-error"]
      | filters == ["break-on-exception"] = ["-fbreak-on-exception",    "-fno-break-on-error"]
      | otherwise                         = ["-fbreak-on-exception",    "-fbreak-on-error" ]

    go opt = do
      let cmd = ":set " ++ opt

      P.cmdAndOut cmd
      P.expectH $ P.stdoutCallBk



-- |
--
setFunctionBreakpointsRequest :: DAP.SetFunctionBreakpointsRequest -> AppContext (Maybe StateTransit)
setFunctionBreakpointsRequest req = flip catchError errHdl $ do
  let args = DAP.argumentsSetFunctionBreakpointsRequest req
      dap = ":dap-set-function-breakpoints "
      cmd = dap ++ U.showDAP args
      dbg = dap ++ show args

  P.cmdAndOut cmd
  U.debugEV _LOG_APP dbg
  P.expectH $ P.funcCallBk lineCallBk

  return Nothing

  where
    lineCallBk :: Bool -> String -> AppContext ()
    lineCallBk True  s = U.sendStdoutEvent s
    lineCallBk False s
      | L.isPrefixOf _DAP_HEADER s = do
        U.debugEV _LOG_APP s
        dapHdl $ drop (length _DAP_HEADER) s
      | otherwise = U.sendStdoutEventLF s

    -- |
    --
    dapHdl :: String -> AppContext ()
    dapHdl str = case R.readEither str of
      Left err -> errHdl err >> return ()
      Right (Left err) -> errHdl err >> return ()
      Right (Right body) -> do
        resSeq <- U.getIncreasedResponseSequence
        let res = DAP.defaultSetFunctionBreakpointsResponse {
                  DAP.seqSetFunctionBreakpointsResponse = resSeq
                , DAP.request_seqSetFunctionBreakpointsResponse = DAP.seqSetFunctionBreakpointsRequest req
                , DAP.successSetFunctionBreakpointsResponse = True
                , DAP.bodySetFunctionBreakpointsResponse = body
                }

        U.addResponse $ SetFunctionBreakpointsResponse res

    -- |
    --
    errHdl :: String -> AppContext (Maybe StateTransit)
    errHdl msg = do
      U.errorEV _LOG_APP msg
      resSeq <- U.getIncreasedResponseSequence
      let res = DAP.defaultSetFunctionBreakpointsResponse {
                DAP.seqSetFunctionBreakpointsResponse = resSeq
              , DAP.request_seqSetFunctionBreakpointsResponse = DAP.seqSetFunctionBreakpointsRequest req
              , DAP.successSetFunctionBreakpointsResponse = False
              , DAP.messageSetFunctionBreakpointsResponse = msg
              }

      U.addResponse $ SetFunctionBreakpointsResponse res
      return Nothing



-- |
--
terminateGHCi :: AppContext ()
terminateGHCi = do
  let cmd = ":quit"

  P.cmdAndOut cmd
  P.expectEOF $ P.stdoutCallBk
  return ()


-- |
--
evaluateRequest :: DAP.EvaluateRequest -> AppContext (Maybe StateTransit)
evaluateRequest req = do

  let args = DAP.argumentsEvaluateRequest req
      dap = ":dap-evaluate "
      cmd = dap ++ U.showDAP args
      dbg = dap ++ show args

  P.cmdAndOut cmd
  U.debugEV _LOG_APP dbg
  P.expectH $ P.funcCallBk lineCallBk

  return Nothing

  where
    lineCallBk :: Bool -> String -> AppContext ()
    lineCallBk True  s = U.sendStdoutEvent s
    lineCallBk False s
      | L.isPrefixOf _DAP_HEADER s = do
        U.debugEV _LOG_APP s
        dapHdl $ drop (length _DAP_HEADER) s
      | otherwise = do
        liftIO $ L.errorM _LOG_APP s
        U.sendStdoutEventLF s

    -- |
    --
    dapHdl :: String -> AppContext ()
    dapHdl str = case R.readEither str of
      Left err -> errHdl $ err ++ " : " ++ str
      Right (Left err) -> errHdl err
      Right (Right body) -> do
        resSeq <- U.getIncreasedResponseSequence
        let res = DAP.defaultEvaluateResponse {
                  DAP.seqEvaluateResponse = resSeq
                , DAP.request_seqEvaluateResponse = DAP.seqEvaluateRequest req
                , DAP.successEvaluateResponse = True
                , DAP.bodyEvaluateResponse = body
                }

        U.addResponse $ EvaluateResponse res


    -- |
    --
    errHdl :: String -> AppContext ()
    errHdl msg = do
      U.sendErrorEventLF msg
      resSeq <- U.getIncreasedResponseSequence
      let res = DAP.defaultEvaluateResponse {
                DAP.seqEvaluateResponse = resSeq
              , DAP.request_seqEvaluateResponse = DAP.seqEvaluateRequest req
              , DAP.successEvaluateResponse = False
              , DAP.messageEvaluateResponse = msg
              }

      U.addResponse $ EvaluateResponse res

-- |
--
completionsRequest :: DAP.CompletionsRequest -> AppContext (Maybe StateTransit)
completionsRequest req = flip catchError errHdl $ do

  let args = DAP.argumentsCompletionsRequest req
      key  = DAP.textCompletionsRequestArguments args
      size = "0-50"
      cmd = ":complete repl " ++ size ++ " \"" ++ key ++ "\""

  P.cmdAndOut cmd
  outs <- P.expectH P.stdoutCallBk

  resSeq <- U.getIncreasedResponseSequence
  let items = createItems outs
      body  = DAP.defaultCompletionsResponseBody {
              DAP.targetsCompletionsResponseBody = items
            }
      res = DAP.defaultCompletionsResponse {
            DAP.seqCompletionsResponse = resSeq
          , DAP.request_seqCompletionsResponse = DAP.seqCompletionsRequest req
          , DAP.successCompletionsResponse = True
          , DAP.bodyCompletionsResponse = body
          }

  U.addResponse $ CompletionsResponse res

  return Nothing

  where
    -- |
    --
    errHdl :: String -> AppContext (Maybe StateTransit)
    errHdl msg = do
      liftIO $ L.errorM _LOG_APP msg
      resSeq <- U.getIncreasedResponseSequence
      let res = DAP.defaultCompletionsResponse {
                DAP.seqCompletionsResponse = resSeq
              , DAP.request_seqCompletionsResponse = DAP.seqCompletionsRequest req
              , DAP.successCompletionsResponse = False
              , DAP.messageCompletionsResponse = msg
              }

      U.addResponse $ CompletionsResponse res
      return Nothing

    -- |
    --
    createItems :: [String] -> [DAP.CompletionsItem]
    createItems = map (createItem . normalize) . extracCompleteList

    -- |
    --
    createItem :: String -> DAP.CompletionsItem
    createItem (':':xs) = DAP.CompletionsItem xs
    createItem xs = DAP.CompletionsItem xs

    -- |
    --
    normalize :: String -> String
    normalize xs
      | 2 < length xs = tail . init $ xs
      | otherwise = xs

    -- |
    --
    extracCompleteList :: [String] -> [String]
    extracCompleteList [] = []
    extracCompleteList (_:[]) = []
    extracCompleteList (_:_:[]) = []
    extracCompleteList xs = tail . init $ xs



-- |
--
loadHsFile :: FilePath -> AppContext ()
loadHsFile file = do
  let cmd  = ":load "++ file

  P.cmdAndOut cmd
  P.expectH P.stdoutCallBk

  return ()


-- |
--
terminateRequest :: DAP.TerminateRequest -> AppContext ()
terminateRequest req = do
  terminateGHCi

  liftIO $ threadDelay _1_SEC

  resSeq <- U.getIncreasedResponseSequence

  let res = DAP.defaultTerminateResponse {
            DAP.seqTerminateResponse         = resSeq
          , DAP.request_seqTerminateResponse = DAP.seqTerminateRequest req
          , DAP.successTerminateResponse     = True
          }

  U.addResponse $ TerminateResponse res
  U.sendTerminatedEvent
  U.sendExitedEvent

-- |
--
internalTerminateRequest :: AppContext ()
internalTerminateRequest = do

    terminateGHCi

    liftIO $ threadDelay _1_SEC

    U.sendTerminatedEvent
    U.sendExitedEvent