-- |
-- Module      : Numeric.Uncertain.Correlated.Interactive
-- Copyright   : (c) Justin Le 2016
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- Exports all of the interface of "Numeric.Uncertain.Correlated", except
-- meant to be run in a /ghci/ session "interactively" for exploratory
-- purposes, or in a plain 'IO' action (instead of inside a 'Corr' monad).
--
-- For example, with the "Numeric.Uncertain.Correlated" interface:
--
-- @
-- ghci> 'evalCorr' $ do
--         x <- sampleUncert $ 12.5 '+/-' 0.8
--         y <- sampleUncert $ 15.9 +/- 0.5
--         z <- sampleUncert $ 1.52 +/- 0.07
--         let k = y**x
--         resolveUncert $ (x+z) * logBase z k
-- 1200 +/- 200
-- @
--
-- And with the interface from this "interactive" module:
--
-- @
-- ghci> x <- 'sampleUncert' $ 12.5 +/- 0.8
-- ghci> y <- sampleUncert $ 15.9 +/- 0.5
-- ghci> z <- sampleUncert $ 1.52 +/- 0.07
-- ghci> let k = y**x
-- ghci> 'resolveUncert' $ (x+z) * logBase z k
-- 1200 +/- 200
-- @
--
-- The main purpose of this module is to allow one to use /ghci/ as a fancy
-- "calculator" for computing and exploring propagated uncertainties of
-- complex and potentially correlated samples with uncertainty.
--
-- Because many of the names overlap with the names from the
-- "Numeric.Uncertain.Correlated" module, it is recommended that you never
-- have both imported at the same time in /ghci/ or in a file, or import
-- them qualified if you must.
--
-- Also note that all of these methods only work with @'Uncert' 'Double'@s,
-- and are not polymorphic over different numeric types.
--
-- Be aware that this module is not robustly tested in heavily concurrent
-- situations/applications.
--
module Numeric.Uncertain.Correlated.Interactive
  ( -- * Uncertain and Correlated Values
    CVar, CVarIO
    -- ** Sampling
  , sampleUncert, sampleExact, constC
    -- ** Resolving
  , resolveUncert
    -- * Applying arbitrary functions
  , liftC, liftC2, liftC3, liftC4, liftC5, liftCF
  )
  where

import           Control.Monad.ST
import           Control.Monad.Trans.State
import           Data.IORef
import           Data.Tuple
import           Numeric.Uncertain
import           Numeric.Uncertain.Correlated.Internal
import           System.IO.Unsafe                      (unsafePerformIO)
import qualified Data.IntMap.Strict                    as M
import qualified Numeric.Uncertain.Correlated          as C

-- | A 'CVar' specialized to work in an "interactive" context, in /ghci/ or
-- 'IO'.
type CVarIO = CVar RealWorld Double

-- ssh, don't tell anyone we're using 'unsafePerformIO'
globalCorrMap :: IORef (M.Key, M.IntMap (Uncert Double))
{-# NOINLINE globalCorrMap #-}
globalCorrMap = unsafePerformIO $ newIORef (0, M.empty)

runCorrIO :: Corr RealWorld Double a -> IO a
runCorrIO c = atomicModifyIORef' globalCorrMap
                                 (swap . runState (corrToState c))
{-# INLINE runCorrIO #-}

-- | Generate a sample in 'IO' from an @'Uncert' 'Double'@ value,
-- independently from all other samples.
sampleUncert :: Uncert Double -> IO CVarIO
sampleUncert u = runCorrIO $ C.sampleUncert u
{-# INLINABLE sampleUncert #-}

-- | Generate an exact sample in 'IO' with zero uncertainty,
-- independently from all other samples.
--
-- Not super useful, since you can do something equivalent with 'constC'
-- or the numeric instances:
--
-- @
-- sampleExact x  ≡ return ('constC' x)
-- sampleExact 10 ≡ return 10
-- @
--
-- But is provided for completeness alongside 'sampleUncert'.
sampleExact :: Double -> IO CVarIO
sampleExact d = runCorrIO $ C.sampleExact d
{-# INLINABLE sampleExact #-}

-- | "Resolve" an 'Uncert' from a 'CVarIO' using its potential multiple
-- samples and sample sources, taking into account inter-correlations
-- between 'CVarIO's and samples.
--
-- Note that if you use 'sampleUncert' on the result, the new sample will
-- be treated as something completely independent.  Usually this should
-- only be used as the "final value" of your computation or exploration.
resolveUncert :: CVarIO -> IO (Uncert Double)
resolveUncert v = runCorrIO $ C.resolveUncert v
{-# INLINABLE resolveUncert #-}