{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
module NixFromNpm.SemVer where

import qualified Prelude as P
import Data.Text (Text)
import qualified Data.Text as T
import Data.Aeson.Parser
import Data.Aeson
import Data.Aeson.Types (typeMismatch)

import NixFromNpm.Common

type SemVer = (Int, Int, Int)

-- | A partially specified semantic version. Implicitly defines
-- a range of acceptable versions, as seen in @wildcardToRange@.
data Wildcard = Any
              | One Int
              | Two Int Int
              | Three Int Int Int
              deriving (Show, Eq)

data SemVerRange
  = Eq SemVer
  | Gt SemVer
  | Lt SemVer
  | Geq SemVer
  | Leq SemVer
  | And SemVerRange SemVerRange
  | Or SemVerRange SemVerRange
  deriving (Eq)

renderSV :: SemVer -> Text
renderSV (x, y, z) = pack (renderSV' (x, y, z))

renderSV' :: SemVer -> String
renderSV' (x, y, z) = show x <> "." <> show y <> "." <> show z

instance Show SemVerRange where
  show = \case
    Eq sv -> "=" <> renderSV' sv
    Gt sv -> ">" <> renderSV' sv
    Lt sv -> "<" <> renderSV' sv
    Geq sv -> ">=" <> renderSV' sv
    Leq sv -> "<=" <> renderSV' sv
    And svr1 svr2 -> show svr1 <> " " <> show svr2
    Or svr1 svr2 -> show svr1 <> " || " <> show svr2

-- | Returns whether a given semantic version matches a range.
matches :: SemVerRange -> SemVer -> Bool
matches range ver = case range of
  Eq sv -> ver == sv
  Gt sv -> ver > sv
  Lt sv -> ver < sv
  Geq sv -> ver >= sv
  Leq sv -> ver <= sv
  And sv1 sv2 -> matches sv1 ver && matches sv2 ver
  Or sv1 sv2 -> matches sv1 ver || matches sv2 ver

-- | Gets the highest-matching semver in a range.
bestMatch :: SemVerRange -> [SemVer] -> Either String SemVer
bestMatch range vs = case filter (matches range) vs of
  [] -> Left "No matching versions"
  vs -> Right $ maximum vs

-- | Fills in zeros in a wildcard.
wildcardToSemver :: Wildcard -> SemVer
wildcardToSemver Any = (0, 0, 0)
wildcardToSemver (One n) = (n, 0, 0)
wildcardToSemver (Two n m) = (n, m, 0)
wildcardToSemver (Three n m o) = (n, m, o)

-- | Translates a wildcard (partially specified version) to a range.
-- Ex: 2 := >=2.0.0 <3.0.0
-- Ex: 1.2.x := 1.2 := >=1.2.0 <1.3.0
wildcardToRange :: Wildcard -> SemVerRange
wildcardToRange = \case
  Any -> Geq (0, 0, 0)
  One n -> Geq (n, 0, 0) `And` Lt (n+1, 0, 0)
  Two n m -> Geq (n, m, 0) `And` Lt (n, m + 1, 0)
  Three n m o -> Eq (n, m, o)

-- | Translates a ~wildcard to a range.
-- Ex: ~1.2.3 := >=1.2.3 <1.(2+1).0 := >=1.2.3 <1.3.0
tildeToRange :: Wildcard -> SemVerRange
tildeToRange = \case
  Any -> tildeToRange (Three 0 0 0)
  One n -> tildeToRange (Three n 0 0)
  Two n m -> tildeToRange (Three n m 0)
  Three n m o -> And (Geq (n, m, o)) (Lt (n, m + 1, 0))

-- | Translates a ^wildcard to a range.
-- Ex: ^1.2.x := >=1.2.0 <2.0.0
caratToRange :: Wildcard -> SemVerRange
caratToRange = \case
  One n -> And (Geq (n, 0, 0)) (Lt (n+1, 0, 0))
  Two n m -> And (Geq (n, m, 0)) (Lt (n+1, 0, 0))
  Three 0 0 n -> Eq (0, 0, n)
  Three 0 n m -> And (Geq (0, n, m)) (Lt (0, n + 1, 0))
  Three n m o -> And (Geq (n, m, o)) (Lt (n+1, 0, 0))

-- | Translates two hyphenated wildcards to an actual range.
-- Ex: 1.2.3 - 2.3.4 := >=1.2.3 <=2.3.4
-- Ex: 1.2 - 2.3.4 := >=1.2.0 <=2.3.4
-- Ex: 1.2.3 - 2 := >=1.2.3 <3.0.0
hyphenatedRange :: Wildcard -> Wildcard -> SemVerRange
hyphenatedRange wc1 wc2 = And sv1 sv2 where
  sv1 = case wc1 of Any -> Geq (0, 0, 0)
                    One n -> Geq (n, 0, 0)
                    Two n m -> Geq (n, m, 0)
                    Three n m o -> Geq (n, m, o)
  sv2 = case wc2 of Any -> Geq (0, 0, 0) -- Refers to "any version"
                    One n -> Lt (n+1, 0, 0)
                    Two n m -> Lt (n, m + 1, 0)
                    Three n m o -> Leq (n, m, o)