packages feed

predicate-typed-0.7.4.1: src/Predicate/Data/Numeric.hs

{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE NoStarIsType #-}
{-# LANGUAGE EmptyDataDeriving #-}
{-# LANGUAGE DerivingStrategies #-}
-- | promoted numeric functions
module Predicate.Data.Numeric (

  -- ** numeric
    type (+)
  , type (-)
  , type (*)
  , type (/)
  , Negate
  , Abs
  , Signum
  , FromInteger
  , FromInteger'
  , FromIntegral
  , FromIntegral'
  , Truncate
  , Truncate'
  , Ceiling
  , Ceiling'
  , Floor
  , Floor'
  , Even
  , Odd
  , Div
  , Mod
  , DivMod
  , QuotRem
  , Quot
  , Rem
  , LogBase
  , type (^)
  , type (**)
  , DivI
  , RoundUp

  -- ** rational numbers
  , type (%)
  , type (-%)
  , ToRational
  , FromRational
  , FromRational'

 -- ** read-show
  , ReadBase
  , ReadBase'
  , ShowBase
  , ShowBaseN
  , UnShowBaseN
  , ToBits
 ) where
import Predicate.Core
import Predicate.Misc
import Predicate.Util
import Predicate.Data.Ordering (type (==))
import GHC.TypeLits (Nat,KnownNat)
import Data.List (elemIndex)
import Data.Function (fix)
import Data.Typeable (Typeable, Proxy(Proxy))
import Data.Kind (Type)
import qualified Numeric
import Data.Char (toLower)
import Data.Ratio ((%))
import GHC.Real (Ratio((:%)))
import qualified Safe (fromJustNote, atNote)
-- $setup
-- >>> :set -XDataKinds
-- >>> :set -XTypeApplications
-- >>> :set -XTypeOperators
-- >>> :set -XOverloadedStrings
-- >>> :set -XNoOverloadedLists
-- >>> import Predicate.Prelude
-- >>> import qualified Data.Semigroup as SG
-- >>> import Data.Time

-- | 'fromInteger' function where you need to provide a reference to the type @t@ of the result
data FromInteger' t n deriving Show

instance ( Num (PP t a)
         , Integral (PP n a)
         , P n a
         , Show (PP t a)
         ) => P (FromInteger' t n) a where
  type PP (FromInteger' t n) a = PP t a
  eval _ opts a = do
    let msg0 = "FromInteger"
    nn <- eval (Proxy @n) opts a
    pure $ case getValueLR NoInline opts msg0 nn [] of
      Left e -> e
      Right n ->
        let b = fromInteger (fromIntegral n)
        in mkNode opts (Val b) (msg0 <> " " <> showL opts b) [hh nn]

-- | 'fromInteger' function where you need to provide the type @t@ of the result
--
-- >>> pz @(FromInteger (SG.Sum _)) 23
-- Val (Sum {getSum = 23})
--
-- >>> pz @(44 >> FromInteger Rational) 12
-- Val (44 % 1)
--
-- >>> pz @(FromInteger Rational) 12
-- Val (12 % 1)
--
-- >>> pl @((Lift (FromInteger _) 12 &&& Id) >> Fst + Snd) (SG.Min 7)
-- Present Min {getMin = 19} ((>>) Min {getMin = 19} | {getMin = 19})
-- Val (Min {getMin = 19})
--
-- >>> pl @(Lift (FromInteger _) 12 <> Id) (SG.Product 7)
-- Present Product {getProduct = 84} (Product {getProduct = 12} <> Product {getProduct = 7} = Product {getProduct = 84})
-- Val (Product {getProduct = 84})
--
-- >>> pl @(Fst >> FromInteger (SG.Sum _)) (3,"A")
-- Present Sum {getSum = 3} ((>>) Sum {getSum = 3} | {getSum = 3})
-- Val (Sum {getSum = 3})
--
-- >>> pl @(Lift (FromInteger DiffTime) 123) 'x'
-- Present 123s ((>>) 123s | {FromInteger 123s})
-- Val 123s
--
data FromInteger (t :: Type) deriving Show
type FromIntegerT (t :: Type) = FromInteger' (Hole t) Id
--type FromIntegerP n = FromInteger' UnproxyT n

instance P (FromIntegerT t) x => P (FromInteger t) x where
  type PP (FromInteger t) x = PP (FromIntegerT t) x
  eval _ = eval (Proxy @(FromIntegerT t))

-- | 'fromIntegral' function where you need to provide a reference to the type @t@ of the result
data FromIntegral' t n deriving Show

instance ( Num (PP t a)
         , Integral (PP n a)
         , P n a
         , Show (PP t a)
         , Show (PP n a)
         ) => P (FromIntegral' t n) a where
  type PP (FromIntegral' t n) a = PP t a
  eval _ opts a = do
    let msg0 = "FromIntegral"
    nn <- eval (Proxy @n) opts a
    pure $ case getValueLR NoInline opts msg0 nn [] of
      Left e -> e
      Right n ->
        let b = fromIntegral n
        in mkNode opts (Val b) (show3 opts msg0 b n) [hh nn]

-- | 'fromIntegral' function where you need to provide the type @t@ of the result
--
-- >>> pz @(FromIntegral (SG.Sum _)) 23
-- Val (Sum {getSum = 23})
--
-- >>> pz @(Pop1' (Proxy FromIntegral) 'Proxy 44) (1 % 0)
-- Val (44 % 1)
--
data FromIntegral (t :: Type) deriving Show
type FromIntegralT (t :: Type) = FromIntegral' (Hole t) Id

instance P (FromIntegralT t) x => P (FromIntegral t) x where
  type PP (FromIntegral t) x = PP (FromIntegralT t) x
  eval _ = eval (Proxy @(FromIntegralT t))

-- | 'toRational' function
--
-- >>> pz @(ToRational Id) 23.5
-- Val (47 % 2)
--
-- >>> pl @((ToRational 123 &&& Id) >> Fst + Snd) 4.2
-- Present 636 % 5 ((>>) 636 % 5 | {123 % 1 + 21 % 5 = 636 % 5})
-- Val (636 % 5)
--
-- >>> pl @(Fst >= Snd || Snd > 23 || 12 -% 5 <= ToRational Fst) (12,13)
-- True (False || True)
-- Val True
--
-- >>> pl @(ToRational 14) ()
-- Present 14 % 1 (ToRational 14 % 1 | 14)
-- Val (14 % 1)
--
-- >>> pl @(ToRational 5 / ToRational 3) 'x'
-- Present 5 % 3 (5 % 1 / 3 % 1 = 5 % 3)
-- Val (5 % 3)
--
data ToRational p deriving Show

instance ( a ~ PP p x
         , Show a
         , Real a
         , P p x
         )
   => P (ToRational p) x where
  type PP (ToRational p) x = Rational
  eval _ opts x = do
    let msg0 = "ToRational"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right a ->
        let r = toRational a
        in mkNode opts (Val r) (show3 opts msg0 r a) [hh pp]

-- | 'fromRational' function where you need to provide a reference to the type @t@ of the result
--
-- >>> pl @(FromRational' Fst Snd) (1,2 % 5)
-- Present 0.4 (FromRational 0.4 | 2 % 5)
-- Val 0.4
--
data FromRational' t p deriving Show

instance ( P p a
         , PP p a ~ Rational
         , Show (PP t a)
         , Fractional (PP t a)
         ) => P (FromRational' t p) a where
  type PP (FromRational' t p) a = PP t a
  eval _ opts a = do
    let msg0 = "FromRational"
    pp <- eval (Proxy @p) opts a
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let b = fromRational @(PP t a) p
        in mkNode opts (Val b) (show3 opts msg0 b p) [hh pp]

-- | 'fromRational' function where you need to provide the type @t@ of the result
--
-- >>> pz @(FromRational Rational) 23.5
-- Val (47 % 2)
--
-- >>> pl @(FromRational Float) (4 % 5)
-- Present 0.8 (FromRational 0.8 | 4 % 5)
-- Val 0.8
--
data FromRational (t :: Type) deriving Show
type FromRationalT (t :: Type) = FromRational' (Hole t) Id

instance P (FromRationalT t) x => P (FromRational t) x where
  type PP (FromRational t) x = PP (FromRationalT t) x
  eval _ = eval (Proxy @(FromRationalT t))

-- | 'truncate' function where you need to provide a reference to the type @t@ of the result
--
-- >>> pl @(Truncate' (Fst >> UnproxyT) Snd) (Proxy @Integer,2.3)
-- Present 2 (Truncate 2 | 2.3)
-- Val 2
--
-- >>> pl @(Truncate' Fst Snd) (1::Int,2.3)
-- Present 2 (Truncate 2 | 2.3)
-- Val 2
--
data Truncate' t p deriving Show

instance ( P p x
         , RealFrac (PP p x)
         , Integral (PP t x)
         , Show (PP t x)
         , Show (PP p x)
         ) => P (Truncate' t p) x where
  type PP (Truncate' t p) x = PP t x
  eval _ opts x = do
    let msg0 = "Truncate"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let b = truncate p
        in mkNode opts (Val b) (show3 opts msg0 b p) [hh pp]

-- | 'truncate' function where you need to provide the type @t@ of the result
--
-- >>> pz @(Truncate Int) (23 % 5)
-- Val 4
--
data Truncate (t :: Type) deriving Show
type TruncateT (t :: Type) = Truncate' (Hole t) Id

instance P (TruncateT t) x => P (Truncate t) x where
  type PP (Truncate t) x = PP (TruncateT t) x
  eval _ = eval (Proxy @(TruncateT t))

-- | 'ceiling' function where you need to provide a reference to the type @t@ of the result
data Ceiling' t p deriving Show

instance ( P p x
         , RealFrac (PP p x)
         , Integral (PP t x)
         , Show (PP t x)
         , Show (PP p x)
         ) => P (Ceiling' t p) x where
  type PP (Ceiling' t p) x = PP t x
  eval _ opts x = do
    let msg0 = "Ceiling"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let b = ceiling p
        in mkNode opts (Val b) (show3 opts msg0 b p) [hh pp]

-- | 'ceiling' function where you need to provide the type @t@ of the result
--
-- >>> pz @(Ceiling Int) (23 % 5)
-- Val 5
--
data Ceiling (t :: Type) deriving Show
type CeilingT (t :: Type) = Ceiling' (Hole t) Id

instance P (CeilingT t) x => P (Ceiling t) x where
  type PP (Ceiling t) x = PP (CeilingT t) x
  eval _ = eval (Proxy @(CeilingT t))

-- | 'floor' function where you need to provide a reference to the type @t@ of the result
data Floor' t p deriving Show

instance ( P p x
         , RealFrac (PP p x)
         , Integral (PP t x)
         , Show (PP t x)
         , Show (PP p x)
         ) => P (Floor' t p) x where
  type PP (Floor' t p) x = PP t x
  eval _ opts x = do
    let msg0 = "Floor"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let b = floor p
        in mkNode opts (Val b) (show3 opts msg0 b p) [hh pp]

-- | 'floor' function where you need to provide the type @t@ of the result
--
-- >>> pz @(Floor Int) (23 % 5)
-- Val 4
--
data Floor (t :: Type) deriving Show
type FloorT (t :: Type) = Floor' (Hole t) Id

instance P (FloorT t) x => P (Floor t) x where
  type PP (Floor t) x = PP (FloorT t) x
  eval _ = eval (Proxy @(FloorT t))

data BinOp = BMult | BSub | BAdd
  deriving stock (Read, Show, Eq)

-- | adds two values together pointed to by @p@ and @q@
data p + q deriving Show
infixl 6 +

type AddT p q = Bin 'BAdd p q

instance P (AddT p q) x => P (p + q) x where
  type PP (p + q) x = PP (AddT p q) x
  eval _ = eval (Proxy @(AddT p q))

-- | subtracts two values together pointed to by @p@ and @q@
data p - q deriving Show
infixl 6 -

type SubT p q = Bin 'BSub p q

instance P (SubT p q) x => P (p - q) x where
  type PP (p - q) x = PP (SubT p q) x
  eval _ = eval (Proxy @(SubT p q))

-- | multiply two values together pointed to by @p@ and @q@
data p * q deriving Show
infixl 7 *

type MultT p q = Bin 'BMult p q

instance P (MultT p q) x => P (p * q) x where
  type PP (p * q) x = PP (MultT p q) x
  eval _ = eval (Proxy @(MultT p q))

-- | similar to 'GHC.Real.(^)'
--
-- >>> pz @(Fst ^ Snd) (10,4)
-- Val 10000
--
data p ^ q deriving Show
infixr 8 ^

instance ( P p a
         , P q a
         , Show (PP p a)
         , Show (PP q a)
         , Num (PP p a)
         , Integral (PP q a)
         ) => P (p ^ q) a where
  type PP (p ^ q) a = PP p a
  eval _ opts a = do
    let msg0 = "Pow"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
        let hhs = [hh pp, hh qq]
        in if q < 0 then mkNode opts (Fail (msg0 <> " negative exponent")) "" hhs
           else let d = p ^ q
                in mkNode opts (Val d) (showL opts p <> " ^ " <> showL opts q <> " = " <> showL opts d) hhs

-- | similar to 'GHC.Float.(**)'
--
-- >>> pz @(Fst ** Snd) (10,4)
-- Val 10000.0
--
-- >>> pz @'(IsPrime,Id ^ 3,(FromIntegral _) ** (Lift (FromRational _) (1 % 2))) 4
-- Val (False,64,2.0)
--
data p ** q deriving Show
infixr 8 **

instance ( PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP p a)
         , Floating (PP p a)
         , Ord (PP q a)
         ) => P (p ** q) a where
  type PP (p ** q) a = PP p a
  eval _ opts a = do
    let msg0 = "Exp"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
         let hhs = [hh pp, hh qq]
         in if q < 0 then mkNode opts (Fail (msg0 <> " negative exponent")) "" hhs
            else if p == 0 && q == 0 then mkNode opts (Fail (msg0 <> " zero/zero")) "" hhs
            else let d = p ** q
                in mkNode opts (Val d) (showL opts p <> " ** " <> showL opts q <> " = " <> showL opts d) hhs

-- | similar to 'logBase'
--
-- >>> pz @(Fst `LogBase` Snd >> Truncate Int) (10,12345)
-- Val 4
--
data LogBase p q deriving Show
instance ( PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP q a)
         , Floating (PP q a)
         , Ord (PP p a)
         ) => P (LogBase p q) a where
  type PP (LogBase p q) a = PP p a
  eval _ opts a = do
    let msg0 = "LogBase"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
         let hhs = [hh pp, hh qq]
         in if p <= 0 then mkNode opts (Fail (msg0 <> " non-positive base")) "" hhs
            else let d = logBase p q
                 in mkNode opts (Val d) (msg0 <> " " <> showL opts p <> " " <> showL opts q <> " = " <> showL opts d) hhs

class GetBinOp (k :: BinOp) where
  getBinOp :: (Num a, a ~ b) => (String, a -> b -> a)

instance GetBinOp 'BMult where
  getBinOp = ("*",(*))
instance GetBinOp 'BSub where
  getBinOp = ("-",(-))
instance GetBinOp 'BAdd where
  getBinOp = ("+",(+))

-- | addition, multiplication and subtraction
--
-- >>> pz @(Fst * Snd) (13,5)
-- Val 65
--
-- >>> pz @(Fst + 4 * Length Snd - 4) (3,"hello")
-- Val 19
--
data Bin (op :: BinOp) p q deriving Show

instance ( GetBinOp op
         , PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP p a)
         , Num (PP p a)
         ) => P (Bin op p q) a where
  type PP (Bin op p q) a = PP p a
  eval _ opts a = do
    let (s,f) = getBinOp @op
    lr <- runPQ NoInline s (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
        let d = p `f` q
        in mkNode opts (Val d) (showL opts p <> " " <> s <> " " <> showL opts q <> " = " <> showL opts d) [hh pp, hh qq]

-- | fractional division
--
-- >>> pz @(Fst / Snd) (13,2)
-- Val 6.5
--
-- >>> pz @(ToRational 13 / Id) 0
-- Fail "(/) zero denominator"
--
-- >>> pz @(12 % 7 / 14 % 5 + Id) 12.4
-- Val (3188 % 245)
--
data p / q deriving Show
infixl 7 /

instance ( PP p a ~ PP q a
         , Eq (PP q a)
         , P p a
         , P q a
         , Show (PP p a)
         , Fractional (PP p a)
         ) => P (p / q) a where
  type PP (p / q) a = PP p a
  eval _ opts a = do
    let msg0 = "(/)"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq)
         | q == 0 -> let msg1 = msg0 <> " zero denominator"
                     in mkNode opts (Fail msg1) "" [hh pp, hh qq]
         | otherwise ->
            let d = p / q
            in mkNode opts (Val d) (showL opts p <> " / " <> showL opts q <> " = " <> showL opts d) [hh pp, hh qq]

-- | divide for integrals
--
-- >>> pz @(On (+) (Id * Id) >> (Id ** (DivI Double 1 2))) (3,4)
-- Val 5.0
--
data DivI t p q deriving Show

type DivIT t p q = (p >> FromIntegral t) / (q >> FromIntegral t)

instance P (DivIT t p q) x => P (DivI t p q) x where
  type PP (DivI t p q) x = PP (DivIT t p q) x
  eval _ = eval (Proxy @(DivIT t p q))


-- | creates a 'Rational' value
--
-- >>> pz @(Id < 21 % 5) (-3.1)
-- Val True
--
-- >>> pz @(Id < 21 % 5) 4.5
-- Val False
--
-- >>> pz @(Fst % Snd) (13,2)
-- Val (13 % 2)
--
-- >>> pz @(13 % Id) 0
-- Fail "(%) zero denominator"
--
-- >>> pz @(4 % 3 + 5 % 7) "asfd"
-- Val (43 % 21)
--
-- >>> pz @(4 -% 7 * 5 -% 3) "asfd"
-- Val (20 % 21)
--
-- >>> pz @(Negate (14 % 3)) ()
-- Val ((-14) % 3)
--
-- >>> pz @(14 % 3) ()
-- Val (14 % 3)
--
-- >>> pz @(Negate (14 % 3) ==! Lift (FromIntegral _) (Negate 5)) ()
-- Val GT
--
-- >>> pz @(14 -% 3 ==! 5 -% 1) "aa"
-- Val GT
--
-- >>> pz @(Negate (14 % 3) ==! Negate 5 % 2) ()
-- Val LT
--
-- >>> pz @(14 -% 3 * 5 -% 1) ()
-- Val (70 % 3)
--
-- >>> pz @(14 % 3 ==! 5 % 1) ()
-- Val LT
--
-- >>> pz @(15 % 3 / 4 % 2) ()
-- Val (5 % 2)
--
data p % q deriving Show
infixl 8 %

instance ( Integral (PP p x)
         , Integral (PP q x)
         , Eq (PP q x)
         , P p x
         , P q x
         , Show (PP p x)
         , Show (PP q x)
         ) => P (p % q) x where
  type PP (p % q) x = Rational
  eval _ opts x = do
    let msg0 = "(%)"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts x []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq)
         | q == 0 -> let msg1 = msg0 <> " zero denominator"
                     in mkNode opts (Fail msg1) "" [hh pp, hh qq]
         | otherwise ->
            let z@(p1,q1) = (fromIntegral p, fromIntegral q)
                d@(dn :% dd) = uncurry (%) z
                zz = if dn == p1 && dd == q1 then ""
                     else litVerbose opts " | " (show p <> " % " <> show q)
            in mkNode opts (Val d) (showL opts d <> zz) [hh pp, hh qq]

-- | negate a ratio
--
-- >>> pl @'[1 % 1 ,3 -% 2,3 -% 1] ()
-- Present [1 % 1,(-3) % 2,(-3) % 1] ('[1 % 1,(-3) % 2,(-3) % 1] (1 % 1) | ())
-- Val [1 % 1,(-3) % 2,(-3) % 1]
--
-- >>> pl @('[1 % 1 ,Negate (33 % 7), 21 % 4,Signum (7 -% 5)] >> Map (Floor _)) ()
-- Present [1,-5,5,-1] ((>>) [1,-5,5,-1] | {Map [1,-5,5,-1] | [1 % 1,(-33) % 7,21 % 4,(-1) % 1]})
-- Val [1,-5,5,-1]
--
-- >>> pl @('[1 % 1 ,Negate (33 % 7), 21 % 4,Signum (7 -% 5)] >> Map (Ceiling _)) ()
-- Present [1,-4,6,-1] ((>>) [1,-4,6,-1] | {Map [1,-4,6,-1] | [1 % 1,(-33) % 7,21 % 4,(-1) % 1]})
-- Val [1,-4,6,-1]
--
-- >>> pl @('[1 % 1 ,Negate (33 % 7), 21 % 4,Signum (7 -% 5)] >> Map (Truncate _)) ()
-- Present [1,-4,5,-1] ((>>) [1,-4,5,-1] | {Map [1,-4,5,-1] | [1 % 1,(-33) % 7,21 % 4,(-1) % 1]})
-- Val [1,-4,5,-1]
--
-- >>> pl @(5 % 1 / 3 -% 1) 'x'
-- Present (-5) % 3 (5 % 1 / (-3) % 1 = (-5) % 3)
-- Val ((-5) % 3)
--
-- >>> pl @(5 -% 1 / Fst) (3,'x')
-- Present (-5) % 3 ((-5) % 1 / 3 % 1 = (-5) % 3)
-- Val ((-5) % 3)
--
data p -% q deriving Show
infixl 8 -%
type NegateRatioT p q = Negate (p % q)

instance P (NegateRatioT p q) x => P (p -% q) x where
  type PP (p -% q) x = PP (NegateRatioT p q) x
  eval _ = eval (Proxy @(NegateRatioT p q))


-- | similar to 'negate'
--
-- >>> pz @(Negate Id) 14
-- Val (-14)
--
-- >>> pz @(Negate (Fst * Snd)) (14,3)
-- Val (-42)
--
-- >>> pz @(Negate (15 -% 4)) "abc"
-- Val (15 % 4)
--
-- >>> pz @(Negate (15 % 3)) ()
-- Val ((-5) % 1)
--
-- >>> pz @(Negate (Fst % Snd)) (14,3)
-- Val ((-14) % 3)
--
data Negate p deriving Show

instance ( Num (PP p x)
         , P p x
         , Show (PP p x)
         ) => P (Negate p) x where
  type PP (Negate p) x = PP p x
  eval _ opts x = do
    let msg0 = "Negate"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let d = negate p
        in mkNode opts (Val d) (show3 opts msg0 d p) [hh pp]


-- | similar to 'abs'
--
-- >>> pz @(Abs Id) (-14)
-- Val 14
--
-- >>> pz @(Abs Snd) ("xx",14)
-- Val 14
--
-- >>> pz @(Abs Id) 0
-- Val 0
--
-- >>> pz @(Abs (Negate 44)) "aaa"
-- Val 44
--
data Abs p deriving Show

instance ( Num (PP p x)
         , P p x
         , Show (PP p x)
         ) => P (Abs p) x where
  type PP (Abs p) x = PP p x
  eval _ opts x = do
    let msg0 = "Abs"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let d = abs p
        in mkNode opts (Val d) (show3 opts msg0 d p) [hh pp]

-- | similar to 'div'
--
-- >>> pz @(Div Fst Snd) (10,4)
-- Val 2
--
-- >>> pz @(Div Fst Snd) (10,0)
-- Fail "Div zero denominator"
--
data Div p q deriving Show
instance ( PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP p a)
         , Integral (PP p a)
         ) => P (Div p q) a where
  type PP (Div p q) a = PP p a
  eval _ opts a = do
    let msg0 = "Div"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
         let hhs = [hh pp, hh qq]
         in case q of
              0 -> mkNode opts (Fail (msg0 <> " zero denominator")) "" hhs
              _ -> let d = p `div` q
                   in mkNode opts (Val d) (showL opts p <> " `div` " <> showL opts q <> " = " <> showL opts d) hhs


-- | similar to 'GHC.Real.mod'
--
-- >>> pz @(Mod Fst Snd) (10,3)
-- Val 1
--
-- >>> pz @(Mod Fst Snd) (10,0)
-- Fail "Mod zero denominator"
--
data Mod p q deriving Show
instance ( PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP p a)
         , Integral (PP p a)
         ) => P (Mod p q) a where
  type PP (Mod p q) a = PP p a
  eval _ opts a = do
    let msg0 = "Mod"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
         let hhs = [hh pp, hh qq]
         in case q of
              0 -> mkNode opts (Fail (msg0 <> " zero denominator")) "" hhs
              _ -> let d = p `mod` q
                   in mkNode opts (Val d) (showL opts p <> " `mod` " <> showL opts q <> " = " <> showL opts d) hhs

-- | similar to 'divMod'
--
-- >>> pz @(DivMod Fst Snd) (10,3)
-- Val (3,1)
--
-- >>> pz @(DivMod Fst Snd) (10,-3)
-- Val (-4,-2)
--
-- >>> pz @(DivMod Fst Snd) (-10,3)
-- Val (-4,2)
--
-- >>> pz @(DivMod Fst Snd) (-10,-3)
-- Val (3,-1)
--
-- >>> pz @(DivMod Fst Snd) (10,0)
-- Fail "DivMod zero denominator"
--
-- >>> pl @(DivMod (Negate Id) 7) 23
-- Present (-4,5) (-23 `divMod` 7 = (-4,5))
-- Val (-4,5)
--
-- >>> pl @(DivMod Fst Snd) (10,-3)
-- Present (-4,-2) (10 `divMod` -3 = (-4,-2))
-- Val (-4,-2)
--
-- >>> pl @(DivMod Fst Snd) (10,0)
-- Error DivMod zero denominator
-- Fail "DivMod zero denominator"
--
-- >>> pl @(DivMod (9 - Fst) (Snd >> Last)) (10,[12,13])
-- Present (-1,12) (-1 `divMod` 13 = (-1,12))
-- Val (-1,12)
--
data DivMod p q deriving Show

instance ( PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP p a)
         , Integral (PP p a)
         ) => P (DivMod p q) a where
  type PP (DivMod p q) a = (PP p a, PP p a)
  eval _ opts a = do
    let msg0 = "DivMod"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
        let hhs = [hh pp, hh qq]
        in case q of
             0 -> mkNode opts (Fail (msg0 <> " zero denominator")) "" hhs
             _ -> let d = p `divMod` q
                  in mkNode opts (Val d) (showL opts p <> " `divMod` " <> showL opts q <> " = " <> showL opts d) hhs

-- | similar to 'quotRem'
--
-- >>> pz @(QuotRem Fst Snd) (10,3)
-- Val (3,1)
--
-- >>> pz @(QuotRem Fst Snd) (10,-3)
-- Val (-3,1)
--
-- >>> pz @(QuotRem Fst Snd) (-10,-3)
-- Val (3,-1)
--
-- >>> pz @(QuotRem Fst Snd) (-10,3)
-- Val (-3,-1)
--
-- >>> pz @(QuotRem Fst Snd) (10,0)
-- Fail "QuotRem zero denominator"
--
-- >>> pl @(QuotRem (Negate Id) 7) 23
-- Present (-3,-2) (-23 `quotRem` 7 = (-3,-2))
-- Val (-3,-2)
--
-- >>> pl @(QuotRem Fst Snd) (10,-3)
-- Present (-3,1) (10 `quotRem` -3 = (-3,1))
-- Val (-3,1)
--
data QuotRem p q deriving Show

instance ( PP p a ~ PP q a
         , P p a
         , P q a
         , Show (PP p a)
         , Integral (PP p a)
         ) => P (QuotRem p q) a where
  type PP (QuotRem p q) a = (PP p a, PP p a)
  eval _ opts a = do
    let msg0 = "QuotRem"
    lr <- runPQ NoInline msg0 (Proxy @p) (Proxy @q) opts a []
    pure $ case lr of
      Left e -> e
      Right (p,q,pp,qq) ->
        let hhs = [hh pp, hh qq]
        in case q of
             0 -> mkNode opts (Fail (msg0 <> " zero denominator")) "" hhs
             _ -> let d = p `quotRem` q
                  in mkNode opts (Val d) (showL opts p <> " `quotRem` " <> showL opts q <> " = " <> showL opts d) hhs

-- | similar to 'quot'
data Quot p q deriving Show
type QuotT p q = QuotRem p q >> Fst

instance P (QuotT p q) x => P (Quot p q) x where
  type PP (Quot p q) x = PP (QuotT p q) x
  eval _ = eval (Proxy @(QuotT p q))

-- | similar to 'rem'
data Rem p q deriving Show
type RemT p q = QuotRem p q >> Snd

instance P (RemT p q) x => P (Rem p q) x where
  type PP (Rem p q) x = PP (RemT p q) x
  eval _ = eval (Proxy @(RemT p q))

-- | similar to 'even'
--
-- >>> pz @(Map Even) [9,-4,12,1,2,3]
-- Val [False,True,True,False,True,False]
--
-- >>> pz @(Map '(Even,Odd)) [9,-4,12,1,2,3]
-- Val [(False,True),(True,False),(True,False),(False,True),(True,False),(False,True)]
--
data Even deriving Show
type EvenT = Mod Id 2 == 0

instance P EvenT x => P Even x where
  type PP Even x = Bool
  eval _ = evalBool (Proxy @EvenT)

-- | similar to 'odd'
data Odd deriving Show
type OddT = Mod Id 2 == 1

instance P OddT x => P Odd x where
  type PP Odd x = Bool
  eval _ = evalBool (Proxy @OddT)

-- | similar to 'signum'
--
-- >>> pz @(Signum Id) (-14)
-- Val (-1)
--
-- >>> pz @(Signum Id) 14
-- Val 1
--
-- >>> pz @(Signum Id) 0
-- Val 0
--
data Signum p deriving Show

instance ( Num (PP p x)
         , P p x
         , Show (PP p x)
         ) => P (Signum p) x where
  type PP (Signum p) x = PP p x
  eval _ opts x = do
    let msg0 = "Signum"
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let d = signum p
        in mkNode opts (Val d) (show3 opts msg0 d p) [hh pp]

-- supports negative numbers unlike readInt
-- | Read a number using base 2 through a maximum of 36 with @t@ a reference to a type
data ReadBase' t (n :: Nat) p deriving Show

instance ( Typeable (PP t x)
         , BetweenT "ReadBase'" 2 36 n
         , Show (PP t x)
         , Num (PP t x)
         , KnownNat n
         , PP p x ~ String
         , P p x
         ) => P (ReadBase' t n p) x where
  type PP (ReadBase' t n p) x = PP t x
  eval _ opts x = do
    let n = nat @n
        xs = getValidBase n
        msg0 = "ReadBase(" <> t <> "," <> show n <> ")"
        t = showT @(PP t x)
    pp <- eval (Proxy @p) opts x
    pure $ case getValueLR NoInline opts msg0 pp [] of
      Left e -> e
      Right p ->
        let (ff,p1) = case p of
                        '-':q -> (negate,q)
                        _ -> (id,p)
        in case Numeric.readInt (fromIntegral n)
            ((`elem` xs) . toLower)
            (Safe.fromJustNote "ReadBase" . (`elemIndex` xs) . toLower)
            p1 of
             [(b,"")] -> mkNode opts (Val (ff b)) (msg0 <> " " <> showL opts (ff b) <> showVerbose opts " | " p) [hh pp]
             o -> mkNode opts (Fail ("invalid base " <> show n)) (msg0 <> " as=" <> p <> " err=" <> showL opts o) [hh pp]

-- | Read a number using base 2 through a maximum of 36
--
-- >>> pz @(ReadBase Int 16) "00feD"
-- Val 4077
--
-- >>> pz @(ReadBase Int 16) "-ff"
-- Val (-255)
--
-- >>> pz @(ReadBase Int 2) "10010011"
-- Val 147
--
-- >>> pz @(ReadBase Int 8) "Abff"
-- Fail "invalid base 8"
--
-- >>> pl @(ReadBase Int 16 >> GuardSimple (Id > 0xffff) >> ShowBase 16) "12344"
-- Present "12344" ((>>) "12344" | {ShowBase(16) 12344 | 74564})
-- Val "12344"
--
-- >>> :set -XBinaryLiterals
-- >>> pz @(ReadBase Int 16 >> GuardSimple (Id > 0b10011111) >> ShowBase 16) "7f"
-- Fail "(127 > 159)"
--
-- >>> pl @(ReadBase Int 16) "fFe0"
-- Present 65504 (ReadBase(Int,16) 65504 | "fFe0")
-- Val 65504
--
-- >>> pl @(ReadBase Int 16) "-ff"
-- Present -255 (ReadBase(Int,16) -255 | "-ff")
-- Val (-255)
--
-- >>> pl @(ReadBase Int 16) "ff"
-- Present 255 (ReadBase(Int,16) 255 | "ff")
-- Val 255
--
-- >>> pl @(ReadBase Int 22) "zzz"
-- Error invalid base 22 (ReadBase(Int,22) as=zzz err=[])
-- Fail "invalid base 22"
--
-- >>> pl @((ReadBase Int 16 &&& Id) >> First (ShowBase 16)) "fFe0"
-- Present ("ffe0","fFe0") ((>>) ("ffe0","fFe0") | {(***) ("ffe0","fFe0") | (65504,"fFe0")})
-- Val ("ffe0","fFe0")
--
-- >>> pl @(ReadBase Int 2) "101111"
-- Present 47 (ReadBase(Int,2) 47 | "101111")
-- Val 47
--
data ReadBase (t :: Type) (n :: Nat) deriving Show
type ReadBaseT (t :: Type) (n :: Nat) = ReadBase' (Hole t) n Id

instance P (ReadBaseT t n) x => P (ReadBase t n) x where
  type PP (ReadBase t n) x = PP (ReadBaseT t n) x
  eval _ = eval (Proxy @(ReadBaseT t n))

getValidBase :: Int -> String
getValidBase n =
  let xs = ['0'..'9'] <> ['a'..'z']
      len = length xs
  in if n > len || n < 2
     then errorInProgram $ "getValidBase: oops invalid base valid is 2 thru " ++ show len ++ " found " ++ show n
     else take n xs

-- | Display a number at base 2 to 36, similar to 'Numeric.showIntAtBase' but passes the sign through
--
-- >>> pz @(ShowBase 16) 4077
-- Val "fed"
--
-- >>> pz @(ShowBase 16) (-255)
-- Val "-ff"
--
-- >>> pz @(ShowBase 2) 147
-- Val "10010011"
--
-- >>> pz @(Lift (ShowBase 2) (Negate 147)) "whatever"
-- Val "-10010011"
--
-- >>> pl @(ShowBase 16) (-123)
-- Present "-7b" (ShowBase(16) -7b | -123)
-- Val "-7b"
--
-- >>> pl @(ShowBase 16) 123
-- Present "7b" (ShowBase(16) 7b | 123)
-- Val "7b"
--
-- >>> pl @(ShowBase 16) 65504
-- Present "ffe0" (ShowBase(16) ffe0 | 65504)
-- Val "ffe0"
--
data ShowBase (n :: Nat) deriving Show

instance ( BetweenT "ShowBase" 2 36 n
         , KnownNat n
         , Integral x
         ) => P (ShowBase n) x where
  type PP (ShowBase n) x = String
  eval _ opts x =
    let n = nat @n
        xs = getValidBase n
        msg0 = "ShowBase(" <> show n <> ")"
        p :: Integer
        p = fromIntegral x
        (ff,a') = if p < 0 then (('-':), abs p) else (id,p)
        b = Numeric.showIntAtBase (fromIntegral n) (Safe.atNote "ShowBase out of range" xs) a' ""
    in pure $ mkNode opts (Val (ff b)) (msg0 <> " " <> litL opts (ff b) <> showVerbose opts " | " p) []

-- | Display a number at base >= 2 but just show as a list of ints: ignores the sign
--
-- >>> pl @(ShowBaseN 16 Id) (256*256*2+256*14+16*7+11)
-- Present [2,0,14,7,11] (ShowBaseN | 16 | 134779)
-- Val [2,0,14,7,11]
--
data ShowBaseN n p deriving Show

instance ( PP p x ~ a
         , P p x
         , PP n x ~ b
         , P n x
         , Integral a
         , Integral b
         ) => P (ShowBaseN n p) x where
  type PP (ShowBaseN n p) x = [Int]
  eval _ opts x = do
    let msg0 = "ShowBaseN"
    lr <- runPQ NoInline msg0 (Proxy @n) (Proxy @p) opts x []
    pure $ case lr of
      Left e -> e
      Right (fromIntegral -> n,fromIntegral -> p,nn,pp) ->
         let hhs = [hh nn, hh pp]
         in if n < 2 then mkNode opts (Fail (msg0 <> " base must be greater than 1")) "" hhs
            else let xs = fix (\f s -> if s<1 then id else let (a,b) = divMod s n in f a . (b:)) (abs p) []
                 in mkNode opts (Val xs) (msg0 <> showVerbose opts " | " n <> showVerbose opts " | " p) hhs

-- | convert to bits
--
-- >>> pl @(ToBits 123 >> UnShowBaseN 2) ()
-- Present 123 ((>>) 123 | {UnShowBaseN | 2 | [1,1,1,1,0,1,1]})
-- Val 123
--
data ToBits p deriving Show
type ToBitsT p = ShowBaseN 2 p

instance P (ToBitsT p) x => P (ToBits p) x where
  type PP (ToBits p) x = PP (ToBitsT p) x
  eval _ = eval (Proxy @(ToBitsT p))

-- | reverse 'ShowBaseN'
--
-- >>> pz @(UnShowBaseN 2) [1,0,0,1,0]
-- Val 18
--
-- >>> pz @(UnShowBaseN 2) [1,1,1]
-- Val 7
--
-- >>> pz @(UnShowBaseN 16) [7,0,3,1]
-- Val 28721
--
-- >>> pz @(UnShowBaseN 16) [0]
-- Val 0
--
-- >>> pz @(UnShowBaseN 16) []
-- Val 0
--
data UnShowBaseN n deriving Show

instance ( x ~ [a]
         , PP n x ~ b
         , P n x
         , Integral a
         , Integral b
         ) => P (UnShowBaseN n) x where
  type PP (UnShowBaseN n) x = Integer
  eval _ opts x = do
    let msg0 = "UnShowBaseN"
    nn <- eval (Proxy @n) opts x
    pure $ case getValueLR NoInline opts msg0 nn [] of
      Left e -> e
      Right (fromIntegral -> n) ->
         let xs = map fromIntegral x
             hhs = [hh nn]
         in if n < 2 then mkNode opts (Fail (msg0 <> " base must be greater than 1")) "" hhs
            else let b = snd $ foldr (\a (m,tot) -> (m*n, a*m+tot)) (1,0) xs
                 in mkNode opts (Val b) (msg0 <> showVerbose opts " | " n <> showVerbose opts " | " xs) hhs


-- | calculate the amount to roundup to the next @n@
--
-- >>> pl @(RoundUp Fst Snd) (3,9)
-- Present 0 (RoundUp 3 9 = 0)
-- Val 0
--
-- >>> pl @(RoundUp Fst Snd) (3,10)
-- Present 2 (RoundUp 3 10 = 2)
-- Val 2
--
-- >>> pl @(RoundUp Fst Snd) (3,11)
-- Present 1 (RoundUp 3 11 = 1)
-- Val 1
--
-- >>> pl @(RoundUp Fst Snd) (3,12)
-- Present 0 (RoundUp 3 12 = 0)
-- Val 0
--
-- >>> pl @(RoundUp 3 0) ()
-- Present 0 (RoundUp 3 0 = 0)
-- Val 0
--
-- >>> pl @(RoundUp 0 10) ()
-- Error RoundUp 'n' cannot be zero
-- Fail "RoundUp 'n' cannot be zero"
--
data RoundUp n p deriving Show

instance ( Integral (PP n x)
         , Show (PP n x)
         , PP n x ~ PP p x
         , P n x
         , P p x
         ) => P (RoundUp n p) x where
  type PP (RoundUp n p) x = PP n x
  eval _ opts x = do
    let msg0 = "RoundUp"
    lr <- runPQ NoInline msg0 (Proxy @n) (Proxy @p) opts x []
    pure $ case lr of
      Left e -> e
      Right (n,p,nn,pp) ->
         let hhs = [hh nn, hh pp]
             d = (n-p) `mod` n
         in if n == 0 then mkNode opts (Fail (msg0 <> " 'n' cannot be zero")) "" hhs
            else mkNode opts (Val d) (msg0 <> " " <> show n <> " " <> show p <> " = " <> show d) hhs