implementation module EdLineText

// a type for dealing with line-oriented operations on texts

import	StdString, StdClass, StdInt, StdList, StdBool, StdFunc, StdArray, StdTuple
import	StrictList
from	EdPosition	import :: LineNr
import	syncol
import	Platform

//-- stuff we want to parametrise...
/*
class TextAnnot a // b
where
	dummyLineAnnot	:: !a
	annotLine		:: !.{#Char} -> (!a,!.{#Char})
	annotWhole		:: !(StrictList {#Char}) -> (Int, StrictList (a,{#Char}))
	annotPart		:: !Int !Int !(StrictList (a,{#Char})) -> (Int,StrictList (a,{#Char}))
//	annotText		:: b

export TextAnnot Void, SyncInf, ConsInf
*/

:: LineAnnot	:== Int
dummyLineAnnot	:== 0
annotLine		:== slMap (\s->(dummyLineAnnot,s))
annotWhole		:== firstParse
annotPart		:== quickParse
annotText		:== id

/*===>
annotDummy	:== 0
annotWhole	:== ...
annotPart	:== ...
*/

//-- ...stuff

:: Text
	= { nrLines :: Int
	  , blocks  :: [StrictList (!LineAnnot,!String)]
	  }
   
BlockSize :== 80

:: TextFragment 
	:== StrictList String

newText :: Text
newText
  # new			= SCons "" SNil
  # new			= annotLine new
  = { nrLines	= 1
    , blocks	= [ new ]
    }

emptyText :: Text
emptyText =
	{ nrLines	= 0
	, blocks	= [ SNil ]
	}

textToStrings :: !Text -> StrictList String
textToStrings { blocks }
  = blocksToStrings blocks

textToStringsC :: !Text -> StrictList (Int,String)
textToStringsC { blocks }
  = blocksToStringsC blocks

stringsToText :: (StrictList String) -> Text
stringsToText lines
  # (_,lines) = annotWhole lines
  = { nrLines	= slLength lines
    , blocks	= group BlockSize lines
    }

stringsToString :: !(StrictList String) -> String
/*
stringsToString SNil					= ""
stringsToString (SCons string SNil)		= string
stringsToString (SCons string strings)	= string +++. "\n" +++. stringsToString strings
*/
stringsToString ss
	# s = createArray (sSize ss) '\xd'
	= sUpdate 0 s ss
where
	sizeNewline
		= PlatformDependant
			2	// silly DOS
			1	// mac
	newline
		= PlatformDependant
			"\xd\xa"				// windows
			"\xd"					// mac
	
	sSize SNil = 0
	sSize (SCons string SNil) = size string
	sSize (SCons string rest) = size string + sizeNewline + sSize rest
	
	sUpdate i s SNil = s
	sUpdate i s (SCons string SNil)
		# (_,s) = sU (size string) i 0 s string
		= s
	sUpdate i s (SCons string rest)
		# (i,s) = sU (size string) i 0 s string
		# (i,s) = sU sizeNewline i 0 s newline
		= sUpdate i s rest
	
	sU l i j s h
		| j >= l = (i,s)
		# s = update s i h.[j]
		= sU l (inc i) (inc j) s h

stringToStrings :: !String -> StrictList String
stringToStrings string
  = stringToStrings` 0 string 0
where
	stringToStrings` i string start
		# stringsize	= size string
		| i >= stringsize
			= SCons  (string % (start, dec stringsize)) SNil
		# lastchar = string.[i]
		| lastchar == '\xa'
			= SCons	(string % (start, i - 1)) (stringToStrings` (i+1) string (i+1))
		| lastchar == '\xd'
			# haslookahead	= i < dec stringsize
			| haslookahead && (string.[i+1] == '\xa')
				= SCons (string % (start, i - 1)) (stringToStrings` (i+2) string (i+2))
			= SCons (string % (start, i - 1)) (stringToStrings` (i+1) string (i+1))
		= stringToStrings` (i+1) string start
	
StringToText s
	:== stringsToText (stringToStrings s)

blockToStrings :: !(StrictList (Int,String)) -> StrictList String
blockToStrings block = slMap snd block

blocksToStrings :: ![StrictList (Int,String)] -> StrictList String
blocksToStrings [] = SNil
blocksToStrings [block:blocks]
  = slAppend (blockToStrings block) (blocksToStrings blocks)
  
blocksToStringsC :: ![StrictList (Int,String)] -> StrictList (Int,String)
blocksToStringsC [] = SNil
blocksToStringsC [block:blocks]
  = slAppend block (blocksToStringsC blocks)

group :: Int !(StrictList a) -> [(StrictList a)]
group _ SNil	= []
group n xs		= [ ys : group n zs ]
where
	(ys, zs) = slSplitAt n xs 

textLength :: !Text -> Int
textLength { nrLines, blocks } = nrLines

lastLineNr :: !Text -> LineNr
lastLineNr text
  = textLength text - 1
  
validateLineNr :: !LineNr Text -> LineNr
validateLineNr lineNr text
  | lineNr < 0
	= 0
  | lineNr > lastNr
	= lastNr
	= lineNr
where
	lastNr = lastLineNr text

getLine :: !LineNr !u:Text -> !(!String, !u:Text)
getLine linenr text=:{nrLines, blocks}
  # (_,line) = slIndex lineNr` theBlock
  = ( line, text)
where
	nrLines` = dec nrLines
	lineNr | linenr > nrLines` = nrLines`
		= linenr
	lineNr`	 = lineNr - nrSkip * BlockSize
	nrSkip	 = lineNr / BlockSize
	theBlock = blocks!!nrSkip
	
getLineC :: !LineNr !u:Text -> !((!Int,!String), !u:Text)
getLineC linenr text=:{nrLines, blocks}
  # line = slIndex lineNr` theBlock
  = ( line, text)
where
	nrLines` = dec nrLines
	lineNr | linenr > nrLines` = nrLines`
		= linenr
	lineNr`	 = lineNr - nrSkip * BlockSize
	nrSkip	 = lineNr / BlockSize
	theBlock = blocks!!nrSkip
	
// getBlocks returns only the blocks that contain the text range
// denotes by the line numbers. It also returns the number of
// the first block

getBlocks :: LineNr LineNr Text -> (Int, [StrictList (Int,String)])
getBlocks first last { blocks }
  = ( nrSkip
	, blocks%(nrSkip,nrTake)
	)
where
	nrSkip = first / BlockSize
	nrTake = last / BlockSize + 1
  
getLines :: LineNr LineNr Text -> (StrictList String, Text)
getLines first last text
  = ( slTake (last - first + 1) 
			 (slDrop first` (blocksToStrings blocks))
	, text
	)
where
	(firstBlockNr, blocks) = getBlocks first last text
	first` = first - firstBlockNr * BlockSize
	
getLinesC :: LineNr LineNr Text -> (StrictList (Int,String), Text)
getLinesC first last text
  # lines		= textToStringsC text
  # lines		= slTake (last - first + 1) (slDrop first lines)
  = ( lines
	, text
	)
	
removeLine :: !LineNr !Text -> Text
removeLine lineNr text
  = removeLines lineNr lineNr text
 
removeLines :: !LineNr !LineNr !Text -> Text
removeLines first last text=:{ nrLines, blocks }
	| first == 0 && last == lastLineNr text
		= newText
	# nrRemoveLines		= last - first + 1
	# lines				= textToStringsC text
	# (before,after)	= slSplitAt first lines
	# after				= slDrop nrRemoveLines after
	#! lines			= slAppend before after
	# (_,lines)			= annotPart (dec first) (dec first) lines
	= {blocks = group BlockSize lines, nrLines = nrLines - nrRemoveLines}

insertLines :: !LineNr !(StrictList String) !Text -> Text
insertLines lineNr strings text=:{ nrLines, blocks }
	# nrInsertLines		= slLength strings
	# lines				= textToStringsC text
	# (before,after)	= slSplitAt lineNr lines
	# strings			= annotLine strings
	#! after			= slAppend strings after
	#! lines			= slAppend before after
	# (_,lines)			= annotPart (dec lineNr) (lineNr + nrInsertLines) lines
	= {blocks = group BlockSize lines, nrLines = nrLines + nrInsertLines}
	
appendLines :: !(StrictList String) !Text -> !Text
appendLines strings text=:{ nrLines, blocks }
	# nrAppendLines		= slLength strings
	# before			= textToStringsC text
	# after				= annotLine strings
	#! lines			= slAppend before after
	# (_,lines)			= annotPart (dec nrLines) (nrLines + nrAppendLines) lines
	= {blocks = group BlockSize lines, nrLines = nrLines + nrAppendLines}
	
appendLines` :: !(StrictList String) !Text -> !Text
appendLines` strings text=:{ nrLines, blocks }
	# (string,strings) = case strings of
		SNil -> ("",SNil)
		(SCons s ss) -> (s,ss)
	# nrAppendLines		= slLength strings
	# before			= textToStringsC text
	# (before,update)	= slSplitAt (dec nrLines) before
	# after				= annotLine strings
	# update = case slHead update of
		(pl,st) -> (pl,st+++.string)
	#! lines			= slAppend before (SCons update after)
	# (_,lines)			= annotPart (dec nrLines) (nrLines + nrAppendLines) lines
	= {blocks = group BlockSize lines, nrLines = nrLines + nrAppendLines}
	
updateLine :: !LineNr !String !.Text -> (!Int,!.Text)
updateLine lineNr string text=:{ nrLines, blocks }
	# lines				= textToStringsC text
	# (before,after)	= slSplitAt lineNr lines
	# (pl,_)			= slHead after
	# after				= SCons (pl,string) (slTail after)
	#! lines			= slAppend before after
	# (fin,lines)		= annotPart lineNr lineNr lines
	= (fin,{blocks = group BlockSize lines, nrLines = nrLines})

