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

{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE RecordWildCards, PackageImports, LambdaCase, PatternSynonyms, BangPatterns #-}
{-# LANGUAGE Rank2Types, ScopedTypeVariables #-}

{-# LANGUAGE TemplateHaskell #-} -- TODO rm this, create two modules Prelude.Spiros.Exception.Quoted.{GHC7,GHC8}. why? to use only TemplateHaskellQuotes.

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

{- |

See 'validate'.

'validateNatural' is a demonstration of it that's type-checked and @doctest@ed
(as well as being useful to me). 

-}
module Prelude.Spiros.Validator where

--------------------------------------------------
-- Imports: Internal -----------------------------
--------------------------------------------------

import Prelude.Spiros.Types

import Prelude.Spiros.Reexports
import Prelude.Spiros.Utilities
--import Prelude.Spiros.GUI
import Prelude.Spiros.Exception

--------------------------------------------------
-- Imports ---------------------------------------
--------------------------------------------------



--------------------------------------------------
-- Types -----------------------------------------
--------------------------------------------------

{-| Represents a validator as something that injects a type into another type, with the possibility of failure. 

Equivalent to:

@
Validator a b ≡ (a -> 'Possibly' b)
Validator a b ≡ (∀m. ('MonadThrow' m) => Kleisli m a b)
@

Specializations:

@
Validator a b ~ (a -> Maybe                b)
Validator a b ~ (a -> []                   b)
Validator a b ~ (a -> Either SomeException b)
Validator a b ~ (a -> IO                   b)
...
@

Usage:

@
-- x :: a
return x :: Validator a a
@

-}
type Validator a b = forall m. (MonadThrow m) => a -> m b

{-| Represents a value that has possibly failed ("or" will possibly fail).

Specializations:

@
Possibly b ~ Maybe    b
Possibly b ~         [b]
Possibly b ~ Either _ b
Possibly b ~ IO       b
...
@

-}
type Possibly b = forall m. (MonadThrow m) => m b

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

{-| 

Parameters:

@
name = validator name check display cast :: 'Validator' _ _
@

e.g. validating naturals:

@
validateNatural :: 'Validator' Integer Natural
validateNatural = validator 'natural
 (\\i -> i >= 0)
 (\\i -> i ++ " must be non-negative")
 (\\i -> fromIntegral i)
@

is the same as the explicit:

@
validateNatural :: ('MonadThrow' m) => Integer -> m Natural
validateNatural i
 | i >= 0    = return $ fromIntegral i
 | otherwise = 'throwN' \'validateNatural $ "must be non-negative"
@

and as the point-free styled:

@
validateNatural :: Integer -> 'Possibly' Natural
validateNatural = validator 'natural
 (>= 0)
 (++ " must be non-negative")
 (fromIntegral)
@

Wraps 'throwN'.

-}
validator
  :: ( MonadThrow m
     , Show a
     )
  => HaskellName
  -> (a -> Bool)
  -> (String -> String)
  -> (a -> b)
  -> (a -> m b)
validator name check display cast = \x ->
  if check x
  then return $ cast x
  else throwN name $ display (show x)

{-# inline validator #-}

{-| More convenient, but less informative, than 'validator'. 

Parameters:

@
name = validator_ \'name check cast :: 'Validator' _ _
@

e.g. validating naturals:

@
validateNatural :: Integer -> 'Possibly' Natural
validateNatural = validator 'validateNatural (>= 0) fromIntegral
@

Wraps 'throwN_'.

-}
validator_
  :: ( MonadThrow m
     )
  => HaskellName
  -> (a -> Bool)
  -> (a -> b)
  -> (a -> m b)
validator_ name check cast = \x ->
  if   check x
  then return $ cast x
  else throwN_ name

{-# inline validator_ #-}

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

{-| 

>>> validateNatural 2
2
>>> validateNatural (-2) :: Maybe Natural
Nothing

@
> validateNatural (-2)

*** Exception: 
...
-2 must be non-negative
...
@

Specializations of @i@:

@
validateNatural @Int     :: 'Validator' Int     Natural
validateNatural @Integer :: 'Validator' Integer Natural
validateNatural @Natural :: 'Validator' Natural Natural
...
@

Specializations of @m@:

@
validateNatural @Int @Maybe      :: Integer ->                Maybe Natural
validateNatural @Int @(Either _) :: Integer -> Either SomeException Natural
validateNatural @Int @[]         :: Integer ->                     [Natural]
validateNatural @Int @IO         :: Integer ->                   IO Natural
@

Definition:

@
validateNatural :: forall i m. ...
validateNatural = 'validator' \'validateNatural
 (>= 0)
 (++ " must be non-negative")
 (fromIntegral)
@

-}
validateNatural
  :: forall i m.
     ( Integral i
     , Show i
     )
  => ( MonadThrow m
     )
  => (i -> m Natural)
validateNatural = validator 'validateNatural
 (>= 0)
 (++ " must be non-negative")
 (fromIntegral)

{-# inline validateNatural #-}

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