module Nbparts.Util.Text where

import Control.Monad qualified as Monad
import Data.List qualified as List
import Data.Ord qualified as Ord
import Data.Text (Text)
import Data.Text qualified as Text

lineColToIndex :: [Text] -> Int -> Int -> Maybe Int
lineColToIndex :: [Text] -> Int -> Int -> Maybe Int
lineColToIndex [Text]
textLines Int
line Int
col
  | Int
line Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 Bool -> Bool -> Bool
|| Int
col Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
0 = Maybe Int
forall a. Maybe a
Nothing
  | Bool
otherwise = Int -> Int -> [Text] -> Maybe Int
go Int
1 Int
0 [Text]
textLines
  where
    go :: Int -> Int -> [Text] -> Maybe Int
    go :: Int -> Int -> [Text] -> Maybe Int
go Int
_ Int
_ [] = Maybe Int
forall a. Maybe a
Nothing -- Not enough lines.
    go Int
currentLine Int
charsBefore (Text
l : [Text]
ls)
      | Int
currentLine Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
line =
          if Int
col Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Text -> Int
Text.length Text
l Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1
            then Int -> Maybe Int
forall a. a -> Maybe a
Just (Int -> Maybe Int) -> Int -> Maybe Int
forall a b. (a -> b) -> a -> b
$ Int
charsBefore Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
col Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
1
            else Maybe Int
forall a. Maybe a
Nothing
      | Bool
otherwise =
          -- +1 to `Text.length l` for the newline `Text.lines` removed
          Int -> Int -> [Text] -> Maybe Int
go (Int
currentLine Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) (Int
charsBefore Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Text -> Int
Text.length Text
l Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
1) [Text]
ls

-- NOTE: Returns Nothing when slices overlap.
replaceSlices :: Text -> [((Int, Int), Text)] -> Maybe Text
replaceSlices :: Text -> [((Int, Int), Text)] -> Maybe Text
replaceSlices Text
input [((Int, Int), Text)]
replacements = Int -> [((Int, Int), Text)] -> Maybe Text
go Int
0 [((Int, Int), Text)]
sortedReplacements
  where
    sortedReplacements :: [((Int, Int), Text)]
sortedReplacements = (((Int, Int), Text) -> ((Int, Int), Text) -> Ordering)
-> [((Int, Int), Text)] -> [((Int, Int), Text)]
forall a. (a -> a -> Ordering) -> [a] -> [a]
List.sortBy ((((Int, Int), Text) -> (Int, Int))
-> ((Int, Int), Text) -> ((Int, Int), Text) -> Ordering
forall a b. Ord a => (b -> a) -> b -> b -> Ordering
Ord.comparing ((Int, Int), Text) -> (Int, Int)
forall a b. (a, b) -> a
fst) [((Int, Int), Text)]
replacements

    go :: Int -> [((Int, Int), Text)] -> Maybe Text
    go :: Int -> [((Int, Int), Text)] -> Maybe Text
go Int
pos [] = Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
Text.drop Int
pos Text
input
    go Int
pos (((Int
start, Int
end), Text
replacement) : [((Int, Int), Text)]
rs) = do
      -- Bounds checking.
      Bool -> Maybe () -> Maybe ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
Monad.unless
        ( Int
start Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0
            Bool -> Bool -> Bool
&& Int
end Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
0
            Bool -> Bool -> Bool
&& Int
start Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
end
            Bool -> Bool -> Bool
&& Int
start Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
>= Int
pos
        )
        Maybe ()
forall a. Maybe a
Nothing

      let current :: Text
current = Int -> Text -> Text
Text.drop Int
pos Text
input
          relStart :: Int
relStart = Int
start Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
pos
          relEnd :: Int
relEnd = Int
end Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
pos

      let (Text
before, Text
remainder) = Int -> Text -> (Text, Text)
Text.splitAt Int
relStart Text
current

      -- If the length of the replaced text we got is less than
      -- the expected length of `relEnd - relStart`, that means
      -- the indices are out of range.
      let replaced :: Text
replaced = Int -> Text -> Text
Text.take (Int
relEnd Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
relStart) Text
remainder
      Bool -> Maybe () -> Maybe ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
Monad.when (Text -> Int
Text.length Text
replaced Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
relEnd Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
relStart) Maybe ()
forall a. Maybe a
Nothing

      Text
after <- Int -> [((Int, Int), Text)] -> Maybe Text
go Int
end [((Int, Int), Text)]
rs
      Text -> Maybe Text
forall a. a -> Maybe a
Just (Text -> Maybe Text) -> Text -> Maybe Text
forall a b. (a -> b) -> a -> b
$ Text
before Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
replacement Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
after

splitKeepNewlines :: Text -> [Text]
splitKeepNewlines :: Text -> [Text]
splitKeepNewlines Text
txt
  | Text -> Bool
Text.null Text
txt = []
  | Bool
otherwise =
      let (Text
before, Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
Text.break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\n') Text
txt
       in case Text -> Maybe (Char, Text)
Text.uncons Text
rest of
            Just (Char
'\n', Text
rest') -> (Text
before Text -> Char -> Text
`Text.snoc` Char
'\n') Text -> [Text] -> [Text]
forall a. a -> [a] -> [a]
: Text -> [Text]
splitKeepNewlines Text
rest'
            Maybe (Char, Text)
_ -> [Text
before]

findSliceBetween :: Int -> Int -> Text -> Text -> Maybe (Int, Int)
findSliceBetween :: Int -> Int -> Text -> Text -> Maybe (Int, Int)
findSliceBetween Int
searchStart Int
searchEnd Text
haystack = Int -> Text -> Text -> Maybe (Int, Int)
findSliceFrom Int
searchStart (Int -> Text -> Text
Text.take Int
searchEnd Text
haystack)

findSliceFrom :: Int -> Text -> Text -> Maybe (Int, Int)
findSliceFrom :: Int -> Text -> Text -> Maybe (Int, Int)
findSliceFrom Int
searchStart Text
haystack Text
needle = do
  (Int
startIdx, Int
endIdx) <- Text -> Text -> Maybe (Int, Int)
findSlice (Int -> Text -> Text
Text.drop Int
searchStart Text
haystack) Text
needle
  (Int, Int) -> Maybe (Int, Int)
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Int
startIdx Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
searchStart, Int
endIdx Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
searchStart)

findSlice :: Text -> Text -> Maybe (Int, Int)
findSlice :: Text -> Text -> Maybe (Int, Int)
findSlice Text
haystack Text
needle = do
  Int
startIdx <- Text -> Text -> Maybe Int
findSliceStart Text
haystack Text
needle
  let endIdx :: Int
endIdx = Int
startIdx Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Text -> Int
Text.length Text
needle
  (Int, Int) -> Maybe (Int, Int)
forall a. a -> Maybe a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Int
startIdx, Int
endIdx)

findSliceStart :: Text -> Text -> Maybe Int
findSliceStart :: Text -> Text -> Maybe Int
findSliceStart Text
haystack Text
needle =
  if Text -> Bool
Text.null Text
remaining
    then Maybe Int
forall a. Maybe a
Nothing
    else Int -> Maybe Int
forall a. a -> Maybe a
Just (Int -> Maybe Int) -> Int -> Maybe Int
forall a b. (a -> b) -> a -> b
$ Text -> Int
Text.length Text
prefix
  where
    (Text
prefix, Text
remaining) = HasCallStack => Text -> Text -> (Text, Text)
Text -> Text -> (Text, Text)
Text.breakOn Text
needle Text
haystack