{- |
Copyright: (c) 2017-2019 Kowainik
SPDX-License-Identifier: MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>

Contains data type for GHC versions supported by Summoner
and some useful functions for manipulation with them.
-}

module Summoner.GhcVer
       ( GhcVer (..)
       , GhcMeta (..)
       , Pvp (..)
       , showGhcVer
       , parseGhcVer
       , latestLts
       , baseVer
       , cabalBaseVersions
       , ghcTable

       , oldGhcs
       ) where

import Data.List (maximum, minimum)
import Relude.Extra.Enum (inverseMap, universe)

import qualified Data.Text as T
import qualified Text.Show as Show


-- | Represents some selected set of GHC versions.
data GhcVer
    = Ghc7103
    | Ghc802
    | Ghc822
    | Ghc844
    | Ghc865
    | Ghc883
    deriving stock (GhcVer -> GhcVer -> Bool
(GhcVer -> GhcVer -> Bool)
-> (GhcVer -> GhcVer -> Bool) -> Eq GhcVer
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: GhcVer -> GhcVer -> Bool
$c/= :: GhcVer -> GhcVer -> Bool
== :: GhcVer -> GhcVer -> Bool
$c== :: GhcVer -> GhcVer -> Bool
Eq, Eq GhcVer
Eq GhcVer =>
(GhcVer -> GhcVer -> Ordering)
-> (GhcVer -> GhcVer -> Bool)
-> (GhcVer -> GhcVer -> Bool)
-> (GhcVer -> GhcVer -> Bool)
-> (GhcVer -> GhcVer -> Bool)
-> (GhcVer -> GhcVer -> GhcVer)
-> (GhcVer -> GhcVer -> GhcVer)
-> Ord GhcVer
GhcVer -> GhcVer -> Bool
GhcVer -> GhcVer -> Ordering
GhcVer -> GhcVer -> GhcVer
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
min :: GhcVer -> GhcVer -> GhcVer
$cmin :: GhcVer -> GhcVer -> GhcVer
max :: GhcVer -> GhcVer -> GhcVer
$cmax :: GhcVer -> GhcVer -> GhcVer
>= :: GhcVer -> GhcVer -> Bool
$c>= :: GhcVer -> GhcVer -> Bool
> :: GhcVer -> GhcVer -> Bool
$c> :: GhcVer -> GhcVer -> Bool
<= :: GhcVer -> GhcVer -> Bool
$c<= :: GhcVer -> GhcVer -> Bool
< :: GhcVer -> GhcVer -> Bool
$c< :: GhcVer -> GhcVer -> Bool
compare :: GhcVer -> GhcVer -> Ordering
$ccompare :: GhcVer -> GhcVer -> Ordering
$cp1Ord :: Eq GhcVer
Ord, Int -> GhcVer -> ShowS
[GhcVer] -> ShowS
GhcVer -> String
(Int -> GhcVer -> ShowS)
-> (GhcVer -> String) -> ([GhcVer] -> ShowS) -> Show GhcVer
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [GhcVer] -> ShowS
$cshowList :: [GhcVer] -> ShowS
show :: GhcVer -> String
$cshow :: GhcVer -> String
showsPrec :: Int -> GhcVer -> ShowS
$cshowsPrec :: Int -> GhcVer -> ShowS
Show, Int -> GhcVer
GhcVer -> Int
GhcVer -> [GhcVer]
GhcVer -> GhcVer
GhcVer -> GhcVer -> [GhcVer]
GhcVer -> GhcVer -> GhcVer -> [GhcVer]
(GhcVer -> GhcVer)
-> (GhcVer -> GhcVer)
-> (Int -> GhcVer)
-> (GhcVer -> Int)
-> (GhcVer -> [GhcVer])
-> (GhcVer -> GhcVer -> [GhcVer])
-> (GhcVer -> GhcVer -> [GhcVer])
-> (GhcVer -> GhcVer -> GhcVer -> [GhcVer])
-> Enum GhcVer
forall a.
(a -> a)
-> (a -> a)
-> (Int -> a)
-> (a -> Int)
-> (a -> [a])
-> (a -> a -> [a])
-> (a -> a -> [a])
-> (a -> a -> a -> [a])
-> Enum a
enumFromThenTo :: GhcVer -> GhcVer -> GhcVer -> [GhcVer]
$cenumFromThenTo :: GhcVer -> GhcVer -> GhcVer -> [GhcVer]
enumFromTo :: GhcVer -> GhcVer -> [GhcVer]
$cenumFromTo :: GhcVer -> GhcVer -> [GhcVer]
enumFromThen :: GhcVer -> GhcVer -> [GhcVer]
$cenumFromThen :: GhcVer -> GhcVer -> [GhcVer]
enumFrom :: GhcVer -> [GhcVer]
$cenumFrom :: GhcVer -> [GhcVer]
fromEnum :: GhcVer -> Int
$cfromEnum :: GhcVer -> Int
toEnum :: Int -> GhcVer
$ctoEnum :: Int -> GhcVer
pred :: GhcVer -> GhcVer
$cpred :: GhcVer -> GhcVer
succ :: GhcVer -> GhcVer
$csucc :: GhcVer -> GhcVer
Enum, GhcVer
GhcVer -> GhcVer -> Bounded GhcVer
forall a. a -> a -> Bounded a
maxBound :: GhcVer
$cmaxBound :: GhcVer
minBound :: GhcVer
$cminBound :: GhcVer
Bounded)

-- | Converts 'GhcVer' into dot-separated string.
showGhcVer :: GhcVer -> Text
showGhcVer :: GhcVer -> Text
showGhcVer = \case
    Ghc7103 -> "7.10.3"
    Ghc802  -> "8.0.2"
    Ghc822  -> "8.2.2"
    Ghc844  -> "8.4.4"
    Ghc865  -> "8.6.5"
    Ghc883  -> "8.8.3"

{- | These are old GHC versions that are not working with default GHC versions
when using Stack.
-}
oldGhcs :: [GhcVer]
oldGhcs :: [GhcVer]
oldGhcs = [GhcVer
forall a. Bounded a => a
minBound .. GhcVer
Ghc844]

parseGhcVer :: Text -> Maybe GhcVer
parseGhcVer :: Text -> Maybe GhcVer
parseGhcVer = (GhcVer -> Text) -> Text -> Maybe GhcVer
forall a k. (Bounded a, Enum a, Ord k) => (a -> k) -> k -> Maybe a
inverseMap GhcVer -> Text
showGhcVer

-- | Returns latest known LTS resolver for all GHC versions except default one.
latestLts :: GhcVer -> Text
latestLts :: GhcVer -> Text
latestLts = \case
    Ghc7103 -> "lts-6.35"
    Ghc802  -> "lts-9.21"
    Ghc822  -> "lts-11.22"
    Ghc844  -> "lts-12.26"
    Ghc865  -> "lts-14.27"
    Ghc883  -> "lts-15.5"

-- | Represents PVP versioning (4 numbers).
data Pvp = Pvp
    { Pvp -> Int
pvpFirst  :: Int
    , Pvp -> Int
pvpSecond :: Int
    , Pvp -> Int
pvpThird  :: Int
    , Pvp -> Int
pvpFourth :: Int
    }

-- | Show PVP version in a standard way: @1.2.3.4@
instance Show Pvp where
    show :: Pvp -> String
show (Pvp a :: Int
a b :: Int
b c :: Int
c d :: Int
d) = String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate "." ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (Int -> String) -> [Int] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map Int -> String
forall a. Show a => a -> String
Show.show [Int
a, Int
b, Int
c, Int
d]

-- | Returns base version by 'GhcVer' as 'Pvp'.
baseVerPvp :: GhcVer -> Pvp
baseVerPvp :: GhcVer -> Pvp
baseVerPvp = \case
    Ghc7103 -> Int -> Int -> Int -> Int -> Pvp
Pvp 4 8 0 2
    Ghc802  -> Int -> Int -> Int -> Int -> Pvp
Pvp 4 9 1 0
    Ghc822  -> Int -> Int -> Int -> Int -> Pvp
Pvp 4 10 1 0
    Ghc844  -> Int -> Int -> Int -> Int -> Pvp
Pvp 4 11 1 0
    Ghc865  -> Int -> Int -> Int -> Int -> Pvp
Pvp 4 12 0 0
    Ghc883  -> Int -> Int -> Int -> Int -> Pvp
Pvp 4 13 0 0

-- | Returns corresponding @base@ version of the given GHC version.
baseVer :: GhcVer -> Text
baseVer :: GhcVer -> Text
baseVer = Pvp -> Text
forall b a. (Show a, IsString b) => a -> b
show (Pvp -> Text) -> (GhcVer -> Pvp) -> GhcVer -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GhcVer -> Pvp
baseVerPvp

{- | Returns the @base@ bounds for the list of the given GHC versions.

>>> cabalBaseVersions [Ghc844]
"^>= 4.11.1.0"

>>> cabalBaseVersions [Ghc802, Ghc822, Ghc844]
">= 4.9.0.0 && < 4.12"

-}
cabalBaseVersions :: [GhcVer] -> Text
cabalBaseVersions :: [GhcVer] -> Text
cabalBaseVersions []   = ""
cabalBaseVersions [v :: GhcVer
v]  = "^>= " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> GhcVer -> Text
baseVer GhcVer
v
cabalBaseVersions ghcs :: [GhcVer]
ghcs = ">= " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> GhcVer -> Text
baseVer ([GhcVer] -> GhcVer
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
minimum [GhcVer]
ghcs) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> " && < " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
upperBound
  where
    upperBound :: Text
    upperBound :: Text
upperBound = let Pvp{..} = GhcVer -> Pvp
baseVerPvp (GhcVer -> Pvp) -> GhcVer -> Pvp
forall a b. (a -> b) -> a -> b
$ [GhcVer] -> GhcVer
forall (t :: * -> *) a. (Foldable t, Ord a) => t a -> a
maximum [GhcVer]
ghcs in
        Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
pvpFirst Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "." Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show (Int
pvpSecond Int -> Int -> Int
forall a. Num a => a -> a -> a
+ 1)

-- | Data type to keep meta information for every 'GhcVer'.
data GhcMeta = GhcMeta
    { GhcMeta -> Text
gmGhc      :: !Text
    , GhcMeta -> Text
gmBase     :: !Text
    , GhcMeta -> Text
gmResolver :: !Text
    }

-- | Create corresponding 'GhcMeta' from the given 'GhcVer'.
toGhcMeta :: GhcVer -> GhcMeta
toGhcMeta :: GhcVer -> GhcMeta
toGhcMeta ghcVer :: GhcVer
ghcVer = $WGhcMeta :: Text -> Text -> Text -> GhcMeta
GhcMeta
    { gmGhc :: Text
gmGhc      = "GHC-" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> GhcVer -> Text
showGhcVer GhcVer
ghcVer
    , gmBase :: Text
gmBase     = "base-" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> GhcVer -> Text
baseVer GhcVer
ghcVer
    , gmResolver :: Text
gmResolver = GhcVer -> Text
latestLts GhcVer
ghcVer
    }

ghcTable :: [Text]
ghcTable :: [Text]
ghcTable = (GhcVer -> Text) -> [GhcVer] -> [Text]
forall a b. (a -> b) -> [a] -> [b]
map (GhcMeta -> Text
formatGhcMeta (GhcMeta -> Text) -> (GhcVer -> GhcMeta) -> GhcVer -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. GhcVer -> GhcMeta
toGhcMeta) [GhcVer]
forall a. (Bounded a, Enum a) => [a]
universe

{- Formats 'GhcMeta' in a special way.
It aligns the meta to the left, filling on the right with the spaces.

As the pad number it takes the maximum possible length of the data manually.

Example:

@
GHC-8.6.5     base-4.12.0.0   lts-14.17
@
-}
formatGhcMeta :: GhcMeta -> Text
formatGhcMeta :: GhcMeta -> Text
formatGhcMeta GhcMeta{..} =
       Int -> Char -> Text -> Text
T.justifyLeft 12 ' ' Text
gmGhc
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "  "
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Char -> Text -> Text
T.justifyLeft 14 ' ' Text
gmBase
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "  "
    Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
gmResolver