Editor/Wall/ArchStoreyCornice.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// The entablature a STOREY wears at its head. It is ONE ring round the storey's own exterior loop - chamfer facets
// and all - rather than a piece per wall, because a corner resolved by each of its two walls from its own join is a
// joinery case nobody owns. A wall still NAMES the style, and one that names none drops its segment out of the ring.
public static class ArchStoreyCornice {
	public static void Build( ArchMesh canvas, ArchRoom room, ArchBuilding building, ArchPlan plan, ArchKit kit, ArchStyle style ) {
		if ( room is null || building is null || !Owns( room, building ) ) {
			return;
		}

		// The loop is a whole footprint union - asked before the walls are, every bare storey in the plan pays for it.
		var dressed = Dressed( room, building ).ToList();

		if ( dressed.Count == 0 ) {
			return;
		}

		var loop = Loop( room, building );

		if ( loop is not { Count: >= 3 } ) {
			return;
		}

		var head = Head( room, building, kit );
		var chain = new[] { room.Palette, building.Palette };
		var connections = new ArchConnections( building, kit );

		foreach ( var named in dressed.GroupBy( wall => wall.Cornice ) ) {
			if ( kit.FindCornice( named.Key ) is not { Stands: true } crown ) {
				continue;
			}

			var laying = named.ToList();

			new ArchCorniceRing {
				Crown = crown,
				Outline = loop,
				Head = head,
				Thickness = kit.WallThickness,
				Abuts = ( point, _ ) => !Carries( laying, kit, point ),
				Returns = Crossings( room, building, plan, kit, connections, crown, head ).ToList(),
				Kit = kit,
				Style = style,
				Chain = chain,
				Plan = plan,
				Level = room.Floor,
				HostId = building.Id,
				Steps = false
			}.Lay( canvas );
		}
	}

	// Where a pier standing on this storey has to die: the ring's own soffit, so the two are resolved once and the
	// pier head can never be driven up into the entablature.
	public static float Soffit( IArchWallTreatment wall, ArchKit kit, float height ) {
		return kit.FindCornice( wall.Cornice ) is { Stands: true } crown
			? ArchCorniceRing.Soffit( crown, height, kit )
			: height;
	}

	// One height for the whole storey, the way a deck's parapet seat is one height for the whole roof, or two walls
	// meeting at a corner arrive at the ring from different heights and neither mitres.
	public static float Head( ArchRoom room, ArchBuilding building, ArchKit kit ) {
		var above = building.Rooms
			.Where( other => other.Floor == room.Floor + 1 )
			.Select( other => other.BaseHeight )
			.DefaultIfEmpty( 0f )
			.Min();

		return above > room.BaseHeight + 0.5f ? above : room.BaseHeight + ArchWallSection.CeilingHeight( room, kit );
	}

	// The lowest-id room on the storey stands the ring for all of them - the same law that lets one wall build a
	// shared boundary, or two rooms on one floor ring the same loop twice.
	static bool Owns( ArchRoom room, ArchBuilding building ) {
		return building.Rooms.Where( other => other.Floor == room.Floor ).OrderBy( other => other.Id ).FirstOrDefault() == room;
	}

	static List<Vector2> Loop( ArchRoom room, ArchBuilding building ) {
		// Slanted loops kept: a chamfered storey is exactly the case the ring exists for.
		var storey = ArchRegion.Footprints( building.Rooms.Where( other => other.Floor == room.Floor ), false );

		return ArchFootprint.Outer( ArchFootprint.Union( storey ) ).FirstOrDefault();
	}

	static IEnumerable<ArchWall> Dressed( ArchRoom room, ArchBuilding building ) {
		return building.Rooms
			.Where( other => other.Floor == room.Floor )
			.SelectMany( other => other.Walls.Where( wall => Standing( wall, other, building ) ) );
	}

	static bool Standing( ArchWall wall, ArchRoom room, ArchBuilding building ) {
		return !string.IsNullOrWhiteSpace( wall.Cornice )
			&& ArchLayerGate.On( wall )
			&& !ArchWallJoins.CoveredBy( building, room, wall )
			&& ArchWallFaces.TryOutward( wall, room, building, out _ );
	}

	// A segment belongs to the ring being laid when one of the walls that named this style covers it. Everything
	// else - a party wall, a wall wearing another entablature, an interior run - is a break in the loop.
	static bool Carries( IReadOnlyList<ArchWall> laying, ArchKit kit, Vector2 point ) {
		foreach ( var wall in laying ) {
			var span = wall.End - wall.Start;
			var length = span.Length;

			if ( length < 0.5f ) {
				continue;
			}

			var along = span / length;
			var travelled = Vector2.Dot( point - wall.Start, along );

			if ( travelled < -0.5f || travelled > length + 0.5f ) {
				continue;
			}

			if ( MathF.Abs( Vector2.Dot( point - wall.Start, wall.Normal ) ) <= ArchWallSection.Thickness( wall, kit ) ) {
				return true;
			}
		}

		return false;
	}

	// Everything standing proud of the storey's elevations, folded into the ONE list the ring steps its path out
	// over. A pier whose cap dies under the soffit never reaches the band, so the ring runs straight over it.
	static IEnumerable<ArchCorniceReturn> Crossings(
		ArchRoom room,
		ArchBuilding building,
		ArchPlan plan,
		ArchKit kit,
		ArchConnections connections,
		ArchCorniceStyle crown,
		float head ) {
		var foot = ArchCorniceRing.Soffit( crown, head, kit ) - room.BaseHeight;
		var top = head - room.BaseHeight;
		var reach = ArchCorniceGen.Reach( crown ) + ArchLap.Bite( kit );

		foreach ( var other in building.Rooms.Where( entry => entry.Floor == room.Floor ) ) {
			foreach ( var wall in other.Walls.Where( wall => ArchLayerGate.On( wall ) ) ) {
				var thickness = ArchWallSection.Thickness( wall, kit );
				var join = connections.JoinWall( wall, other, thickness );

				foreach ( var shape in Proud( wall, other, building, plan, kit, join, top, thickness ) ) {
					if ( !shape.Outside || shape.Carves || shape.Depth <= reach || !shape.Spans( foot, top, top ) ) {
						continue;
					}

					yield return new ArchCorniceReturn(
						wall.Start + wall.Direction * shape.From,
						wall.Start + wall.Direction * shape.To,
						shape.Depth );
				}
			}
		}
	}

	static IEnumerable<ArchWallModShape> Proud(
		ArchWall wall,
		ArchRoom room,
		ArchBuilding building,
		ArchPlan plan,
		ArchKit kit,
		ArchWallJoin join,
		float height,
		float thickness ) {
		var cuts = wall.Openings.Where( opening => ArchLayerGate.On( opening ) ).OrderBy( opening => opening.Left ).ToList();
		var shapes = ArchWallModShape.For( wall, join, height, thickness, kit );

		shapes.AddRange( ArchWallExtrude.Zones( plan, kit, building, room, wall, join.StartExtend, join.EndExtend, height, thickness, cuts ) );
		shapes.AddRange( ArchWallPiers.Shapes( wall, join, kit, height, thickness, Soffit( wall, kit, height ) ) );

		return shapes;
	}
}