package d2graph

import (
	
	
	

	
	
	
	
)

const MIN_SEGMENT_LEN = 10

func ( *Object) (,  float64) {
	.TopLeft.X += 
	.TopLeft.Y += 
	for ,  := range .ChildrenArray {
		.(, )
	}
}

func ( *Object) (,  float64) {
	 :=  - .TopLeft.X
	 :=  - .TopLeft.Y
	.MoveWithDescendants(, )
}

func ( *Object) ( *Object) {
	delete(.Children, strings.ToLower(.ID))
	for  := 0;  < len(.ChildrenArray); ++ {
		if .ChildrenArray[] ==  {
			.ChildrenArray = append(.ChildrenArray[:], .ChildrenArray[+1:]...)
			break
		}
	}
}

// remove obj and all descendants from graph, as a new Graph
func ( *Graph) ( *Object) *Graph {
	,  := pluckObjAndEdges(, )

	 := NewGraph()
	.RootLevel = int(.Level()) - 1
	.Root.ChildrenArray = []*Object{}
	.Root.Children[strings.ToLower(.ID)] = 

	for ,  := range  {
		.Graph = 
	}
	.Objects = 
	.Edges = 

	.Parent.RemoveChild()
	.Parent = .Root

	return 
}

func pluckObjAndEdges( *Graph,  *Object) ( []*Object,  []*Edge) {
	for  := 0;  < len(.Edges); ++ {
		 := .Edges[]
		if .Src.IsDescendantOf() && .Dst.IsDescendantOf() {
			 = append(, )
			.Edges = append(.Edges[:], .Edges[+1:]...)
			--
		}
	}

	for  := 0;  < len(.Objects); ++ {
		 := .Objects[]
		if .IsDescendantOf() {
			 = append(, )
			.Objects = append(.Objects[:], .Objects[+1:]...)
			--
		}
	}

	return , 
}

func ( *Graph) ( *Graph,  *Object) {
	 := .Root.ChildrenArray[0]
	 := 0 - .TopLeft.X
	 := 0 - .TopLeft.Y
	.MoveWithDescendants(, )
	for ,  := range .Edges {
		.Move(, )
	}
	.Parent = 
	for ,  := range .Objects {
		.Graph = 
	}
	.Objects = append(.Objects, .Objects...)
	.Children[strings.ToLower(.ID)] = 
	.ChildrenArray = append(.ChildrenArray, )
	.Edges = append(.Edges, .Edges...)
}

// ShiftDescendants moves Object's descendants (not including itself)
// descendants' edges are also moved by the same dx and dy (the whole route is moved if both ends are a descendant)
func ( *Object) (,  float64) {
	// also need to shift edges of descendants that are shifted
	 := make(map[*Edge]struct{})
	for ,  := range .Graph.Edges {
		 := .Src.IsDescendantOf()
		 := .Dst.IsDescendantOf()

		if  &&  {
			[] = struct{}{}
			for ,  := range .Route {
				.X += 
				.Y += 
			}
		}
	}

	.IterDescendants(func(,  *Object) {
		.TopLeft.X += 
		.TopLeft.Y += 
		for ,  := range .Graph.Edges {
			if ,  := [];  {
				continue
			}
			 := .Src == 
			 := .Dst == 

			if  &&  {
				for ,  := range .Route {
					.X += 
					.Y += 
				}
			} else if  {
				if  == 0 {
					.ShiftStart(, false)
				} else if  == 0 {
					.ShiftStart(, true)
				} else {
					.Route[0].X += 
					.Route[0].Y += 
				}
			} else if  {
				if  == 0 {
					.ShiftEnd(, false)
				} else if  == 0 {
					.ShiftEnd(, true)
				} else {
					.Route[len(.Route)-1].X += 
					.Route[len(.Route)-1].Y += 
				}
			}

			if  ||  {
				[] = struct{}{}
			}
		}
	})
}

// ShiftStart moves the starting point of the route by delta either horizontally or vertically
// if subsequent points are in line with the movement, they will be removed (unless it is the last point)
// start                   end
// . ├────┼────┼───┼────┼───┤   before
// . ├──dx──►
// .        ├──┼───┼────┼───┤   after
func ( *Edge) ( float64,  bool) {
	 := func( *geo.Point) float64 {
		if  {
			return .X
		}
		return .Y
	}

	 := .Route[0]
	 := .Route[1]
	 := () < ()
	if  {
		.X += 
	} else {
		.Y += 
	}

	if  == ( < 0) {
		// nothing more to do when moving away from the next point
		return
	}

	 := func( *geo.Point) bool {
		if  {
			return .Y == .Y
		}
		return .X == .X
	}
	 := func( *geo.Point) bool {
		if  > 0 {
			return () < ()
		} else {
			return () > ()
		}
	}

	 := false
	 := make([]bool, len(.Route))
	for  := 1;  < len(.Route)-1; ++ {
		if !(.Route[]) {
			break
		}
		if (.Route[]) {
			[] = true
			 = true
		}
	}
	if  {
		.Route = geo.RemovePoints(.Route, )
	}
}

// ShiftEnd moves the ending point of the route by delta either horizontally or vertically
// if prior points are in line with the movement, they will be removed (unless it is the first point)
func ( *Edge) ( float64,  bool) {
	 := func( *geo.Point) float64 {
		if  {
			return .X
		}
		return .Y
	}

	 := .Route[len(.Route)-1]
	 := .Route[len(.Route)-2]
	 := () < ()
	if  {
		.X += 
	} else {
		.Y += 
	}

	if  == ( > 0) {
		// nothing more to do when moving away from the next point
		return
	}

	 := func( *geo.Point) bool {
		if  {
			return .Y == .Y
		}
		return .X == .X
	}
	 := func( *geo.Point) bool {
		if  > 0 {
			return () < ()
		} else {
			return () > ()
		}
	}

	 := false
	 := make([]bool, len(.Route))
	for  := len(.Route) - 2;  > 0; -- {
		if !(.Route[]) {
			break
		}
		if (.Route[]) {
			[] = true
			 = true
		}
	}
	if  {
		.Route = geo.RemovePoints(.Route, )
	}
}

// GetModifierElementAdjustments returns width/height adjustments to account for shapes with 3d or multiple
func ( *Object) () (,  float64) {
	if .Is3D() {
		if .Shape.Value == d2target.ShapeHexagon {
			 = d2target.THREE_DEE_OFFSET / 2
		} else {
			 = d2target.THREE_DEE_OFFSET
		}
		 = d2target.THREE_DEE_OFFSET
	} else if .IsMultiple() {
		 = d2target.MULTIPLE_OFFSET
		 = d2target.MULTIPLE_OFFSET
	}
	return , 
}

func ( *Object) () geo.Spacing {
	 := geo.Spacing{}

	if .HasLabel() && .LabelPosition != nil {
		 := label.FromString(*.LabelPosition)

		 := float64(.LabelDimensions.Width + label.PADDING)
		 := float64(.LabelDimensions.Height + label.PADDING)

		switch  {
		case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight:
			.Top = 
		case label.OutsideBottomLeft, label.OutsideBottomCenter, label.OutsideBottomRight:
			.Bottom = 
		case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
			.Left = 
		case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
			.Right = 
		}

		// if an outside label is larger than the object add margin accordingly
		if  > .Width {
			 :=  - .Width
			switch  {
			case label.OutsideTopLeft, label.OutsideBottomLeft:
				// label fixed at left will overflow on right
				.Right = 
			case label.OutsideTopCenter, label.OutsideBottomCenter:
				.Left = math.Ceil( / 2)
				.Right = math.Ceil( / 2)
			case label.OutsideTopRight, label.OutsideBottomRight:
				.Left = 
			}
		}
		if  > .Height {
			 :=  - .Height
			switch  {
			case label.OutsideLeftTop, label.OutsideRightTop:
				.Bottom = 
			case label.OutsideLeftMiddle, label.OutsideRightMiddle:
				.Top = math.Ceil( / 2)
				.Bottom = math.Ceil( / 2)
			case label.OutsideLeftBottom, label.OutsideRightBottom:
				.Top = 
			}
		}
	}

	if .HasIcon() && .IconPosition != nil {
		 := label.FromString(*.IconPosition)

		 := float64(d2target.MAX_ICON_SIZE + label.PADDING)
		switch  {
		case label.OutsideTopLeft, label.OutsideTopCenter, label.OutsideTopRight:
			.Top = math.Max(.Top, )
		case label.OutsideBottomLeft, label.OutsideBottomCenter, label.OutsideBottomRight:
			.Bottom = math.Max(.Bottom, )
		case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
			.Left = math.Max(.Left, )
		case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
			.Right = math.Max(.Right, )
		}
	}

	,  := .GetModifierElementAdjustments()
	.Right += 
	.Top += 
	return 
}

func ( *Object) () shape.Shape {
	 := .TopLeft
	if  == nil {
		 = geo.NewPoint(0, 0)
	}
	 := strings.ToLower(.Shape.Value)
	 := d2target.DSL_SHAPE_TO_SHAPE_TYPE[]
	 := geo.NewBox(, .Width, .Height)
	 := shape.NewShape(, )
	if  == shape.CLOUD_TYPE && .ContentAspectRatio != nil {
		.SetInnerBoxAspectRatio(*.ContentAspectRatio)
	}
	return 
}

func ( *Object) () *geo.Point {
	if .LabelPosition == nil {
		return nil
	}

	 := .ToShape()
	 := label.FromString(*.LabelPosition)

	var  *geo.Box
	if .IsOutside() {
		 = .GetBox()
	} else {
		 = .GetInnerBox()
	}

	 := .GetPointOnBox(, label.PADDING,
		float64(.LabelDimensions.Width),
		float64(.LabelDimensions.Height),
	)
	return 
}

func ( *Object) () *geo.Point {
	if .IconPosition == nil {
		return nil
	}

	 := .ToShape()
	 := label.FromString(*.IconPosition)

	var  *geo.Box
	if .IsOutside() {
		 = .GetBox()
	} else {
		 = .GetInnerBox()
	}

	return .GetPointOnBox(, label.PADDING, d2target.MAX_ICON_SIZE, d2target.MAX_ICON_SIZE)
}

func ( *Edge) ( []*geo.Point, ,  int) (,  int) {
	 := .Src.ToShape()
	 := .Dst.ToShape()

	 := geo.Segment{Start: [+1], End: []}
	// if an edge runs into an outside label, stop the edge at the label instead
	var ,  bool
	if .Src.HasLabel() {
		// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
		 := label.FromString(*.Src.LabelPosition)
		if .IsOutside() {
			 := float64(.Src.LabelDimensions.Width)
			 := float64(.Src.LabelDimensions.Height)
			 := .GetPointOnBox(.Src.Box, label.PADDING, , )

			 := geo.NewBox(, , )
			// add left/right padding to box
			.TopLeft.X -= label.PADDING
			.Width += 2 * label.PADDING

			for .Contains(.End) && +1 >  {
				.Start = .End
				.End = [+2]
				++
			}
			if  := .Intersections(); len() > 0 {
				 = true
				 := [0]
				if len() > 1 {
					 = findOuterIntersection(, )
				}
				// move starting segment to label intersection point
				[] = 
				.End = 
				// if the segment becomes too short, just merge it with the next segment
				if +1 <  && .Length() < MIN_SEGMENT_LEN {
					[+1] = []
					++
				}
			}
		}
	}
	if ! && .Src.HasIcon() {
		// assumes IconPosition is set if there is an Icon
		 := label.FromString(*.Src.IconPosition)
		if .IsOutside() {
			 := float64(d2target.MAX_ICON_SIZE)
			 := float64(d2target.MAX_ICON_SIZE)
			 := .GetPointOnBox(.Src.Box, label.PADDING, , )

			 := geo.NewBox(, , )
			for .Contains(.End) && +1 >  {
				.Start = .End
				.End = [+2]
				++
			}
			if  := .Intersections(); len() > 0 {
				 = true
				 := [0]
				if len() > 1 {
					 = findOuterIntersection(, )
				}
				// move starting segment to icon intersection point
				[] = 
				.End = 
				// if the segment becomes too short, just merge it with the next segment
				if +1 <  && .Length() < MIN_SEGMENT_LEN {
					[+1] = []
					++
				}
			}
		}
	}
	if ! && ! {
		if  := .Src.Intersections(); len() > 0 {
			// move starting segment to intersection point
			[] = [0]
			.End = [0]
			// if the segment becomes too short, just merge it with the next segment
			if +1 <  && .Length() < MIN_SEGMENT_LEN {
				[+1] = []
				++
			}
		}
		// trace the edge to the specific shape's border
		[] = shape.TraceToShapeBorder(, [], [+1])
	}
	 := geo.Segment{Start: [-1], End: []}
	 = false
	 = false
	if .Dst.HasLabel() {
		// assumes LabelPosition, LabelWidth, LabelHeight are all set if there is a label
		 := label.FromString(*.Dst.LabelPosition)
		if .IsOutside() {
			 := float64(.Dst.LabelDimensions.Width)
			 := float64(.Dst.LabelDimensions.Height)
			 := .GetPointOnBox(.Dst.Box, label.PADDING, , )

			 := geo.NewBox(, , )
			// add left/right padding to box
			.TopLeft.X -= label.PADDING
			.Width += 2 * label.PADDING
			for .Contains(.Start) && -1 >  {
				.End = .Start
				.Start = [-2]
				--
			}
			if  := .Intersections(); len() > 0 {
				 = true
				 := [0]
				if len() > 1 {
					 = findOuterIntersection(, )
				}
				// move ending segment to label intersection point
				[] = 
				.End = 
				// if the segment becomes too short, just merge it with the previous segment
				if -1 >  && .Length() < MIN_SEGMENT_LEN {
					[-1] = []
					--
				}
			}
		}
	}
	if ! && .Dst.HasIcon() {
		// assumes IconPosition is set if there is an Icon
		 := label.FromString(*.Dst.IconPosition)
		if .IsOutside() {
			 := d2target.GetIconSize(.Dst.Box, .String())
			 := float64()
			 := float64()
			 := .GetPointOnBox(.Dst.Box, label.PADDING, , )

			 := geo.NewBox(, , )
			for .Contains(.Start) && -1 >  {
				.End = .Start
				.Start = [-2]
				--
			}
			if  := .Intersections(); len() > 0 {
				 = true
				 := [0]
				if len() > 1 {
					 = findOuterIntersection(, )
				}
				// move ending segment to icon intersection point
				[] = 
				.End = 
				// if the segment becomes too short, just merge it with the previous segment
				if -1 >  && .Length() < MIN_SEGMENT_LEN {
					[-1] = []
					--
				}
			}
		}
	}
	if ! && ! {
		if  := .Dst.Intersections(); len() > 0 {
			// move ending segment to intersection point
			[] = [0]
			.End = [0]
			// if the segment becomes too short, just merge it with the previous segment
			if -1 >  && .Length() < MIN_SEGMENT_LEN {
				[-1] = []
				--
			}
		}
		[] = shape.TraceToShapeBorder(, [], [-1])
	}
	return , 
}

func findOuterIntersection( label.Position,  []*geo.Point) *geo.Point {
	switch  {
	case label.OutsideTopLeft, label.OutsideTopRight, label.OutsideTopCenter:
		sort.Slice(, func(,  int) bool {
			return [].Y < [].Y
		})
	case label.OutsideBottomLeft, label.OutsideBottomRight, label.OutsideBottomCenter:
		sort.Slice(, func(,  int) bool {
			return [].Y > [].Y
		})
	case label.OutsideLeftTop, label.OutsideLeftMiddle, label.OutsideLeftBottom:
		sort.Slice(, func(,  int) bool {
			return [].X < [].X
		})
	case label.OutsideRightTop, label.OutsideRightMiddle, label.OutsideRightBottom:
		sort.Slice(, func(,  int) bool {
			return [].X > [].X
		})
	}
	return [0]
}