using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace OmiLAXR.Composers.HigherComposers
{
///
/// Abstract base class for higher-level composers that aggregate and process multiple related statements.
/// Higher composers can analyze patterns of statements to detect complex events or behaviors.
///
/// The type of statement this composer processes.
public abstract class HigherComposer : PipelineComponent, IComposer
where T : IStatement
{
private string _name;
///
/// Gets the name of this composer.
///
/// The name of the composer.
public virtual string GetName() => _name;
///
/// Defines the conditions that this composer looks for in statements.
/// Must be implemented by derived classes to specify matching criteria.
///
/// Dictionary mapping condition names to their match conditions.
protected abstract Dictionary> Conditions();
///
/// Helper method to find a pipeline of the specified type.
///
/// The type of pipeline to find.
/// The first pipeline of the specified type found in the scene.
protected static TP GetPipeline()
where TP : Pipeline => FindObject(true);
///
/// Structure that defines a condition for matching statements.
/// Contains the condition logic, expected number of matches, and collection of matching statements.
///
/// The type of statement this condition applies to.
protected readonly struct MatchCondition
where T0 : IStatement
{
///
/// Delegate type for the condition function that evaluates statements.
///
/// The statement to evaluate.
/// True if the statement matches the condition, false otherwise.
public delegate bool MatchConditionHandler(T0 statement);
///
/// The condition function that determines if a statement matches.
///
public readonly MatchConditionHandler Condition;
///
/// The minimum number of matching statements required to satisfy this condition.
///
public readonly int ExpectedMatches;
///
/// Collection of statements that have matched this condition.
///
public readonly List MatchingStatements;
///
/// Gets the current number of statements that match this condition.
///
public int ActualMatches => MatchingStatements.Count;
///
/// Determines if the condition has collected enough matching statements.
///
public bool HasEnoughMatches => ActualMatches >= ExpectedMatches;
///
/// Initializes a new instance of the MatchCondition struct.
///
/// The minimum number of matching statements required.
/// The function that evaluates if statements match.
public MatchCondition(int expectedMatches, MatchConditionHandler condition)
{
Condition = condition;
ExpectedMatches = expectedMatches;
MatchingStatements = new List();
}
///
/// Evaluates a statement against the condition and adds it to matching statements if it matches.
///
/// The statement to evaluate.
public void CheckCondition(T0 statement)
{
if (Condition(statement))
MatchingStatements.Add(statement);
}
}
///
/// Event that fires when this composer creates a new statement.
///
public event ComposerAction AfterComposed;
///
/// Indicates that this is a higher-level composer.
///
public bool IsHigherComposer => true;
///
/// Indicates whether this composer is currently enabled.
///
public bool IsEnabled => enabled;
///
/// Gets information about the author of this composer.
///
/// Author information.
public abstract Author GetAuthor();
///
/// Examines a statement to see if it matches any of the conditions defined by this composer.
/// If enough statements match either any or all conditions, triggers the appropriate handler.
///
/// The statement to examine.
public void LookFor(IStatement statement)
{
// Skip if statement is not of the expected type
if (statement.GetType() != typeof(T))
return;
var conditions = Conditions();
var stmt = (T)statement;
// Check statement against all conditions
foreach (var condition in conditions.Values)
{
condition.CheckCondition(stmt);
}
// Check if any conditions have enough matches
var matchesAny =
conditions.Where(c => c.Value.HasEnoughMatches)
.ToDictionary>, string, IEnumerable>(
c => c.Key,
c => c.Value.MatchingStatements
);
// If any conditions have enough matches, call the any-match handler
if (matchesAny.Count > 0)
{
OnMatchAnyConditions(matchesAny);
}
// Check if all conditions have enough matches
var matchesAll = conditions.Values.All(c => c.HasEnoughMatches);
if (!matchesAll)
return;
// If all conditions have enough matches, call the all-match handler
var matchingStatements = conditions.ToDictionary(
i => i.Key,
i => (IEnumerable)i.Value.MatchingStatements
);
OnMatchAllConditions(matchingStatements);
}
///
/// Called when all conditions have collected enough matching statements.
/// Must be implemented by derived classes to handle the matched statements.
///
/// Dictionary mapping condition names to their matching statements.
protected abstract void OnMatchAllConditions(Dictionary> matchingStatements);
///
/// Called when any conditions have collected enough matching statements.
/// Can be overridden by derived classes to handle partial matches.
///
/// Dictionary mapping condition names to their matching statements.
protected virtual void OnMatchAnyConditions(Dictionary> matchingStatements)
{
// Default implementation does nothing - derived classes can override
}
///
/// Sends a new statement through the pipeline.
///
/// The statement to send.
/// Whether to process the statement immediately or queue it.
[Obsolete("Use SendStatement(IStatement) instead. Immediate is not needed anymore due efficient thread queue handling.", true)]
protected void SendStatement(IStatement statement, bool immediate)
=> SendStatement(statement);
protected void SendStatement(IStatement statement)
{
if (!IsEnabled)
return;
AfterComposed?.Invoke(this, statement);
}
///
/// Convenience method to send a statement for immediate processing.
///
/// The statement to send immediately.
[Obsolete("Use SendStatement(ITrackingBehaviour, IStatement) instead. Immediate is not needed anymore due efficient thread queue handling.", true)]
protected void SendStatementImmediate(IStatement statement)
=> SendStatement(statement, immediate: true);
}
}