Editor/Decompiler/Lumps/EntityLump.cs
namespace BspImport.Decompiler.Lumps;

public class EntityLump : BaseLump
{
	public EntityLump( ImportContext context, byte[] data, int version = 0 ) : base( context, data, version ) { }

	protected override void Parse( BinaryReader reader )
	{
		var pairs = Encoding.ASCII.GetString( Data );
		var ents = FromKeyValues( pairs );

		//Log.Info( $"ENTITIES: {ents.Count()}" );

		Context.Entities = ents.ToArray();
	}

	private static IEnumerable<LumpEntity> FromKeyValues( string keyValues )
	{
		var data = keyValues.Split( '\n' );

		LumpEntity? ent = null;

		foreach ( var line in data )
		{
			switch ( line )
			{
				case "{":
					ent = new LumpEntity();
					break;
				case "}":
					if ( ent is not null )
						yield return ent;
					break;
				default:
					if ( ent is null )
						continue;

					var kv = line.Split( ' ', 2 );
					if ( kv.Length == 2 )
					{
						ent?.Data.Add( new( kv[0].Trim( '\"' ), kv[1].Trim( '\"' ) ) );
					}
					break;
			}
		}
	}
}

public class LumpEntity
{
	public List<KeyValuePair<string, string>> Data { get; private set; } = new();

	public string? GetValue( string key )
	{
		if ( !Data.Any() )
		{
			return null;
		}

		var find = Data.Where( x => x.Key == key );

		if ( !find.Any() )
		{
			return null;
		}

		var val = find.FirstOrDefault().Value;
		return val;
	}

	private void TryReplaceData( string key, string value )
	{
		var index = Data.FindIndex( x => x.Key == key );

		if ( index <= -1 )
		{
			Data.Add( new KeyValuePair<string, string>( key, value ) );
		}
		else
		{
			Data[index] = new KeyValuePair<string, string>( key, value );
		}
	}

	// helpers for constructing artificial entities, like prop static
	public void SetClassName( string name ) => TryReplaceData( "classname", name );

	public void SetPosition( Vector3 origin ) => TryReplaceData( "origin", $"[{origin.x} {origin.y} {origin.z}]" );

	public void SetAngles( Angles angles ) => TryReplaceData( "angles", $"{angles.pitch},{angles.yaw},{angles.roll}" );

	public void SetModel( string model ) => TryReplaceData( "model", model );

	// getting data for making actual MapEntity refs, parsed from kv data
	public string? ClassName => GetValue( "classname" );
	public string? TargetName => GetValue( "targetname" ) ?? ClassName ?? "entity";
	public Vector3 Position => Vector3.Parse( $"[{GetValue( "origin" ) ?? ""}]" );
	public bool IsBrushEntity => Model is not null && Model.StartsWith( '*' );

	public Angles Angles => GetRotation().Angles();
	public string? Model => GetValue( "model" );

	public Rotation GetRotation()
	{
		var vec = Vector3.Parse( GetValue( "angles" ) );

		var rotation = Rotation.Identity.RotateAroundAxis( Vector3.Up, vec.y ) // yaw
			.RotateAroundAxis( Vector3.Right, vec.x ) // pitch
			.RotateAroundAxis( Vector3.Forward, vec.z ); // roll

		return rotation;
	}

	public Rotation GetLightRotation()
	{
		float yaw = Angles.yaw;
		float pitch = GetValue( "pitch" )?.ToInt() ?? Angles.pitch;

		pitch = -pitch;

		var rotation = Rotation.FromYaw( yaw ) * Rotation.FromPitch( pitch );
		return rotation;
	}

	public void LogData()
	{
		foreach ( var entry in Data )
		{
			Log.Info( $"{entry.Key} {entry.Value}" );
		}
	}
}