Editor/Services/ArchCarry.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

public interface IArchCarries {
	ArchKind Kind { get; }

	void Carry( ArchPlan plan, ArchBuilding building, ArchRemap map );
}

public sealed class ArchCarriers : ArchTable<ArchCarriers, ArchKind, IArchCarries> {
	protected override IEnumerable<IArchCarries> Standing() => Enumerable.Empty<IArchCarries>();

	protected override ArchKind KeyOf( IArchCarries carrier ) => carrier.Kind;

	protected override bool Accepts( IArchCarries carrier ) => carrier.Kind.Known;

	protected override string Collision( IArchCarries standing, IArchCarries carrier ) {
		return $"{carrier.Kind.Ident} is carried by both {standing.GetType().Name} and {carrier.GetType().Name}";
	}

	public IReadOnlyCollection<IArchCarries> All => Entries;
}

public static class ArchCarry {
	public const float CornerReach = 1f;

	public static void Shift( ArchBuilding building, Vector2 shift, ArchPlan plan = null )
		=> Unit( building, ArchRemap.Translation( shift ), plan );

	public static bool Turn( ArchBuilding building, Vector2 about, float degrees, ArchPlan plan = null ) {
		if ( building is null || MathF.Abs( degrees ) < 0.001f ) {
			return false;
		}

		Unit( building, ArchRemap.Spun( about, degrees ), plan );

		building.Facing = (building.Facing + degrees) % 360f;

		if ( building.Facing < 0f ) {
			building.Facing += 360f;
		}

		return true;
	}

	public static void Shift( ArchRoom room, Vector2 shift ) => Room( room, ArchRemap.Translation( shift ) );

	public static void Unit( ArchBuilding building, ArchRemap map, ArchPlan plan = null ) {
		if ( building is null ) {
			return;
		}

		if ( plan is not null ) {
			foreach ( var carrier in ArchCarriers.Load().All ) {
				carrier.Carry( plan, building, map );
			}
		}

		foreach ( var room in building.Rooms ) {
			Room( room, map );
		}

		foreach ( var roof in building.Roofs ) {
			Roof( roof, map );
		}

		foreach ( var fence in building.Fences ) {
			map.Nodes( fence.Nodes );
		}

		foreach ( var platform in building.Platforms ) {
			Platform( platform, map );
		}

		foreach ( var pipe in building.Downpipes ) {
			pipe.Outlet = map.Of( pipe.Outlet );
			pipe.Wall = map.Of( pipe.Wall );
		}

		foreach ( var run in building.Pipes ) {
			map.Nodes( run.Nodes );

			run.Outward = map.Spun( run.Outward );
		}

		foreach ( var bracket in building.Brackets ) {
			var min = bracket.Min;
			var max = bracket.Max;

			map.Centred( ref min, ref max );

			bracket.Min = min;
			bracket.Max = max;
			bracket.Outward = map.Spun( bracket.Outward );
			bracket.AlongX = map.Axis( bracket.AlongX );
		}

		foreach ( var ladder in building.Ladders ) {
			ladder.Anchor = map.Of( ladder.Anchor );
			ladder.Outward = map.Spun( ladder.Outward );
		}

		foreach ( var balcony in building.Balconies ) {
			balcony.Anchor = map.Of( balcony.Anchor );
			balcony.Outward = map.Spun( balcony.Outward );
		}

		foreach ( var flight in building.ExteriorStairs ) {
			flight.Anchor = map.Of( flight.Anchor );
			flight.Outward = map.Spun( flight.Outward );
		}

		foreach ( var cut in building.Cuts ) {
			Cut( cut, map );
		}

		foreach ( var cutout in building.Cutouts ) {
			Cutout( cutout, map );
		}
	}

	public static void Room( ArchRoom room, ArchRemap map ) {
		if ( room is null ) {
			return;
		}

		foreach ( var wall in room.Walls ) {
			wall.Start = map.Of( wall.Start );
			wall.End = map.Of( wall.End );
		}

		map.Loop( room.Footprint );

		room.FloorBoardYaw = map.Spun( room.FloorBoardYaw );

		foreach ( var stair in room.Stairs ) {
			Stair( stair, map );
		}

		foreach ( var trim in room.Trims ) {
			map.Path( trim.Path );
		}

		foreach ( var pillar in room.Pillars ) {
			Pillar( pillar, map );
		}

		foreach ( var span in room.PierSpans ) {
			span.From.At = map.Of( span.From.At );
			span.To.At = map.Of( span.To.At );
		}

		foreach ( var beam in room.Beams ) {
			var outline = beam.Outline();

			map.Loop( outline );
			beam.Reshape( outline );

			beam.Yaw = map.Spun( beam.Yaw );
		}

		foreach ( var porch in room.Porches ) {
			Porch( porch, map );
		}

		foreach ( var approach in room.Approaches ) {
			approach.Origin = map.Of( approach.Origin );
			approach.Yaw = map.Spun( approach.Yaw );

			map.Nodes( approach.Nodes );
		}
	}

	// On odd turns the axes swap — counts and spans must trade places too
	static void Pillar( ArchPillarPart pillar, ArchRemap map ) {
		pillar.Origin = map.Of( pillar.Origin );
		pillar.Yaw = map.Spun( pillar.Yaw );
		pillar.Spacing = map.Swapped( pillar.Spacing );

		if ( !map.SwapsAxes ) {
			return;
		}

		(pillar.CountX, pillar.CountY) = (pillar.CountY, pillar.CountX);
		(pillar.SpanAlongX, pillar.SpanAlongY) = (pillar.SpanAlongY, pillar.SpanAlongX);
	}

	public static void Roof( ArchRoofPart roof, ArchRemap map ) {
		if ( roof is null ) {
			return;
		}

		if ( map.KeepsAxes ) {
			var min = roof.Min;
			var max = roof.Max;

			map.Box( ref min, ref max );

			roof.Min = min;
			roof.Max = max;

			map.Loop( roof.Footprint );
		} else {
			roof.Reshape( Turning( map, roof.Outline() ) );
		}

		roof.RidgeAlongX = map.Axis( roof.RidgeAlongX );

		foreach ( var wall in roof.Walls ) {
			wall.Start = map.Of( wall.Start );
			wall.End = map.Of( wall.End );
		}

		foreach ( var light in roof.Lights ) {
			var lightMin = light.Min;
			var lightMax = light.Max;

			map.Centred( ref lightMin, ref lightMax );

			light.Min = lightMin;
			light.Max = lightMax;
		}
	}

	public static void Platform( ArchPlatformPart platform, ArchRemap map ) {
		if ( platform is null ) {
			return;
		}

		if ( map.KeepsAxes ) {
			var min = platform.Min;
			var max = platform.Max;

			map.Box( ref min, ref max );

			platform.Min = min;
			platform.Max = max;

			map.Loop( platform.Footprint );
		} else {
			platform.Reshape( Turning( map, platform.Outline() ) );
		}

		platform.RampYaw = map.Spun( platform.RampYaw );

		foreach ( var stair in platform.Stairs ) {
			Stair( stair, map );
		}
	}

	public static void Porch( ArchPorchPart porch, ArchRemap map ) {
		if ( porch is null ) {
			return;
		}

		foreach ( var leg in porch.Legs ) {
			var min = leg.Min;
			var max = leg.Max;

			map.Centred( ref min, ref max );

			leg.Min = min;
			leg.Max = max;
		}

		foreach ( var stair in porch.Stairs ) {
			Stair( stair, map );
		}

		foreach ( var pillar in porch.Pillars ) {
			Pillar( pillar, map );
		}

		foreach ( var trim in porch.Trims ) {
			map.Path( trim.Path );
		}
	}

	public static void Stair( ArchStairPart stair, ArchRemap map ) {
		if ( stair?.Core is not { } core ) {
			return;
		}

		var opening = stair.TopOpening is { Count: >= 3 }
			? stair.TopOpening.Select( point => core.Flat( point.x, point.y ) ).ToList()
			: null;
		var openingSnap = stair.TopOpeningSnap is { Count: >= 3 }
			? stair.TopOpeningSnap.Select( point => core.Flat( point.x, point.y ) ).ToList()
			: null;
		var frame = core.Axes;
		var origin = map.Of( core.Origin );
		var along = map.Of( frame.Flat( core.Length, 0f ) ) - origin;
		var across = map.Of( frame.Flat( 0f, core.Width ) ) - origin;

		core.Origin = origin;
		core.Yaw = along.IsNearZeroLength ? core.Yaw : MathF.Atan2( along.y, along.x ).RadianToDegree();
		core.Length = MathF.Max( 1f, along.Length );
		core.Width = MathF.Max( 1f, across.Length );

		if ( opening is not null ) {
			map.Loop( opening );
			stair.TopOpening = opening.Select( point => ArchStairLanes.Local( core, point ) ).ToList();
		}

		if ( openingSnap is not null ) {
			map.Loop( openingSnap );
			stair.TopOpeningSnap = openingSnap.Select( point => ArchStairLanes.Local( core, point ) ).ToList();
		}
	}

	public static void Cut( ArchCutPart cut, ArchRemap map ) {
		if ( cut is null ) {
			return;
		}

		cut.RampYaw = map.Spun( cut.RampYaw );

		foreach ( var segment in cut.Segments ) {
			segment.Start = map.Of( segment.Start );
			segment.End = map.Of( segment.End );

			if ( segment.HasLoop ) {
				map.Loop( segment.Loop );
			}
		}
	}

	public static void Cutout( ArchFloorCutout cutout, ArchRemap map ) {
		if ( cutout is null ) {
			return;
		}

		if ( cutout.HasLoop || !map.KeepsAxes ) {
			cutout.Reshape( Turning( map, cutout.Outline() ) );

			return;
		}

		var min = cutout.Min;
		var max = cutout.Max;

		map.Box( ref min, ref max );

		cutout.Min = min;
		cutout.Max = max;
	}

	static List<Vector2> Turning( ArchRemap map, List<Vector2> outline ) {
		map.Loop( outline );

		return outline;
	}

	public static bool Corner( ArchBuilding building, Vector2 from, Vector2 to ) {
		if ( building is null || (to - from).Length < 0.01f ) {
			return false;
		}

		return Carrying( building, point => Coincident( point, from ), _ => to );
	}

	public static bool Along( ArchBuilding building, Vector2 start, Vector2 end, Vector2 shift ) {
		if ( building is null || shift.Length < 0.01f ) {
			return false;
		}

		return Carrying( building, point => OnRun( point, start, end ), point => point + shift );
	}

	public static bool Coincident( Vector2 point, Vector2 corner ) => (point - corner).Length < CornerReach;

	static bool OnRun( Vector2 point, Vector2 start, Vector2 end ) {
		var span = end - start;
		var length = span.Length;

		if ( length < 0.01f ) {
			return Coincident( point, start );
		}

		var along = Vector2.Dot( point - start, span / length );

		if ( along < -CornerReach || along > length + CornerReach ) {
			return false;
		}

		var nearest = start + span * Math.Clamp( along / length, 0f, 1f );

		return (point - nearest).Length < CornerReach;
	}

	static bool Carrying( ArchBuilding building, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		var moved = false;

		foreach ( var room in building.Rooms ) {
			moved |= Loop( room.Footprint, reaches, landed );
			moved |= Ends( room.Walls, reaches, landed );

			foreach ( var pillar in room.Pillars.Where( pillar => reaches( pillar.Origin ) ) ) {
				pillar.Origin = landed( pillar.Origin );
				moved = true;
			}

			foreach ( var trim in room.Trims.Where( trim => !trim.Follows ) ) {
				moved |= Path( trim.Path, reaches, landed );
			}

			foreach ( var stair in room.Stairs ) {
				moved |= Shaft( stair, reaches, landed );
			}

			foreach ( var beam in room.Beams ) {
				var outline = beam.Outline();

				if ( !Loop( outline, reaches, landed ) ) {
					continue;
				}

				beam.Reshape( outline );
				moved = true;
			}

			foreach ( var porch in room.Porches ) {
				moved |= Carrying( porch, reaches, landed );
			}

			foreach ( var approach in room.Approaches ) {
				if ( reaches( approach.Origin ) ) {
					approach.Origin = landed( approach.Origin );
					moved = true;
				}

				moved |= Nodes( approach.Nodes, reaches, landed );
			}
		}

		foreach ( var roof in building.Roofs ) {
			var outline = roof.Outline();

			if ( Loop( outline, reaches, landed ) ) {
				roof.Reshape( outline );
				moved = true;
			}

			moved |= Ends( roof.Walls, reaches, landed );
		}

		foreach ( var platform in building.Platforms ) {
			var outline = platform.Outline();

			if ( Loop( outline, reaches, landed ) ) {
				platform.Reshape( outline );
				moved = true;
			}

			foreach ( var stair in platform.Stairs ) {
				moved |= Shaft( stair, reaches, landed );
			}
		}

		foreach ( var fence in building.Fences ) {
			moved |= Nodes( fence.Nodes, reaches, landed );
		}

		foreach ( var pipe in building.Downpipes ) {
			if ( reaches( pipe.Outlet ) ) {
				pipe.Outlet = landed( pipe.Outlet );
				moved = true;
			}

			if ( reaches( pipe.Wall ) ) {
				pipe.Wall = landed( pipe.Wall );
				moved = true;
			}
		}

		foreach ( var run in building.Pipes ) {
			moved |= Nodes( run.Nodes, reaches, landed );
		}

		foreach ( var bracket in building.Brackets ) {
			var min = bracket.Min;
			var max = bracket.Max;

			if ( !Box( ref min, ref max, reaches, landed ) ) {
				continue;
			}

			bracket.Min = min;
			bracket.Max = max;
			moved = true;
		}

		foreach ( var ladder in building.Ladders.Where( ladder => reaches( ladder.Anchor ) ) ) {
			ladder.Anchor = landed( ladder.Anchor );
			moved = true;
		}

		foreach ( var balcony in building.Balconies.Where( balcony => reaches( balcony.Anchor ) ) ) {
			balcony.Anchor = landed( balcony.Anchor );
			moved = true;
		}

		foreach ( var flight in building.ExteriorStairs.Where( flight => reaches( flight.Anchor ) ) ) {
			flight.Anchor = landed( flight.Anchor );
			moved = true;
		}

		// Refit at existing yaw — deriving a new one from the moved corner would swing the frame
		foreach ( var segment in building.Cuts.SelectMany( cut => cut.Segments ).Where( segment => segment.HasLoop ) ) {
			var yaw = segment.Yaw;

			if ( !Loop( segment.Loop, reaches, landed ) ) {
				continue;
			}

			ArchCut.Fit( segment, yaw );
			moved = true;
		}

		foreach ( var cutout in building.Cutouts.Where( cutout => cutout.HasLoop ) ) {
			var loop = cutout.Loop.ToList();

			if ( !Loop( loop, reaches, landed ) ) {
				continue;
			}

			cutout.Reshape( loop );
			moved = true;
		}

		return moved;
	}

	static bool Carrying( ArchPorchPart porch, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		var moved = false;

		foreach ( var leg in porch.Legs ) {
			var min = leg.Min;
			var max = leg.Max;

			if ( !Box( ref min, ref max, reaches, landed ) ) {
				continue;
			}

			leg.Min = min;
			leg.Max = max;
			moved = true;
		}

		foreach ( var stair in porch.Stairs ) {
			moved |= Shaft( stair, reaches, landed );
		}

		foreach ( var pillar in porch.Pillars.Where( pillar => reaches( pillar.Origin ) ) ) {
			pillar.Origin = landed( pillar.Origin );
			moved = true;
		}

		foreach ( var trim in porch.Trims ) {
			moved |= Path( trim.Path, reaches, landed );
		}

		return moved;
	}

	// Translates the whole shaft — pulling one corner of a rectangle apart is not editing
	static bool Shaft( ArchStairPart stair, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		if ( stair?.Core is not { } core ) {
			return false;
		}

		foreach ( var corner in core.Outline() ) {
			if ( !reaches( corner ) ) {
				continue;
			}

			core.Origin += landed( corner ) - corner;

			return true;
		}

		return false;
	}

	static bool Box( ref Vector2 min, ref Vector2 max, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		foreach ( var corner in new[] { min, max, new Vector2( min.x, max.y ), new Vector2( max.x, min.y ) } ) {
			if ( !reaches( corner ) ) {
				continue;
			}

			var shift = landed( corner ) - corner;

			min += shift;
			max += shift;

			return true;
		}

		return false;
	}

	static bool Ends( IEnumerable<ArchWall> walls, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		var moved = false;

		foreach ( var wall in walls ) {
			if ( reaches( wall.Start ) ) {
				wall.Start = landed( wall.Start );
				moved = true;
			}

			if ( reaches( wall.End ) ) {
				wall.End = landed( wall.End );
				moved = true;
			}
		}

		return moved;
	}

	static bool Loop( List<Vector2> loop, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		var moved = false;

		for ( var index = 0; index < loop.Count; index++ ) {
			if ( !reaches( loop[index] ) ) {
				continue;
			}

			loop[index] = landed( loop[index] );
			moved = true;
		}

		return moved;
	}

	static bool Path( List<Vector3> path, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		var moved = false;

		for ( var index = 0; index < path.Count; index++ ) {
			var flat = new Vector2( path[index].x, path[index].y );

			if ( !reaches( flat ) ) {
				continue;
			}

			var to = landed( flat );

			path[index] = new Vector3( to.x, to.y, path[index].z );
			moved = true;
		}

		return moved;
	}

	static bool Nodes( List<ArchCurveNode> nodes, Func<Vector2, bool> reaches, Func<Vector2, Vector2> landed ) {
		var moved = false;

		foreach ( var node in nodes ) {
			var flat = new Vector2( node.Position.x, node.Position.y );

			if ( !reaches( flat ) ) {
				continue;
			}

			var to = landed( flat );

			node.Position = new Vector3( to.x, to.y, node.Position.z );
			moved = true;
		}

		return moved;
	}
}