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

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

import Data.Char (isDigit)
import Data.List (isInfixOf)
import Text.Printf (printf)
import Text.Read (readMaybe)

data Must
  = MtDisabled
  | MtExact Int
  | MtRange (Maybe Int) (Maybe Int)
  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 Int
n) = Int -> String
forall a. Show a => a -> String
show Int
n
  show (MtRange Maybe Int
Nothing Maybe Int
Nothing) = String
".."
  show (MtRange Maybe Int
Nothing (Just Int
hi)) = String
".." String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
hi
  show (MtRange (Just Int
lo) Maybe Int
Nothing) = Int -> String
forall a. Show a => a -> String
show Int
lo String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
".."
  show (MtRange (Just Int
lo) (Just Int
hi)) = Int -> String
forall a. Show a => a -> String
show Int
lo String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
".." String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show Int
hi

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
      readInt :: String -> Maybe Int
      readInt :: String -> Maybe Int
readInt String
str
        | String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
str Bool -> Bool -> Bool
|| Bool -> Bool
not ((Char -> Bool) -> String -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all Char -> Bool
isDigit String
str) = Maybe Int
forall a. Maybe a
Nothing
        | Bool
otherwise = do
            Integer
num <- String -> Maybe Integer
forall a. Read a => String -> Maybe a
readMaybe String
str :: Maybe Integer
            if Integer
num Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
> Int -> Integer
forall a. Integral a => a -> Integer
toInteger (Int
forall a. Bounded a => a
maxBound :: Int) Bool -> Bool -> Bool
|| Integer
num Integer -> Integer -> Bool
forall a. Ord a => a -> a -> Bool
< Int -> Integer
forall a. Integral a => a -> Integer
toInteger (Int
forall a. Bounded a => a
minBound :: Int)
              then Maybe Int
forall a. Maybe a
Nothing
              else Int -> Maybe Int
forall a. a -> Maybe a
Just (Integer -> Int
forall a. Num a => Integer -> a
fromInteger Integer
num)
      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
loStr, Char
'.' : Char
'.' : String
hiStr) ->
          let loPart :: Maybe Int
loPart = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
loStr then Maybe Int
forall a. Maybe a
Nothing else String -> Maybe Int
readInt String
loStr
              hiPart :: Maybe Int
hiPart = if String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
hiStr then Maybe Int
forall a. Maybe a
Nothing else String -> Maybe Int
readInt String
hiStr
           in case (Maybe Int
loPart, Maybe Int
hiPart, String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
loStr, String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null String
hiStr) of
                (Maybe Int
Nothing, Maybe Int
Nothing, Bool
False, Bool
False) -> []
                (Maybe Int
Nothing, Maybe Int
Nothing, Bool
True, Bool
True) -> [(Maybe Int -> Maybe Int -> Must
MtRange Maybe Int
forall a. Maybe a
Nothing Maybe Int
forall a. Maybe a
Nothing, String
"")]
                (Maybe Int
Nothing, Just Int
hi, Bool
True, Bool
False) ->
                  [(Maybe Int -> Maybe Int -> Must
MtRange Maybe Int
forall a. Maybe a
Nothing (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
hi), String
"") | Int
hi Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0]
                (Just Int
lo, Maybe Int
Nothing, Bool
False, Bool
True) ->
                  [(Maybe Int -> Maybe Int -> Must
MtRange (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
lo) Maybe Int
forall a. Maybe a
Nothing, String
"") | Int
lo Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0]
                (Just Int
lo, Just Int
hi, Bool
False, Bool
False) ->
                  [(Maybe Int -> Maybe Int -> Must
MtRange (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
lo) (Int -> Maybe Int
forall a. a -> Maybe a
Just Int
hi), String
"") | Int
lo Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0 Bool -> Bool -> Bool
&& Int
hi Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0 Bool -> Bool -> Bool
&& Int
lo Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
hi]
                (Maybe Int, Maybe Int, Bool, Bool)
_ -> []
        (String, String)
_ -> []
      parseExact :: String -> [(Must, String)]
      parseExact :: ReadS Must
parseExact String
str = case String -> Maybe Int
readInt String
str of
        Just Int
n | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0 -> [(if Int
n Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
0 then Must
MtDisabled else Int -> Must
MtExact Int
n, String
"")]
        Just Int
_ -> []
        Maybe Int
Nothing -> []

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

exceedsUpperBound :: Must -> Int -> Bool
exceedsUpperBound :: Must -> Int -> Bool
exceedsUpperBound Must
MtDisabled Int
_ = Bool
False
exceedsUpperBound (MtExact Int
n) Int
current = Int
current Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
n
exceedsUpperBound (MtRange Maybe Int
_ (Just Int
hi)) Int
current = Int
current Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
hi
exceedsUpperBound (MtRange Maybe Int
_ Maybe Int
Nothing) Int
_ = Bool
False

validateMust :: Must -> Maybe String
validateMust :: Must -> Maybe String
validateMust Must
MtDisabled = Maybe String
forall a. Maybe a
Nothing
validateMust (MtExact Int
n) | Int
n Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 = String -> Maybe String
forall a. a -> Maybe a
Just String
"--must exact value must be positive"
validateMust (MtRange (Just Int
lo) Maybe Int
_) | Int
lo Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 = String -> Maybe String
forall a. a -> Maybe a
Just String
"--must minimum must be non-negative"
validateMust (MtRange Maybe Int
_ (Just Int
hi)) | Int
hi Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
0 = String -> Maybe String
forall a. a -> Maybe a
Just String
"--must maximum must be non-negative"
validateMust (MtRange Maybe Int
Nothing Maybe Int
_) = Maybe String
forall a. Maybe a
Nothing
validateMust (MtRange Maybe Int
_ Maybe Int
Nothing) = Maybe String
forall a. Maybe a
Nothing
validateMust (MtRange (Just Int
lo) (Just Int
hi))
  | Int
lo Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Int
hi = String -> Maybe String
forall a. a -> Maybe a
Just (String -> Int -> Int -> String
forall r. PrintfType r => String -> r
printf String
"--must range invalid: minimum (%d) is greater than maximum (%d)" Int
lo Int
hi)
  | Bool
otherwise = Maybe String
forall a. Maybe a
Nothing
validateMust Must
_ = Maybe String
forall a. Maybe a
Nothing