using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RecrownedAthenaeum.Render;
using RecrownedAthenaeum.UI.Modular;
using System;

namespace RhythmBullet.Audio.Visualizer
{
    internal class HorizontalVisualizer : UIModule, IDisposable
    {
        bool disposed;
        private RectangleRenderer rectangleRenderer;
        private const int BAR_COUNT = 70;
        private const int SMOOTH_RANGE = 3;
        private readonly int binsPerBar;
        private readonly int spaceBetweenBars;
        private Rectangle bar;
        private TransparentSampleProvider tsp;
        private int[] barValue;

        internal HorizontalVisualizer(TransparentSampleProvider transparentSampleProvider, GraphicsDevice graphicsDevice, Camera2D camera2D)
        {
            tsp = transparentSampleProvider;
            bar.Width = (int)(graphicsDevice.Viewport.Width / 70f);
            spaceBetweenBars = (int)(0.25f * bar.Width);
            bar.Width -= spaceBetweenBars;

            binsPerBar = tsp.GetCurrentSpectrum().Length / BAR_COUNT;
            barValue = new int[BAR_COUNT];
            rectangleRenderer = new RectangleRenderer(graphicsDevice);
        }

        public override void Update(GameTime gameTime)
        {
            if (disposed) throw new ObjectDisposedException(GetType().Name);
            UpdateBars((float)gameTime.ElapsedGameTime.TotalSeconds);
            AverageBars();
            base.Update(gameTime);
        }

        public override void Draw(ConsistentSpriteBatch batch)
        {
            if (disposed) throw new ObjectDisposedException(GetType().Name);
            rectangleRenderer.Begin(true);
            for (int i = 0; i < BAR_COUNT; i++)
            {
                bar.X = (i * (bar.Width + spaceBetweenBars)) + Boundaries.X;
                bar.Y = Boundaries.Y;

                bar.Height = barValue[i];
                rectangleRenderer.Draw(bar.X, bar.Y, bar.Width, bar.Height, color);

                bar.Height = -barValue[BAR_COUNT - i - 1];
                rectangleRenderer.Draw(bar.X, bar.Y, bar.Width, bar.Height, color);
            }
            rectangleRenderer.End();
            base.Draw(batch);
        }

        private void UpdateBars(float delta)
        {
            const float ALPHA = 0.5f;
            float[] spectrum = tsp.GetCurrentSpectrum();

            for (int barID = 0; barID < BAR_COUNT; barID++)
            {
                int targetBarHeight = 0;

                for (int bin = barID * binsPerBar; bin < (barID + 1) * (binsPerBar); bin++)
                {
                    targetBarHeight += (int)spectrum[bin];
                }
                targetBarHeight /= binsPerBar;

                int distance = targetBarHeight - barValue[barID];
                distance *= (int)Math.Round((1.0f - Math.Pow(1 - ALPHA, delta / 0.02f)));
                barValue[barID] += distance;
            }
        }

        private void AverageBars()
        {
            for (int barID = 0; barID < BAR_COUNT; barID++)
            {
                int terms = 0;
                for (int pos = 0; pos < SMOOTH_RANGE; pos++)
                {
                    if (barID + pos < BAR_COUNT)
                    {
                        barValue[barID] += barValue[barID + pos];
                        terms++;
                    }
                    if (barID - pos > 0)
                    {
                        barValue[barID] += barValue[barID - pos];
                        terms++;
                    }
                }
                barValue[barID] /= terms;
            }
        }

        public void Dispose()
        {
            if (disposed) throw new ObjectDisposedException(GetType().Name);
            Dispose(true);
        }

        public virtual void Dispose(bool disposing)
        {
            if (disposing && !disposed)
            {
                rectangleRenderer.Dispose();
            }
            disposed = true;
        }

        ~HorizontalVisualizer()
        {
            Dispose(false);
        }
    }
}
