module Enum (toEnumMay, fromEnum, predMay, succMay, compareEnum) where

import Prelude hiding (filter, fromEnum, head, tail)
import qualified Prelude

import Control.Monad
import Control.Exception
import Data.List (genericLength)
import System.IO.Unsafe

toEnumMay :: Enum a => Integer -> Maybe a
toEnumMay n
  | n < minInt, Just lower <- lowerEdgeMay, Just lower' <- predMay lower = [lower, lower'..] !!? (minInt - n)
  | n > maxInt, Just upper <- upperEdgeMay = [upper..] !!? (n - maxInt)
  | otherwise = (opMay toEnum <=< toIntMay) n

toIntMay :: Integer -> Maybe Int
toIntMay n = n' <$ guard (n == toInteger n') where n' = fromIntegral n

fromEnum :: Enum a => a -> Integer
fromEnum a
  | Just edge <- lowerEdgeMay, l <- genericLength [a..edge] - 1, l >= 0 = minInt - l
  | Just edge <- upperEdgeMay, l <- genericLength [edge..a] - 1, l >= 0 = maxInt + l
  | otherwise = (toInteger . Prelude.fromEnum) a

minInt, maxInt :: Integer
minInt = toEnum minBound
maxInt = toEnum maxBound

lowerEdgeMay, upperEdgeMay :: Enum a => Maybe a
lowerEdgeMay = opMay Prelude.toEnum minBound
upperEdgeMay = opMay Prelude.toEnum maxBound

compareEnum :: Enum a => a -> a -> Ordering
compareEnum a b
  | _:_ <- enumFromTo a b = LT
  | _:_ <- enumFromTo b a = GT
  | otherwise = EQ

predMay, succMay :: Enum a => a -> Maybe a
predMay = opMay pred
succMay = opMay succ

opMay :: (a -> b) -> a -> Maybe b
opMay f a = unsafePerformIO $ (Just <$> evaluate (f a)) `catches` handlers Nothing

handlers :: a -> [Handler a]
handlers a = [Handler $ \ (_ :: ArithException) -> pure a,
              Handler $ \ (_ :: ArrayException) -> pure a,
              Handler $ \ (_ :: AssertionFailed) -> pure a,
              Handler $ \ (_ :: NonTermination) -> pure a,
              Handler $ \ (_ :: NoMethodError) -> pure a,
              Handler $ \ (_ :: PatternMatchFail) -> pure a,
              Handler $ \ (_ :: RecConError) -> pure a,
              Handler $ \ (_ :: RecSelError) -> pure a,
              Handler $ \ (_ :: RecUpdError) -> pure a,
              Handler $ \ (_ :: ErrorCall) -> pure a]

(!!?) :: [a] -> Integer -> Maybe a
[] !!? _ = Nothing
(x:_) !!? 0 = Just x
(_:xs) !!? n = xs !!? (n-1)