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

    {- |
       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
        )
    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

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