using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Random = UnityEngine.Random;
namespace RR.Common.Controls
{
///
/// Управляет подсветкой периферийной платы по протоколу 85XXYY:
///
/// - XX — код эффекта ()
/// - YY — время в секундах (00–99)
///
/// Команда 85 (без параметров) отключает рендеринг и освобождает память.
/// Поддерживает цепочки эффектов через :
///
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop();
///
///
public class LightController : MonoBehaviour
{
// ───────────────────────── Инспектор ─────────────────────────
[Header("Команды шляпы")]
[Tooltip("COM-команда для включения шляпы")]
[SerializeField] private string hatOnCommand = "2131";
[Tooltip("COM-команда для выключения шляпы")]
[SerializeField] private string hatOffCommand = "2130";
[Header("Мигание")]
[Tooltip("Интервал между переключениями при мигании (секунды)")]
[SerializeField] [Range(0.05f, 5f)] private float blinkInterval = 0.5f;
[Tooltip("Случайный разброс интервала (±секунды). 0 = строго равномерно")]
[SerializeField] [Range(0f, 2f)] private float blinkIntervalVariance;
[Header("Световые эффекты")]
[Tooltip("Эффект по умолчанию при вызове SetLight без аргументов")]
[SerializeField] private LightMode defaultLightMode = LightMode.Rainbow;
[Tooltip("Время эффекта по умолчанию (секунды, 0–99)")]
[SerializeField] [Range(0, 99)] private int defaultDuration = 3;
// ───────────────────────── Приватное состояние ─────────────────────────
private Coroutine _blinkingCoroutine;
private LightSequence _activeSequence;
private const string EffectPrefix = "85";
// ───────────────────────── Свойства ─────────────────────────
/// Время эффекта по умолчанию (используется в ).
public int DefaultDuration => defaultDuration;
// ===========================start: PUBLIC API===========================
///
/// Включает эффект по умолчанию и возвращает для построения цепочки.
///
public LightSequence SetLight() => SetLight(defaultLightMode, defaultDuration);
///
/// Включает эффект на время по умолчанию и возвращает для построения цепочки.
///
public LightSequence SetLight(LightMode mode) => SetLight(mode, defaultDuration);
///
/// Включает световой эффект на указанное время и возвращает для построения цепочки.
/// Если вызвать без .Then() / .Loop() — эффект запустится немедленно как одиночный.
///
/// Эффект подсветки
/// Время эффекта (0–99 секунд)
public LightSequence SetLight(LightMode mode, int durationSeconds)
{
StopSequence();
_activeSequence = new LightSequence(this, mode, durationSeconds);
_activeSequence.Play();
return _activeSequence;
}
///
/// Останавливает текущую активную последовательность эффектов и отключает рендеринг.
///
public void StopSequence()
{
_activeSequence?.Stop();
_activeSequence = null;
}
///
/// Останавливает все эффекты и отключает рендеринг платы (85).
///
public void StopAllEffects()
{
StopSequence();
ComWrapper.Send(EffectPrefix);
}
///
/// Запускает мигание шляпы с настроенным интервалом.
///
public void Blink() => Blink(blinkInterval);
///
/// Запускает мигание шляпы с явно указанным интервалом.
///
public void Blink(float interval)
{
StopBlink(enableHat: false);
_blinkingCoroutine = StartCoroutine(BlinkingCoroutine(interval));
}
///
/// Меняет интервал мигания на лету; перезапускает мигание, если оно активно.
///
public void SetBlinkInterval(float interval)
{
blinkInterval = Mathf.Max(0.05f, interval);
if (_blinkingCoroutine != null) Blink(blinkInterval);
}
///
/// Останавливает мигание шляпы.
///
/// Оставить шляпу включённой после остановки
public void StopBlink(bool enableHat = true)
{
if (_blinkingCoroutine != null)
{
StopCoroutine(_blinkingCoroutine);
_blinkingCoroutine = null;
}
ComWrapper.Send(enableHat ? hatOnCommand : hatOffCommand);
}
/// Возвращает true, если мигание шляпы сейчас активно.
public bool IsBlinking => _blinkingCoroutine != null;
/// Возвращает true, если последовательность эффектов сейчас активна.
public bool IsSequencePlaying => _activeSequence?.IsPlaying ?? false;
// ===========================end: PUBLIC API===========================
///
/// Отправляет COM-команду для одного шага последовательности.
/// Используется из .
///
public void SendCommand(LightMode mode, int durationSeconds)
{
ComWrapper.Send(BuildCommand(mode, durationSeconds));
}
// ReSharper disable once IteratorNeverReturns — корутина намеренно бесконечная
private IEnumerator BlinkingCoroutine(float interval)
{
while (true)
{
ComWrapper.Send(Random.Range(0f, 1f) < 0.5f ? hatOnCommand : hatOffCommand);
float wait = blinkIntervalVariance > 0f
? Mathf.Max(0.05f, interval + Random.Range(-blinkIntervalVariance, blinkIntervalVariance))
: interval;
yield return new WaitForSeconds(wait);
}
}
private static string BuildCommand(LightMode mode, int durationSeconds)
{
if (mode == LightMode.Off)
return EffectPrefix;
int xx = (int)mode;
int yy = Mathf.Clamp(durationSeconds, 0, 99);
return $"{EffectPrefix}{xx:D2}{yy:D2}";
}
///
/// Строитель последовательности световых эффектов.
/// Создаётся через .
///
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.DiagonalWave, 3).Loop();
/// SetLight(LightMode.VerticalWave, 3).Then(LightMode.Off);
/// SetLight(LightMode.Rainbow, 5);
///
///
public class LightSequence
{
private readonly struct Step
{
public readonly LightMode Mode;
public readonly int DurationSeconds;
public Step(LightMode mode, int durationSeconds) { Mode = mode; DurationSeconds = durationSeconds; }
}
private readonly List _steps = new();
private readonly LightController _controller;
private bool _looping;
private Coroutine _coroutine;
internal LightSequence(LightController controller, LightMode firstMode, int firstDuration)
{
_controller = controller;
_steps.Add(new Step(firstMode, firstDuration));
}
/// Добавляет следующий эффект с временем по умолчанию.
public LightSequence Then(LightMode mode) => Then(mode, _controller.DefaultDuration);
/// Добавляет следующий эффект с указанным временем.
public LightSequence Then(LightMode mode, int durationSeconds)
{
_steps.Add(new Step(mode, durationSeconds));
return this;
}
///
/// Зацикливает последовательность. Для остановки — .
///
public LightSequence Loop()
{
_looping = true;
Play();
return this;
}
/// Запускает последовательность один раз.
public LightSequence Play()
{
Stop();
_coroutine = _controller.StartCoroutine(RunSequence());
return this;
}
/// Останавливает выполнение последовательности.
public void Stop()
{
if (_coroutine == null) return;
_controller.StopCoroutine(_coroutine);
_coroutine = null;
}
/// Возвращает true, если последовательность сейчас выполняется.
public bool IsPlaying => _coroutine != null;
// ReSharper disable once IteratorNeverReturns — цикл намеренно бесконечный при _looping=true
private IEnumerator RunSequence()
{
do
{
foreach (Step step in _steps)
{
_controller.SendCommand(step.Mode, step.DurationSeconds);
if (step.Mode != LightMode.Off && step.DurationSeconds > 0)
yield return new WaitForSeconds(step.DurationSeconds);
}
}
while (_looping);
_coroutine = null;
}
}
}
}