package placementcost

import (
	
	
	

	
	
	
)

type symmetryBoolScratch struct {
	values []bool
}

type symmetryNodeScratch struct {
	nodes []*layoutgraph.Node
	added map[*layoutgraph.Node]struct{}
}

var symmetryBoolPool = typedpool.New(func() *symmetryBoolScratch {
	return &symmetryBoolScratch{values: make([]bool, 0, 16)}
})

var symmetryNodePool = typedpool.New(func() *symmetryNodeScratch {
	return &symmetryNodeScratch{
		nodes: make([]*layoutgraph.Node, 0, 16),
		added: make(map[*layoutgraph.Node]struct{}, 16),
	}
})

func borrowSymmetryBools( int) *symmetryBoolScratch {
	 := symmetryBoolPool.Get()
	if cap(.values) <  {
		.values = make([]bool, )
		return 
	}
	.values = .values[:]
	clear(.values)
	return 
}

func returnSymmetryBools( *symmetryBoolScratch) {
	.values = .values[:0]
	symmetryBoolPool.Put()
}

func borrowSymmetryNodes( int) *symmetryNodeScratch {
	 := symmetryNodePool.Get()
	if cap(.nodes) <  {
		.nodes = make([]*layoutgraph.Node, 0, )
	} else {
		.nodes = .nodes[:0]
	}
	clear(.added)
	return 
}

func returnSymmetryNodes( *symmetryNodeScratch) {
	clear(.nodes[:cap(.nodes)])
	.nodes = .nodes[:0]
	clear(.added)
	symmetryNodePool.Put()
}

// NodesSymmetry sums local symmetry scores for nodes in order.
func ( context.Context,  layoutgraph.Nodes,  []*layoutgraph.EdgeAbduction) (float64, error) {
	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	 := 0.0
	for ,  := range  {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		,  := nodeSymmetry(, , , true)
		if  != nil {
			return 0, 
		}
		 += 
	}
	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	return , nil
}

// NodeSymmetry evaluates a node and the neighboring symmetry it contributes to.
func ( context.Context,  *layoutgraph.Node,  []*layoutgraph.EdgeAbduction) (float64, error) {
	return nodeSymmetry(, , , true)
}

func nodeSymmetry( context.Context,  *layoutgraph.Node,  []*layoutgraph.EdgeAbduction,  bool) (float64, error) {
	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	 := 0.0
	 := 0

	 := borrowSymmetryBools(len())
	defer returnSymmetryBools()
	 := .values
	 := borrowSymmetryNodes(len(.Edges))
	 := .nodes
	defer func() {
		.nodes = 
		returnSymmetryNodes()
	}()
	 := .added

	// We consider container children with protruding edges like neighbors for symmetry, their score matters for the container score
	if .IsContainer() {
		 := make([]*layoutgraph.Node, 0)
		// Within node placement, we use edge abductions to discern children; otherwise, the AdjacentNodes are correct
		if  == nil {
			for ,  := range .Graph.Containers[] {
				if  := scoringCancellationError(, );  != nil {
					return 0, 
				}
				for ,  := range .Edges {
					if  := scoringCancellationError(, );  != nil {
						return 0, 
					}
					 := .Adjacent()
					// We only want to consider symmetry of child nodes with protrutions
					if !.Container.IsDescendantOf() {
						[] = struct{}{}
						 = append(, )
						break
					}
				}
			}
		} else {
			for ,  := range  {
				if  := scoringCancellationError(, );  != nil {
					return 0, 
				}
				if [] {
					continue
				}
				if .CurrentFrom ==  {
					if .OriginallyFrom != nil {
						[] = true
						if ,  := [.OriginallyFrom];  {
							continue
						}
						[.OriginallyFrom] = struct{}{}
						 = append(, .OriginallyFrom)
					}
				}
				if .CurrentTo ==  {
					if .OriginallyTo != nil {
						[] = true
						if ,  := [.OriginallyTo];  {
							continue
						}
						[.OriginallyTo] = struct{}{}
						 = append(, .OriginallyTo)
					}
				}
			}
		}

		for ,  := range  {
			if  := scoringCancellationError(, );  != nil {
				return 0, 
			}
			,  := (, , , )
			if  != nil {
				return 0, 
			}
			 += 
			++
		}
	}

	// If a node has multiple edges to node A on one side, and only 1 edge to node B on another symmetric side, count that as symmetrical
	//   ┌───────────┐
	//   ▼           │
	// ┌──┐        ┌─┴┐       ┌──┐
	// │  │◄───────┤  ├──────►│  │
	// └──┘        └──┘       └──┘
	for ,  := range .Edges {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		 := .Adjacent()
		 := 

		// Let's say A and A's container are both connected to B
		// ┌──────────┐
		// │          │
		// │   ┌───┐  │  ┌───┐
		// │   │ A ├──┼─►│B  │
		// │   └───┘  │  └───┘
		// │          │    ▲
		// └──────┬───┘    │
		//        │        │
		//        └────────┘
		// We've already considered A's symmetry above, so we should only count B as an adjacent node if it's not connected to a child
		 := false
		if .IsContainer() {
			for ,  := range  {
				if  := scoringCancellationError(, );  != nil {
					return 0, 
				}
				if (.CurrentFrom == ) && (.CurrentTo == ) {
					if .OriginallyFrom != nil {
						 = true
						break
					}
				}
				if (.CurrentFrom == ) && (.CurrentTo == ) {
					[] = true
					if .OriginallyTo != nil {
						 = true
						break
					}
				}
			}
		}
		if  {
			continue
		}

		// If we're considering symmetry for node B in the above diagram, we'll want to resolve to adjacent node to A, not container for A
		for ,  := range  {
			if  := scoringCancellationError(, );  != nil {
				return 0, 
			}
			if [] {
				continue
			}
			// For symmetry, use cluster vessels
			if .CurrentFrom != nil && .CurrentFrom.IsClusterVessel() {
				continue
			}
			if .CurrentTo != nil && .CurrentTo.IsClusterVessel() {
				continue
			}
			if .Graph.IsSequenceVessel(.CurrentFrom) || .Graph.IsSequenceVessel(.CurrentTo) {
				continue
			}
			if (.CurrentFrom == ) && (.CurrentTo == ) {
				[] = true
				if .OriginallyTo != nil {
					 = .OriginallyTo
				}
				break
			}
			if (.CurrentFrom == ) && (.CurrentTo == ) {
				[] = true
				if .OriginallyFrom != nil {
					 = .OriginallyFrom
				}
				break
			}
		}

		// For clusters within containers, the Originally is pointed to the cluster node still
		//              ┌─────────┐
		//              │         │
		//              │         │
		//              │  ┌─┐    │
		// ┌──┐     ┌───┼─►│ │    │
		// │  ├─────┤   │  └─┘    │
		// └──┘     │   │         │
		//          │   │  ┌─┐    │
		//          └───┼─►│ │    │
		//              │  └─┘    │
		//              │         │
		//              └─────────┘
		if .Cluster.IsActive() {
			 = .Cluster.Vessel
		} else if .Sequence.IsActive() {
			 = .Sequence.Vessel
		}

		if ,  := [];  {
			continue
		}

		 = append(, )
		[] = struct{}{}
	}

	// When we're resolving symmetry for children during node placement, all their edges were abducted to their containers, so here we find those
	for ,  := range  {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		var  *layoutgraph.Node
		if .OriginallyFrom ==  {
			// When the adjacent node is also a container
			if .OriginallyTo != nil &&
				.CurrentTo != nil &&
				!.CurrentTo.IsClusterVessel() &&
				!.Graph.IsSequenceVessel(.CurrentTo) {
				 = .OriginallyTo
			} else {
				 = .CurrentTo
			}
		}
		if .OriginallyTo ==  {
			if .OriginallyFrom != nil &&
				.CurrentFrom != nil &&
				!.CurrentFrom.IsClusterVessel() &&
				!.Graph.IsSequenceVessel(.CurrentFrom) {
				 = .OriginallyFrom
			} else {
				 = .CurrentFrom
			}
		}
		if  != nil {
			if ,  := [];  {
				continue
			}

			 = append(, )
			[] = struct{}{}
		}
	}

	for  := 0;  < len(); ++ {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		 := .DistanceTo([], true)
		if  > 1200 {
			 = append([:], [+1:]...)
			--
		}
	}

	 := borrowSymmetryBools(len())
	defer returnSymmetryBools()
	 := .values
	,  := computeSymmetryScoreInto(, , , )
	if  != nil {
		return 0, 
	}
	 += 
	 += len()

	// Node A may not have symmetry itself, but we still don't want it to move because it contributes to B's symmetry
	// So we check neighbors, see that B is symmetrical, which makes A also symmetrical
	// ┌──┐   ┌──┐   ┌──┐
	// │A │◄──┤B ├──►│C │
	// └──┘   └──┘   └──┘
	if  {
		for ,  := range  {
			if  := scoringCancellationError(, );  != nil {
				return 0, 
			}
			if [] {
				continue
			}
			,  := (, , , false)
			if  != nil {
				return 0, 
			}
			 += 
		}
	}

	// No adjacent nodes, not a container of a child with protruding edges
	if  == 0 {
		return 0.0, nil
	}
	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	return  / float64(), nil
}

/*
*
// This is ideal. Symmetrical through the center
// ┌──┐   ┌──┐   ┌──┐
// │  │◄──┤  ├──►│  │
// └──┘   └──┘   └──┘
//
// But this is not bad and should still be rewarded
//
//	  ┌──┐
//	┌─┤  ├─┐
//	│ └──┘ │
//	│      │
// ┌▼┐    ┌▼─┐
// │ │    │  │
// └─┘    └──┘
// So we give it a small score
*/
// computeSymmetryScoreInto writes match state into caller-owned storage so the
// optimizer can score symmetry without allocating.
func computeSymmetryScoreInto( context.Context,  *layoutgraph.Node,  []*layoutgraph.Node,  []bool) (float64, error) {
	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	 := len()
	if  < 2 {
		return 0, nil
	}
	 = [:]
	clear()

	 := 0.0

	 := .Graph.Containers[.Container]
	var  []*layoutgraph.Node

	for ,  := range  {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		if [] {
			continue
		}
		if .Container != .Container {
			 = .Graph.Containers[.Container]
		} else {
			 = nil
		}

		 := -1
		 := 0.0

		// When two nodes are considered mirrored for the sake of symmetry, we only want ones under the same container
		// This just doesn't look aesthetically symmetrical, even if it is (in a larger graph at least)
		//                    ┌──────────┐
		//                    │          │
		// ┌──┐        ┌─-┐   │   ┌──┐   │
		// │  │◄───────┤  ├───┼──►│  │   │
		// └──┘        └──┘   │   └──┘   │
		//                    │          │
		//                    └──────────┘
		for  :=  + 1;  < ; ++ {
			if  := scoringCancellationError(, --1);  != nil {
				return 0, 
			}
			if [] {
				continue
			}
			 := []
			if .Container != .Container {
				continue
			}
			// When nodes vary by a lot in size, it also doesn't come off as symmetrical
			,  := .Area(), .Area()
			if  > 2* ||  > 2* {
				continue
			}

			 := math.Inf(-1)
			// The two nodes can be symmetrical across the x or y axis
			for ,  := range [...]bool{true, false} {
				 := .TopLeft.X + .Width/2
				if ! {
					 = .TopLeft.Y + .Height/2
				}
				if isMirrored(, , , ) {
					if .OverlapsAlongDimension(, , true) &&
						.OverlapsAlongDimension(, , true) {
						 = 2
					} else {
						 = 0.5
					}
					// no need to continue, as there's no way n1-n2 is aligned both horizontally and vertically
					break
				}
			}
			// skip obstruction search if the nodes aren't aligned
			if  < 0 {
				continue
			}

			// We only want to count mirrored objects when they have no obstructions from node to it
			// This won't look symmetrical
			//             ┌─────────┐
			//             │         │
			// ┌───┐     ┌─┴┐  ┌─┐  ┌▼─┐
			// │   ◄─────┤  │  │ │  │  │
			// └───┘     └──┘  │ │  └──┘
			//                 └─┘
			,  := obstructed(, , , , , )
			if  != nil {
				return 0, 
			}
			if  {
				continue
			}

			if  >  {
				,  = , 
				if  == 2 {
					break
				}
			}
		}

		if  != -1 {
			[], [] = true, true
			 += 
		}
	}

	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	return , nil
}

func obstructed( context.Context, , ,  *layoutgraph.Node, ,  []*layoutgraph.Node) (bool, error) {
	 := scoringNodeBounds().including(scoringNodeBounds()).including(scoringNodeBounds())
	 := func( []*layoutgraph.Node) (bool, error) {
		for ,  := range  {
			if  := scoringCancellationError(, );  != nil {
				return false, 
			}
			if  == nil || .TopLeft == nil || .Graph != .Graph {
				continue
			}
			if  ==  ||  ==  ||  ==  {
				continue
			}
			if .excludes() {
				continue
			}
			if .IsDescendantOf() || .IsDescendantOf() || .IsDescendantOf() {
				continue
			}
			if .IsDescendantOf() || .IsDescendantOf() || .IsDescendantOf() {
				continue
			}
			if .PassesThrough(.Center(), .Center()) ||
				.PassesThrough(.Center(), .Center()) {
				return true, nil
			}
		}
		return false, nil
	}
	,  := ()
	if  != nil ||  {
		return , 
	}
	return ()
}

type columnCrossingScratch struct {
	edgeToAbduction map[*layoutgraph.Edge]*layoutgraph.EdgeAbduction
	abductionInput  int
	segments        []layoutgraph.CrossingSegment
}

const maxPooledColumnCrossingEntries = 4096

var columnCrossingScratchPool = typedpool.New(func() *columnCrossingScratch {
	return new(columnCrossingScratch)
})

func putColumnCrossingScratch( *columnCrossingScratch) {
	if .abductionInput > maxPooledColumnCrossingEntries ||
		len(.edgeToAbduction) > maxPooledColumnCrossingEntries ||
		cap(.segments) > maxPooledColumnCrossingEntries {
		return
	}
	clear(.edgeToAbduction)
	.abductionInput = 0
	.segments = .segments[:0]
	columnCrossingScratchPool.Put()
}

// ColumnCrossingCost scores crossings between labeled table-column edges.
func ( context.Context,  *layoutgraph.Node,  []*layoutgraph.EdgeAbduction) (float64, error) {
	if  := .Err();  != nil {
		return 0, fmt.Errorf("EdgeLength: %w", )
	}
	if !.IsTable() {
		return 0, nil
	}

	 := columnCrossingScratchPool.Get()
	defer putColumnCrossingScratch()
	clear(.edgeToAbduction)
	.abductionInput = len()
	.segments = .segments[:0]
	if len() > 0 && .edgeToAbduction == nil {
		.edgeToAbduction = make(map[*layoutgraph.Edge]*layoutgraph.EdgeAbduction, min(len(), maxPooledColumnCrossingEntries))
	}
	for ,  := range  {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		.edgeToAbduction[.Edge] = 
	}

	for ,  := range .Edges {
		if  := scoringCancellationError(, );  != nil {
			return 0, 
		}
		var ,  geo.Point
		var ,  bool
		if ,  := .edgeToAbduction[];  {
			, , , , _ = .FacingTablePortValues(.OriginallyFrom, .OriginallyTo)
		} else {
			, , , , _ = .FacingTablePortValues(nil, nil)
		}

		if ! || ! {
			continue
		}
		.segments = append(.segments, layoutgraph.CrossingSegment{Start: , End: })
	}

	,  := layoutgraph.CountSegmentCrossingsContext(, .segments)
	if  != nil {
		return 0, 
	}
	return layoutgraph.CrossingCostWeight * float64() * .Graph.CellSize, nil
}

// isMirrored checks if nodeA is mirrored to nodeB along the given axis line
// isXAxis = true means they are mirrored along a vertical line
func isMirrored(,  *layoutgraph.Node,  bool,  float64) bool {
	 := .TopLeft.X
	 := .TopLeft.Y
	 := .TopLeft.X + .Width
	 := .TopLeft.Y + .Height

	 := .TopLeft.X
	 := .TopLeft.Y
	 := .TopLeft.X + .Width
	 := .TopLeft.Y + .Height

	 := symmetryToleranceBand * .Graph.CellSize
	if  {
		if .TopLeft.X == .TopLeft.X {
			return false
		}

		// The left edge of node A should be on other side of axisVal from right edge of node B
		if .TopLeft.X > .TopLeft.X {
			// Axis val must be in between
			if !(( > ) && ( < )) {
				return false
			}
			// Their distances from the axis line should be roughly equal
			if math.Abs((-)-(-)) >  {
				return false
			}
		} else if .TopLeft.X < .TopLeft.X {
			if !(( > ) && ( < )) {
				return false
			}
			if math.Abs((-)-(-)) >  {
				return false
			}
		}

		// Centers match
		return math.Abs(((+)/2.0)-((+)/2.0)) <= 
	}
	if .TopLeft.Y == .TopLeft.Y {
		return false
	}
	if .TopLeft.Y > .TopLeft.Y {
		if !(( > ) && ( < )) {
			return false
		}
		if math.Abs((-)-(-)) >  {
			return false
		}
	} else if .TopLeft.Y < .TopLeft.Y {
		if !(( > ) && ( < )) {
			return false
		}
		if math.Abs((-)-(-)) >  {
			return false
		}
	}

	// Centers match
	return math.Abs(((+)/2.0)-((+)/2.0)) <= 
}