{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-}
module Text.Regex.TDFA.Pipes
( Regex
, CompOption
, ExecOption
, compile
, execute
, regexec
, ProjectChar(..)
) where

import Prelude
  ( Monad(..)
  , Functor(..)
  , String
  , Either(..)
  , (.)
  , (++)
  , Maybe(..)
  , show
  , Char
  , fst
  , tail
  , map
  , id
  , either
  )
--

import Text.Regex.TDFA.NewDFA.Uncons
  ( Uncons(..)
  )
--

import Text.Regex.TDFA.NewDFA.Engine
  ( execMatch
  )
--

import Text.Regex.Base.Impl
  ( polymatch
  , polymatchM
  )
--

import Text.Regex.TDFA.Common
 ( CompOption
 , ExecOption
 )
--

import Text.Regex.TDFA.TDFA
 ( patternToRegex
 )
--

import Text.Regex.Base
 ( RegexLike(..)
 , RegexOptions
 , Extract(..)
 , RegexContext(..)
 , MatchArray
 , matchOnce
 )
--

import Text.Regex.TDFA
 ( Regex
 )
--

import Text.Regex.TDFA.ReadRegex
 ( parseRegex
 )
--

import Pipes
 ( (<-<)
 )
--

import Pipes as Pipe
 ( next
 )
--

import qualified Pipes.Prelude as Pipe
 ( take
 , drop
 , toListM
 )
--

import Pipes.Core
 ( Producer
 )
--

import Control.Lens
 ( (^?)
 , over
 , _Just
 , _1
 , _Right
 , each
 , firstOf
 )
--

import Data.Foldable
 ( Foldable
 , asum
 )
--

import Data.Array.IArray
 ( (!)
 , elems
 , amap
 )
--

import Control.Monad.Identity
 ( Identity(..)
 )
--

------------------------------------------------------------------------
-- ProjectChar
------------------------------------------------------------------------

-- | Types which can have a Char projected from them.
--
class ProjectChar a where
  -- | Project a Char from the type.
  --
  projectChar :: a -> Char
--

instance ProjectChar Char where
  projectChar = id
--

instance (ProjectChar a, ProjectChar b) =>
         ProjectChar (Either a b)
  where
  projectChar = either projectChar projectChar
--

instance (ProjectChar a) => ProjectChar (Identity a) where
  projectChar = projectChar . runIdentity
--

instance (ProjectChar a, Monad m, Foldable m, Functor m) =>
         Uncons (Producer a m r)
  where
  uncons = over (_Just . _1) projectChar
           . asum
           . fmap (^? _Right)
           . Pipe.next
--

------------------------------------------------------------------------
-- Adapting Pipes to TDFA
------------------------------------------------------------------------

instance (Monad m) => Extract (Producer a m ()) where
  before = (<-<) . Pipe.take
  after = (<-<) . Pipe.drop
  empty = return ()
  extract (off, len) = before len . after off
--

instance (ProjectChar a, Monad m, Foldable m, Functor m) =>
         RegexLike Regex (Producer a m ())
  where
  matchOnce r p = firstOf each (matchAll r p)
  matchAll r p = execMatch r 0 '\n' p
--

instance (ProjectChar a, Monad m, Foldable m, Functor m) =>
         RegexContext Regex (Producer a m ()) (Producer a m ())
  where
  match = polymatch
  matchM = polymatchM
--

------------------------------------------------------------------------
-- Exported API
------------------------------------------------------------------------

-- | Takes the output of a Producer to form a regular expression. This
--   expression is then compiled using the options provided. If
--   compilation fails an error message is returned.
--
--   If your regular expression is expressed as a String literal or
--   some other "Data.Foldable" then "Pipes.each" is useful.
--
compile :: (ProjectChar a, Monad m, Foldable m, Functor m) =>
           CompOption
           -> ExecOption
           -> Producer a m ()
           -> Either String Regex
compile compOpt execOpt regexp =
  case parseRegex (map projectChar (asum (Pipe.toListM regexp))) of
    Left  err -> Left ( "parseRegex for Text.Regex.TDFA.Pipes failed: "
                        ++ show err
                      )
    Right pat -> Right (patternToRegex pat compOpt execOpt)
--

-- | Execute a compiled regular expression on the output of a Producer.
--
execute :: (ProjectChar a, Monad m, Foldable m, Functor m) =>
           Regex -> Producer a m () -> Either a (Maybe MatchArray)
execute r p = Right (matchOnce r p)


regexec :: (ProjectChar a, Monad m, Foldable m, Functor m) =>
           Regex
           -> Producer a m ()
           -> Either String ( Maybe ( Producer a m ()
                                    , Producer a m ()
                                    , Producer a m ()
                                    , [Producer a m ()]
                                    )
                            )
regexec r bs =
  case matchOnceText r bs of
    Nothing -> Right Nothing
    Just (pre,mt,post) ->
      let main = fst (mt!0)
          rest = map fst (tail (elems mt)) -- will be []
      in Right (Just (pre,main,post,rest))
--