using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using FluidSim.Components;
using FluidSim.Interfaces;

namespace FluidSim.Core
{
    public class Solver
    {
        private readonly List<IComponent> _components = new();
        private readonly List<OrificeLink> _orificeLinks = new();
        private readonly List<OpenEndLink> _openEndLinks = new();

        private double _dt;

        /// <summary>CFL target for sub‑stepping (0.3‑0.8). Lower values are safer for shocks.</summary>
        public double CflTarget { get; set; } = 0.9;

        // ---------- Timing accumulators (reset every LogInterval steps) ----------
        private long _stepCount;
        private double _timeTotal, _timeCFL, _timeOrifice, _timeOpenEnd,
                       _timePipe, _timeClearGhosts, _timeUpdateState;

        private const int LogInterval = 5000;
        private const bool EnableLogging = false;   // temporarily ON for debugging

        public void SetTimeStep(double dt) => _dt = dt;

        public void AddComponent(IComponent component) => _components.Add(component);
        public void AddOrificeLink(OrificeLink link) => _orificeLinks.Add(link);
        public void AddOpenEndLink(OpenEndLink link) => _openEndLinks.Add(link);

        public void Step()
        {
            var pipes = _components.OfType<Pipe1D>().ToList();
            if (pipes.Count == 0) return;

            var sw = Stopwatch.StartNew();

            // CFL count – track which pipe demands the most sub‑steps
            int nSub = 1;
            Pipe1D worstPipe = pipes[0];
            foreach (var p in pipes)
            {
                int n = p.GetRequiredSubSteps(_dt, CflTarget);
                if (n > nSub)
                {
                    nSub = n;
                    worstPipe = p;
                }
            }
            double dtSub = _dt / nSub;

            // ----- Diagnostic: warn if nSub is high -----
            if (nSub > 50)
            {
                double maxW = 0;
                for (int i = 0; i < worstPipe.CellCount; i++)
                {
                    double rho = worstPipe.GetCellDensity(i);
                    double u = Math.Abs(worstPipe.GetCellVelocity(i));
                    double p = worstPipe.GetCellPressure(i);
                    double c = Math.Sqrt(1.4 * p / Math.Max(rho, 1e-12));
                    if (u + c > maxW) maxW = u + c;
                }
                Console.WriteLine($"nSub = {nSub}  (worst pipe: {worstPipe.Name}, maxW = {maxW:F0} m/s)");
            }

            _timeCFL += sw.Elapsed.TotalSeconds;

            // ----- Safety cap – prevent the solver from hanging -----
            const int maxSubSteps = 10000;
            const int hardLimit = 500;   // temporary low cap for debugging

            if (nSub > hardLimit)
            {
                Console.WriteLine($"nSub ({nSub}) exceeds hard limit {hardLimit}. Simulation step skipped.");
                return;
            }

            for (int sub = 0; sub < nSub; sub++)
            {
                double t0;

                t0 = sw.Elapsed.TotalSeconds;
                foreach (var link in _orificeLinks)
                    link.Resolve(dtSub);
                _timeOrifice += sw.Elapsed.TotalSeconds - t0;

                t0 = sw.Elapsed.TotalSeconds;
                foreach (var link in _openEndLinks)
                    link.Resolve(dtSub);
                _timeOpenEnd += sw.Elapsed.TotalSeconds - t0;

                t0 = sw.Elapsed.TotalSeconds;
                foreach (var p in pipes)
                    p.SimulateSingleStep(dtSub);
                _timePipe += sw.Elapsed.TotalSeconds - t0;
            }

            double tCG = sw.Elapsed.TotalSeconds;
            foreach (var p in pipes)
                p.ClearGhostFlags();
            _timeClearGhosts += sw.Elapsed.TotalSeconds - tCG;

            double tUS = sw.Elapsed.TotalSeconds;
            foreach (var comp in _components)
                comp.UpdateState(_dt);
            _timeUpdateState += sw.Elapsed.TotalSeconds - tUS;

            _timeTotal += sw.Elapsed.TotalSeconds;

            _stepCount++;
            if (_stepCount % LogInterval == 0 && EnableLogging)
            {
                if (_timeTotal > 0)
                {
                    double stepsPerSec = LogInterval / _timeTotal;
                    double avgUs = (_timeTotal / LogInterval) * 1e6;

                    Console.WriteLine($"--- Solver timing ({LogInterval} steps) ---");
                    Console.WriteLine($"  Steps per second: {stepsPerSec:F1}");
                    Console.WriteLine($"  Avg step time:    {avgUs:F1} µs  (last nSub = {nSub})");
                    Console.WriteLine($"  CFL calc:         {_timeCFL / _timeTotal * 100:F1} %");
                    Console.WriteLine($"  Sub‑step loop:");
                    Console.WriteLine($"    Orifice:        {_timeOrifice / _timeTotal * 100:F1} %");
                    Console.WriteLine($"    OpenEnd:        {_timeOpenEnd / _timeTotal * 100:F1} %");
                    Console.WriteLine($"    Pipe steps:     {_timePipe / _timeTotal * 100:F1} %");
                    Console.WriteLine($"  Clear ghosts:     {_timeClearGhosts / _timeTotal * 100:F1} %");
                    Console.WriteLine($"  Update state:     {_timeUpdateState / _timeTotal * 100:F1} %");
                    Console.WriteLine();
                }

                _timeTotal = 0;
                _timeCFL = 0;
                _timeOrifice = 0;
                _timeOpenEnd = 0;
                _timePipe = 0;
                _timeClearGhosts = 0;
                _timeUpdateState = 0;
            }
        }
    }
}