{-# LANGUAGE DerivingVia, StandaloneDeriving, ViewPatterns, ImpredicativeTypes #-}
-- | The home of customizability for visualizing variables and values with @haskell-debugger@
module GHC.Debugger.View.Class
  (
    -- * Writing custom debug visualizations
    --
    -- | The entry point for custom visualizations is 'DebugView'.
    -- There are two axis of configuration:
    --
    -- 1. What to display inline in front of the variable name and whether it
    -- is expandable
    --
    -- 2. What fields are displayed when the value is expanded and what are
    -- their corresponding values
    --
    -- The former is answered by 'debugValue' / 'VarValue' and the latter by
    -- 'debugFields' / 'VarFields'.
    DebugView(..)

  , VarValue(..)
  , VarFields(..)
  , VarFieldValue(..)
  , simpleValue


    -- * A 'Program' can describe a more complicated visualisation method which
    -- can query some information from the debugger.
  , Program(..)
  , isThunk
  , ifP


  -- * Utilities
  --
  -- | These can make it easier to write your own custom instances.
  -- We also use them for the built-in custom instances.
  , BoringTy(..)

  -- * The internals
  --
  -- | These are used by @haskell-debugger@ when invoking these instances at
  -- runtime and reconstructing the result from the heap.
  --
  -- They should never be used by a user looking to write custom visualizations.
  , VarValueIO(..)
  , debugValueIOWrapper
  , VarFieldsIO(..)
  , debugFieldsIOWrapper
  )
  where

import Data.Int
import Data.Word

-- | Custom handling of debug terms (e.g. in the variables pane, or when
-- inspecting a lazy variable)
class DebugView a where

  -- | Compute the representation of a variable with the given value.
  --
  -- INVARIANT: this method should only called on values which are already in
  -- WHNF, never thunks.
  --
  -- That said, this method is responsible for determining how much it is
  -- forced when displaying it inline as a variable.
  --
  -- For instance, for @String@, @a@ will be fully forced to display the entire
  -- string in one go rather than as a linked list of @'Char'@.
  debugValue :: a -> VarValue

  -- | Compute the fields to display when expanding a value of type @a@.
  --
  -- This method should only be called to get the fields if the corresponding
  -- @'VarValue'@ has @'varExpandable' = True@.
  debugFields :: a -> Program VarFields

-- | The 'Program' abstraction allows more complicated 'DebugView' instances
-- to be constructed. The debugger will interpreter a 'Program' lazily when
-- determining how to display a variable.
--
-- At the moment the only interesting query when constructing a program is determining
-- if a value is already evaluated or not. This can be used to only display the evaluated
-- prefix of a list for example.
data Program a where
    -- | Lift a value to a program
    PureProgram :: a -> Program a
    -- | Program application
    ProgramAp :: Program (a -> b) -> Program a -> Program b
    -- | Evaluate the conditional, and branch on the result
    ProgramBranch :: Program Bool -> Program a -> Program a -> Program a
    -- | Is the value a thunk or evaluated?
    ProgramAskThunk :: a -> Program Bool

instance Functor Program where
   fmap f x = ProgramAp (PureProgram f) x

instance Applicative Program where
   pure = PureProgram
   fx <*> fy = ProgramAp fx fy

-- | Construct a 'VarValue' which doesn't require a 'Program'.
simpleValue :: String -> Bool -> VarValue
simpleValue s b = VarValue (pure s) b

-- | Construct a 'Program' which determines if 'a' is a thunk or not.
isThunk :: a -> Program Bool
isThunk = ProgramAskThunk

-- | Construct a program which branches
ifP :: Program Bool -> Program a -> Program a -> Program a
ifP = ProgramBranch

-- | The representation of the value for some variable on the debugger
data VarValue = VarValue
  { -- | The value to display inline for this variable
    varValue      :: Program String

    -- | Can this variable further be expanded (s.t. @'debugFields'@ is not null?)
  , varExpandable :: Bool
  }

-- | The representation for fields of a value which is expandable in the debugger
newtype VarFields = VarFields
  { varFields :: [(String, VarFieldValue)]
  }

-- | A box for subfields of a value.
--
-- Used to construct the debug-view list of fields one gets from expanding a datatype.
-- See, for instance, the @DebugView (a, b)@ instance for an example of how it is used.
--
-- The boxed value is returned as is and can be further forced or expanded by
-- the debugger, using either the existing @'DebugView'@ instance for the
-- existential @a@ (the instance is found at runtime), or the generic runtime
-- term inspection mechanisms otherwise.
data VarFieldValue = forall a. VarFieldValue a

--------------------------------------------------------------------------------

-- | Boring types scaffolding.
--
-- Meant to be used like:
--
-- @
-- deriving via (BoringTy Int) instance (DebugView Int)
-- @
--
-- to derive a 'DebugView' for a type whose terms should always be fully forced
-- and displayed whole rather than as parts.
--
-- A boring type is one for which we don't care about the structure and would
-- rather see "whole" when being inspected. Strings and literals are a good
-- example, because it's more useful to see the string value than it is to see
-- a linked list of characters where each has to be forced individually.
newtype BoringTy a = BoringTy a

instance Show a => DebugView (BoringTy a) where
  debugValue (BoringTy x) = simpleValue (show x) False
  debugFields _           = pure $ VarFields []

deriving via BoringTy Int     instance DebugView Int
deriving via BoringTy Int8    instance DebugView Int8
deriving via BoringTy Int16   instance DebugView Int16
deriving via BoringTy Int32   instance DebugView Int32
deriving via BoringTy Int64   instance DebugView Int64
deriving via BoringTy Word    instance DebugView Word
deriving via BoringTy Word8   instance DebugView Word8
deriving via BoringTy Word16  instance DebugView Word16
deriving via BoringTy Word32  instance DebugView Word32
deriving via BoringTy Word64  instance DebugView Word64
deriving via BoringTy Double  instance DebugView Double
deriving via BoringTy Float   instance DebugView Float
deriving via BoringTy Integer instance DebugView Integer
deriving via BoringTy Char    instance DebugView Char
deriving via BoringTy String  instance DebugView String

instance DebugView (a, b) where
  debugValue _ = simpleValue "( , )" True
  debugFields (x, y) = pure $ VarFields
    [ ("fst", VarFieldValue x)
    , ("snd", VarFieldValue y) ]

-- | This instance will display up to the first 50 forced elements of a list.
instance {-# OVERLAPPABLE #-} DebugView [a] where
  debugValue [] = simpleValue "[]" False
  debugValue (_:_) = simpleValue "[...]" True
  debugFields v = VarFields <$> go 0 v
    where
      go :: Int -> [a] -> Program [(String, VarFieldValue)]
      go 50 xs = pure [("tail", VarFieldValue xs)]
      go _ [] = pure []
      go n (x:xs) = ((show n, VarFieldValue x) :) <$>
                      (ifP (isThunk xs) (pure $ [("tail", VarFieldValue xs)])
                                        (go (n + 1) xs))

--------------------------------------------------------------------------------
-- * (Internal) Wrappers required to call `evalStmt` on methods more easily
--------------------------------------------------------------------------------

-- | Wrapper to make evaluating from debugger easier
data VarValueIO = VarValueIO
  { varValueIO :: Program (IO String)
  , varExpandableIO :: Bool
  }

debugValueIOWrapper :: DebugView a => a -> IO [VarValueIO]
debugValueIOWrapper x = case debugValue x of
  VarValue str b ->
    pure [VarValueIO (pure <$> str) b]

newtype VarFieldsIO = VarFieldsIO
  { varFieldsIO :: Program [(IO String, VarFieldValue)]
  }

debugFieldsIOWrapper :: DebugView a => a -> IO [VarFieldsIO]
debugFieldsIOWrapper x = pure [VarFieldsIO (toVarFieldsIO <$> (debugFields x))]

toVarFieldsIO :: VarFields -> [(IO String, VarFieldValue)]
toVarFieldsIO x =
  case x of
    VarFields fls -> [ (pure fl_s, b) | (fl_s, b) <- fls]

