{-# LANGUAGE CPP                  #-}

#if __GLASGOW_HASKELL__ >= 902
{-# OPTIONS_GHC -Wno-operator-whitespace #-}
#endif

{-# LANGUAGE ConstraintKinds      #-}
{-# LANGUAGE DataKinds            #-}
{-# LANGUAGE DeriveDataTypeable   #-}
{-# LANGUAGE DeriveGeneric        #-}
{-# LANGUAGE DerivingStrategies   #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE PolyKinds            #-}
{-# LANGUAGE RankNTypes           #-}
{-# LANGUAGE Trustworthy          #-}
{-# LANGUAGE TypeFamilies         #-}
#if ( __GLASGOW_HASKELL__ < 906 )
{-# LANGUAGE TypeInType           #-}
#endif
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

{- |
Module                  : Relude.Debug
Copyright               : (c) 2016 Stephen Diehl
                          (c) 2016-2018 Serokell
                          (c) 2018-2023 Kowainik
SPDX-License-Identifier : MIT
Maintainer              : Kowainik <xrom.xkov@gmail.com>
Stability               : Stable
Portability             : Portable

This module contains functions for debugging __pure__ functions. You
can't use functions like 'System.IO.putStrLn' for this purpose because
they require changes to the type signature, but functions in this module
avoid this problem by being pure on their own.

Additionally, these functions produce compile-time warnings, if you leave them
in your code. Warnings help you to cleanup all debugging usages before
releasing.

@
__ghci>__ foo = trace "I forgot trace in code"

\<interactive\>:4:7: __warning__: [-Wdeprecations]
    In the use of ‘trace’ (imported from "Relude"):
    "'trace' remains in code"
@

The following table briefly shows names and types of all functions in
this module.

+-----------------+----------------------------------------+
| __Name__        | __Type__                               |
+=================+========================================+
| 'trace'         | @String -> a -> a@                     |
+-----------------+----------------------------------------+
| 'traceShow'     | @Show a => a -> b -> b@                |
+-----------------+----------------------------------------+
| 'traceShowId'   | @Show a => a -> a@                     |
+-----------------+----------------------------------------+
| 'traceShowWith' | @Show b => (a -> b) -> a -> a@         |
+-----------------+----------------------------------------+
| 'traceId'       | @String -> String@                     |
+-----------------+----------------------------------------+
| 'traceM'        | @(Applicative f) => String -> f ()@    |
+-----------------+----------------------------------------+
| 'traceShowM'    | @(Show a, Applicative f) => a -> f ()@ |
+-----------------+----------------------------------------+

__⚠ NOTE:__ Use these functions only for local debugging
purposes. They break referential transparency, they are only useful
when you want to observe intermediate values of your pure functions
and to understand the behaviour locally. If you want to log debug
messages in your application, consider using a logging library
instead.
-}

module Relude.Debug
    ( -- * Tracing
      trace
    , traceShow
    , traceShowId
    , traceShowWith
    , traceId
    , traceM
    , traceShowM

      -- * Imprecise error
    , error
    , Undefined (..)
    , undefined
    ) where

import Data.Data (Data)
#if __GLASGOW_HASKELL__ >= 904
import Data.Type.Equality (type (~))
#endif
import GHC.Exts (RuntimeRep, TYPE)
import GHC.TypeLits (ErrorMessage (..), TypeError)

import Relude.Applicative (Applicative)
import Relude.Base (Char, Constraint, Eq, Generic, HasCallStack, Ord, Show, Type, Typeable)
import Relude.Enum (Bounded, Enum)
import Relude.String (Read, String, Text, toString)

import qualified Debug.Trace as Debug
import qualified Prelude


-- $setup
-- >>> import Relude
-- >>> :set -Wno-deprecations

----------------------------------------------------------------------------
-- trace
----------------------------------------------------------------------------

{- | Prints the given 'String' message and returns the passed value of
type @a@.

>>> increment l = map (+1) l
>>> increment [2, 3, 4]
[3,4,5]

>>> increment l = trace ("incrementing each value of: " ++ show l) (map (+1) l)
>>> increment [2, 3, 4]
incrementing each value of: [2,3,4]
[3,4,5]

* If you want to print a 'Show'able value instead of 'String', use 'traceShow'.
* If you want to print the value itself, use 'traceShowId'.
* If you want to print by specifying a custom formatting function, use 'traceShowWith'.

-}
trace :: String -> a -> a
trace = Debug.trace
{-# WARNING trace "'trace' remains in code" #-}

{- | Similar to 'trace' but prints a given value with the 'Show'
instance instead of a 'String'.

>>> increment l = map (+1) l
>>> increment [2, 3, 4]
[3,4,5]

>>> increment l = traceShow l (map (+1) l)
>>> increment [2, 3, 4]
[2,3,4]
[3,4,5]

* If you want to print a specific 'String' instead, use 'trace'
* If you want to print and return the same value, use 'traceShowId'
* If you want to specify a custom printing function, use 'traceShowWith'
-}
traceShow :: Show a => a -> b -> b
traceShow = Debug.traceShow
{-# WARNING traceShow "'traceShow' remains in code" #-}

{- | Similar to 'traceShow' but prints the given value itself instead
of a separate value.

>>> traceShowId (1+2+3, "hello" ++ "world")
(6,"helloworld")
(6,"helloworld")

* If you to specify a different value to print, use 'trace' or 'traceShow'
* If you want to have more control over printing, use 'traceShowWith'
-}
traceShowId :: Show a => a -> a
traceShowId = Debug.traceShowId
{-# WARNING traceShowId "'traceShowId' remains in code" #-}

{- | Similar 'traceShowId', but uses a provided function to convert the
argument to a value with the 'Show' constraint.

>>> traceShowWith fst (1, "ABC")
1
(1,"ABC")

In other words, @'traceShowId' ≡ 'traceShowWith' id@.

This function is useful for debugging values that do not have 'Show'
instance:

>>> fst $ traceShowWith fst (1, id)
1
1

* If you don't need such flexibility, use simpler 'trace', 'traceShow' or 'traceShowId'

@since 1.0.0.0
-}
traceShowWith :: Show b => (a -> b) -> a -> a
traceShowWith f v = Debug.traceShow (f v) v
{-# WARNING traceShowWith "'traceShowWith remains in code" #-}

{- | Trace function to print values while working a pure monad
(e.g. 'Maybe', 'State', etc.)

>>> :{
let action :: Maybe Int
    action = do
        x <- Just 3
        traceM ("x: " ++ show x)
        y <- pure 12
        traceM ("y: " ++ show y)
        pure (x*2 + y)
in action
:}
x: 3
y: 12
Just 18

* If you want to print a value with the 'Show' instance instead, use 'traceShowM'
-}
traceM :: (Applicative f) => String -> f ()
traceM = Debug.traceM
{-# WARNING traceM "'traceM' remains in code" #-}

{-|
Like 'traceM', but uses 'Prelude.show' on the argument to convert it to a
'String'.

>>> :{
let action :: Maybe Int
    action = do
        x <- Just 3
        traceShowM x
        y <- pure 12
        traceShowM y
        pure (x*2 + y)
in action
:}
3
12
Just 18
-}
traceShowM :: (Show a, Applicative f) => a -> f ()
traceShowM = Debug.traceShowM
{-# WARNING traceShowM "'traceShowM' remains in code" #-}

{- | Similar to 'traceShowId' but specialised for 'String'.

>>> traceId "hello"
"hello
hello"
-}
traceId :: String -> String
traceId = Debug.traceId
{-# WARNING traceId "'traceId' remains in code" #-}

----------------------------------------------------------------------------
-- error
----------------------------------------------------------------------------

{- | Throw pure errors. Use this function only to when you are sure that this
branch of code execution is not possible.  __DO NOT USE__ 'error' as a normal
error handling mechanism.

⚠️__CAUTION__⚠️ Unlike "Prelude" version, 'error' takes
 t'Relude.String.Reexport.Text' as an argument. In case it used by mistake,
the user will see the following:

>>> error ("oops" :: String)
...
... 'error' expects 'Text' but was given 'String'.
      Possible fixes:
          * Make sure OverloadedStrings extension is enabled
          * Use 'error (toText msg)' instead of 'error msg'
...
>>> error False
...
... 'error' works with 'Text'
      But given: Bool
...
-}
error
    :: forall (r :: RuntimeRep) (a :: TYPE r) (t :: Type) .
       (HasCallStack, IsText t)
    => t
    -> a
error e = Prelude.error (toString e)

type IsText (t :: Type) = (t ~ Text, CheckIsText t)

type family CheckIsText (t :: Type) :: Constraint where
    CheckIsText Text = ()
    CheckIsText [Char] = TypeError
        ( 'Text "'error' expects 'Text' but was given 'String'."
        ':$$: 'Text "Possible fixes:"
        ':$$: 'Text "    * Make sure OverloadedStrings extension is enabled"
        ':$$: 'Text "    * Use 'error (toText msg)' instead of 'error msg'"
        )
    CheckIsText a = TypeError
        ( 'Text "'error' works with 'Text'"
        ':$$: 'Text "But given: " ':<>: 'ShowType a
        )

----------------------------------------------------------------------------
-- Undefined and undefined
----------------------------------------------------------------------------

-- | Similar to 'undefined' but data type.
data Undefined = Undefined
    deriving stock (Eq, Ord, Show, Read, Enum, Bounded, Data, Typeable, Generic)
{-# WARNING Undefined "'Undefined' type remains in code" #-}

-- | 'Prelude.undefined' that leaves warning in code on every usage.
undefined :: forall (r :: RuntimeRep) . forall (a :: TYPE r) . HasCallStack => a
undefined = Prelude.undefined
{-# WARNING undefined "'undefined' function remains in code" #-}
