{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE Safe #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}

-- | Class used for parsing command-line options.
module Text.LambdaOptions.Parseable (
  Parseable(..),
  maybeParse,
  boundedParse,
  repeatedParse,

  simpleParse, -- Deprecated
) where

import           Data.Proxy
                  ( Proxy(..)
                  )
import           GHC.TypeLits
                  ( KnownNat, SomeNat, natVal, someNatVal
                  , KnownSymbol, SomeSymbol, symbolVal, someSymbolVal
                  )
import           Text.Read
                  ( readMaybe
                  )
import           Text.Read.Bounded
                  ( BoundedRead(..), ReadBounded(..)
                  )

--------------------------------------------------------------------------------

-- | Class describing parseable values. Much like the 'Read' class.
class Parseable a where
  -- | Given a sequence of strings, `parse` returns 'Nothing' and the number
  -- of strings (not characters) consumed if the parse failed.
  -- Otherwise, `parse` returns 'Just' the parsed value and the number of
  -- strings consumed.
  --
  -- Element-wise, an entire string must be parsed in the sequence to be
  -- considered a successful parse.
  parse :: [String] -> (Maybe a, Int)

-- | Turns a parser of a single string into a parser suitable for a `Parseable` instance.
--
-- Useful for implementing a `Parseable` for a type with a `Read` instance by
-- supplying `readMaybe` to this function.
--
-- __Note:__
-- The string is /not/ tokenized in any way before being passed into the input parser.
maybeParse :: (String -> Maybe a) -> ([String] -> (Maybe a, Int))
maybeParse parser = \case
  [] -> (Nothing, 0)
  s : _ -> case parser s of
    Nothing -> (Nothing, 0)
    Just x -> (Just x, 1)

{-# DEPRECATED simpleParse "Use 'maybeParse' instead." #-}
-- | Deprecated: Use 'maybeParse' instead.
simpleParse :: (String -> Maybe a) -> ([String] -> (Maybe a, Int))
simpleParse = maybeParse

-- | Turns a parser of a single string into a parser suitable for a `Parseable` instance.
--
-- Useful for implementing a `Parseable` for a type with a `Text.Read.Bounded.ReadBounded` instance by
-- supplying `readMaybe` to this function.
--
-- __Note:__
-- The string is /not/ tokenized in any way before being passed into the input parser.
boundedParse :: (String -> BoundedRead a) -> ([String] -> (Maybe a, Int))
boundedParse parser = maybeParse $ \s -> case parser s of
  NoRead -> Nothing
  ClampedRead _ -> Nothing
  ExactRead x -> Just x

-- | Repeatedly applies `parse` the given number of times, accumulating the results.
--
-- Useful for implementing new parsers.
--
-- Example:
--
-- @
--  data Point = Point Float Float Float
--
--  instance Parseable Point where
--    parse args = case repeatedParse 3 args of
--      (Just [x,y,z], n) -> (Just (Point x y z), n)`
--      (Nothing, n) -> (Nothing, n)
-- @
repeatedParse :: (Parseable a) => Int -> [String] -> (Maybe [a], Int)
repeatedParse n = toPair . repeatedParse' n

repeatedParse' :: (Parseable a) => Int -> [String] -> (Maybe [a], Int, [String])
repeatedParse' n ss = case n <= 0 of
  True -> (Just [], 0, ss)
  False -> let
    (mx, nx) = parse ss
    sx = drop nx ss
    in case mx of
      Nothing -> (Nothing, nx, sx)
      Just x -> let
        (mxs, nxs, sxs) = repeatedParse' (n - 1) sx
        in (fmap (x :) mxs, nx + nxs, sxs)

-- | Parses a 'Word' using its 'Text.Read.Bounded.ReadBounded' instance.
instance Parseable Word where
  parse = boundedParse readBounded

-- | Parses an 'Int' using its 'Text.Read.Bounded.ReadBounded' instance.
instance Parseable Int where
  parse = boundedParse readBounded

-- | Parses an 'Integer' using its 'Read' instance.
instance Parseable Integer where
  parse = maybeParse readMaybe

-- | Parses a single character string.
instance Parseable Char where
  parse strs = case strs of
    [c] : _ -> (Just c, 1)
    _ -> (Nothing, 0)

-- | Identity parser.
--
-- @
--  parse [""] == (Just "", 1)
--  parse ["foo"] == (Just "foo", 1)
--  parse [] == (Nothing, 0)
-- @
instance Parseable String where
  parse = maybeParse Just

-- | Parses a 'Float' using its 'Read' instance.
instance Parseable Float where
  parse = maybeParse readMaybe

-- | Parses an 'Double' using its 'Read' instance.
instance Parseable Double where
  parse = maybeParse readMaybe

-- | Greedily parses a single argument or no argument. Never fails.
instance (Parseable a) => Parseable (Maybe a) where
  parse args = case parse args of
    (Nothing, n) -> (Just Nothing, n)
    (Just x, n) -> (Just $ Just x, n)

-- | Parses a 'KnownNat' by matching its corresponding shown 'natVal'.
--
-- Ex:
-- @
--  parse ["0"] == (Just (Proxy :: Proxy 0), 1)
--  parse ["13"] == (Just (Proxy :: Proxy 13), 1)
--  parse ["00"] == Nothing
--  parse ["0xFF"] == Nothing
-- @
instance (KnownNat n) => Parseable (Proxy n) where
  parse = \case
    [] -> (Nothing, 0)
    str : _ -> case show (natVal nat) == str of
      True  -> (Just nat, 1)
      False -> (Nothing, 0)
    where
      nat = Proxy :: Proxy n

-- | Parses the exact string given by 'symbolVal'.
--
-- Ex:
-- @
--  parse [""] == (Just (Proxy :: Proxy ""), 1)
--  parse ["foo"] == (Just (Proxy :: Proxy "foo"), 1)
--  parse ["foo"] == (Nothing :: Maybe (Proxy :: "bar"), 0)
--  parse [] == (Nothing, 0)
-- @
instance (KnownSymbol s) => Parseable (Proxy s) where
  parse = \case
    [] -> (Nothing, 0)
    str : _ -> case symbolVal sym == str of
      True  -> (Just sym, 1)
      False -> (Nothing, 0)
    where
      sym = Proxy :: Proxy s

-- | Opaque identity parser for 'GHC.TypeLits.SymbolVal'.
--
-- Ex:
-- @
--  parse [""] == (Just (SomeSymbol (Proxy :: Proxy "")), 1)
--  parse ["foo"] == (Just (SomeSymbol (Proxy :: Proxy "foo")), 1)
--  parse [] == (Nothing, 0)
-- @
instance Parseable SomeSymbol where
  parse = \case
    [] -> (Nothing, 0)
    str : _ -> (Just $ someSymbolVal str, 1)

-- | Parses a 'SomeNat' by matching its corresponding shown 'natVal'.
--
-- Ex:
-- @
--  parse ["0"] == (Just (Proxy :: SomeNat (Proxy 0)), 1)
--  parse ["13"] == (Just (Proxy :: SomeNat (Proxy 13)), 1)
--  parse ["00"] == Nothing
--  parse ["0xFF"] == Nothing
-- @
instance Parseable SomeNat where
  parse = \case
    [] -> (Nothing, 0)
    s : _ -> case readMaybe s of
      Nothing -> (Nothing, 0)
      Just n  -> case show n == s of
        False -> (Nothing, 0)
        True  -> case someNatVal n of
          Nothing  -> (Nothing, 0)
          j@Just{} -> (j, 1)

-- | Always succeeds and never consumes any input.
instance Parseable () where
  parse _ = (Just (), 0)

-- | Parses two values sequentially.
instance
  ( Parseable a
  , Parseable b)
  => Parseable (a, b) where
  parse = toPair . parse2Tuple

-- | Parses three values sequentially.
instance
  ( Parseable a
  , Parseable b
  , Parseable c)
  => Parseable (a,b,c) where
  parse = toPair . parse3Tuple

toPair :: (a, b, c) -> (a, b)
toPair (a, b, _) = (a, b)

parse2Tuple
  :: (Parseable a, Parseable b)
  => [String]
  -> (Maybe (a, b), Int, [String])
parse2Tuple ss = let
  (ma, na) = parse ss
  sa = drop na ss
  in case ma of
    Nothing -> (Nothing, na, sa)
    Just a -> let
      (mb, nb) = parse sa
      sb = drop nb sa
      mTup = fmap (\b -> (a, b)) mb
      in (mTup, na + nb, sb)

parse3Tuple
  :: (Parseable a, Parseable b, Parseable c)
  => [String]
  -> (Maybe (a, b, c), Int, [String])
parse3Tuple ss = case parse2Tuple ss of
  (mt, nt, st) -> case mt of
    Nothing -> (Nothing, nt, st)
    Just (a, b) -> let
      (mc, nc) = parse st
      sc = drop nc st
      mTup = fmap (\c -> (a, b, c)) mc
      in (mTup, nt + nc, sc)