using Friflo.Engine.ECS;
using Friflo.Engine.ECS.Systems;
using LittleSim.Content;
using LittleSim.Net;
using LittleSim.Scenes;
using LittleSim.Sim;
using Microsoft.Xna.Framework;
using MrGameEng.Core;
using MrGameEng.Graphics;
using MrGameEng.Net;

namespace LittleSim.Server;

/// <summary>
/// Серверный мир: жители бродят, устают и отдыхают (те же системы, что в WorldScene) на
/// фиксированном тике <see cref="HeadlessHost"/> — без окна, GPU и спрайтов. С
/// <see cref="WebSocketServer"/> мир ещё и реплицируется подключённым клиентам по схеме
/// <see cref="NetSchema"/> (10 снапшотов в секунду, дельты). Демонстрация MrGameEng.Net.
/// </summary>
internal sealed class HeadlessWorldScene : Scene
{
    /// <summary>Жителей в серверном мире.</summary>
    public const int PawnCount = 24;

    /// <summary>Сид мира — рельефа пока нет, но блуждание детерминировано им.</summary>
    public const int Seed = 424242;

    private static readonly RectF Bounds = new(0f, 0f, 1280f, 720f);

    private readonly GameContent _content;
    private readonly WebSocketServer? _server;

    /// <summary>Мир без сети (fast-forward) или, с <paramref name="server"/>, онлайн-мир.</summary>
    public HeadlessWorldScene(GameContent content, WebSocketServer? server = null)
    {
        _content = content;
        _server = server;
    }

    protected override void OnLoad()
    {
        Context.Services.Add(_content);
        var calendar = Context.UseCalendar(WorldScene.SecondsPerDay);
        var climate = Context.UseClimate(ClimateSettings.Default);

        var replication = _server is null ? null : new ReplicationServer(NetSchema.Create(), Store);
        var random = new Random(Seed);
        for (var i = 0; i < PawnCount; i++)
        {
            var position = new Vector2(
                random.NextSingle() * Bounds.Width,
                random.NextSingle() * Bounds.Height
            );
            var transform = new Transform2D(position, scale: new Vector2(12f));
            var needs = new PawnNeeds { Energy = 0.4f + random.NextSingle() * 0.6f };
            if (replication is null)
            {
                Store.CreateEntity(transform, new Wander(), new PawnBrain(), needs);
            }
            else
            {
                Store.CreateEntity(
                    transform,
                    new Wander(),
                    new PawnBrain(),
                    needs,
                    new NetId { Value = replication.NextNetId() }
                );
            }
        }

        UpdateSystems.Add(new PawnDecisionSystem());
        UpdateSystems.Add(new PawnNeedsSystem());
        UpdateSystems.Add(new WanderSystem(Seed, Bounds));
        UpdateSystems.Add(new DayReportSystem(calendar, climate));
        if (_server is not null && replication is not null)
        {
            UpdateSystems.Add(new NetworkSystem(_server, replication));
        }
    }

    /// <summary>Принимает новые соединения и шлёт дельта-снапшоты с сетевой частотой.</summary>
    private sealed class NetworkSystem(WebSocketServer server, ReplicationServer replication)
        : BaseSystem
    {
        // 60 тиков симуляции / 6 = 10 снапшотов в секунду.
        private const int SendEveryTicks = 6;

        private int _ticks;

        protected override void OnUpdateGroup()
        {
            while (server.TryAcceptConnection(out var connection))
            {
                Log.Info(
                    $"Клиент #{connection.Id} подключился ({server.Connections.Count} онлайн)"
                );
            }

            if (++_ticks % SendEveryTicks == 0)
            {
                replication.Send(server.Connections);
            }
        }
    }

    /// <summary>Пишет строку состояния мира в лог на рассвете каждого игрового дня.</summary>
    private sealed class DayReportSystem(Calendar calendar, Climate climate) : BaseSystem
    {
        private int _lastDay;

        protected override void OnUpdateGroup()
        {
            if (calendar.Day == _lastDay)
            {
                return;
            }

            _lastDay = calendar.Day;
            Log.Info(
                $"День {calendar.Day} | год {climate.Year}, день года {climate.DayOfYear + 1}, "
                    + $"{climate.Season} | {climate.Temperature:+0.0;-0.0;0.0} °C"
            );
        }
    }
}
