content: {-# LANGUAGE BangPatterns #-}
  {-# LANGUAGE UnboxedTuples #-}
  
  module Gren.Version
    ( Version (..),
      one,
      max,
      compiler,
      bumpPatch,
      bumpMinor,
      bumpMajor,
      toChars,
      --
      decoder,
      encode,
      --
      parser,
    )
  where
  
  import Control.Monad (liftM3)
  import Data.Binary (Binary, get, getWord8, put, putWord8)
  import Data.Version qualified as Version
  import Data.Word (Word16, Word8)
  import Foreign.Ptr (Ptr, minusPtr, plusPtr)
  import Json.Decode qualified as D
  import Json.Encode qualified as E
  import Parse.Primitives (Col, Row)
  import Parse.Primitives qualified as P
  import Paths_gren qualified
  import Prelude hiding (max)
  
  -- VERSION
  
  data Version = Version
    { _major :: {-# UNPACK #-} !Word16,
      _minor :: {-# UNPACK #-} !Word16,
      _patch :: {-# UNPACK #-} !Word16
    }
    deriving (Eq, Ord, Show)
  
  one :: Version
  one =
    Version 1 0 0
  
  max :: Version
  max =
    Version maxBound 0 0
  
  compiler :: Version
  compiler =
    case map fromIntegral (Version.versionBranch Paths_gren.version) of
      major : minor : patch : _ ->
        Version major minor patch
      [major, minor] ->
        Version major minor 0
      [major] ->
        Version major 0 0
      [] ->
        error "could not detect version of the compiler you are using"
  
  -- BUMP
  
  bumpPatch :: Version -> Version
  bumpPatch (Version major minor patch) =
    Version major minor (patch + 1)
  
  bumpMinor :: Version -> Version
  bumpMinor (Version major minor _patch) =
    Version major (minor + 1) 0
  
  bumpMajor :: Version -> Version
  bumpMajor (Version major _minor _patch) =
    Version (major + 1) 0 0
  
  -- TO CHARS
  
  toChars :: Version -> [Char]
  toChars (Version major minor patch) =
    show major ++ '.' : show minor ++ '.' : show patch
  
  -- JSON
  
  decoder :: D.Decoder (Row, Col) Version
  decoder =
    D.customString parser (,)
  
  encode :: Version -> E.Value
  encode version =
    E.chars (toChars version)
  
  -- BINARY
  
  instance Binary Version where
    get =
      do
        word <- getWord8
        if word == 255
          then liftM3 Version get get get
          else do
            minor <- getWord8
            patch <- getWord8
            return (Version (fromIntegral word) (fromIntegral minor) (fromIntegral patch))
  
    put (Version major minor patch) =
      if major < 255 && minor < 256 && patch < 256
        then do
          putWord8 (fromIntegral major)
          putWord8 (fromIntegral minor)
          putWord8 (fromIntegral patch)
        else do
          putWord8 255
          put major
          put minor
          put patch
  
  -- PARSER
  
  parser :: P.Parser (Row, Col) Version
  parser =
    do
      major <- numberParser
      P.word1 0x2E {-.-} (,)
      minor <- numberParser
      P.word1 0x2E {-.-} (,)
      patch <- numberParser
      return (Version major minor patch)
  
  numberParser :: P.Parser (Row, Col) Word16
  numberParser =
    P.Parser $ \(P.State src pos end indent row col) cok _ _ eerr ->
      if pos >= end
        then eerr row col (,)
        else
          let !word = P.unsafeIndex pos
           in if word == 0x30 {-0-}
                then
                  let !newState = P.State src (plusPtr pos 1) end indent row (col + 1)
                   in cok 0 newState
                else
                  if isDigit word
                    then
                      let (# total, newPos #) = chompWord16 (plusPtr pos 1) end (fromIntegral (word - 0x30))
                          !newState = P.State src newPos end indent row (col + fromIntegral (minusPtr newPos pos))
                       in cok total newState
                    else eerr row col (,)
  
  chompWord16 :: Ptr Word8 -> Ptr Word8 -> Word16 -> (# Word16, Ptr Word8 #)
  chompWord16 pos end total =
    if pos >= end
      then (# total, pos #)
      else
        let !word = P.unsafeIndex pos
         in if isDigit word
              then chompWord16 (plusPtr pos 1) end (10 * total + fromIntegral (word - 0x30))
              else (# total, pos #)
  
  isDigit :: Word8 -> Bool
  isDigit word =
    0x30 {-0-} <= word && word <= 0x39 {-9-}
  