module Coda.Syntax.Rope
  ( Rope(..)
  , splitAtPosition
  , splitAtDelta
  , insertAt
  , deleteRange
  , replaceRange
  , deltaToPosition
  , positionToDelta
  
  , Line(..)
  , foldLines
  , FromText(..)
  
  , LineMeasure(..)
  , HasLineMeasure(..)
  ) where
import Coda.Relative.Delta
import Coda.Syntax.Dyck
import Coda.Syntax.FromText
import Coda.Syntax.Lexer
import Coda.Syntax.Summary
import Control.Lens
import Data.Data
import Data.Default
import qualified Coda.FingerTree as F
import Coda.FingerTree hiding (SearchResult(..))
import Data.Hashable
import Data.Profunctor.Unsafe
import Data.Semigroup
import Data.String
import Data.Text as Text hiding (split)
import qualified Data.Text.Array as Text
import Data.Text.Internal (Text(..))
import Data.Text.Unsafe as Text
import GHC.Generics
import Language.Server.Protocol hiding (error)
import Prelude hiding (lex)
data Line = Line { runLine ::  !Text, content ::  !Dyck }
  deriving (Eq, Ord, Show, Read)
instance Hashable Line where
  hashWithSalt e = hashWithSalt e . runLine
  
  hash = hash . runLine
  
instance FromText Line where
  fromText t = Line t (lex t)
instance IsString Line where
  fromString = fromText . fromString
instance HasDelta Line where
  delta = Delta #. Text.lengthWord16 . runLine
instance Default Line where
  def = fromText ""
cr, lf, crlf :: Text
cr   = "\r"
lf   = "\n"
crlf = "\r\n"
foldLines :: (a -> Text -> a) -> a -> Text -> a
foldLines f z0 (Text a0 o0 l0) = go o0 o0 (o0+l0) a0 z0 where
  
  go !s !i !e !a z
    | i < e = case Text.unsafeIndex a i of
      10             -> go (i+1) (i+1) e a $ f z $ if s < i then Text a s (i+1s) else lf
      13 | i+1 < e -> case Text.unsafeIndex a (i+1) of
           10        -> go (i+2) (i+2) e a $ f z $ if s < i then Text a s (i+2s) else crlf
           _         -> go (i+1) (i+1) e a $ f z $ if s < i then Text a s (i+1s) else cr
         | otherwise -> go (i+1) (i+1) e a $ f z $ if s < i then Text a s (i+1s) else cr
      _ -> go s (i+1) e a z
    | s < e = f z $ Text a s (es)
    | otherwise = z
data LineMeasure = LineMeasure  !Int  !Delta  !Summary
  deriving (Eq, Ord, Show, Read, Data, Generic)
instance HasDelta LineMeasure where
  delta (LineMeasure _ d _) = d
instance Hashable LineMeasure
instance Semigroup LineMeasure where
  LineMeasure l d v <> LineMeasure l' d' v' = LineMeasure (l + l') (d + d') (mergeSummary l d v l' d' v')
instance Monoid LineMeasure where
  mempty = LineMeasure 0 0 def
  mappend = (<>)
instance Measured Line where
  type Measure Line = LineMeasure
  measure (Line l a) = LineMeasure 1 (Delta $ Text.lengthWord16 l) (summarize l a)
instance Default LineMeasure where
  def = LineMeasure 0 0 def
class HasDelta t => HasLineMeasure t where
  lineMeasure :: t -> LineMeasure
  lineCount :: t -> Int
  lineCount = lineCount . lineMeasure
instance HasLineMeasure LineMeasure where
  lineCount (LineMeasure l _ _) = l
  lineMeasure = id
newtype Rope = Rope (FingerTree Line)
  deriving Show
instance Semigroup Rope where
  Rope l <> Rope r = Rope $ case l of
    ll :> Line m _
      | Text.lengthWord16 m > 0 -> case Text.last m of
        '\n' -> l <> r
        '\r' -> case r of
          Line "\n" _ :< rr -> ll <> (fromText (Text.snoc m '\n') <| rr) 
          _ -> l <> r
        _ -> case r of 
          Line n _ :< rr -> ll <> (fromText (mappend m n) <| rr)
          _ -> l 
     | otherwise -> ll <> r
    _ -> r
instance Monoid Rope where
  mempty = Rope mempty
  mappend = (<>)
instance Measured Rope where
  type Measure Rope = LineMeasure
  measure (Rope r) = measure r
instance FromText Rope where
  fromText = foldLines step (Rope mempty) where
    step (Rope xs) x = Rope (xs |> fromText x)
instance IsString Rope where
 fromString = fromText . pack
splitAtPosition :: Position -> Rope -> (Rope, Rope)
splitAtPosition (Position lno cno) (Rope xs) = case search (\x _ -> lineCount x >= lno) xs of
  F.Position l lm@(Line m _) r
    | cno < Text.lengthWord16 m ->
      (Rope $ l |> fromText (Text.takeWord16 cno m), Rope $ fromText (Text.dropWord16 cno m) <| r)
    | otherwise -> (Rope (l |> lm), Rope r)
  F.OnLeft -> (Rope xs, mempty)
  F.OnRight -> (mempty, Rope xs)
  F.Nowhere -> error "splitAtPosition: nowhere"
splitAtDelta :: Delta -> Rope -> (Rope, Rope)
splitAtDelta d (Rope xs) = case search (\x _ -> delta x >= d) xs of
  F.Position l (Line m _) r
      | !cno <- units d  units (measure l) ->
        (Rope $ l |> fromText (Text.takeWord16 cno m), Rope $ fromText (Text.dropWord16 cno m) <| r)
  F.OnLeft -> (Rope xs, mempty)
  F.OnRight -> (mempty, Rope xs)
  F.Nowhere -> error "splitAtDelta: nowhere"
insertAt :: Position -> Text -> Rope -> Rope
insertAt p t rope = case splitAtPosition p rope of
  (l, r) -> l <> fromText t <> r
deleteRange :: Range -> Rope -> Rope
deleteRange (Range lo hi) doc = case splitAtPosition hi doc of
  (m, r) -> fst (splitAtPosition lo m) <> r
replaceRange :: Range -> Text -> Rope -> Rope
replaceRange (Range lo hi) t doc = case splitAtPosition hi doc of
  (m, r) -> fst (splitAtPosition lo m) <> fromText t <> r
deltaToPosition :: Rope -> Delta -> Position
deltaToPosition (Rope t) (Delta d) = case split (\x -> units x >= d) t of
  (l, _) | ml <- measure l -> Position (lineCount ml) (d  units ml)
positionToDelta :: Rope -> Position -> Delta
positionToDelta (Rope t) (Position nl c16) = case split (\x -> lineCount x >= nl) t of
  (l, _) -> Delta $ units (measure l) + c16