{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -fno-omit-interface-pragmas #-}
module PlutusTx.Enum (Enum(..)) where

import PlutusTx.Bool (Bool (..), otherwise)
import PlutusTx.Builtins
import PlutusTx.Eq ((==))
import PlutusTx.ErrorCodes
import PlutusTx.List
import PlutusTx.Ord (Ord (..), Ordering (..))
import PlutusTx.Trace

-- | Class 'Enum' defines operations on sequentially ordered types.
class Enum a where
  -- | The successor of a value.  For numeric types, 'succ' adds 1.
  --
  -- For types that implement 'Ord', @succ x@ should be the least element
  -- that is greater than @x@, and 'error' if there is none.
  succ :: a -> a
  -- | The predecessor of a value.  For numeric types, 'pred' subtracts 1.
  --
  -- For types that implement 'Ord', @pred x@ should be the greatest element
  -- that is less than @x@, and 'error' if there is none.
  pred :: a -> a
  -- | Convert from an 'Integer'.
  toEnum :: Integer -> a
  -- | Convert to an 'Integer'.
  fromEnum :: a -> Integer
  -- | Construct a list from the given range (corresponds to [a..b]).
  enumFromTo :: a -> a -> [a]
  -- | Construct a list from the given range (corresponds to [a,b..c]).  This
  -- has the same semantics as the Haskell version,so if a==b and c>=b then you
  -- get an infinite list, which you probably don't want in Plutus Core.
  enumFromThenTo :: a -> a -> a -> [a]

instance Enum Integer where
  {-# INLINABLE succ #-}
  succ :: Integer -> Integer
succ Integer
x = Integer -> Integer -> Integer
addInteger Integer
x Integer
1

  {-# INLINABLE pred #-}
  pred :: Integer -> Integer
pred Integer
x = Integer -> Integer -> Integer
subtractInteger Integer
x Integer
1

  {-# INLINABLE toEnum #-}
  toEnum :: Integer -> Integer
toEnum Integer
x = Integer
x

  {-# INLINABLE fromEnum #-}
  fromEnum :: Integer -> Integer
fromEnum Integer
x = Integer
x

  {-# INLINABLE enumFromTo #-}
  enumFromTo :: Integer -> Integer -> [Integer]
enumFromTo Integer
x Integer
lim
    | Integer
x Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
lim = []
    | Bool
otherwise = Integer
x Integer -> [Integer] -> [Integer]
forall a. a -> [a] -> [a]
: Integer -> Integer -> [Integer]
forall a. Enum a => a -> a -> [a]
enumFromTo (Integer -> Integer
forall a. Enum a => a -> a
succ Integer
x) Integer
lim

  {-# INLINABLE enumFromThenTo #-}
  enumFromThenTo :: Integer -> Integer -> Integer -> [Integer]
enumFromThenTo Integer
x Integer
y Integer
lim =
      if Integer
delta Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0
      then Integer -> [Integer]
up_list Integer
x
      else Integer -> [Integer]
dn_list Integer
x
          where delta :: Integer
delta = Integer -> Integer -> Integer
subtractInteger Integer
y Integer
x
                up_list :: Integer -> [Integer]
up_list Integer
x1 =
                    if Integer
x1 Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
lim
                    then []
                    else Integer
x1 Integer -> [Integer] -> [Integer]
forall a. a -> [a] -> [a]
: Integer -> [Integer]
up_list (Integer -> Integer -> Integer
addInteger Integer
x1 Integer
delta)
                dn_list :: Integer -> [Integer]
dn_list Integer
x1 =
                    if Integer
x1 Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Integer
lim
                    then []
                    else Integer
x1 Integer -> [Integer] -> [Integer]
forall a. a -> [a] -> [a]
: Integer -> [Integer]
dn_list (Integer -> Integer -> Integer
addInteger Integer
x1 Integer
delta)

instance Enum () where
  {-# INLINABLE succ #-}
  succ :: () -> ()
succ ()
_ = BuiltinString -> ()
forall a. BuiltinString -> a
traceError BuiltinString
succVoidBadArgumentError

  {-# INLINABLE pred #-}
  pred :: () -> ()
pred ()
_ = BuiltinString -> ()
forall a. BuiltinString -> a
traceError BuiltinString
predVoidBadArgumentError

  {-# INLINABLE toEnum #-}
  toEnum :: Integer -> ()
toEnum Integer
x | Integer
x Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0 = ()
           | Bool
otherwise = BuiltinString -> ()
forall a. BuiltinString -> a
traceError BuiltinString
toEnumVoidBadArgumentError

  {-# INLINABLE fromEnum #-}
  fromEnum :: () -> Integer
fromEnum () = Integer
0

  {-# INLINABLE enumFromTo #-}
  enumFromTo :: () -> () -> [()]
enumFromTo ()
_ ()
_ = [()]

  {-# INLINABLE enumFromThenTo #-}
  -- enumFromThenTo () () () is an infinite list of ()'s, so this isn't too useful.
  enumFromThenTo :: () -> () -> () -> [()]
enumFromThenTo ()
x ()
y ()
lim = (Integer -> ()) -> [Integer] -> [()]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> ()
forall a. Enum a => Integer -> a
toEnum (Integer -> Integer -> Integer -> [Integer]
forall a. Enum a => a -> a -> a -> [a]
enumFromThenTo (() -> Integer
forall a. Enum a => a -> Integer
fromEnum ()
x) (() -> Integer
forall a. Enum a => a -> Integer
fromEnum ()
y) (() -> Integer
forall a. Enum a => a -> Integer
fromEnum ()
lim))

instance Enum Bool where
  {-# INLINABLE succ #-}
  succ :: Bool -> Bool
succ Bool
False = Bool
True
  succ Bool
True  = BuiltinString -> Bool
forall a. BuiltinString -> a
traceError BuiltinString
succBoolBadArgumentError

  {-# INLINABLE pred #-}
  pred :: Bool -> Bool
pred Bool
True  = Bool
False
  pred Bool
False = BuiltinString -> Bool
forall a. BuiltinString -> a
traceError BuiltinString
predBoolBadArgumentError

  {-# INLINABLE toEnum #-}
  toEnum :: Integer -> Bool
toEnum Integer
n | Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0    = Bool
False
           | Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
1    = Bool
True
           | Bool
otherwise = BuiltinString -> Bool
forall a. BuiltinString -> a
traceError BuiltinString
toEnumBoolBadArgumentError

  {-# INLINABLE fromEnum #-}
  fromEnum :: Bool -> Integer
fromEnum Bool
False = Integer
0
  fromEnum Bool
True  = Integer
1

  {-# INLINABLE enumFromTo #-}
  enumFromTo :: Bool -> Bool -> [Bool]
enumFromTo Bool
x Bool
lim = (Integer -> Bool) -> [Integer] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> Bool
forall a. Enum a => Integer -> a
toEnum (Integer -> Integer -> [Integer]
forall a. Enum a => a -> a -> [a]
enumFromTo (Bool -> Integer
forall a. Enum a => a -> Integer
fromEnum Bool
x) (Bool -> Integer
forall a. Enum a => a -> Integer
fromEnum Bool
lim))

  {-# INLINABLE enumFromThenTo #-}
  enumFromThenTo :: Bool -> Bool -> Bool -> [Bool]
enumFromThenTo Bool
x Bool
y Bool
lim = (Integer -> Bool) -> [Integer] -> [Bool]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> Bool
forall a. Enum a => Integer -> a
toEnum (Integer -> Integer -> Integer -> [Integer]
forall a. Enum a => a -> a -> a -> [a]
enumFromThenTo (Bool -> Integer
forall a. Enum a => a -> Integer
fromEnum Bool
x) (Bool -> Integer
forall a. Enum a => a -> Integer
fromEnum Bool
y) (Bool -> Integer
forall a. Enum a => a -> Integer
fromEnum Bool
lim))

instance Enum Ordering where
  {-# INLINABLE succ #-}
  succ :: Ordering -> Ordering
succ Ordering
LT = Ordering
EQ
  succ Ordering
EQ = Ordering
GT
  succ Ordering
GT = BuiltinString -> Ordering
forall a. BuiltinString -> a
traceError BuiltinString
succOrderingBadArgumentError

  {-# INLINABLE pred #-}
  pred :: Ordering -> Ordering
pred Ordering
GT = Ordering
EQ
  pred Ordering
EQ = Ordering
LT
  pred Ordering
LT = BuiltinString -> Ordering
forall a. BuiltinString -> a
traceError BuiltinString
predOrderingBadArgumentError

  {-# INLINABLE toEnum #-}
  toEnum :: Integer -> Ordering
toEnum Integer
n | Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0 = Ordering
LT
           | Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
1 = Ordering
EQ
           | Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
2 = Ordering
GT
  toEnum Integer
_ = BuiltinString -> Ordering
forall a. BuiltinString -> a
traceError BuiltinString
toEnumOrderingBadArgumentError

  {-# INLINABLE fromEnum #-}
  fromEnum :: Ordering -> Integer
fromEnum Ordering
LT = Integer
0
  fromEnum Ordering
EQ = Integer
1
  fromEnum Ordering
GT = Integer
2

  {-# INLINABLE enumFromTo #-}
  enumFromTo :: Ordering -> Ordering -> [Ordering]
enumFromTo Ordering
x Ordering
y = (Integer -> Ordering) -> [Integer] -> [Ordering]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> Ordering
forall a. Enum a => Integer -> a
toEnum (Integer -> Integer -> [Integer]
forall a. Enum a => a -> a -> [a]
enumFromTo (Ordering -> Integer
forall a. Enum a => a -> Integer
fromEnum Ordering
x) (Ordering -> Integer
forall a. Enum a => a -> Integer
fromEnum Ordering
y))

  {-# INLINABLE enumFromThenTo #-}
  enumFromThenTo :: Ordering -> Ordering -> Ordering -> [Ordering]
enumFromThenTo Ordering
x Ordering
y Ordering
lim = (Integer -> Ordering) -> [Integer] -> [Ordering]
forall a b. (a -> b) -> [a] -> [b]
map Integer -> Ordering
forall a. Enum a => Integer -> a
toEnum (Integer -> Integer -> Integer -> [Integer]
forall a. Enum a => a -> a -> a -> [a]
enumFromThenTo (Ordering -> Integer
forall a. Enum a => a -> Integer
fromEnum Ordering
x) (Ordering -> Integer
forall a. Enum a => a -> Integer
fromEnum Ordering
y) (Ordering -> Integer
forall a. Enum a => a -> Integer
fromEnum Ordering
lim))