// Copyright (c) 2018, 2020 Kiel University and others.
// Copyright (c) 2020 Kiel University and others.
// Copyright (c) 2026 The elk-go Authors.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

package rectpacking

import (
	

	
	
)

type blockRow struct {
	x, y, width, height, spacing float64
	nodes                        []graph.NodeID
}

func newBlockRow(, ,  float64) *blockRow { return &blockRow{x: , y: , spacing: } }

func ( *blockRow) ( *graph.Model,  graph.NodeID) {
	 := .Node()
	.Shape.SetLocation(.x+.width+func() float64 {
		if len(.nodes) > 0 {
			return .spacing
		}
		return 0
	}(), .y)
	.height = math.Max(.height, .Shape.Height)
	if len(.nodes) > 0 {
		.width += .spacing
	}
	.width += .Shape.Width
	.nodes = append(.nodes, )
}

func ( *blockRow) ( *graph.Model,  graph.NodeID,  bool) {
	for ,  := range .nodes {
		if  ==  {
			.nodes = append(.nodes[:], .nodes[+1:]...)
			break
		}
	}
	if  {
		.update()
	}
}

func ( *blockRow) ( *graph.Model) {
	,  := 0.0, 0.0
	for ,  := range .nodes {
		 := .Node()
		.Shape.SetLocation(.x+, .y)
		 += .Shape.Width + .spacing
		 = math.Max(, .Shape.Height+.spacing)
	}
	.width =  - .spacing
	.height =  - .spacing
}

func ( *blockRow) ( *graph.Model, ,  float64,  int) {
	 := ( - .width) / float64(len(.nodes))
	.height += 
	.width = 
	for ,  := range .nodes {
		 := .Node()
		.Shape.SetLocation(.Shape.X+float64()*, .Shape.Y+float64()*)
		.Shape.SetDimensions(.Shape.Width+, .height)
	}
}

type block struct {
	model                                        *graph.Model
	children                                     []graph.NodeID
	rows                                         []*blockRow
	x, y, width, height                          float64
	minWidth, minHeight, maxHeight               float64
	smallestWidth, smallestHeight, averageHeight float64
	parent                                       *rectRow
	stack                                        *blockStack
	spacing                                      float64
	fixed, positionFixed                         bool
}

func newBlock( *graph.Model, ,  float64,  *rectRow,  float64) *block {
	return &block{model: , x: , y: , parent: , spacing: , smallestWidth: math.Inf(1), smallestHeight: math.Inf(1)}
}

func ( *block) () *blockRow { return .rows[len(.rows)-1] }

func ( *block) ( graph.NodeID) {
	if len(.rows) == 0 {
		.rows = append(.rows, newBlockRow(.x, .y, .spacing))
	}
	.children = append(.children, )
	.lastRow().add(.model, )
	.adjustAdd()
}

func ( *block) ( graph.NodeID) {
	.children = append(.children, )
	 := .lastRow()
	.rows = append(.rows, newBlockRow(.x, .y+.height+.spacing, .spacing))
	.lastRow().add(.model, )
	.adjustAdd()
}

func ( *block) ( graph.NodeID) {
	for ,  := range .children {
		if  ==  {
			.children = append(.children[:], .children[+1:]...)
			break
		}
	}
	for ,  := range .rows {
		 := false
		for ,  := range .nodes {
			if  ==  {
				 = true
				break
			}
		}
		if  {
			.remove(.model, , true)
			if len(.nodes) == 0 {
				.rows = append(.rows[:], .rows[+1:]...)
			}
			break
		}
	}
	.adjustRemove()
}

func ( *block) (,  float64) {
	,  := -.x, -.y
	for ,  := range .children {
		 := .model.Node()
		.Shape.SetLocation(.Shape.X+, .Shape.Y+)
	}
	for ,  := range .rows {
		.x += 
		.y += 
	}
	.x, .y = , 
}

func ( *block) ( graph.NodeID) {
	 := .model.Node()
	.smallestWidth = math.Min(.smallestWidth, .Shape.Width)
	.width = math.Max(.width, .lastRow().width)
	.minWidth = math.Max(.minWidth, .Shape.Width+func() float64 {
		if len(.children) == 1 {
			return 0
		}
		return .spacing
	}())
	.smallestHeight = math.Min(.smallestHeight, .Shape.Height)
	if len(.children) > 1 {
		.maxHeight += .spacing
	}
	.maxHeight += .Shape.Height
	.minHeight = math.Max(.minHeight, .Shape.Height)
	 := float64(max(0, len(.rows)-1)) * .spacing
	for ,  := range .rows {
		 += .height
	}
	.height = 
	.averageHeight = .maxHeight/float64(len(.children)) - .spacing*(float64(len(.children)-1)/float64(len(.children)))
	.parent.notify()
}

func ( *block) () {
	,  := 0.0, 0.0
	for ,  := range .rows {
		 = math.Max(, .width)
		 += .height
		if  > 0 {
			 += .spacing
		}
	}
	.width, .height = , 
	.minWidth, .minHeight, .maxHeight = 0, 0, 0
	.smallestWidth, .smallestHeight = math.Inf(1), math.Inf(1)
	for ,  := range .children {
		 := .model.Node()
		.smallestWidth = math.Min(.smallestWidth, .Shape.Width)
		.minWidth = math.Max(.minWidth, .Shape.Width)
		.minHeight = math.Max(.minHeight, .Shape.Height)
		.smallestHeight = math.Min(.smallestHeight, .Shape.Height)
		.maxHeight += .Shape.Height + .spacing
	}
	if len(.children) > 0 {
		.averageHeight = .maxHeight/float64(len(.children)) - .spacing*(float64(len(.children)-1)/float64(len(.children)))
	}
	.parent.notify()
}

func ( *block) ( float64,  bool) (float64, float64) {
	,  := 0.0, .y
	, , ,  := 0.0, 0.0, 0.0, 0.0
	,  := 0, 0
	if  {
		.rows = []*blockRow{newBlockRow(.x, .y, .spacing)}
	}
	for ,  := range .children {
		 := .model.Node()
		 := 0.0
		if  > 0 {
			 = .spacing
		}
		if +.Shape.Width+ >  &&  > 0 {
			 = 0
			 +=  + .spacing
			 = math.Max(, )
			 +=  + .spacing
			, ,  = 0, 0, 0
			if  {
				++
				.rows = append(.rows, newBlockRow(.x, , .spacing))
			}
		}
		 = 0
		if  > 0 {
			 = .spacing
		}
		 += .Shape.Width + 
		 = math.Max(, .Shape.Height)
		if  {
			.rows[].add(.model, )
		}
		 += .Shape.Width + 
		++
	}
	 = math.Max(, )
	 += 
	if  {
		.width, .height = , 
		.parent.notify()
	}
	return , 
}

func ( *block) ( float64) bool {
	,  := .width, .height
	,  := .layoutIn(, true)
	return  !=  ||  != 
}

func ( *block) ( float64) float64 {
	,  := .layoutIn(, false)
	return 
}

func ( *block) ( float64) float64 {
	if .maxHeight <=  {
		return .minWidth
	}
	if ,  := .layoutIn(.minWidth, false);  <= .minWidth &&  <=  {
		return .minWidth
	}
	, ,  := .width, .minWidth, .width
	 := (-)/2 + 
	for +1 <  {
		,  := .layoutIn(, false)
		if  <=  &&  <=  {
			,  = , 
		} else {
			 = 
		}
		 = (-)/2 + 
	}
	return 
}

func ( *block) (,  float64) {
	 := .width + 
	.width, .height = , .height+
	 :=  / float64(len(.rows))
	for ,  := range .rows {
		.expand(.model, , , )
	}
}

type blockStack struct {
	blocks                       []*block
	x, y, width, height, spacing float64
}

func newBlockStack(, ,  float64) *blockStack {
	return &blockStack{x: , y: , spacing: }
}
func ( *blockStack) ( *block) {
	.stack = 
	.width = math.Max(.width, .width)
	if len(.blocks) > 0 {
		.height += .spacing
	}
	.height += .height
	.blocks = append(.blocks, )
}
func ( *blockStack) () {
	.width, .height = 0, 0
	for ,  := range .blocks {
		.width = math.Max(.width, .width)
		if  > 0 {
			.height += .spacing
		}
		.height += .height
	}
}
func ( *blockStack) (,  float64) {
	,  := -.x, -.y
	for ,  := range .blocks {
		.setLocation(.x+, .y+)
	}
	.x, .y = , 
}
func ( *blockStack) () float64 {
	 := 0.0
	for ,  := range .blocks {
		 = math.Max(, .minWidth)
	}
	return 
}
func ( *blockStack) ( float64) float64 {
	if len(.blocks) == 1 {
		return .blocks[0].widthForHeight()
	}
	, ,  := .minimumWidth(), .width, .width
	 := (-)/2 + 
	for +1 <  {
		 := 0.0
		for ,  := range .blocks {
			 += .heightForWidth()
		}
		if  <  {
			,  = , 
		} else {
			 = 
		}
		 = (-)/2 + 
	}
	return 
}
func ( *blockStack) ( float64) {
	, ,  := .y, 0.0, 0.0
	for ,  := range .blocks {
		.setLocation(.x, )
		.placeIn()
		 = math.Max(, .width)
		 += .height + .spacing
		 = 
	}
	.width, .height = , 
}
func ( *blockStack) (,  float64) {
	 :=  / float64(len(.blocks))
	for ,  := range .blocks {
		.setLocation(.x, .y+float64()*)
		.expand(.width-.width+, )
	}
}

type rectRow struct {
	y, width, height, spacing float64
	blocks                    []*block
	stacks                    []*blockStack
}

func newRectRow(,  float64) *rectRow { return &rectRow{y: , spacing: } }
func ( *rectRow) () {
	.width = 0
	.height = math.Inf(-1)
	for ,  := range .blocks {
		if  > 0 {
			.width += .spacing
		}
		.width += .width
		.height = math.Max(.height, .height)
	}
}
func ( *rectRow) ( *block) {
	.height = math.Max(.height, .height)
	if len(.blocks) > 0 {
		.width += .spacing
	}
	.width += .width
	.blocks = append(.blocks, )
}
func ( *rectRow) ( *block) {
	for ,  := range .blocks {
		if  ==  {
			.blocks = append(.blocks[:], .blocks[+1:]...)
			break
		}
	}
	.width -= .width
	if len(.blocks) > 0 {
		.width -= .spacing
	}
	.height = math.SmallestNonzeroFloat64
	for ,  := range .blocks {
		.height = math.Max(.height, .height)
	}
}
func ( *rectRow) ( float64) {
	 :=  - .y
	for ,  := range .stacks {
		.setLocation(.x, .y+)
	}
	.y = 
}
func ( *rectRow) ( *graph.Model, ,  float64) {
	 := ( - .width) / float64(len(.stacks))
	for ,  := range .stacks {
		.setLocation(.x+float64()*, .y)
		.expand(, .height-.height+)
	}
}

func initialPlacement( *graph.Model,  []graph.NodeID, ,  float64,  bool) []*rectRow {
	 := make([]*rectRow, 0)
	 := newRectRow(0, )
	.add(newBlock(, 0, 0, , ))
	,  := 0.0, 0.0
	for ,  := range  {
		 := .blocks[len(.blocks)-1]
		 := .Node()
		 :=  + .Shape.Width
		if len(.blocks[0].children) > 0 {
			 += 
		}
		if  >  || graph.Get(&.Properties, InNewRow) {
			 = 0
			 += .height + 
			 = append(, )
			 = newRectRow(, )
			 = newBlock(, 0, .y, , )
			.add()
		}
		if len(.children) == 0 || ! && similarHeight(, , ) {
			.add()
		} else {
			 = newBlock(, .x+.width+, .y, , )
			.add()
			.add()
		}
		 = .Shape.X + .Shape.Width
	}
	return append(, )
}

func similarHeight( *graph.Model,  *block,  graph.NodeID) bool {
	 := .Node().Shape.Height
	if  >= .smallestHeight &&  <= .minHeight {
		return true
	}
	return .averageHeight*.5 <=  && .averageHeight*1.5 >= 
}

func placeRectInBlock( *rectRow,  *block,  graph.NodeID, ,  float64) bool {
	 := .model.Node()
	if !similarHeight(.model, , ) {
		return false
	}
	 := .lastRow()
	if .x+.width+.Shape.Width+ <=  &&
		(.y-.y+.Shape.Height <= .height || len(.blocks) == 1) {
		.add()
		return true
	}
	if .x+.Shape.Width <=  &&
		.y+.height+.Shape.Height+ <= .y+.height {
		.addNewRow()
		return true
	}
	return false
}

func resetCoordinates( *graph.Model,  []graph.NodeID) {
	for ,  := range  {
		.Node().Shape.SetLocation(0, 0)
	}
}

func rectangleDimensions( *graph.Model,  []graph.NodeID) geometry.Point {
	 := geometry.Point{}
	for ,  := range  {
		 := .Node()
		.X = math.Max(.X, .Shape.X+.Shape.Width)
		.Y = math.Max(.Y, .Shape.Y+.Shape.Height)
	}
	return 
}

func rowsDimensions( []*rectRow,  float64) geometry.Point {
	 := geometry.Point{}
	for ,  := range  {
		.X = math.Max(.X, .width)
		if  > 0 {
			.Y += 
		}
		.Y += .height
	}
	return 
}

func rowsHeight( []*rectRow,  float64) float64 {
	return rowsDimensions(, ).Y
}

func finishPacking(,  geometry.Point,  geometry.Spacing,  float64) drawingData {
	 := math.Max(.X, .X-.Horizontal())
	 := math.Max(.Y, .Y-.Vertical())
	 :=  - .Y
	// SimplePlacement and NoPlacement in ELK 0.12 assign height plus the
	// additional height. The compactor uses size.y + additionalHeight, which
	// is simply height; callers select the appropriate behavior below.
	 := newDrawing(, , +, wholeDrawing, 0, 0)
	.additionalHeight = 
	return 
}

func simplePlacement(
	 *graph.Model,
	 []graph.NodeID,
	 float64,
	 geometry.Point,
	 geometry.Spacing,
	,  float64,
) (drawingData, []*rectRow) {
	resetCoordinates(, )
	 := initialPlacement(, , , , false)
	for ,  := range  {
		for ,  := range .blocks {
			 := newBlockStack(.x, .y, )
			.add()
			.stacks = append(.stacks, )
		}
	}
	return finishPacking(rowsDimensions(, ), , , ), 
}

func noPlacement(
	 *graph.Model,
	 []graph.NodeID,
	 geometry.Point,
	 geometry.Spacing,
	 float64,
) drawingData {
	return finishPacking(rectangleDimensions(, ), , , )
}

func compactPlacement(
	 *graph.Model,
	 []graph.NodeID,
	 float64,
	 geometry.Point,
	 geometry.Spacing,
	,  float64,
	 bool,
	 int,
) (drawingData, []*rectRow) {
	, ,  := compactOnce(
		, , , , , , , ,
	)
	for  > 1 {
		 := make([]geometry.Rectangle, len())
		for ,  := range  {
			[] = .Node().Shape.Rectangle()
		}

		 := 
		 := (.width + .Horizontal()) / (.height + .Vertical())
		if len() > 1 && !math.IsInf(.minIncrease, 1) &&  <  {
			 =  + .minIncrease
		} else if len() > 1 && !math.IsInf(.minDecrease, 1) &&  >  {
			 = math.Max(.X, -.minDecrease)
		}

		, ,  := compactOnce(
			, , , , , , , ,
		)
		if .scale >= .scale && !math.IsNaN(.scale) {
			, ,  = , , 
		} else {
			for ,  := range  {
				 := .Node()
				 := []
				.Shape.SetLocation(.X, .Y)
				.Shape.SetDimensions(.Width, .Height)
			}
		}
		--
	}
	return , 
}

type rowWidthChanges struct {
	minIncrease, maxIncrease float64
	minDecrease, maxDecrease float64
}

func compactOnce(
	 *graph.Model,
	 []graph.NodeID,
	 float64,
	 geometry.Point,
	 geometry.Spacing,
	,  float64,
	 bool,
) (drawingData, []*rectRow, rowWidthChanges) {
	resetCoordinates(, )
	 := initialPlacement(, , , , )
	 := rowWidthChanges{minIncrease: math.Inf(1), minDecrease: math.Inf(1)}
	for  := 0;  < len(); ++ {
		 := []
		if  > 0 {
			.setY([-1].y + [-1].height + )
		}
		,  := compact(, &, , , )
		if  {
			for ,  := range .blocks {
				.fixed = false
				.positionFixed = false
				.adjustRemove()
			}
			.stacks = nil
			.width = 
			--
			continue
		}
		adjustRow(, )
		if +1 < len() {
			 := .width +  + [+1].blocks[0].width - 
			// Preserve ELK 0.12's observable update order, including the two
			// cross-field references in RowFillingAndCompaction.
			.maxIncrease = math.Max(, .maxDecrease)
			.minIncrease = math.Min(, .minDecrease)
			if len(.stacks) != 0 {
				 := .stacks[len(.stacks)-1].width
				if len(.stacks) > 1 {
					 += 
				}
				.maxDecrease = math.Max(.maxDecrease, )
				.minDecrease = math.Min(.maxDecrease, )
			}
		}
	}
	if len() == 1 && len([0].stacks) != 0 {
		 := [0].stacks[len([0].stacks)-1]
		if len(.blocks) != 0 {
			 := .blocks[len(.blocks)-1]
			for ,  := range .rows {
				 := .width - .width
				.maxDecrease = math.Max(.maxDecrease, )
				.minDecrease = math.Min(.minDecrease, )
				 := .width + 
				.maxIncrease = math.Max(.maxIncrease, )
				.minIncrease = math.Min(.minIncrease, )
			}
		}
	}
	 := rowsDimensions(, )
	 := math.Max(.X, .X-.Horizontal())
	 := math.Max(.Y, .Y-.Vertical())
	 := newDrawing(, , , wholeDrawing, 0, 0)
	.additionalHeight =  - .Y
	return , , 
}

func expandRows( *graph.Model,  []*rectRow, ,  float64) {
	if len() == 0 {
		return
	}
	 :=  / float64(len())
	for ,  := range  {
		.setY(.y + *float64())
		.expand(, , )
	}
}

func adjustRow( *rectRow,  float64) {
	.height, .width = 0, 0
	for ,  := range .stacks {
		.update()
		.height = math.Max(.height, .height)
		if  > 0 {
			.width += 
		}
		.width += .width
	}
}

func nextBlock( []*rectRow,  *rectRow, ,  int) *block {
	if  < len(.blocks)-1 {
		return .blocks[+1]
	}
	if  < len() && len([].blocks) > 0 {
		return [].blocks[0]
	}
	return nil
}

func compact( int,  *[]*rectRow, ,  float64,  bool) (bool, bool) {
	 := false
	 := false
	 :=  + 1
	 := (*)[]
	var  *blockStack
	for  := 0;  < len(.blocks); ++ {
		 := .blocks[]
		if .fixed {
			continue
		}
		if len(.children) == 0 {
			.remove()
			--
			 = true
			continue
		}
		if !.positionFixed {
			if  != nil {
				.update()
			}
			 := 0.0
			if  != nil {
				 = .x + .width + 
			}
			 = newBlockStack(, .y, )
			.setLocation(.x+.width, .y)
			.stacks = append(.stacks, )
			.add()
			.positionFixed = true
		}
		 := nextBlock(*, , , )
		 :=  != nil && .parent != 
		if  != nil {
			if len(.children) > 0 && !graph.Get(&modelNode().Properties, InNewRow) {
				.placeIn( - .x)
				.stack.update()
				 = absorb(, , , , ) || 
			} else {
				// ELK 0.12 takes this branch both for an empty next block and
				// for an inNewRow boundary. It calls remove on the current row
				// even when the block belongs to the following row, then stops
				// compacting this row.
				.remove()
				break
			}
			if len(.children) == 0 {
				if  < len(*) {
					(*)[].remove()
				}
				 = nil
				for len(*) >  && len((*)[].blocks) == 0 {
					* = append((*)[:], (*)[+1:]...)
				}
			}
			if  == nil {
				--
				continue
			}
			if !graph.Get(&modelNode().Properties, InNewRow) &&
				placeBelow(, , , , , , , ) {
				 = true
				continue
			}
			if  {
				 := .height
				 := .minHeight
				if !graph.Get(&modelNode().Properties, InNewRow) &&
					placeBeside(, , , , , , , ) {
					 = true
					if  <  {
						 = true
						.parent = 
						break
					}
					continue
				}
				if useRowHeight(, ) {
					.fixed = true
					 = true
					continue
				}
			} else if useRowHeight(, ) {
				.fixed = true
				 = true
				continue
			}
			if  {
				continue
			}
		}
		if useRowHeight(, ) {
			.fixed = true
			 = true
			if  != nil {
				.positionFixed = false
			}
		} else {
			.stack.update()
		}
	}
	return , 
}

func modelNode( *block) *graph.Node {
	return .model.Node(.children[0])
}

func useRowHeight( *rectRow,  *block) bool {
	 := .stack.width
	if .height < .height {
		 := .stack.widthForHeight(.height)
		if .stack.width >  {
			.stack.placeIn()
			return  != .stack.width
		}
	}
	return false
}

func absorb( *rectRow, ,  *block, ,  float64) bool {
	 := false
	for len(.children) > 0 {
		 := .children[0]
		if graph.Get(&.model.Node().Properties, InNewRow) {
			break
		}
		if !placeRectInBlock(, , , , ) {
			break
		}
		 = true
		.remove()
	}
	if len(.children) == 0 {
		.parent.remove()
	}
	if  {
		.stack.update()
	}
	return 
}

func placeBelow( *[]*rectRow,  *rectRow, ,  *block,  bool,  float64,  int,  float64) bool {
	 :=  - .x
	 := .y - .y + .heightForWidth()
	if .minWidth+ >  {
		return false
	}
	 := .heightForWidth()
	if ++ > .height {
		return false
	}
	.placeIn()
	.fixed = true
	.placeIn()
	.setLocation(.x, .y+.height+)
	.positionFixed = true
	.stack.add()
	if  {
		 := .parent
		.add()
		.parent = 
		if  < len(*) {
			.remove()
			if len((*)[].blocks) == 0 {
				* = append((*)[:], (*)[+1:]...)
			}
		}
	}
	return true
}

func placeBeside( *[]*rectRow,  *rectRow, ,  *block,  float64,  int,  float64,  bool) bool {
	 := .stack.widthForHeight(.y + .height - .stack.y)
	 := .minHeight > .height && 
	 :=  - (.stack.x +  - )
	 := .heightForWidth()
	if  &&  > .minHeight {
		return false
	}
	if  {
		 := 0.0
		for ,  := range .stacks {
			 += .widthForHeight(.minHeight) + 
		}
		 =  - 
	}
	if  < .minWidth {
		return false
	}
	 :=  == len(*)-1 &&  >= (*)[].width
	if ! &&  > .height && ! {
		return false
	}
	if  &&  > .height {
		.height = 
		.placeIn(.widthForHeight())
	} else {
		.stack.placeIn()
		.fixed = true
	}
	.placeIn( - (.x + .width))
	.setLocation(.stack.x+.stack.width, .y)
	 := .parent
	.add()
	.parent = 
	if  < len(*) {
		.remove()
		if len((*)[].blocks) == 0 {
			* = append((*)[:], (*)[+1:]...)
		}
	}
	return true
}