{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE TypeOperators         #-}

module Language.LSP.Protocol.Types.Edit where

import           Data.Text                         (Text)
import qualified Data.Text                         as T

import           Control.Lens                      hiding (index)
import           Language.LSP.Protocol.Types.Common
import           Language.LSP.Protocol.Internal.Types

-- | Convenience alias for the type in the 'WorkspaceEdit._documentChanges' field.
type DocumentChange = TextDocumentEdit |? CreateFile |? RenameFile |? DeleteFile

-- TODO: get rid of this in favour of the more correct things in VFS
-- | Applies a 'TextEdit' to some 'Text'.
--
-- >>> applyTextEdit (TextEdit (Range (Position 0 1) (Position 0 2)) "i") "foo"
-- "fio"
applyTextEdit :: TextEdit -> Text -> Text
applyTextEdit (TextEdit (Range sp ep) newText) oldText =
  let (_, afterEnd) = splitAtPos ep oldText
      (beforeStart, _) = splitAtPos sp oldText
    in mconcat [beforeStart, newText, afterEnd]
  where
    splitAtPos :: Position -> Text -> (Text, Text)
    splitAtPos (Position sl sc) t =
      -- If we are looking for a line beyond the end of the text, this will give us an index
      -- past the end. Fortunately, T.splitAt is fine with this, and just gives us the whole
      -- string and an empty string, which is what we want.
      let index = sc + startLineIndex sl t
        in T.splitAt (fromIntegral index) t

    -- The index of the first character of line 'line'
    startLineIndex :: UInt -> Text -> UInt
    startLineIndex 0 _ = 0
    startLineIndex line t' =
      case T.findIndex (== '\n') t' of
        Just i -> fromIntegral i + 1 + startLineIndex (line - 1) (T.drop (i + 1) t')
        -- i != 0, and there are no newlines, so this is a line beyond the end of the text.
        -- In this case give the "start index" as the end, so we will at least append the text.
        Nothing -> fromIntegral $ T.length t'

-- | 'editTextEdit' @outer@ @inner@ applies @inner@ to the text inside @outer@.
editTextEdit :: TextEdit -> TextEdit -> TextEdit
editTextEdit (TextEdit origRange origText) innerEdit =
  let newText = applyTextEdit innerEdit origText
    in TextEdit origRange newText

-- | Conversion between 'OptionalVersionedTextDocumentIdentifier' and 'VersionedTextDocumentIdentifier'.
_versionedTextDocumentIdentifier :: Prism' OptionalVersionedTextDocumentIdentifier VersionedTextDocumentIdentifier
_versionedTextDocumentIdentifier = prism down up
  where
    down (VersionedTextDocumentIdentifier uri v) = OptionalVersionedTextDocumentIdentifier uri (InL v)
    up (OptionalVersionedTextDocumentIdentifier uri (InL v)) = Right $ VersionedTextDocumentIdentifier uri v
    up i@(OptionalVersionedTextDocumentIdentifier _ (InR _)) = Left i
