//	B-Tree Maintenance Routines
// Copyright Xerox Corporation 1979, 1981


//	BTreeWrtMS1.bcpl  -- Routines for writing in B-Trees
//		Uses pagination strategy MS to minimize the sum of lengths
//		of records posted to the father page.

//	last edited November 26, 1981  12:38 PM by Taft

get "btree.decl"


external
	[ Allocate	// Defined by OS
	Free
	MoveBlock

	ReadBTreePage	// defined by OpenBTree
	WriteBTreePage
	LockBTreePtr
	UnlockBTreePtr
	AllocateBTreePage
	BTreeBug

	PathRecLE		// Defined in BTreeRead.bcpl
	BackUpOneRecord
	ReadPageAndLockBTreePtr
	]


external
	[ UpdateRecord	// Defined in BTreeWrtMS.bcpl
	InsertRecords
	ComplexInsertRecords
	MakeNewRoot
	PageLength
	EntryLength
	FillLeftPage
	FillRightPage
	AppendEntSeqEnt
	PushEntSeqEnt
	MakeEntSeqEnt
	ComputeEntLens
	PushEntSeqEntLens
	AppendEntSeqEntLens
	DepositESL
	FindRightBrother
	FindLeftBrother
	RemoveEntry
	BasicRemoveEntry
	WriteRightBrother
	WritePage
	AddToHeap
	TrickleDown
	RemoveFromHeap
	SiftUp
	]


let ComplexInsertRecords(Tree, PathStk, IS) = valof

	[
	let PathStkTop = PathStk>>PS.PathStkTop
	let SP = lv PathStk>>PS.PSE↑PathStkTop
	let SFP = SP - size PSE/16
	let MaxFreeWords = IS>>IS.MaxFreeWords

//	Not all the entries will fit on the current page. Try
//	spilling over onto the right brother page, or onto the
//	left brother page if there isn't a right brother.

	SFP>>PSE.LeastSon = SP>>PSE.PageNo  // In case this is the
					   // root page splitting

	let RtBroPg1 = Empty
	if PathStkTop gr 1 then
		[ RtBroPg1 = FindRightBrother(IS, SFP, -MaxFreeWords, SP)
		if RtBroPg1 eq Empty then
			RtBroPg1 = FindLeftBrother(IS, SFP, -MaxFreeWords, SP)
		]

	if RtBroPg1 eq Empty then
		RtBroPg1 = AllocateBTreePage(Tree)

	let OneBrotherEnough = false
	if IS>>IS.ELLLen ls 3 then
		BTreeBug(Tree, ecEntryListTooShort)

	let FatherEnt = FillLeftPage(IS, 0)

	let BestFatherEnt = nil
	let BestFatherLen = MaxFreeWords+1 // Causes BestFatherEnt
					//   to be updated the
					//   first time around.

//	The idea next is to send the shortest entry into
//	the father page such that the current page is at least
//	"pretty" full (if we have such a choice).

	while valof
		[ let PL1 = PageLength(IS, FatherEnt)
		if PL1 gr MaxFreeWords then resultis false
		let PL0 = PageLength(IS, 0, FatherEnt)
		resultis (PL0 gr 0) & (PL0+PL1 le
					MaxFreeWords+IS>>IS.AwfullyFull)
		]
	     do
		// Still enough room in brother page. See if this
		// is shortest father entry, and try moving one
		// more entry into brother page.

		[ let FatherLen = EntryLength(IS, FatherEnt)
		if FatherLen ls BestFatherLen then
			[ BestFatherEnt = FatherEnt
			BestFatherLen = FatherLen
			OneBrotherEnough = true
			]
		FatherEnt = FatherEnt-1
		]

	if OneBrotherEnough then
		[ let BreakLen = PageLength(IS, 0, BestFatherEnt)
		let TotLen = PageLength(IS, 0)

		WritePage(IS, SP, SP>>PSE.PageNo, BreakLen)

		PushEntSeqEnt(SFP,
			WriteRightBrother(IS, SP, SFP, RtBroPg1,
				TotLen-BreakLen))
		resultis Empty
		]

//	At this point we know that at least the current page and
//	two brother pages are involved.

	resultis RtBroPg1
 	]


and FindRightBrother(IS, StkFatherPtr, SpaceNeeded, PathStkEntry)
		= valof

	[ let Tree = IS>>IS.Tree
	let LR = Tree>>TREE.LengthRtn
	let FatherEnt = nil
	let FatherPtr = 0
	LockBTreePtr(Tree, lv FatherPtr)

	test StkFatherPtr>>PSE.ESLFront eq Empty
	ifso
		[ FatherPtr = ReadBTreePage(Tree, StkFatherPtr>>PSE.PageNo)
		let FatherFreeWords = FatherPtr>>BTP.FreeWords
		let FatherOffset = StkFatherPtr>>PSE.Offset
		FatherEnt = FatherPtr+FatherOffset

		if ((FatherOffset-Rec1Offset) ge
			(IS>>IS.MaxFreeWords-FatherFreeWords)) then
				[ UnlockBTreePtr(Tree, lv FatherPtr)
				resultis Empty
				]
		]
	ifnot FatherEnt = (StkFatherPtr>>PSE.ESLFront)>>ESLE.EntSeqP

	let  FatherEntLen = (offset BTE.Record/16)+
		LR(lv FatherEnt>>BTE.Record)
	let RtBroPg = FatherEnt>>BTE.GrPtr

	let RtBroPtr = ReadBTreePage(Tree, RtBroPg)
	unless RtBroPtr>>BTP.FreeWords-FatherEntLen ge SpaceNeeded
		do
		[ UnlockBTreePtr(Tree, lv FatherPtr)
		resultis Empty
		]


	LockBTreePtr(Tree, lv RtBroPtr)

	let RtBroLen = IS>>IS.MaxFreeWords-(RtBroPtr>>BTP.
			FreeWords)

	let NewESE = MakeEntSeqEnt(Tree>>TREE.Zone, lv (RtBroPtr>>BTP.BTEBlock),
				RtBroLen)

	let ESE = RemoveEntry(Tree, StkFatherPtr)
	AppendEntSeqEntLens(IS, ESE)
	AppendEntSeqEnt(PathStkEntry, ESE)
	AppendEntSeqEntLens(IS, NewESE)
	AppendEntSeqEnt(PathStkEntry, NewESE)

	WriteBTreePage(Tree, RtBroPg)
	UnlockBTreePtr(Tree, lv FatherPtr)
	UnlockBTreePtr(Tree, lv RtBroPtr)
	resultis RtBroPg
	]


and RemoveEntry(Tree, StkPtr, lvOldGrPtr; numargs na) = valof

	[ let ResultESE = (StkPtr>>PSE.ESLFront eq Empty)?
		BasicRemoveEntry(Tree, StkPtr),
		RemoveESE(Tree, StkPtr)
	let GreaterSonPg = (ResultESE>>ESLE.EntSeqP)>>BTE.GrPtr
	if na eq 3 then @lvOldGrPtr = GreaterSonPg
	(ResultESE>>ESLE.EntSeqP)>>BTE.GrPtr =
		(GreaterSonPg eq Empty)?
			Empty,
			(ReadBTreePage(Tree, GreaterSonPg))>>BTP.MinPtr
				// no need to lock transient reference
	resultis ResultESE
	]


and BasicRemoveEntry(Tree, StkPtr) = valof

	[ let Ptr = WriteBTreePage(Tree, StkPtr>>PSE.PageNo)
	LockBTreePtr(Tree, lv Ptr)

	let Offset = StkPtr>>PSE.Offset
	let Entry = Ptr+Offset
	let EntLen = (offset BTE.Record/16)+
		(Tree>>TREE.LengthRtn)(lv (Entry>>BTE.Record))

	let ResultESE = MakeEntSeqEnt(Tree>>TREE.Zone, Entry, EntLen)

	Ptr>>BTP.FreeWords = Ptr>>BTP.FreeWords+EntLen
	let TailBlkLen =
		(Tree>>TREE.PageLength-Ptr>>BTP.FreeWords)-Offset
	MoveBlock(Entry, Entry+EntLen, TailBlkLen)

	UnlockBTreePtr(Tree, lv Ptr)
	resultis ResultESE
	]


and RemoveESE(Tree, StkPtr) = valof

	[ let Ent = (StkPtr>>PSE.ESLFront)>>
			ESLE.EntSeqP
	let EntLen = (offset BTE.Record/16)+
		(Tree>>TREE.LengthRtn)(lv (Ent>>BTE.Record))

	let ESE = Allocate(Tree>>TREE.Zone,
		(offset ESLE.EntSeq/16)+EntLen)
	ESE>>ESLE.EntSeqP = lv (ESE>>ESLE.EntSeq)
	ESE>>ESLE.EntSeqLen = EntLen
	DepositESL(Tree, StkPtr, lv (ESE>>ESLE.EntSeq), EntLen)
	resultis ESE
	]


and WriteRightBrother(IS, StkPtr, StkFatherPtr, RtBroPg,
					NWords) = valof

	[ let Tree = IS>>IS.Tree

	let ESE = RemoveESE(Tree, StkPtr)
	let WordsLeft = NWords-ESE>>ESLE.EntSeqLen
	let MinPtr = (ESE>>ESLE.EntSeqP)>>BTE.GrPtr
	(ESE>>ESLE.EntSeqP)>>BTE.GrPtr = RtBroPg


	WriteBTreePage(Tree, RtBroPg)>>BTP.MinPtr = MinPtr
	unless WritePage(IS, StkPtr, RtBroPg, WordsLeft) eq
		WordsLeft do BTreeBug(Tree, ecWritePageWrong)
	resultis ESE // for father page
	]


and WritePage(IS, PSE, PageNo, NWords) = valof

	[ let Tree = IS>>IS.Tree
	let PagePtr = WriteBTreePage(Tree, PageNo)
	LockBTreePtr(Tree, lv PagePtr)
	let SentWords = DepositESL(Tree, PSE, lv (PagePtr>>BTP.BTEBlock),
		NWords)
	PagePtr>>BTP.FreeWords = IS>>IS.MaxFreeWords-SentWords
	UnlockBTreePtr(Tree, lv PagePtr)
	resultis SentWords
	]


and EntryLength(IS, Entry) = PageLength(IS, Entry-1, Entry+1)


and FillLeftPage(IS, LeftFather, RightFather; numargs na) = valof

	[ if na ls 3 then RightFather = IS>>IS.ELLLen+1

	let MidFather = LeftFather+2
	while MidFather ls RightFather-2 &
		PageLength(IS, LeftFather, MidFather+1) le IS>>IS.MaxFreeWords
		do MidFather = MidFather+1
	resultis MidFather
	]


and FillRightPage(IS, LeftFather, RightFather; numargs na) = valof

	[ if na ls 3 then RightFather = IS>>IS.ELLLen+1

	let MidFather = RightFather-2
	while MidFather gr LeftFather+2 &
		PageLength(IS, MidFather-1, RightFather) le IS>>IS.MaxFreeWords
		do MidFather = MidFather-1
	resultis MidFather
	]


and PushEntSeqEnt(PathStkEntry, Entry) be

	[ Entry>>ESLE.FwdP = PathStkEntry>>PSE.ESLFront
	PathStkEntry>>PSE.ESLFront = Entry

	if PathStkEntry>>PSE.ESLRear eq Empty then
		PathStkEntry>>PSE.ESLRear = Entry
	]


and PushEntSeqEntLens(IS, EntSeqEnt) be

	[ //	The idea is to add the entries on the tail end and
	// then to take the added bunch of entries and move them
	// to the head end, adjusting cumulative lengths
	// appropriately.

	// N.B. This must never be done while we have an
	// active heap, or disaster will ensue.

	let OldEnd = IS>>IS.ELLLen
	AppendEntSeqEntLens(IS, EntSeqEnt)
	let NewEnd = IS>>IS.ELLLen

	let ELL = IS>>IS.ELLPtr
	let OldLen = ELL>>ELL.CumEntLen↑OldEnd
	let NewLen = ELL>>ELL.CumEntLen↑NewEnd
	let OldBase = ELL>>ELL.CumEntLen↑0

	let Zone = (IS>>IS.Tree)>>TREE.Zone
	let TSize = OldEnd*(size ELE/16)
	let T = Allocate(Zone, TSize)
	MoveBlock(T, lv ELL>>ELL.ELE↑1, TSize)
	MoveBlock(lv ELL>>ELL.ELE↑1, lv ELL>>ELL.ELE↑(OldEnd+1),
		((NewEnd-OldEnd)*(size ELE/16)))
	MoveBlock(lv ELL>>ELL.ELE↑(1+NewEnd-OldEnd), T, TSize)
	Free(Zone, T)

	ELL>>ELL.CumEntLen↑0 = OldBase-(NewLen-OldLen)
	let Delta = OldBase-NewLen
	for i=1 to NewEnd-OldEnd do
		ELL>>ELL.CumEntLen↑i = ELL>>ELL.CumEntLen↑i+Delta
	]