{-# LANGUAGE NamedFieldPuns #-}
module Data.Owoify.Internal.Entity.Word
  ( InnerWord(..)
  , innerReplace
  , innerReplaceWithFuncSingle
  , innerReplaceWithFuncMultiple
  , toText
  )
  where

import Prelude

import Data.Maybe (listToMaybe)
import Data.Text.Lazy (strip, Text)
import qualified Data.Text.Lazy (replace)
import Text.RE.PCRE.Text.Lazy ((*=~), anyMatches, matches, RE)
import Text.RE.Replace (replaceAll)
import Data.List (nub)

-- | Basic type for manipulating strings.
data InnerWord = InnerWord
  { InnerWord -> Text
innerWord :: Text
  , InnerWord -> [Text]
innerReplacedWords :: [Text]
  } deriving (InnerWord -> InnerWord -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: InnerWord -> InnerWord -> Bool
$c/= :: InnerWord -> InnerWord -> Bool
== :: InnerWord -> InnerWord -> Bool
$c== :: InnerWord -> InnerWord -> Bool
Eq, Int -> InnerWord -> ShowS
[InnerWord] -> ShowS
InnerWord -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [InnerWord] -> ShowS
$cshowList :: [InnerWord] -> ShowS
show :: InnerWord -> String
$cshow :: InnerWord -> String
showsPrec :: Int -> InnerWord -> ShowS
$cshowsPrec :: Int -> InnerWord -> ShowS
Show)

toText :: InnerWord -> Text
toText :: InnerWord -> Text
toText InnerWord{ Text
innerWord :: Text
innerWord :: InnerWord -> Text
innerWord } = Text
innerWord

testAndGetReplacingWord :: RE -> Text -> Text -> Text
testAndGetReplacingWord :: RE -> Text -> Text -> Text
testAndGetReplacingWord RE
searchValue Text
replaceValue Text
str =
  let matchedItems :: Matches Text
matchedItems = Text
str Text -> RE -> Matches Text
*=~ RE
searchValue in
  if forall a. Matches a -> Bool
anyMatches Matches Text
matchedItems then
    case forall a. [a] -> Maybe a
listToMaybe forall a b. (a -> b) -> a -> b
$ forall a. Matches a -> [a]
matches Matches Text
matchedItems of
      Maybe Text
Nothing -> Text
str
      Just Text
hd -> HasCallStack => Text -> Text -> Text -> Text
Data.Text.Lazy.replace Text
hd Text
replaceValue Text
str
  else
    Text
str

containsReplacedWords :: InnerWord -> RE -> Text -> Bool
containsReplacedWords :: InnerWord -> RE -> Text -> Bool
containsReplacedWords InnerWord { [Text]
innerReplacedWords :: [Text]
innerReplacedWords :: InnerWord -> [Text]
innerReplacedWords } RE
searchValue Text
replaceValue =
  forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
any (\Text
s -> let matchedItems :: Matches Text
matchedItems = Text
s Text -> RE -> Matches Text
*=~ RE
searchValue in
    forall a. Matches a -> Bool
anyMatches Matches Text
matchedItems Bool -> Bool -> Bool
&& (
    let replacedWord :: Text
replacedWord = HasCallStack => Text -> Text -> Text -> Text
Data.Text.Lazy.replace (forall a. [a] -> a
head forall a b. (a -> b) -> a -> b
$ forall a. Matches a -> [a]
matches Matches Text
matchedItems) Text
replaceValue Text
s in
    Text
replacedWord forall a. Eq a => a -> a -> Bool
== Text
s)) [Text]
innerReplacedWords

buildCollection :: RE -> Text -> [Text]
buildCollection :: RE -> Text -> [Text]
buildCollection RE
searchValue Text
str = forall a. Matches a -> [a]
matches forall a b. (a -> b) -> a -> b
$ Text
str Text -> RE -> Matches Text
*=~ RE
searchValue

buildReplacedWords :: Functor f => Text -> f Text -> f Text
buildReplacedWords :: forall (f :: * -> *). Functor f => Text -> f Text -> f Text
buildReplacedWords Text
replaceValue f Text
texts = (\Text
s -> HasCallStack => Text -> Text -> Text -> Text
Data.Text.Lazy.replace Text
s Text
replaceValue Text
s) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> f Text
texts

-- | Match the `word` against `searchValue` and replace matched strings with `replaceValue`.
innerReplace :: InnerWord -> RE -> Text -> Bool -> InnerWord
innerReplace :: InnerWord -> RE -> Text -> Bool -> InnerWord
innerReplace word :: InnerWord
word@InnerWord { Text
innerWord :: Text
innerWord :: InnerWord -> Text
innerWord, [Text]
innerReplacedWords :: [Text]
innerReplacedWords :: InnerWord -> [Text]
innerReplacedWords } RE
searchValue Text
replaceValue Bool
replaceReplacedWords
  | Bool -> Bool
not Bool
replaceReplacedWords Bool -> Bool -> Bool
&& InnerWord -> RE -> Text -> Bool
containsReplacedWords InnerWord
word RE
searchValue Text
replaceValue = InnerWord
word
  | Bool
otherwise = if Text
replacingWord forall a. Eq a => a -> a -> Bool
== Text
innerWord then InnerWord
word else InnerWord { innerWord :: Text
innerWord = Text
replacingWord, innerReplacedWords :: [Text]
innerReplacedWords = forall a. Eq a => [a] -> [a]
nub forall a b. (a -> b) -> a -> b
$ [Text]
innerReplacedWords forall a. Semigroup a => a -> a -> a
<> [Text]
replacedWords }
    where
      matchedItems :: Matches Text
matchedItems = Text
innerWord Text -> RE -> Matches Text
*=~ RE
searchValue
      collection :: [Text]
collection = forall a. Matches a -> [a]
matches Matches Text
matchedItems
      replacingWord :: Text
replacingWord = case forall a. [a] -> Maybe a
listToMaybe [Text]
collection of
        Maybe Text
Nothing -> Text
innerWord
        Just Text
_ -> Text -> Text
strip forall a b. (a -> b) -> a -> b
$ forall a. Replace a => a -> Matches a -> a
replaceAll Text
replaceValue Matches Text
matchedItems
      replacedWords :: [Text]
replacedWords = forall (f :: * -> *). Functor f => Text -> f Text -> f Text
buildReplacedWords Text
replaceValue [Text]
collection

-- | Match the `word` against `searchValue` and replace matched strings with the string resulting from invoking `f`.
innerReplaceWithFuncSingle :: InnerWord -> RE -> (() -> Text) -> Bool -> InnerWord
innerReplaceWithFuncSingle :: InnerWord -> RE -> (() -> Text) -> Bool -> InnerWord
innerReplaceWithFuncSingle word :: InnerWord
word@InnerWord { Text
innerWord :: Text
innerWord :: InnerWord -> Text
innerWord, [Text]
innerReplacedWords :: [Text]
innerReplacedWords :: InnerWord -> [Text]
innerReplacedWords } RE
searchValue () -> Text
f Bool
replaceReplacedWords
  | Bool -> Bool
not Bool
replaceReplacedWords Bool -> Bool -> Bool
&& InnerWord -> RE -> Text -> Bool
containsReplacedWords InnerWord
word RE
searchValue Text
replaceValue = InnerWord
word
  | Text
replacingWord forall a. Eq a => a -> a -> Bool
== Text
innerWord = InnerWord
word
  | Bool
otherwise = InnerWord { innerWord :: Text
innerWord = Text
replacingWord, innerReplacedWords :: [Text]
innerReplacedWords = forall a. Eq a => [a] -> [a]
nub forall a b. (a -> b) -> a -> b
$ [Text]
innerReplacedWords forall a. Semigroup a => a -> a -> a
<> [Text]
replacedWords }
  where
      replaceValue :: Text
replaceValue = () -> Text
f ()
      replacingWord :: Text
replacingWord
        = Text -> Text
strip
            forall a b. (a -> b) -> a -> b
$ RE -> Text -> Text -> Text
testAndGetReplacingWord RE
searchValue Text
replaceValue Text
innerWord
      collection :: [Text]
collection = RE -> Text -> [Text]
buildCollection RE
searchValue Text
replaceValue
      replacedWords :: [Text]
replacedWords = forall (f :: * -> *). Functor f => Text -> f Text -> f Text
buildReplacedWords Text
replaceValue [Text]
collection

-- | Match the `word` against `searchValue` and replace matched strings with the string resulting from invoking `f`.
-- 
-- The difference between this and `replaceWithFuncSingle` is that the `f` here takes two `String` arguments.
innerReplaceWithFuncMultiple :: InnerWord -> RE -> (Text -> Text -> Text) -> Bool -> InnerWord
innerReplaceWithFuncMultiple :: InnerWord -> RE -> (Text -> Text -> Text) -> Bool -> InnerWord
innerReplaceWithFuncMultiple word :: InnerWord
word@InnerWord { Text
innerWord :: Text
innerWord :: InnerWord -> Text
innerWord, [Text]
innerReplacedWords :: [Text]
innerReplacedWords :: InnerWord -> [Text]
innerReplacedWords } RE
searchValue Text -> Text -> Text
f Bool
replaceReplacedWords
  | Bool -> Bool
not forall a b. (a -> b) -> a -> b
$ forall a. Matches a -> Bool
anyMatches Matches Text
matchedItems = InnerWord
word
  | Bool
otherwise =
    if (Bool -> Bool
not Bool
replaceReplacedWords Bool -> Bool -> Bool
&& InnerWord -> RE -> Text -> Bool
containsReplacedWords InnerWord
word RE
searchValue Text
replaceValue) Bool -> Bool -> Bool
|| (Text
replacingWord forall a. Eq a => a -> a -> Bool
== Text
innerWord) then InnerWord
word
    else InnerWord { innerWord :: Text
innerWord = Text
replacingWord, innerReplacedWords :: [Text]
innerReplacedWords = forall a. Eq a => [a] -> [a]
nub forall a b. (a -> b) -> a -> b
$ [Text]
innerReplacedWords forall a. Semigroup a => a -> a -> a
<> [Text]
replacedWords }
  where
    matchedItems :: Matches Text
matchedItems = Text
innerWord Text -> RE -> Matches Text
*=~ RE
searchValue
    collection :: [Text]
collection = forall a. Matches a -> [a]
matches Matches Text
matchedItems
    (Text
s1 : Text
s2 : Text
s3 : [Text]
_) = if forall (t :: * -> *) a. Foldable t => t a -> Int
length [Text]
collection forall a. Eq a => a -> a -> Bool
== Int
3 then [Text]
collection else [Text
innerWord, Text
innerWord, Text
innerWord]
    replaceValue :: Text
replaceValue = Text -> Text -> Text
f Text
s2 Text
s3
    replacingWord :: Text
replacingWord = Text -> Text
strip forall a b. (a -> b) -> a -> b
$ HasCallStack => Text -> Text -> Text -> Text
Data.Text.Lazy.replace Text
s1 Text
replaceValue Text
innerWord
    replacedWords :: [Text]
replacedWords = forall (f :: * -> *). Functor f => Text -> f Text -> f Text
buildReplacedWords Text
replaceValue [Text]
collection