{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}

module Network.Bugsnag.StackFrame
  ( attachBugsnagCode
  , currentStackFrame
  ) where

import Prelude

import Data.Bugsnag
import Data.HashMap.Strict (HashMap)
import qualified Data.HashMap.Strict as HashMap
import Data.Text (Text, pack, unpack)
import Instances.TH.Lift ()
import Language.Haskell.TH.Syntax
import Network.Bugsnag.CodeIndex

-- | Attempt to attach code to a 'StackFrame'
--
-- Looks up the content in the Index by File/LineNumber and, if found, sets it
-- on the record.
attachBugsnagCode :: CodeIndex -> StackFrame -> StackFrame
attachBugsnagCode :: CodeIndex -> StackFrame -> StackFrame
attachBugsnagCode CodeIndex
index StackFrame
sf =
  StackFrame
sf
    { stackFrame_code =
        findBugsnagCode
          (unpack $ stackFrame_file sf)
          (stackFrame_lineNumber sf)
          index
    }

findBugsnagCode :: FilePath -> Int -> CodeIndex -> Maybe (HashMap Int Text)
findBugsnagCode :: String -> Int -> CodeIndex -> Maybe (HashMap Int Text)
findBugsnagCode String
path Int
n =
  ([(Int, Text)] -> HashMap Int Text)
-> Maybe [(Int, Text)] -> Maybe (HashMap Int Text)
forall a b. (a -> b) -> Maybe a -> Maybe b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap [(Int, Text)] -> HashMap Int Text
forall k v. (Eq k, Hashable k) => [(k, v)] -> HashMap k v
HashMap.fromList
    (Maybe [(Int, Text)] -> Maybe (HashMap Int Text))
-> (CodeIndex -> Maybe [(Int, Text)])
-> CodeIndex
-> Maybe (HashMap Int Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> (Int, Int) -> CodeIndex -> Maybe [(Int, Text)]
findSourceRange String
path (Int
begin, Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
3)
 where
  begin :: Int
begin
    | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
3 = Int
0
    | Bool
otherwise = Int
n Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
3

-- | Construct a 'StackFrame' from the point of this splice
--
-- Unfortunately there's no way to know the function, so that must be given:
currentStackFrame :: Q Exp
currentStackFrame :: Q Exp
currentStackFrame = [|locStackFrame $(Q Loc
forall (m :: * -> *). Quasi m => m Loc
qLocation Q Loc -> (Loc -> Q Exp) -> Q Exp
forall a b. Q a -> (a -> Q b) -> Q b
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= Loc -> Q Exp
liftLoc)|]

-- brittany-disable-next-binding

locStackFrame :: Loc -> Text -> StackFrame
locStackFrame :: Loc -> Text -> StackFrame
locStackFrame (Loc String
path String
_ String
_ (Int
ls, Int
cs) (Int, Int)
_) Text
func =
  StackFrame
defaultStackFrame
    { stackFrame_file = pack path
    , stackFrame_lineNumber = ls
    , stackFrame_columnNumber = Just cs
    , stackFrame_method = func
    , stackFrame_inProject = Just True
    , -- N.B. this assumes we're unlikely to see adoption within libraries, or
      -- that such a thing would even work. If this function's used, it's
      -- assumed to be in end-user code.
      stackFrame_code = Nothing
    }

-- brittany-disable-next-binding

-- Taken from monad-logger
liftLoc :: Loc -> Q Exp
liftLoc :: Loc -> Q Exp
liftLoc (Loc String
a String
b String
c (Int
d1, Int
d2) (Int
e1, Int
e2)) =
  [|
    Loc
      $(String -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => String -> m Exp
lift String
a)
      $(String -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => String -> m Exp
lift String
b)
      $(String -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => String -> m Exp
lift String
c)
      ($(Int -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => Int -> m Exp
lift Int
d1), $(Int -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => Int -> m Exp
lift Int
d2))
      ($(Int -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => Int -> m Exp
lift Int
e1), $(Int -> Q Exp
forall t (m :: * -> *). (Lift t, Quote m) => t -> m Exp
forall (m :: * -> *). Quote m => Int -> m Exp
lift Int
e2))
    |]