{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
module Text.StringLike.Matchable (
    Matchable (..),

    checkNull
) where

import Data.List
import Text.StringLike


{- |
    An extention of string-like types for sub-string matching at various postions.
-}
class StringLike str => Matchable str where

    -- | Checks if two strings match exactly. This should by equal to '(==)' for most types.
    matchesExactly  :: str -> str -> Bool
    matchesExactly = (==)

    -- | Checks if the first string is a prefix of of the second. If the first string is empty, the
    --   result is always 'False'.
    matchesPrefixOf :: str -> str -> Bool

    -- | Checks if the first string is an infix of the second, i.e. if the first string appears
    --   somewhere in the second. If the first string is empty, the result is always 'False'.
    matchesInfixOf  :: str -> str -> Bool

    -- | Checks if the first string is a suffix of the second. If the first string is empty, the
    --   result is always 'False'.
    matchesSuffixOf :: str -> str -> Bool

    -- | Checks if the first string matches any of the whitespace-separated substrings in the second
    --   string exactly. If the first string is empty, the result is always 'False'.
    matchesWordOf   :: str -> str -> Bool


instance Matchable String where
    matchesPrefixOf = checkNull null isPrefixOf
    matchesInfixOf  = checkNull null isInfixOf
    matchesSuffixOf = checkNull null isSuffixOf
    matchesWordOf   = \s -> any (`matchesExactly` s) . splitOn (`elem` " \t\n\r")


-- | @checkNull null comp s1 s2@ returns 'False' if either @null s1 == True@ or @comp s1 s2 == False@,
--   and returns 'True' otherwise.
checkNull :: (s -> Bool) -> (s -> s -> Bool) -> (s -> s -> Bool)
checkNull null comp s1 s2 = not (null s1) && comp s1 s2


splitOn :: (Char -> Bool) -> String -> [String]
splitOn pred s = case break pred $ dropWhile pred s of
    ("", "") -> []
    (w, s')  -> w : splitOn pred s'