{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleInstances #-}

-- | pcre-light utility functions for more user friendly usage.
-- Instead of adding execution and compile options to the matching and
-- compiling functions, the options are added to the regular expression to be
-- compiled or run. Furthermore support for different matching return types
-- (using typeclass 'MatchResult') and different regular expression types
-- (compiled or uncompiled using typeclass 'RegexLike') are supported.
--
-- examples using GHC's -XOverloadedStrings flag:
--
-- simple matching with uncompiled pattern "abc" of type ByteString:
--
-- >>> ("abc" =~ ("abc" :: ByteString)) :: Bool
-- True
--
-- case insensitive matching with uncompiled pattern of type ByteString:
--
-- >>> ("AbCasf" =~ caseSensitive False "abc") :: Bool
-- True
--
-- >>> ("AbCasf" =~ caseSensitive False "abc") :: Maybe [ByteString]
-- Just ["AbC"]
--
module Text.Regex.PCRE.Light.Extra where

import Control.Arrow (right)
import Data.ByteString (ByteString)
import Data.Maybe (isJust)

import qualified Text.Regex.PCRE.Light as R

-- | Uncompiled regular expression with compile and execution options.
-- Compiles to RegexExecPattern preserving execution options.
data RegexPattern = Regex [R.PCREOption] [R.PCREExecOption] ByteString

-- | Compiled regular expression with execution results.
data RegexExecPattern = RegexExec [R.PCREExecOption] R.Regex

-- | Typeclass defining automatic conversion of PCRE matching results
-- to user defined type. Used in typeclass 'RegexLike' and function ('=~').
class MatchResult x where
  convert :: Maybe [ByteString] -> x

instance MatchResult (Maybe [ByteString]) where 
  convert = id

instance MatchResult Bool where 
  convert = isJust

-- | RegexLike types can be compiled to regular epxression or directly used
-- as regular expression.
class RegexLike rl where
  -- | Type of compiled regular expression for instance rl.
  type RegexType rl 

  -- | compiles a perl-compatible regular expression to an executable
  -- regular expression. See 'Text.Regex.PCRE.Light.compileM'.
  compile :: rl -> Either String (RegexType rl)

  -- | matches a Bytestring with a regular expression of type rl.
  -- If rl needs to be compiled (for example when rl ~ ByteString or
  -- rl ~ RegexPattern) but is invalid, an exception will be thrown. It is
  -- recommended to use compiled patterns with match for better error handling.
  match   :: (MatchResult res) => rl -> ByteString -> res

instance RegexLike R.Regex where
  type RegexType R.Regex = R.Regex

  compile = Right . id
  match regex str = convert $ R.match regex str []

instance RegexLike ByteString where
  type RegexType ByteString = R.Regex

  compile regex     = R.compileM regex []
  match pattern str = either error (`match` str) $ compile pattern

instance RegexLike RegexExecPattern where
  type RegexType RegexExecPattern = RegexExecPattern

  compile = Right . id
  match (RegexExec opts regex) str = convert $ R.match regex str opts

instance RegexLike RegexPattern where
  type RegexType RegexPattern = RegexExecPattern

  compile (Regex opt execOpt pattern) = 
    right (RegexExec execOpt) $ R.compileM pattern opt

  match r str = either error (`match` str) $ compile r

-- | matches a ByteString with a regular expression.
(=~) :: (RegexLike regex, MatchResult ret) => ByteString -> regex -> ret
(=~) = flip match

-- | create regular expression with compile and execution options.
cfg :: [R.PCREOption] -> [R.PCREExecOption] -> ByteString -> RegexPattern
cfg = Regex

-- | Add execution options to compiled regular expression of pcre-light's 
--   compiled regular expression type 'R.Regex'
withExecOpts :: [R.PCREExecOption] -> R.Regex -> RegexExecPattern
withExecOpts = RegexExec

-- | Create case sensitive (first parameter is 'True') or case insensitive regular expression from pattern.
caseSensitive :: Bool -> ByteString -> RegexPattern
caseSensitive False = cfg [R.caseless, R.no_utf8_check] []
caseSensitive True  = cfg [R.no_utf8_check] []