content: {-# LANGUAGE CPP #-}
  {-|
      Author      :  Andres Loeh <kosmikus@gentoo.org>
      Stability   :  provisional
      Portability :  haskell98
  
      Version parser, according to Portage spec.
  
      Shamelessly borrowed from exi, ported from Parsec to ReadP
  
  -}
  module Portage.Version (
      Version(..),
      Suffix(..),
      fromCabalVersion,
      toCabalVersion,
      is_live
    ) where
  
  import qualified Distribution.Version as Cabal
  
  import           Distribution.Pretty (Pretty(..))
  
  import           Distribution.Parsec (Parsec(..))
  import qualified Distribution.Compat.CharParsing as P
  import qualified Text.PrettyPrint as Disp
  import           Text.PrettyPrint ((<>))
  import qualified Data.List.NonEmpty as NE
  
  import           Control.DeepSeq (NFData(..))
  
  #if MIN_VERSION_base(4,11,0)
  import Prelude hiding ((<>))
  #endif
  
  -- | Portage-style version type.
  data Version = Version { versionNumber   :: [Int]        -- ^ @[1,42,3]@ ~= 1.42.3
                         , versionChar     :: (Maybe Char) -- ^ optional letter
                         , versionSuffix   :: [Suffix]
                         , versionRevision :: Int          -- ^ revision, 0 means none
                         }
    deriving (Eq, Ord, Show, Read)
  
  instance NFData Version where
    rnf (Version n c s r) = rnf n `seq` rnf c `seq` rnf s `seq` rnf r
  
  -- | Prints a valid Portage 'Version' string.
  instance Pretty Version where
    pretty (Version ver c suf rev) =
      dispVer ver <> dispC c <> dispSuf suf <> dispRev rev
      where
        dispVer   = Disp.hcat . Disp.punctuate (Disp.char '.') . map Disp.int
        dispC     = maybe Disp.empty Disp.char
        dispSuf   = Disp.hcat . map pretty
        dispRev 0 = Disp.empty
        dispRev n = Disp.text "-r" <> Disp.int n
  
  -- | 'Version' parser using 'Parsec'.
  instance Parsec Version where
    parsec = do
      ver <- P.sepByNonEmpty digits (P.char '.')
      c   <- P.optional P.lower
      suf <- P.many parsec
      rev <- P.option 0 $ P.string "-r" *> digits
      return $ Version (NE.toList ver) c suf rev
  
  -- | Check if the ebuild is a live ebuild, i.e. if its 'Version' is @9999@.
  --
  -- foo-9999* is treated as live ebuild
  -- Cabal-1.17.9999* as well
  --
  -- >>> let (c,s,r) = (Nothing,[],0)
  -- >>> is_live (Version [1,0,0] c s r)
  -- False
  -- >>> is_live (Version [999] c s r)
  -- False
  -- >>> is_live (Version [1,0,0,9999] c s r)
  -- True
  -- >>> is_live (Version [9999] c s r)
  -- True
  --
  -- $
  -- prop> \verNum char rev -> is_live (Version verNum char [] rev) == if length verNum >= 1 && last verNum >= 9999 then True else False
  is_live :: Version -> Bool
  is_live v =
      case vs of
          -- nonempty
          (_:_) | many_nines (last vs) -> True
          _                            -> False
    where vs = versionNumber v
          many_nines n = is_big n && all_nines n
          is_big n     = n >= 9999
          all_nines n  = (all (== '9') . show) n
  
  -- | Various allowed suffixes in Portage versions.
  data Suffix = Alpha Int | Beta Int | Pre Int | RC Int | P Int
    deriving (Eq, Ord, Show, Read)
  
  instance NFData Suffix where
    rnf (Alpha n) = rnf n
    rnf (Beta n)  = rnf n
    rnf (Pre n)   = rnf n
    rnf (RC n)    = rnf n
    rnf (P n)     = rnf n
  
  instance Pretty Suffix where
    pretty suf = case suf of
      Alpha n -> Disp.text "_alpha" <> dispPos n
      Beta n  -> Disp.text "_beta"  <> dispPos n
      Pre n   -> Disp.text "_pre"   <> dispPos n
      RC n    -> Disp.text "_rc"    <> dispPos n
      P  n    -> Disp.text "_p"     <> dispPos n
  
      where
        dispPos :: Int -> Disp.Doc
        dispPos 0 = Disp.empty
        dispPos n = Disp.int n
  
  instance Parsec Suffix where
    parsec = P.char '_'
         *> P.choice
      [ P.string "alpha"       >> fmap Alpha maybeDigits
      , P.string "beta"        >> fmap Beta  maybeDigits
      , P.try (P.string "pre") >> fmap Pre   maybeDigits
      , P.string "rc"          >> fmap RC    maybeDigits
      , P.string "p"           >> fmap P     maybeDigits
      ]
      where
        maybeDigits = P.option 0 digits
  
  -- | Convert from a 'Cabal.Version' to a Portage 'Version'.
  -- 
  -- prop> \verNum -> fromCabalVersion (Cabal.mkVersion verNum) == Version verNum Nothing [] 0
  fromCabalVersion :: Cabal.Version -> Version
  fromCabalVersion cabal_version = Version (Cabal.versionNumbers cabal_version) Nothing [] 0
  
  -- | Convert from a Portage 'Version' to a 'Cabal.Version'.
  -- $
  -- prop> \verNum char rev -> toCabalVersion (Version verNum char [] rev) == if char == Nothing then Just (Cabal.mkVersion verNum) else Nothing
  toCabalVersion :: Version -> Maybe Cabal.Version
  toCabalVersion (Version nums Nothing [] _) = Just (Cabal.mkVersion nums)
  toCabalVersion _                           = Nothing
  
  -- | Parser which munches digits.
  digits :: P.CharParsing m => m Int
  digits = read <$> P.some P.digit
  