{-# LANGUAGE CPP   #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE Safe  #-}
module Kleene.Functor.NonEmpty (
    K1,
    Greediness (..),
    -- * Constructors
    some1,
    anyChar,
    oneof,
    char,
    charRange,
    dot,
    everything1,
    string,
    -- * Queries
    isEmpty,
    isEverything,
    -- * Matching
    match,
    -- * Conversions
    toRE,
    toKleene,
    toRA,
    nullableProof,
    ) where

import Prelude ()
import Prelude.Compat

import Control.Applicative (Alternative (..), liftA2)
import Data.Foldable       (toList)
import Data.Functor.Alt    ((<!>))
import Data.Functor.Apply  (Apply (..))
import Data.List.NonEmpty  (NonEmpty (..))
import Data.RangeSet.Map   (RSet)

import qualified Data.Functor.Alt       as Alt
import qualified Data.List.NonEmpty     as NE
import qualified Data.RangeSet.Map      as RSet
import qualified Text.Regex.Applicative as R

import qualified Kleene.Classes          as C
import           Kleene.Internal.Functor (Greediness (..), K (..))
import           Kleene.Internal.Pretty
import           Kleene.Internal.Sets
import qualified Kleene.RE               as RE

-- | 'Applicative' 'Functor' regular expression.
data K1 c a where
    K1Empty  :: K1 c a
    K1Char   :: (Ord c, Enum c) => RSet c -> K1 c c
    K1Append :: (a -> b -> r) -> K1 c a -> K1 c b -> K1 c r
    K1Union  :: K1 c a -> K1 c a -> K1 c a
    KPlus    :: Greediness -> K1 c a -> K1 c (NonEmpty a)

    -- optimisations
    K1Map    :: (a -> b) -> K1 c a -> K1 c b -- could use Pure and Append
    K1String :: Eq c => NonEmpty c -> K1 c (NonEmpty c)     -- could use Char and Append

instance Functor (K1 c) where
    fmap _ K1Empty          = K1Empty
    fmap f (K1Map g k)      = K1Map (f . g) k
    fmap f (K1Append g a b) = K1Append (\x y -> f (g x y)) a b
    fmap f k                = K1Map f k

instance Apply (K1 c) where
    K1Empty <.> _ = K1Empty
    _ <.> K1Empty = K1Empty

    f <.> x = K1Append ($) f x

    liftF2 = K1Append

instance Alt.Alt (K1 c) where
    K1Empty <!> k = k
    k <!> K1Empty = k
    K1Char a <!> K1Char b = K1Char (RSet.union a b)

    a <!> b = K1Union a b

--
some1 :: K1 c a -> K1 c (NonEmpty a)
some1 K1Empty     = K1Empty
some1 (KPlus _ k) = K1Map pure (KPlus Greedy k)
some1 k           = KPlus Greedy k

-- | 'few1', not 'some1'.
--
-- Let's define two similar regexps
--
-- >>> let re1 = liftF2 (,) (few1 $ char 'a')  (some1 $ char 'a')
-- >>> let re2 = liftF2 (,) (some1 $ char 'a') (few1  $ char 'a')
--
-- Their 'RE' behaviour is the same:
--
-- >>> C.equivalent (toRE re1) (toRE re2)
-- True
--
-- >>> map (C.match $ toRE re1) ["aaa","bbb"]
-- [True,False]
--
-- However, the 'RA' behaviour is different!
--
-- >>> R.match (toRA re1) "aaaaa"
-- Just ('a' :| "",'a' :| "aaa")
--
-- >>> R.match (toRA re2) "aaaaa"
-- Just ('a' :| "aaa",'a' :| "")
--
few1 :: K1 c a -> K1 c (NonEmpty a)
few1 K1Empty     = K1Empty
few1 (KPlus _ k) = K1Map pure (KPlus NonGreedy k)
few1 k           = KPlus NonGreedy k

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

-- | >>> putPretty anyChar
-- ^[^]$
anyChar :: (Ord c, Enum c, Bounded c) => K1 c c
anyChar = K1Char RSet.full

-- | >>> putPretty $ oneof ("foobar" :: [Char])
-- ^[a-bfor]$
oneof :: (Ord c, Enum c, Foldable f) => f c -> K1 c c
oneof = K1Char . RSet.fromList . toList

-- | >>> putPretty $ char 'x'
-- ^x$
char :: (Ord c, Enum c) => c -> K1 c c
char = K1Char . RSet.singleton

-- | >>> putPretty $ charRange 'a' 'z'
-- ^[a-z]$
charRange :: (Enum c, Ord c) => c -> c -> K1 c c
charRange a b = K1Char (RSet.singletonRange (a, b))

-- | >>> putPretty dot
-- ^.$
dot :: K1 Char Char
dot = K1Char dotRSet

-- | >>> putPretty everything1
-- ^[^][^]*$
everything1 :: (Ord c, Enum c, Bounded c) => K1 c (NonEmpty c)
everything1 = some1 anyChar

-- | Matches nothing?
isEmpty :: (Ord c, Enum c, Bounded c) => K1 c a -> Bool
isEmpty k = C.equivalent (toRE k) C.empty

-- | Matches whole input?
isEverything :: (Ord c, Enum c, Bounded c) => K1 c a -> Bool
isEverything k = C.equivalent (toRE k) C.everything

string :: String -> K1 Char (NonEmpty Char)
string []       = error "panic! K1.string []"
string (x : xs) = K1String (x :| xs)

-------------------------------------------------------------------------------
-- Matching
-------------------------------------------------------------------------------

-- | Match using @regex-applicative@
match :: K1 c a -> [c] -> Maybe a
match = R.match . toRA

-------------------------------------------------------------------------------
-- RE
-------------------------------------------------------------------------------

-- | Convert to 'RE'.
--
-- >>> putPretty (toRE $ some1 (string "foo") :: RE.RE Char)
-- ^foo(foo)*$
--
toRE :: (Ord c, Enum c, Bounded c) => K1 c a -> RE.RE c
toRE = toKleene

-- | Convert to any 'Kleene'
toKleene :: C.FiniteKleene c k => K1 c a -> k
toKleene (K1Map _ a)      = toKleene a
toKleene (K1Union a b)    = C.unions [toKleene a, toKleene b]
toKleene (K1Append _ a b) = C.appends [toKleene a, toKleene b]
toKleene (KPlus _ a)      = let k = toKleene a in C.appends [k, C.star k]
toKleene (K1String s)     = C.appends (map C.char $ NE.toList s)
toKleene K1Empty          = C.empty
toKleene (K1Char cs)      = C.fromRSet cs

-------------------------------------------------------------------------------
-- regex-applicative
-------------------------------------------------------------------------------

-- | Convert 'K' to 'R.RE' from @regex-applicative@.
--
-- >>> R.match (toRA (string "xx" .> everything1 <. string "zz" :: K1 Char (NonEmpty Char))) "xxyyzyyzz"
-- Just ('y' :| "yzyy")
--
-- See also 'match'.
--
toRA :: K1 c a -> R.RE c a
toRA K1Empty              = empty
toRA (K1Char cs)          = R.psym (\c -> RSet.member c cs)
toRA (K1Append f a b)     = liftA2 f (toRA a) (toRA b)
toRA (K1Union a b)        = toRA a <|> toRA b
toRA (KPlus Greedy a)     = (:|) <$> toRA a <*> many (toRA a)
toRA (KPlus NonGreedy a)  = (:|) <$> toRA a <*> R.few (toRA a)
toRA (K1Map f a)          = fmap f (toRA a)
toRA (K1String (x :| xs)) = (:|) <$> R.sym x <*> R.string xs

-------------------------------------------------------------------------------
-- nullableProof
-------------------------------------------------------------------------------

-- |
-- >>> putPretty $ nullableProof (pure True)
-- Right 1 , ^[]$
--
-- >>> putPretty $ nullableProof (many "xyz" :: K Char [String])
-- Right [] , ^xyz(xyz)*$
--
-- >>> putPretty $ nullableProof (many $ toList <$> optional "x" <|> many "yz" :: K Char [[String]])
-- Right [] , ^(x|yz(yz)*)(x|yz(yz)*)*$
--
nullableProof :: K c a -> Either (K1 c a) (a, K1 c a)
nullableProof KEmpty    = Left K1Empty
nullableProof (KPure x) = Right (x, K1Empty)
nullableProof (KChar c) = Left (K1Char c)

nullableProof (KAppend f a b) = case (nullableProof a, nullableProof b) of
    (Left x, Left y)               -> Left (K1Append f x y)
    (Left x, Right (y', y))        -> Left ((`f` y') <$> x <!> K1Append f x y)
    (Right (x', x), Left y)        -> Left (K1Append f x y <!> f x' <$> y)
    (Right (x', x), Right (y', y)) -> Right
        (f x' y'
        , K1Append f x y
        <!> flip f y' <$> x
        <!> f x' <$> y
        )

nullableProof (KUnion a b) = case (nullableProof a, nullableProof b) of
    (Left x', Left _)              -> Left x'
    (Right (x, x'), Left y')       -> Right (x, x' <!> y')
    (Left x', Right (y, y'))       -> Right (y, x' <!> y')
    (Right (x, x'), Right (_, y')) -> Right (x, x' <!> y')

nullableProof (KStar g a) = case nullableProof a of
    Left x       -> Right ([], NE.toList <$> star1 x)
    Right (_, x) -> Right ([], NE.toList <$> star1 x) -- note, we don't left recurse
  where
    star1 = case g of
        Greedy    -> some1
        NonGreedy -> few1

nullableProof (KMap f a) = case nullableProof a of
    Right (x, x') -> Right (f x, fmap f x')
    Left x'       -> Left (fmap f x')

nullableProof (KString [])       = Right ([], K1Empty)
nullableProof (KString (c : cs)) = Left (NE.toList <$> K1String (c :| cs))

-------------------------------------------------------------------------------
-- JavaScript
-------------------------------------------------------------------------------

-- | Convert to non-matching JavaScript string which can be used
-- as an argument to @new RegExp@
--
-- >>> putPretty ("foobar" :: K Char String)
-- ^foobar$
--
-- >>> putPretty $ many ("foobar" :: K Char String)
-- ^(foobar)*$
--
instance c ~ Char => Pretty (K1 c a) where
    pretty = pretty . toRE

-------------------------------------------------------------------------------
-- Doctest
-------------------------------------------------------------------------------

-- $setup
--
-- >>> :set -XOverloadedStrings
-- >>> import Control.Applicative (optional)