{-# OPTIONS_GHC -Wall #-}
{-# LANGUAGE LambdaCase #-}

module Data.SemanticVersion.AlphanumericIdentifier where

import Control.Applicative
import Control.Lens
import Data.Digit
import Data.List.NonEmpty
import Data.SemanticVersion.NonDigit
import Data.SemanticVersion.IdentifierCharacter
import Data.SemanticVersion.IdentifierCharacters
import Text.Parser.Char
import Text.Parser.Combinators

-- $setup
-- >>> import Text.Parsec(parse)
-- >>> import Data.Either(isLeft)

-- <alphanumeric identifier> ::= <non-digit>
--                             | <non-digit> <identifier characters>
--                             | <identifier characters> <non-digit>
--                             | <identifier characters> <non-digit> <identifier characters>
data AlphanumericIdentifier =
  AlphanumericIdentifierNonDigit NonDigit
  | AlphanumericIdentifierNonDigits NonDigit IdentifierCharacters
  | AlphanumericIdentifierCharacters IdentifierCharacters NonDigit
  | AlphanumericIdentifierCharacters2 IdentifierCharacters NonDigit IdentifierCharacters
  deriving (Eq, Show)

class HasAlphanumericIdentifier a where
  alphanumericIdentifier ::
    Lens' a AlphanumericIdentifier

instance HasAlphanumericIdentifier AlphanumericIdentifier where
  alphanumericIdentifier =
    id

class AsAlphanumericIdentifier a where
  _AlphanumericIdentifier ::
    Prism' a AlphanumericIdentifier
  _AlphanumericIdentifierNonDigit ::
    Prism' a NonDigit
  _AlphanumericIdentifierNonDigit =
    _AlphanumericIdentifier .
    prism'
      AlphanumericIdentifierNonDigit
      (\case
        AlphanumericIdentifierNonDigit a ->
          Just a
        _ ->
          Nothing)
  _AlphanumericIdentifierNonDigits ::
    Prism' a (NonDigit, IdentifierCharacters)
  _AlphanumericIdentifierNonDigits =
    _AlphanumericIdentifier .
    prism'
      (uncurry AlphanumericIdentifierNonDigits)
      (\case
        AlphanumericIdentifierNonDigits d cs ->
          Just (d, cs)
        _ ->
          Nothing)
  _AlphanumericIdentifierCharacters ::
    Prism' a (IdentifierCharacters, NonDigit)
  _AlphanumericIdentifierCharacters =
    _AlphanumericIdentifier .
    prism'
      (uncurry AlphanumericIdentifierCharacters)
      (\case
        AlphanumericIdentifierCharacters cs d ->
          Just (cs, d)
        _ ->
          Nothing)
  _AlphanumericIdentifierCharacters2 ::
    Prism' a (IdentifierCharacters, NonDigit, IdentifierCharacters)
  _AlphanumericIdentifierCharacters2 =
    _AlphanumericIdentifier .
    prism'
      (\(cs1, d, cs2) -> AlphanumericIdentifierCharacters2 cs1 d cs2)
      (\case
        AlphanumericIdentifierCharacters2 cs1 d cs2 ->
          Just (cs1, d, cs2)
        _ ->
          Nothing)

instance AsAlphanumericIdentifier AlphanumericIdentifier where
  _AlphanumericIdentifier =
    id

instance HasNonDigit AlphanumericIdentifier where
  nonDigit f (AlphanumericIdentifierNonDigit d) =
    fmap AlphanumericIdentifierNonDigit (f d)
  nonDigit f (AlphanumericIdentifierNonDigits d cs) =
    fmap (`AlphanumericIdentifierNonDigits` cs) (f d)
  nonDigit f (AlphanumericIdentifierCharacters cs d) =
    fmap (AlphanumericIdentifierCharacters cs) (f d)
  nonDigit f (AlphanumericIdentifierCharacters2 cs1 d cs2) =
    fmap (\d' -> AlphanumericIdentifierCharacters2 cs1 d' cs2) (f d)

-- |
--
-- >>> parse (parseAlphanumericIdentifier <* eof) "parseAlphanumericIdentifier" "A"
-- Right (AlphanumericIdentifierNonDigit (NonDigitLetter (AlphaUpper Upper_A)))
--
-- >>> isLeft (parse parseAlphanumericIdentifier "parseAlphanumericIdentifier" "")
-- True
--
-- >>> parse (parseAlphanumericIdentifier <* eof) "parseAlphanumericIdentifier" "Abc-123--XYZ"
-- Right (AlphanumericIdentifierNonDigits (NonDigitLetter (AlphaUpper Upper_A)) (IdentifierCharacters (IdentifierCharacterNonDigit (NonDigitLetter (AlphaLower Lower_b)) :| [IdentifierCharacterNonDigit (NonDigitLetter (AlphaLower Lower_c)),IdentifierCharacterNonDigit NonDigitHyphen,IdentifierCharacterDigit DecDigit1,IdentifierCharacterDigit DecDigit2,IdentifierCharacterDigit DecDigit3,IdentifierCharacterNonDigit NonDigitHyphen,IdentifierCharacterNonDigit NonDigitHyphen,IdentifierCharacterNonDigit (NonDigitLetter (AlphaUpper Upper_X)),IdentifierCharacterNonDigit (NonDigitLetter (AlphaUpper Upper_Y)),IdentifierCharacterNonDigit (NonDigitLetter (AlphaUpper Upper_Z))])))
--
-- >>> isLeft (parse parseAlphanumericIdentifier "parseAlphanumericIdentifier" "+")
-- True
--
-- >>> isLeft (parse parseAlphanumericIdentifier "parseAlphanumericIdentifier" "0")
-- True
--
-- >>> isLeft (parse parseAlphanumericIdentifier "parseAlphanumericIdentifier" "0+")
-- True
--
-- >>> parse (parseAlphanumericIdentifier <* eof) "parseAlphanumericIdentifier" "0a"
-- Right (AlphanumericIdentifierCharacters (IdentifierCharacters (IdentifierCharacterDigit DecDigit0 :| [])) (NonDigitLetter (AlphaLower Lower_a)))
--
-- >>> parse parseAlphanumericIdentifier "parseAlphanumericIdentifier" "-"
-- Right (AlphanumericIdentifierNonDigit NonDigitHyphen)
--
-- >>> parse parseAlphanumericIdentifier "parseAlphanumericIdentifier" "Abc-123--XYZ+"
-- Right (AlphanumericIdentifierNonDigits (NonDigitLetter (AlphaUpper Upper_A)) (IdentifierCharacters (IdentifierCharacterNonDigit (NonDigitLetter (AlphaLower Lower_b)) :| [IdentifierCharacterNonDigit (NonDigitLetter (AlphaLower Lower_c)),IdentifierCharacterNonDigit NonDigitHyphen,IdentifierCharacterDigit DecDigit1,IdentifierCharacterDigit DecDigit2,IdentifierCharacterDigit DecDigit3,IdentifierCharacterNonDigit NonDigitHyphen,IdentifierCharacterNonDigit NonDigitHyphen,IdentifierCharacterNonDigit (NonDigitLetter (AlphaUpper Upper_X)),IdentifierCharacterNonDigit (NonDigitLetter (AlphaUpper Upper_Y)),IdentifierCharacterNonDigit (NonDigitLetter (AlphaUpper Upper_Z))])))
parseAlphanumericIdentifier ::
  CharParsing p =>
  p AlphanumericIdentifier
parseAlphanumericIdentifier =
  try (liftA2 maybe AlphanumericIdentifierNonDigit AlphanumericIdentifierNonDigits <$> parseNonDigit <*> try (optional parseIdentifierCharacters)) <|>
  (\c d ->
    let c' = IdentifierCharacters (IdentifierCharacterDigit <$> c)
    in  maybe (AlphanumericIdentifierCharacters c' d) (AlphanumericIdentifierCharacters2 c' d)) <$> some1 parseDecimal <*> parseNonDigit <*> try (optional parseIdentifierCharacters)