{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeOperators #-}

{-# OPTIONS_GHC -fno-warn-missing-methods #-}
{-# OPTIONS_GHC -Wall #-}

module Data.Discrete
  ( Discrete(..)
  ) where

import Control.Applicative ((<$>))
import Data.Either
import Data.Maybe
import Data.Monoid
import Data.Functor.Const
import Data.Functor.Identity
import Data.Type.Equality
import GHC.Base
import GHC.Enum (Bounded(..))
import GHC.Int
import GHC.Num
import GHC.Real
import GHC.Word
import Control.Monad ((>=>))

-- | A 'Discrete' type is a set X with at least one element, along with two
--   functions, @'succ' :: X -> 'Maybe' X@ and @'pred' :: X -> 'Maybe' X@,
--   such that all inhabitants of the set X can be constructed given at least
--   a single element of the set and these two functions. The following must hold:
--   
--   @'pred' '>=>' 'succ' '>=>' 'pred' = 'pred'@
--
--   @'succ' '>=>' 'pred' '>=>' 'succ' = 'succ'@ 
--
--   This means that 'Int' is a discrete type, because given any x :: 'Int', one
--   can construct any other 'Int' with the following functions:
-- 
--     @'succ' x = if x '==' 'maxBound' then 'Nothing' else 'Just' (x '+' 1)@
--
--     @'pred' x = if x '==' 'minBound' then 'Nothing' else 'Just' (x '-' 1)@
--
--   This also means that something like 'Double' is /not/ a discrete type, because
--   there are no such functions 'succ' and 'pred' that given any value of type 'Double'
--   can allow the construction of all values of type 'Double'.
--
--   'Discrete' acts as a replacement for 'GHC.Enum.Enum'. The motivation for
--   'Discrete' is two-fold: firstly, totality of all typeclass instances, and
--   secondly, that 'GHC.Enum.Enum' is a typeclass that does too many things,
--   and does them poorly. If 'succ' or 'pred' are called on 'maxBound'
--   or 'minBound', respectively, the result will be 'Nothing'.
class Discrete a where
  {-# MINIMAL pred, succ #-}
  succ :: a -> Maybe a
  pred :: a -> Maybe a

deriving newtype instance Discrete (f a) => Discrete (Alt f a)
deriving newtype instance Discrete a => Discrete (Identity a)
deriving newtype instance Discrete a => Discrete (Const a b)

instance Discrete a => Discrete (Maybe a) where
  succ Nothing  = Nothing
  succ (Just x) = Just (succ x)
  pred Nothing  = Nothing
  pred (Just x) = Just (pred x)

instance Integral a => Discrete (Ratio a) where
  {-# SPECIALIZE instance Discrete Rational #-}
  succ x = Just $ x + 1
  pred x = Just $ x - 1

instance a ~ b => Discrete (a :~: b) where
  succ _ = Just Refl
  pred _ = Just Refl

instance a ~~ b => Discrete (a :~~: b) where
  succ _ = Just HRefl
  pred _ = Just HRefl

instance Discrete () where
  succ _ = Just ()
  pred _ = Just ()

instance (Bounded b, Discrete a, Discrete b) => Discrete (a,b) where
  succ (a,b) = maybe (flip (,) minBound <$> succ a) (Just . (,) a) (succ b)
  pred (a,b) = maybe (flip (,) maxBound <$> pred a) (Just . (,) a) (pred b)

instance (Bounded a, Bounded b, Discrete a, Discrete b) => Discrete (Either a b) where
  succ (Left a) = maybe (Just $ Right minBound) (Just . Left) (succ a)
  succ (Right b) = maybe (Nothing) (Just . Right) (succ b)

  pred (Left a) = maybe (Nothing) (Just . Left) (pred a)
  pred (Right b) = maybe (Just $ Left maxBound) (Just . Right) (pred b)

instance Discrete Bool where
  succ False = Just True
  succ _     = Nothing

  pred True  = Just False
  pred _     = Nothing

instance Discrete Ordering where
  succ LT = Just EQ
  succ EQ = Just GT
  succ GT = Nothing

  pred GT = Just EQ
  pred EQ = Just LT
  pred LT = Nothing

instance Discrete Char where
  succ (C# c#)
    | isTrue# (ord# c# /=# 0x10FFFF#) = Just $ C# (chr# (ord# c# +# 1#))
    | otherwise = Nothing
  pred (C# c#)
    | isTrue# (ord# c# /=# 0#) = Just $ C# (chr# (ord# c# -# 1#))
    | otherwise = Nothing

instance Discrete Integer where
  succ x = Just $ x + 1
  pred x = Just $ x - 1

instance Discrete Int where
  succ x
    | x == maxBound = Nothing
    | otherwise     = Just $ x + 1

  pred x
    | x == minBound = Nothing
    | otherwise     = Just $ x - 1

instance Discrete Int8 where
  succ x
    | x == maxBound = Nothing
    | otherwise     = Just $ x + 1

  pred x
    | x == minBound = Nothing
    | otherwise     = Just $ x - 1

instance Discrete Int16 where
  succ x
    | x == maxBound = Nothing
    | otherwise     = Just $ x + 1

  pred x
    | x == minBound = Nothing
    | otherwise     = Just $ x - 1

instance Discrete Int32 where
  succ x
    | x == maxBound = Nothing
    | otherwise     = Just $ x + 1

  pred x
    | x == minBound = Nothing
    | otherwise     = Just $ x - 1

instance Discrete Int64 where
  succ x
    | x == maxBound = Nothing
    | otherwise     = Just $ x + 1

  pred x
    | x == minBound = Nothing
    | otherwise     = Just $ x - 1

instance Discrete Word where
  succ x
    | x /= maxBound = Just $ x + 1
    | otherwise     = Nothing
  pred x
    | x /= minBound = Just $ x - 1
    | otherwise     = Nothing

instance Discrete Word8 where
  succ x
    | x /= maxBound = Just $ x + 1
    | otherwise     = Nothing
  pred x
    | x /= minBound = Just $ x - 1
    | otherwise     = Nothing

instance Discrete Word16 where
  succ x
    | x /= maxBound = Just $ x + 1
    | otherwise     = Nothing
  pred x
    | x /= minBound = Just $ x - 1
    | otherwise     = Nothing

instance Discrete Word32 where
  succ x
    | x /= maxBound = Just $ x + 1
    | otherwise     = Nothing
  pred x
    | x /= minBound = Just $ x - 1
    | otherwise     = Nothing

instance Discrete Word64 where
  succ x
    | x /= maxBound = Just $ x + 1
    | otherwise     = Nothing
  pred x
    | x /= minBound = Just $ x - 1
    | otherwise     = Nothing