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

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


import Text.Read
import Text.Read.Bounded


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


-- | Class describing parseable values. Much like the 'Text.Read.Read' class.
class Parseable a where
    -- | Given a sequence of strings, `parse` returns 'Nothing' and the number of strings 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 `Text.Read.Read` instance by supplying `Text.Read.readMaybe` to this function.
--
-- __Note:__ The string is /not/ tokenized in any way before being passed into the input parser.
simpleParse :: (String -> Maybe a) -> ([String] -> (Maybe a, Int))
simpleParse parser = \case
    [] -> (Nothing, 0)
    s : _ -> case parser s of
        Nothing -> (Nothing, 0)
        Just x -> (Just x, 1)


-- | 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 strs = case repeatedParse 3 strs 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)


parseBounded :: (ReadBounded a) => [String] -> (Maybe a, Int)
parseBounded = simpleParse $ \str -> case readBounded str of
    NoRead -> Nothing
    ClampedRead _ -> Nothing
    ExactRead x -> Just x


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


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


-- | Parses an 'Integer' using its 'Prelude.Read' instance.
instance Parseable Integer where
    parse = parseBounded


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


-- | Identity parser.
-- Ex: @parse "abc" == (Just "abc", 1)@
instance Parseable String where
    parse = simpleParse Just


-- | Parses a 'Float' using its 'Prelude.Read' instance.
instance Parseable Float where
    parse = simpleParse 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)


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


instance (Parseable a, Parseable b) => Parseable (a,b) where
    parse = toPair . parse2Tuple


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)