{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE TemplateHaskell #-}

module Simplex.Chat.Remote.AppVersion
  ( AppVersionRange (minVersion, maxVersion),
    pattern AppVersionRange,
    AppVersion (..),
    pattern AppCompatible,
    mkAppVersionRange,
    compatibleAppVersion,
    isAppCompatible,
  )
where

import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as J
import qualified Data.Aeson.Encoding as JE
import qualified Data.Aeson.TH as JQ
import qualified Data.Text as T
import Data.Version (parseVersion, showVersion)
import qualified Data.Version as V
import Simplex.Messaging.Parsers (defaultJSON)
import Text.ParserCombinators.ReadP (readP_to_S)

newtype AppVersion = AppVersion {AppVersion -> Version
appVersion :: V.Version}
  deriving (AppVersion -> AppVersion -> Bool
(AppVersion -> AppVersion -> Bool)
-> (AppVersion -> AppVersion -> Bool) -> Eq AppVersion
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: AppVersion -> AppVersion -> Bool
== :: AppVersion -> AppVersion -> Bool
$c/= :: AppVersion -> AppVersion -> Bool
/= :: AppVersion -> AppVersion -> Bool
Eq, Eq AppVersion
Eq AppVersion =>
(AppVersion -> AppVersion -> Ordering)
-> (AppVersion -> AppVersion -> Bool)
-> (AppVersion -> AppVersion -> Bool)
-> (AppVersion -> AppVersion -> Bool)
-> (AppVersion -> AppVersion -> Bool)
-> (AppVersion -> AppVersion -> AppVersion)
-> (AppVersion -> AppVersion -> AppVersion)
-> Ord AppVersion
AppVersion -> AppVersion -> Bool
AppVersion -> AppVersion -> Ordering
AppVersion -> AppVersion -> AppVersion
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: AppVersion -> AppVersion -> Ordering
compare :: AppVersion -> AppVersion -> Ordering
$c< :: AppVersion -> AppVersion -> Bool
< :: AppVersion -> AppVersion -> Bool
$c<= :: AppVersion -> AppVersion -> Bool
<= :: AppVersion -> AppVersion -> Bool
$c> :: AppVersion -> AppVersion -> Bool
> :: AppVersion -> AppVersion -> Bool
$c>= :: AppVersion -> AppVersion -> Bool
>= :: AppVersion -> AppVersion -> Bool
$cmax :: AppVersion -> AppVersion -> AppVersion
max :: AppVersion -> AppVersion -> AppVersion
$cmin :: AppVersion -> AppVersion -> AppVersion
min :: AppVersion -> AppVersion -> AppVersion
Ord, Int -> AppVersion -> ShowS
[AppVersion] -> ShowS
AppVersion -> String
(Int -> AppVersion -> ShowS)
-> (AppVersion -> String)
-> ([AppVersion] -> ShowS)
-> Show AppVersion
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AppVersion -> ShowS
showsPrec :: Int -> AppVersion -> ShowS
$cshow :: AppVersion -> String
show :: AppVersion -> String
$cshowList :: [AppVersion] -> ShowS
showList :: [AppVersion] -> ShowS
Show)

instance ToJSON AppVersion where
  toJSON :: AppVersion -> Value
toJSON (AppVersion Version
v) = Text -> Value
J.String (Text -> Value) -> (String -> Text) -> String -> Value
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack (String -> Value) -> String -> Value
forall a b. (a -> b) -> a -> b
$ Version -> String
showVersion Version
v
  toEncoding :: AppVersion -> Encoding
toEncoding (AppVersion Version
v) = Text -> Encoding
forall a. Text -> Encoding' a
JE.text (Text -> Encoding) -> (String -> Text) -> String -> Encoding
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
T.pack (String -> Encoding) -> String -> Encoding
forall a b. (a -> b) -> a -> b
$ Version -> String
showVersion Version
v

instance FromJSON AppVersion where
  parseJSON :: Value -> Parser AppVersion
parseJSON = String -> (Text -> Parser AppVersion) -> Value -> Parser AppVersion
forall a. String -> (Text -> Parser a) -> Value -> Parser a
J.withText String
"AppVersion" ((Text -> Parser AppVersion) -> Value -> Parser AppVersion)
-> (Text -> Parser AppVersion) -> Value -> Parser AppVersion
forall a b. (a -> b) -> a -> b
$ String -> Parser AppVersion
forall {f :: * -> *}. MonadFail f => String -> f AppVersion
parse (String -> Parser AppVersion)
-> (Text -> String) -> Text -> Parser AppVersion
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack
    where
      parse :: String -> f AppVersion
parse String
s = case ((Version, String) -> Bool)
-> [(Version, String)] -> [(Version, String)]
forall a. (a -> Bool) -> [a] -> [a]
filter (String -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null (String -> Bool)
-> ((Version, String) -> String) -> (Version, String) -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Version, String) -> String
forall a b. (a, b) -> b
snd) ([(Version, String)] -> [(Version, String)])
-> [(Version, String)] -> [(Version, String)]
forall a b. (a -> b) -> a -> b
$ ReadP Version -> ReadS Version
forall a. ReadP a -> ReadS a
readP_to_S ReadP Version
parseVersion String
s of
        (Version
v, String
_) : [(Version, String)]
_ -> AppVersion -> f AppVersion
forall a. a -> f a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (AppVersion -> f AppVersion) -> AppVersion -> f AppVersion
forall a b. (a -> b) -> a -> b
$ Version -> AppVersion
AppVersion Version
v
        [(Version, String)]
_ -> String -> f AppVersion
forall a. String -> f a
forall (m :: * -> *) a. MonadFail m => String -> m a
fail (String -> f AppVersion) -> String -> f AppVersion
forall a b. (a -> b) -> a -> b
$ String
"bad AppVersion: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
s

data AppVersionRange = AppVRange
  { AppVersionRange -> AppVersion
minVersion :: AppVersion,
    AppVersionRange -> AppVersion
maxVersion :: AppVersion
  }
  deriving (Int -> AppVersionRange -> ShowS
[AppVersionRange] -> ShowS
AppVersionRange -> String
(Int -> AppVersionRange -> ShowS)
-> (AppVersionRange -> String)
-> ([AppVersionRange] -> ShowS)
-> Show AppVersionRange
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> AppVersionRange -> ShowS
showsPrec :: Int -> AppVersionRange -> ShowS
$cshow :: AppVersionRange -> String
show :: AppVersionRange -> String
$cshowList :: [AppVersionRange] -> ShowS
showList :: [AppVersionRange] -> ShowS
Show)

pattern AppVersionRange :: AppVersion -> AppVersion -> AppVersionRange
pattern $mAppVersionRange :: forall {r}.
AppVersionRange
-> (AppVersion -> AppVersion -> r) -> ((# #) -> r) -> r
AppVersionRange v1 v2 <- AppVRange v1 v2

{-# COMPLETE AppVersionRange #-}

mkAppVersionRange :: AppVersion -> AppVersion -> AppVersionRange
mkAppVersionRange :: AppVersion -> AppVersion -> AppVersionRange
mkAppVersionRange AppVersion
v1 AppVersion
v2
  | AppVersion
v1 AppVersion -> AppVersion -> Bool
forall a. Ord a => a -> a -> Bool
<= AppVersion
v2 = AppVersion -> AppVersion -> AppVersionRange
AppVRange AppVersion
v1 AppVersion
v2
  | Bool
otherwise = String -> AppVersionRange
forall a. HasCallStack => String -> a
error String
"invalid version range"

newtype AppCompatible a = AppCompatible_ a

pattern AppCompatible :: a -> AppCompatible a
pattern $mAppCompatible :: forall {r} {a}. AppCompatible a -> (a -> r) -> ((# #) -> r) -> r
AppCompatible a <- AppCompatible_ a

{-# COMPLETE AppCompatible #-}

isAppCompatible :: AppVersion -> AppVersionRange -> Bool
isAppCompatible :: AppVersion -> AppVersionRange -> Bool
isAppCompatible AppVersion
v (AppVRange AppVersion
v1 AppVersion
v2) = AppVersion
v1 AppVersion -> AppVersion -> Bool
forall a. Ord a => a -> a -> Bool
<= AppVersion
v Bool -> Bool -> Bool
&& AppVersion
v AppVersion -> AppVersion -> Bool
forall a. Ord a => a -> a -> Bool
<= AppVersion
v2

isCompatibleAppRange :: AppVersionRange -> AppVersionRange -> Bool
isCompatibleAppRange :: AppVersionRange -> AppVersionRange -> Bool
isCompatibleAppRange (AppVRange AppVersion
min1 AppVersion
max1) (AppVRange AppVersion
min2 AppVersion
max2) = AppVersion
min1 AppVersion -> AppVersion -> Bool
forall a. Ord a => a -> a -> Bool
<= AppVersion
max2 Bool -> Bool -> Bool
&& AppVersion
min2 AppVersion -> AppVersion -> Bool
forall a. Ord a => a -> a -> Bool
<= AppVersion
max1

compatibleAppVersion :: AppVersionRange -> AppVersionRange -> Maybe (AppCompatible AppVersion)
compatibleAppVersion :: AppVersionRange
-> AppVersionRange -> Maybe (AppCompatible AppVersion)
compatibleAppVersion AppVersionRange
vr1 AppVersionRange
vr2 =
  AppVersion -> AppVersion -> AppVersion
forall a. Ord a => a -> a -> a
min (AppVersionRange -> AppVersion
maxVersion AppVersionRange
vr1) (AppVersionRange -> AppVersion
maxVersion AppVersionRange
vr2) AppVersion -> Bool -> Maybe (AppCompatible AppVersion)
`mkCompatibleIf` AppVersionRange -> AppVersionRange -> Bool
isCompatibleAppRange AppVersionRange
vr1 AppVersionRange
vr2

mkCompatibleIf :: AppVersion -> Bool -> Maybe (AppCompatible AppVersion)
AppVersion
v mkCompatibleIf :: AppVersion -> Bool -> Maybe (AppCompatible AppVersion)
`mkCompatibleIf` Bool
cond = if Bool
cond then AppCompatible AppVersion -> Maybe (AppCompatible AppVersion)
forall a. a -> Maybe a
Just (AppCompatible AppVersion -> Maybe (AppCompatible AppVersion))
-> AppCompatible AppVersion -> Maybe (AppCompatible AppVersion)
forall a b. (a -> b) -> a -> b
$ AppVersion -> AppCompatible AppVersion
forall a. a -> AppCompatible a
AppCompatible_ AppVersion
v else Maybe (AppCompatible AppVersion)
forall a. Maybe a
Nothing

$(JQ.deriveJSON defaultJSON ''AppVersionRange)