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

    {- |
       Module     : Text.XML.HXT.RelaxNG.XmlSchema.RegexMatch
       Copyright  : Copyright (C) 2005 Uwe Schmidt
       License    : MIT

       Maintainer : Uwe Schmidt (uwe@fh-wedel.de)
       Stability  : experimental
       Portability: portable

       Convenient functions for W3C XML Schema Regular Expression Matcher.
       For internals see 'Text.XML.HXT.RelaxNG.XmlSchema.Regex'

       Grammar can be found under <http://www.w3.org/TR/xmlschema11-2/#regexs>

    -}

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

    module Text.XML.HXT.RelaxNG.XmlSchema.RegexMatch
        ( matchRE
        , splitRE
        , sedRE
        , tokenizeRE
        , tokenizeRE'

        , match
        , tokenize
        , tokenize'
        , sed
        , split

        , splitRegex
        )
    where

    import Data.Maybe

    import Text.XML.HXT.RelaxNG.XmlSchema.Regex
    import Text.XML.HXT.RelaxNG.XmlSchema.RegexParser

    {-
    import qualified Debug.Trace as T
    -}

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

    splitRegex              :: Regex -> String -> Maybe (String, String)
    splitRegex re ""
        | nullable re       = Just ("", "")
        | otherwise         = Nothing

    splitRegex re inp@(c : inp')
        | isZero   re       = Nothing
        | otherwise         = evalRes . splitRegex {- (T.trace (show re') re') -} re' $ inp'
        where
        re' = delta re c
        evalRes Nothing
            | nullable re   = Just ("", inp)
            | otherwise     = Nothing

        evalRes (Just (tok, rest))
                            = Just (c : tok, rest)

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

    -- | split a string by taking the longest prefix matching a regular expression
    --
    -- @Nothing@ is returned in case of a syntactically wrong regex string
    -- or in case there is no matching prefix, else the pair of prefix and rest is returned
    --
    -- examples:
    --
    -- > splitRE "a*b" "abc" = Just ("ab","c")
    -- > splitRE "a*"  "bc"  = Just ("", "bc")
    -- > splitRE "a+"  "bc"  = Nothing
    -- > splitRE "["   "abc" = Nothing

    splitRE :: String -> String -> Maybe (String, String)
    splitRE re input
        = either (const Nothing) (flip splitRegex input) . parseRegex $ re

    -- | convenient function for splitRE
    --
    -- syntax errors in R.E. are interpreted as no matching prefix found

    split           :: String -> String -> (String, String)
    split re input
        = fromMaybe ("", input) . splitRE re $ input

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

    -- | split a string into tokens (words) by giving a regular expression
    -- which all tokens must match
    --
    -- This can be used for simple tokenizers.
    -- The words in the result list contain at least one char.
    -- All none matching chars are discarded. If the given regex contains syntax errors,
    -- @Nothing@ is returned
    --
    -- examples:
    --
    -- > tokenizeRE "a*b" ""         = Just []
    -- > tokenizeRE "a*b" "abc"      = Just ["ab"]
    -- > tokenizeRE "a*b" "abaab ab" = Just ["ab","aab","ab"]
    -- >
    -- > tokenizeRE "[a-z]{2,}|[0-9]{2,}|[0-9]+[.][0-9]+" "ab123 456.7abc"
    -- >                                = Just ["ab","123","456.7","abc"]
    -- >
    -- > tokenizeRE "[a-z]*|[0-9]{2,}|[0-9]+[.][0-9]+" "cab123 456.7abc"
    -- >                                = Just ["cab","123","456.7","abc"]
    -- >
    -- > tokenizeRE "[^ \t\n\r]*" "abc def\t\n\rxyz"
    -- >                                = Just ["abc","def","xyz"]
    -- >
    -- > tokenizeRE "[^ \t\n\r]*"    = words

    tokenizeRE      :: String -> String -> Maybe [String]
    tokenizeRE regex input
        = either (const Nothing) (Just . flip token' input) $ parseRegex regex
        where
        token'      :: Regex -> String -> [String]
        token' re inp
            | null inp      = []
            | otherwise     = evalRes . splitRegex re $ inp
            where
            token''         = token' re
            evalRes Nothing = token'' (tail inp)            -- re does not match any prefix
            evalRes (Just (tok, rest))
                | null tok  = token'' (tail rest)           -- re is nullable and only the empty prefix matches
                | otherwise = tok : token'' rest            -- token found, tokenize the rest

    -- | convenient function for tokenizeRE a string
    --
    -- syntax errors in R.E. result in an empty list

    tokenize        :: String -> String -> [String]
    tokenize re
        = fromMaybe [] . tokenizeRE re

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

    -- | split a string into tokens and delimierter by giving a regular expression
    -- wich all tokens must match
    --
    -- This is a generalisation of the above 'tokenizeRE' functions.
    -- The none matching char sequences are marked with @Left@, the matching ones are marked with @Right@
    --
    -- If the regular expression contains syntax errors @Nothing@ is returned
    --
    -- The following Law holds:
    --
    -- > concat . map (either id id) . fromJust . tokenizeRE' re == id

    tokenizeRE'     :: String -> String -> Maybe [Either String String]
    tokenizeRE' regex input
        = either (const Nothing) (Just . flip token' input) $ parseRegex regex
        where
        token'      :: Regex -> String -> [Either String String]
        token' re
            = tok2 ""
            where
            tok2 :: String -> String -> [Either String String]
            tok2 noMatchPrefix inp
                | null inp  = addNoMatch []
                | otherwise = evalRes . splitRegex re $ inp
                where
                addNoMatch res
                    | null noMatchPrefix    = res
                    | otherwise             = (Left . reverse $ noMatchPrefix) : res

                evalRes Nothing             = tok2 (head inp : noMatchPrefix) (tail inp)            -- re does not match any prefix
                evalRes (Just (tok, rest))
                    | null tok              = tok2 (head rest : noMatchPrefix) (tail rest)          -- re is nullable and only the empty prefix matches
                    | otherwise             = addNoMatch . (Right tok :) . tok2 "" $ rest           -- token found, tokenize the rest

    -- | convenient function for tokenizeRE'
    --
    -- When the regular expression contains errors @[Left input]@ is returned, that means tokens are found

    tokenize'       :: String -> String -> [Either String String]
    tokenize' regex input
        = fromMaybe [Left input] . tokenizeRE' regex $ input

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

    -- | sed like editing function
    --
    -- All matching tokens are edited by the 1. argument, the editing function,
    -- all other chars remain as they are
    --
    -- examples:
    --
    -- > sedRE (const "b") "a" "xaxax"       = Just "xbxbx"
    -- > sedRE (\ x -> x ++ x) "a" "xax"     = Just "xaax"
    -- > sedRE undefined       "[" undefined = Nothing

    sedRE           :: (String -> String) -> String -> String -> Maybe String
    sedRE edit regex input
        = maybe Nothing (Just . concatMap (either id edit)) $ tokenizeRE' regex input

    -- | convenient function for sedRE
    --
    -- When the regular expression contains errors, sed is the identity, else
    -- the funtionality is like 'sedRE'
    --
    -- > sed undefined "["  == id

    sed             :: (String -> String) -> String -> String -> String
    sed edit regex input
        = fromMaybe input . sedRE edit regex $ input

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

    -- | match a string with a regular expression
    --
    -- First argument is the regex, second the input string,
    -- if the regex is not well formed, @Nothing@ is returned,
    -- else @Just@ the match result
    --
    -- Examples:
    --
    -- > matchRE "x*" "xxx" = Just True
    -- > matchRE "x" "xxx"  = Just False
    -- > matchRE "[" "xxx"  = Nothing

    matchRE :: String -> String -> Maybe Bool
    matchRE regex input
        = either (const Nothing) (Just . isNothing . flip matchWithRE input) $ parseRegex regex

    -- | convenient function for matchRE
    --
    -- syntax errors in R.E. are interpreted as no match found

    match           :: String -> String -> Bool
    match re        = fromMaybe False . matchRE re

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