using FluidSim.Audio;
using FluidSim.Core;
using FluidSim.Tests;
using SFML.Graphics;
using SFML.System;
using SFML.Window;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace FluidSim;

public class Program
{
    private const int SampleRate = 44100;
    private const double DrawFrequency = 60.0;

    // Playback speed
    private static double _desiredSpeed = 0.001;
    private static double _currentDisplaySpeed = _desiredSpeed;
    private const double MinSpeed = 0.0001;
    private const double MaxSpeed = 1.0;
    private const double ScrollFactor = 1.1;
    private static double _lastNormalSpeed = 0.1;
    private static bool _isRealTime = false;

    private static volatile bool _timeWarpActive;

    // Thread load tracking
    private static ThreadLoadTracker _loadTracker = new ThreadLoadTracker();

    // Audio & simulation
    private static SimulationRingBuffer _simRingBuffer = null!;
    private static SoundEngine _soundEngine = null!;
    private static Scenario _scenario = null!;
    private static Font? _overlayFont;
    private static Text? _overlayText;

    // Throttle control
    private static float _throttleTarget = 1.0f;
    private static float _throttleCurrent = 0.0f;
    private const float ThrottleLerpRate = 10.0f;
    private static bool _wKeyHeld = false;
    private static float _lastThrottleUpdateTime;

    // Load
    private static float _loadTarget = 0.0f;          // 0‑1
    private static float _loadCurrent = 0.0f;

    private static float _clutchTarget = 0f;
    private static float _clutchCurrent = 0f;
    private static bool _cKeyHeld = false;

    private const int TargetMaxFill = (int)(SampleRate * 0.2);

    public static void Main()
    {
        var window = CreateWindow();
        LoadFont();
        _scenario = new TwoStrokeScenario();
        _scenario.Font = _overlayFont;
        _scenario.Initialize(SampleRate);
        _lastThrottleUpdateTime = 0.0f;

        _simRingBuffer = new SimulationRingBuffer(8192);
        _soundEngine = new SoundEngine(_simRingBuffer) { Volume = 100 };
        _soundEngine.Start();

        var cts = new CancellationTokenSource();
        Task.Run(() => SimulationLoop(cts.Token), cts.Token);

        var stopwatch = Stopwatch.StartNew();
        double lastDrawTime = 0.0;

        while (window.IsOpen)
        {
            window.DispatchEvents();

            double now = stopwatch.Elapsed.TotalSeconds;

            // ---- Playback speed smoothing ----
            double targetSpeed = _timeWarpActive ? 1.0 : _desiredSpeed;
            _currentDisplaySpeed += (targetSpeed - _currentDisplaySpeed) *
                (1.0 - Math.Exp(-8.0 * (now - lastDrawTime)));
            _soundEngine.Speed = _currentDisplaySpeed;

            // ---- Throttle & Load update (shared dt) ----
            float dtThrottle = (float)now - _lastThrottleUpdateTime;
            _lastThrottleUpdateTime = (float)now;

            float throttleDesiredFraction = _wKeyHeld ? _throttleTarget : 0.0f;
            if (throttleDesiredFraction == 0.0f)
            {
                _throttleCurrent = 0.0f;
            }
            else
            {
                float smoothing = 1.0f - MathF.Exp(-ThrottleLerpRate * dtThrottle);
                _throttleCurrent += (throttleDesiredFraction - _throttleCurrent) * smoothing;
            }

            float loadSmoothing = 1.0f - MathF.Exp(-ThrottleLerpRate * dtThrottle);
            _loadCurrent += (_loadTarget - _loadCurrent) * loadSmoothing;
            _scenario.Load = _loadCurrent;

            _scenario.Throttle = _throttleCurrent;

            float clutchDesired = _cKeyHeld ? 1f : 0f;
            float clutchSmoothing = 1f - MathF.Exp(-ThrottleLerpRate * dtThrottle);
            _clutchCurrent += (clutchDesired - _clutchCurrent) * clutchSmoothing;
            _scenario.Clutch = _clutchCurrent;


            // ---- Drawing ----
            if (now - lastDrawTime >= 1.0 / DrawFrequency)
            {
                if (_overlayText != null)
                {
                    string toggleHint = _isRealTime ? "[Space] slow mo" : "[Space] real time";
                    _overlayText.DisplayedString =
                        $"{toggleHint}  Speed: {_currentDisplaySpeed:F3}x  RT: {(_currentDisplaySpeed * 100.0):F1}%  Sim load: {_loadTracker.LoadPercent:F0}%\n" +
                        $"Clutch: {_clutchCurrent*100:F0}%  [C]" +
                        $"Load: {_loadCurrent*100:F0}%  [←][→]  Throttle: {_throttleCurrent * 100:F0}%  Target: {_throttleTarget * 100:F0}%  [W] {(_wKeyHeld ? "BLIP" : "---")}";
                }

                window.Clear(Color.Black);
                _scenario.Draw(window);
                if (_overlayText != null) window.Draw(_overlayText);
                window.Display();
                lastDrawTime = now;
            }
        }

        cts.Cancel();
        _soundEngine.Dispose();
        window.Dispose();
    }

    private static void SimulationLoop(CancellationToken token)
    {
        while (!token.IsCancellationRequested)
        {
            long cycleStart = Stopwatch.GetTimestamp();

            long workStart = Stopwatch.GetTimestamp();
            float sample = _scenario.Process();
            _simRingBuffer.Write(sample);
            long workEnd = Stopwatch.GetTimestamp();

            while (_simRingBuffer.AvailableSamples > TargetMaxFill &&
                   !token.IsCancellationRequested)
            {
                Thread.Sleep(1);
            }

            long cycleEnd = Stopwatch.GetTimestamp();

            double busyMs = (workEnd - workStart) / (double)Stopwatch.Frequency * 1000.0;
            double totalMs = (cycleEnd - cycleStart) / (double)Stopwatch.Frequency * 1000.0;
            _loadTracker.Record(busyMs, totalMs);
        }
    }

    // ---------- Window & input ----------
    private static RenderWindow CreateWindow()
    {
        var mode = new VideoMode(new Vector2u(1280, 720));
        var window = new RenderWindow(mode, "FluidSim");
        window.SetVerticalSyncEnabled(false);
        window.SetFramerateLimit(60);
        window.Closed += (_, _) => window.Close();
        window.MouseWheelScrolled += OnMouseWheel;
        window.KeyPressed += OnKeyPressed;
        window.KeyReleased += OnKeyReleased;
        return window;
    }

    private static void LoadFont()
    {
        try { _overlayFont = new Font("fonts/FiraCodeNerdFont-Medium.ttf"); }
        catch { _overlayFont = null; }

        if (_overlayFont != null)
            _overlayText = new Text(_overlayFont)
            {
                FillColor = Color.White,
                Position = new Vector2f(10, 10)
            };
    }

    private static void OnMouseWheel(object? sender, MouseWheelScrollEventArgs e)
    {
        if (_timeWarpActive) return;
        if (e.Delta > 0) _desiredSpeed *= ScrollFactor;
        else if (e.Delta < 0) _desiredSpeed /= ScrollFactor;
        _desiredSpeed = Math.Clamp(_desiredSpeed, MinSpeed, MaxSpeed);
        _lastNormalSpeed = _desiredSpeed;
        _isRealTime = Math.Abs(_desiredSpeed - 1.0) < 1e-6;
    }

    private static void OnKeyPressed(object? sender, KeyEventArgs e)
    {
        switch (e.Code)
        {
            case Keyboard.Key.Space:
                _timeWarpActive = !_timeWarpActive;
                if (!_timeWarpActive)
                {
                    _desiredSpeed = _lastNormalSpeed;
                    _isRealTime = false;
                }
                break;

            case Keyboard.Key.W:
                _wKeyHeld = true;
                break;

            case Keyboard.Key.Up:
                _throttleTarget = MathF.Min(1.0f, _throttleTarget + 0.05f);
                break;

            case Keyboard.Key.Down:
                _throttleTarget = MathF.Max(0.0f, _throttleTarget - 0.05f);
                break;

            case Keyboard.Key.Left:
                _loadTarget = MathF.Max(0.0f, _loadTarget - 0.05f);
                break;

            case Keyboard.Key.Right:
                _loadTarget = MathF.Min(1.0f, _loadTarget + 0.05f);
                break;

            case Keyboard.Key.E:
                _scenario.ShiftUp();
                break;
            case Keyboard.Key.Q:
                _scenario.ShiftDown();
                break;

            case Keyboard.Key.C:
                _cKeyHeld = true;
                break;
        }
    }

    private static void OnKeyReleased(object? sender, KeyEventArgs e)
    {
        if (e.Code == Keyboard.Key.W)
            _wKeyHeld = false;

        if (e.Code == Keyboard.Key.C)
            _cKeyHeld = false;
    }
}