using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using NAudio.Wave;

namespace SortVisualizer;

public class VisualizerForm : Form
{
    int[] array = Array.Empty<int>();
    Random rand = new();
    int delay = 10;
    bool sorting = false;

    ComboBox algoSelector;
    Button startButton, stopButton, shuffleButton;
    TrackBar speedSlider;
    CheckBox soundToggle;

    List<ISortAlgorithm> algorithms;
    CancellationTokenSource? cts;

    // Audio
    BufferedWaveProvider? waveProvider;
    WaveOutEvent? waveOut;

    int highlightedIndex1 = -1;
    int highlightedIndex2 = -1;

    public VisualizerForm()
    {
        Text = "C# Sorting";
        DoubleBuffered = true;
        MinimumSize = new Size(800, 500);
        Width = 1000;
        Height = 600;

        algorithms = new()
        {
            new BubbleSort(), new InsertionSort(), new BogoSort(),
            new MergeSort(), new StoogeSort(), new RadixSort(),
            new BeadSort()
        };

        var topPanel = new FlowLayoutPanel
        {
            Dock = DockStyle.Top,
            AutoSize = true,
            WrapContents = true,
            FlowDirection = FlowDirection.LeftToRight,
            Padding = new Padding(5),
            AutoScroll = true
        };

        algoSelector = new ComboBox { Width = 160, DropDownStyle = ComboBoxStyle.DropDownList };
        algoSelector.DataSource = algorithms;
        algoSelector.DisplayMember = "Name";

        int buttonHeight = algoSelector.Height;

        startButton = new Button { Width = 100, Height = buttonHeight, Text = "Start" };
        stopButton = new Button { Width = 100, Height = buttonHeight, Text = "Stop" };
        shuffleButton = new Button { Width = 100, Height = buttonHeight, Text = "Shuffle" };

        soundToggle = new CheckBox { Width = 100, Height = buttonHeight, Text = "Sound On", Checked = true };

        speedSlider = new TrackBar
        {
            Width = 250,
            Minimum = 1,
            Maximum = 100,
            Value = 10,
            TickFrequency = 10,
            SmallChange = 1,
            LargeChange = 10,
            Height = buttonHeight
        };

        topPanel.Controls.AddRange(new Control[]
        {
            algoSelector, startButton, stopButton, shuffleButton,
            soundToggle, speedSlider
        });

        Controls.Add(topPanel);

        speedSlider.ValueChanged += (s, e) =>
        {
            delay = 101 - speedSlider.Value;
        };

        startButton.Click += async (s, e) => await StartSort();
        stopButton.Click += (s, e) => StopSort();
        shuffleButton.Click += (s, e) => Shuffle();

        this.Resize += (s, e) => Invalidate();

        Shuffle();
    }

    void Shuffle()
    {
        if (sorting) return;
        array = Enumerable.Range(1, 100).OrderBy(_ => rand.Next()).ToArray();
        highlightedIndex1 = highlightedIndex2 = -1;
        Invalidate();
    }

    async Task StartSort()
    {
        if (sorting) return;
        sorting = true;
        cts = new CancellationTokenSource();

        var algo = algoSelector.SelectedItem as ISortAlgorithm;
        if (algo == null)
        {
            sorting = false;
            return;
        }

        try
        {
            if (soundToggle.Checked)
            {
                InitializeAudio();
                if (waveProvider != null)
                {
                    _ = Task.Run(() => AudioLoopAsync(waveProvider, cts.Token));
                }
            }

            await algo.Sort(
                array,
                () => Invalidate(),
                delay,
                value => { highlightedIndex1 = value; },
                cts.Token
            );

            highlightedIndex1 = highlightedIndex2 = -1;
            Invalidate();
        }
        finally
        {
            sorting = false;
            StopAudio();
        }
    }

    void StopSort()
    {
        if (!sorting) return;
        cts?.Cancel();
        sorting = false;
        highlightedIndex1 = highlightedIndex2 = -1;
        Invalidate();
        StopAudio();
    }

    void InitializeAudio()
    {
        waveProvider = new BufferedWaveProvider(new WaveFormat(44100, 16, 1))
        {
            BufferDuration = TimeSpan.FromSeconds(5)
        };

        waveOut = new WaveOutEvent();
        waveOut.Init(waveProvider);
        waveOut.Play();
    }

    void StopAudio()
    {
        waveOut?.Stop();
        waveOut?.Dispose();
        waveOut = null;
        waveProvider = null;
    }

    async Task AudioLoopAsync(BufferedWaveProvider provider, CancellationToken token)
    {
        short amplitude = 8000;
        int sampleRate = 44100;
        int chunkSamples = 512;
        var buffer = new byte[chunkSamples * 2];
        double phase = 0;

        while (!token.IsCancellationRequested)
        {
            // Calculate frequency based on highlighted element or average
            int freq = 220;
            if (highlightedIndex1 >= 0 && highlightedIndex1 < array.Length)
            {
                freq = 220 + array[highlightedIndex1] * 15; // Rising pitch based on value
            }
            else if (array.Length > 0)
            {
                freq = 220 + (int)array.Average() * 15;
            }

            for (int n = 0; n < chunkSamples; n++)
            {
                double sample = Math.Sin(phase);
                phase += 2 * Math.PI * freq / sampleRate;

                // Keep phase in reasonable range
                if (phase > 2 * Math.PI * 1000)
                    phase -= 2 * Math.PI * 1000;

                short s = (short)(sample * amplitude);
                buffer[n * 2] = (byte)(s & 0xFF);
                buffer[n * 2 + 1] = (byte)((s >> 8) & 0xFF);
            }

            int space = provider.BufferLength - provider.BufferedBytes;
            if (space >= buffer.Length)
            {
                provider.AddSamples(buffer, 0, buffer.Length);
            }
            else if (space > 0)
            {
                provider.AddSamples(buffer, buffer.Length - space, space);
            }

            await Task.Delay(1, token);
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        var g = e.Graphics;

        int offsetY = 50;
        int marginX = 20;
        int paddingTop = 5;
        int paddingBottom = 5;

        if (array.Length == 0) return;

        int barWidth = (ClientSize.Width - 2 * marginX) / array.Length;
        int top = offsetY + paddingTop;
        int bottom = ClientSize.Height - paddingBottom;
        int availableHeight = bottom - top;

        int maxVal = array.Max();

        for (int i = 0; i < array.Length; i++)
        {
            int height = (int)Math.Round(array[i] * (availableHeight / (float)maxVal));
            Brush brush = Brushes.LightGreen;

            if (sorting)
            {
                if (i == highlightedIndex1) brush = Brushes.Red;
                else if (i == highlightedIndex2) brush = Brushes.Orange;
                else brush = Brushes.DeepSkyBlue;
            }

            int y = bottom - height;
            if (y < top) y = top;

            g.FillRectangle(brush, marginX + i * barWidth, y, barWidth - 1, height);
        }
    }
}