-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
-- SPDX-License-Identifier: MIT

module Must (Must (..), inRange, exceedsUpperBound) where

import Data.List (isInfixOf)
import Text.Read (readMaybe)

data Must
  = MtDisabled
  | MtExact Integer
  | MtRange (Maybe Integer) (Maybe Integer)
  deriving (Must -> Must -> Bool
(Must -> Must -> Bool) -> (Must -> Must -> Bool) -> Eq Must
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Must -> Must -> Bool
== :: Must -> Must -> Bool
$c/= :: Must -> Must -> Bool
/= :: Must -> Must -> Bool
Eq)

instance Show Must where
  show :: Must -> String
show Must
MtDisabled = String
"0"
  show (MtExact Integer
n) = Integer -> String
forall a. Show a => a -> String
show Integer
n
  show (MtRange Maybe Integer
Nothing Maybe Integer
Nothing) = String
".."
  show (MtRange Maybe Integer
Nothing (Just Integer
max)) = String
".." String -> ShowS
forall a. [a] -> [a] -> [a]
++ Integer -> String
forall a. Show a => a -> String
show Integer
max
  show (MtRange (Just Integer
min) Maybe Integer
Nothing) = Integer -> String
forall a. Show a => a -> String
show Integer
min String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
".."
  show (MtRange (Just Integer
min) (Just Integer
max)) = Integer -> String
forall a. Show a => a -> String
show Integer
min String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
".." String -> ShowS
forall a. [a] -> [a] -> [a]
++ Integer -> String
forall a. Show a => a -> String
show Integer
max

instance Read Must where
  readsPrec :: Int -> ReadS Must
readsPrec Int
_ String
"0" = [(Must
MtDisabled, String
"")]
  readsPrec Int
_ String
s
    | String
".." String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isInfixOf` String
s = ReadS Must
parseRange String
s
    | Bool
otherwise = ReadS Must
parseExact String
s
    where
      parseRange :: String -> [(Must, String)]
      parseRange :: ReadS Must
parseRange String
str = case (Char -> Bool) -> String -> (String, String)
forall a. (a -> Bool) -> [a] -> ([a], [a])
break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') String
str of
        (String
minStr, Char
'.' : Char
'.' : String
maxStr) ->
          let minPart :: Maybe Integer
minPart = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
minStr then Maybe Integer
forall a. Maybe a
Nothing else String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe String
minStr
              maxPart :: Maybe Integer
maxPart = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
maxStr then Maybe Integer
forall a. Maybe a
Nothing else String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe String
maxStr
           in case (Maybe Integer
minPart, Maybe Integer
maxPart, String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
minStr, String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
maxStr) of
                (Maybe Integer
Nothing, Maybe Integer
Nothing, Bool
False, Bool
False) -> [] -- Invalid range: non-numeric values
                (Maybe Integer
Nothing, Maybe Integer
Nothing, Bool
True, Bool
True) -> [] -- Invalid range: empty range '..'
                (Maybe Integer
Nothing, Just Integer
max, Bool
True, Bool
False) ->
                  [(Maybe Integer -> Maybe Integer -> Must
MtRange Maybe Integer
forall a. Maybe a
Nothing (Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
max), String
"") | Integer
max Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0]
                (Just Integer
min, Maybe Integer
Nothing, Bool
False, Bool
True) ->
                  [(Maybe Integer -> Maybe Integer -> Must
MtRange (Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
min) Maybe Integer
forall a. Maybe a
Nothing, String
"") | Integer
min Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0]
                (Just Integer
min, Just Integer
max, Bool
False, Bool
False) ->
                  [(Maybe Integer -> Maybe Integer -> Must
MtRange (Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
min) (Integer -> Maybe Integer
forall a. a -> Maybe a
Just Integer
max), String
"") | Integer
min Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0 Bool -> Bool -> Bool
&& Integer
max Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0 Bool -> Bool -> Bool
&& Integer
min Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
max]
                (Maybe Integer, Maybe Integer, Bool, Bool)
_ -> [] -- Invalid range format
        (String, String)
_ -> [] -- Invalid range: expected format like '3..5', '3..', or '..5'
      parseExact :: String -> [(Must, String)]
      parseExact :: ReadS Must
parseExact String
str = case String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe String
str of
        Just Integer
n | Integer
n Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
0 -> [(if Integer
n Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
0 then Must
MtDisabled else Integer -> Must
MtExact Integer
n, String
"")]
        Just Integer
_ -> [] -- Invalid value: must be non-negative
        Maybe Integer
Nothing -> [] -- Invalid value: expected integer

inRange :: Must -> Integer -> Bool
inRange :: Must -> Integer -> Bool
inRange Must
MtDisabled Integer
_ = Bool
True
inRange (MtExact Integer
expected) Integer
actual = Integer
actual Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
== Integer
expected
inRange (MtRange Maybe Integer
minVal Maybe Integer
maxVal) Integer
actual =
  Bool
checkMin Bool -> Bool -> Bool
&& Bool
checkMax
  where
    checkMin :: Bool
checkMin = Bool -> (Integer -> Bool) -> Maybe Integer -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
True (Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
<= Integer
actual) Maybe Integer
minVal
    checkMax :: Bool
checkMax = Bool -> (Integer -> Bool) -> Maybe Integer -> Bool
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Bool
True (Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
>= Integer
actual) Maybe Integer
maxVal

-- | Check if a value exceeds the upper bound of the range
exceedsUpperBound :: Must -> Integer -> Bool
exceedsUpperBound :: Must -> Integer -> Bool
exceedsUpperBound Must
MtDisabled Integer
_ = Bool
False
exceedsUpperBound (MtExact Integer
n) Integer
current = Integer
current Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
n
exceedsUpperBound (MtRange Maybe Integer
_ (Just Integer
max)) Integer
current = Integer
current Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Integer
max
exceedsUpperBound (MtRange Maybe Integer
_ Maybe Integer
Nothing) Integer
_ = Bool
False