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


//	BTreeWrtMS2.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:43 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
	ReadPageAndLockBTreePtr
	BackUpOneRecord
	RepairOffsets
	]


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


let HairyInsertRecords(Tree, PathStk, IS, RtBroPg1) be

	[
//	From this point on, we know that at least the current page
//	and two right brother pages are involved. First we
//	calculate the minimum space required in the second right
//	brother in order that he can handle our overflow. Then
//	we see if our second brother exists and has that
//	much space.

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

	if IS>>IS.ELLLen ls 5 then
		BTreeBug(Tree, ecEntryListTooShort)

//	See how much space our second brother page would have to
//	contain in order to handle the overflow. This is done
//	by pretending to fill up this page and the first right brother
//	page and seeing what is left over.

	let FatherEnt = FillLeftPage(IS, 0)
	let FatherEnt2 = FillLeftPage(IS, FatherEnt)

//	CurPage can't be the root, because
//	one brother would surely have been enough in that
//	case, so we don't have to pussyfoot when calling 
//	FindRightBrother.

	let RtBroPg2 = FindRightBrother(IS, SFP,
		PageLength(IS, FatherEnt2)+
			2*(IS>>IS.BreathingSpace), SP)

	if RtBroPg2 eq Empty then
		// No luck. Try the left brother.
		[ let FE2 = FillRightPage(IS, 0)
		let FE = FillRightPage(IS, 0, FE2)
		RtBroPg2 = FindLeftBrother(IS, SFP, PageLength(IS, 0, FE)+
						2*(IS>>IS.BreathingSpace),
						SP)

		test RtBroPg2 eq Empty

		ifso	// Still no luck, get empty page
			RtBroPg2 = AllocateBTreePage(Tree)

		ifnot	// Left brother had space, but
			//	FatherEnt's are now invalid
			[ FatherEnt = FillLeftPage(IS, 0)
			FatherEnt2 = FillLeftPage(IS, FatherEnt)
			]
		]

	let MaxFeasEnt = FatherEnt2
	while PageLength(IS, MaxFeasEnt) le IS>>IS.FairlyFull do
		MaxFeasEnt = MaxFeasEnt-1
	let MinFeasEnt = MaxFeasEnt+1

	let TwoBrothersEnough = false
	let BestFatherEnt = nil
	let BestFatherEnt2 = nil
	let BestFatherLenSum = MaxFreeWords+1
	IS>>IS.HeapSize = 0

	while (PageLength(IS, 0, FatherEnt) ge IS>>IS.PrettyFull) %
		(not TwoBrothersEnough &
			(PageLength(IS, 0, FatherEnt) gr 0))
		do

		[ while (PageLength(IS, FatherEnt, MinFeasEnt-1) gr 0) &
			(PageLength(IS, MinFeasEnt-1) le
			MaxFreeWords) do

				[ MinFeasEnt = MinFeasEnt-1
				if MinFeasEnt le MaxFeasEnt then
					AddToHeap(IS, MinFeasEnt)
				]

		while PageLength(IS, FatherEnt, MaxFeasEnt) gr
			MaxFreeWords do

				[ if MaxFeasEnt ge MinFeasEnt then
					RemoveFromHeap(IS, MaxFeasEnt)
				MaxFeasEnt = MaxFeasEnt-1
				]


		if IS>>IS.HeapSize gr 0 then
			[ FatherEnt2 = (IS>>IS.HeapPtr)>>HP.HeapEnt↑1
			let SL = EntryLength(IS, FatherEnt)+
					EntryLength(IS, FatherEnt2)

			if SL ls BestFatherLenSum then
				[ TwoBrothersEnough = true
				BestFatherLenSum = SL
				BestFatherEnt = FatherEnt
				BestFatherEnt2 = FatherEnt2
				]
			]

		FatherEnt = FatherEnt-1
		]

	unless TwoBrothersEnough do
		BTreeBug(Tree, ecTwoBrothersNotEnough)

	let BreakLen1 = PageLength(IS, 0, BestFatherEnt)
	let BreakLen2 = PageLength(IS, 0, BestFatherEnt2)
	let TotLen = PageLength(IS, 0)

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

	let t = WriteRightBrother(IS, SP, SFP, RtBroPg1,
				BreakLen2-BreakLen1)

	PushEntSeqEnt(SFP,
		WriteRightBrother(IS, SP, SFP, RtBroPg2,
				TotLen-BreakLen2))
	PushEntSeqEnt(SFP, t)
	]


and MakeNewRoot(IS, SP, Tree) be

	[ let NewWords = PageLength(IS, 0)

	if NewWords gr IS>>IS.MaxFreeWords then
		BTreeBug(Tree, ecNewRootOverflow)

	let NewRootPage = AllocateBTreePage(Tree)

	WriteBTreePage(Tree, NewRootPage)>>BTP.MinPtr = SP>>PSE.LeastSon

	unless WritePage(IS, SP, NewRootPage, NewWords) eq NewWords
		do BTreeBug(Tree, ecWritePageWrong)

	Tree>>TREE.RootPage = NewRootPage
	Tree>>TREE.StateDirty = true
	]


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

	[ let Tree = IS>>IS.Tree
	let FatherPg = StkFatherPtr>>PSE.PageNo
	let FatherPtr = nil
	ReadPageAndLockBTreePtr(Tree, FatherPg, lv FatherPtr)
	let FatherFreeWords = FatherPtr>>BTP.FreeWords
	let FatherOffset = StkFatherPtr>>PSE.Offset
	let FatherLOffset = StkFatherPtr>>PSE.LastOffset
	let FatherNTLOffset = StkFatherPtr>>PSE.NextToLastOffset

	if (FatherOffset le Rec1Offset) then
		[ UnlockBTreePtr(Tree, lv FatherPtr)
		resultis Empty
		]

	let FatherEnt = FatherPtr+FatherLOffset
	let FatherEntLen = (Tree>>TREE.LengthRtn)(lv FatherEnt>>BTE.Record)+
		offset BTE.Record/16
	let LFatherEnt = FatherPtr+FatherNTLOffset
	let LeftBroPg = LFatherEnt>>BTE.GrPtr
	let LeftBroPtr = ReadBTreePage(Tree, LeftBroPg)
	unless LeftBroPtr>>BTP.FreeWords-FatherEntLen ge
		SpaceNeeded do
		[ UnlockBTreePtr(Tree, lv FatherPtr)
		resultis Empty
		]

	LockBTreePtr(Tree, lv LeftBroPtr)
	WriteBTreePage(Tree, FatherPg)
	WriteBTreePage(Tree, LeftBroPg)

	let LeftBroLen = IS>>IS.MaxFreeWords-(LeftBroPtr>>BTP.
			FreeWords)

	let NewESE = MakeEntSeqEnt(Tree>>TREE.Zone,
		lv (LeftBroPtr>>BTP.BTEBlock), LeftBroLen)

	let RightBroPg = FatherEnt>>BTE.GrPtr
	LFatherEnt>>BTE.GrPtr = RightBroPg

	BackUpOneRecord(Tree, StkFatherPtr)
	let ESE = BasicRemoveEntry(Tree, StkFatherPtr)
	let RightBroPtr = WriteBTreePage(Tree, RightBroPg)
	(ESE>>ESLE.EntSeqP)>>BTE.GrPtr = RightBroPtr>>BTP.MinPtr
	RightBroPtr>>BTP.MinPtr = LeftBroPtr>>BTP.MinPtr

	PushEntSeqEntLens(IS, ESE)
	PushEntSeqEnt(PathStkEntry, ESE)
	PushEntSeqEntLens(IS, NewESE)
	PushEntSeqEnt(PathStkEntry, NewESE)

	UnlockBTreePtr(Tree, lv LeftBroPtr)
	UnlockBTreePtr(Tree, lv FatherPtr)
	resultis LeftBroPg
	]


and AddToHeap(IS, Entry) be

	[ IS>>IS.HeapSize = IS>>IS.HeapSize+1
	TrickleDown(IS, IS>>IS.HeapSize, Entry)
	]


and TrickleDown(IS, EmptyLoc, Entry) be

	[ let Heap = IS>>IS.HeapPtr
	let EntLens = IS>>IS.ELLPtr

	let SonLen = EntryLength(IS, Entry)

	let Son = EmptyLoc
	let Father = nil
	let FE = nil

	while valof
		[ Father = Son rshift 1	// Son/2
		if Father le 0 then resultis false
		FE = Heap>>HP.HeapEnt↑Father
		resultis (EntryLength(IS, FE) gr SonLen)
		]
	    do
		[ Heap>>HP.HeapEnt↑Son = FE
		EntLens>>ELL.HeapPos↑FE = Son
		Son = Father
		]

	Heap>>HP.HeapEnt↑Son = Entry
	EntLens>>ELL.HeapPos↑Entry = Son
	]


and RemoveFromHeap(IS, Entry) be

	[ let Heap = IS>>IS.HeapPtr
	let EntLens = IS>>IS.ELLPtr

	let HeapPos = EntLens>>ELL.HeapPos↑Entry
	IS>>IS.HeapSize = IS>>IS.HeapSize-1
	if HeapPos gr IS>>IS.HeapSize then return	// Our guy was last

	let ReplacementEntry = Heap>>HP.HeapEnt↑(IS>>IS.HeapSize+1)
	test EntryLength(IS, ReplacementEntry) le
		EntryLength(IS, Entry)

	ifso	TrickleDown(IS, HeapPos, ReplacementEntry)
	ifnot	SiftUp(IS, HeapPos, ReplacementEntry)
	]


and SiftUp(IS, EmptyLoc, Entry) be

	[ let Heap = IS>>IS.HeapPtr
	let HeapSize = IS>>IS.HeapSize
	let EntLens = IS>>IS.ELLPtr

	let EELen = EntryLength(IS, Entry)

	while true do
		[ let Son = EmptyLoc+EmptyLoc
		let ERSon = nil

		if Son gr HeapSize then break

		let ESon = Heap>>HP.HeapEnt↑Son
		if Son ls HeapSize then
			[ let ERSon = Heap>>HP.HeapEnt↑(Son+1)
			if EntryLength(IS, ERSon) ls
				EntryLength(IS, ESon) do
					[ Son = Son+1
					ESon = ERSon
					]
			]

		if EntryLength(IS, Heap>>HP.HeapEnt↑Son) ge EELen then
			break

		Heap>>HP.HeapEnt↑EmptyLoc = ESon
		EntLens>>ELL.HeapPos↑ESon = EmptyLoc

		EmptyLoc = Son
		]

	Heap>>HP.HeapEnt↑EmptyLoc = Entry
	EntLens>>ELL.HeapPos↑Entry = EmptyLoc
	]