using System;
using System.Drawing;
using System.Windows.Forms;
using NAudio.Wave;
using Timer = System.Windows.Forms.Timer;

namespace ByteBreath
{
    public partial class AudioEditorForm : Form
    {
        public Color mainColor;
        public Color accentColor;
        public Color textColor;

        private float[] audioSamples; // interleaved samples: totalFrames * channels
        private WaveFormat waveFormat;
        private string currentFilePath;
        public string SelectionPath;
        private bool isSelecting = false;
        private int selectionStart = -1; // frame index
        private int selectionEnd = -1;   // frame index

        private int sampleRate;

        private WaveOutEvent waveOut;
        private BufferedWaveProvider bufferedWaveProvider;

        private Timer playbackTimer;
        private long playStartFrame = 0;
        private long playEndFrame = 0;
        private bool isPlaying = false;

        private int viewStartFrame = 0; // viewing frames start (for zooming and scrolling)
        private int viewFrames = 0;
        private const int minViewFrames = 1000;
        private const int maxViewFramesFactor = 1;

        private bool isMouseInWaveform = false;

        public AudioEditorForm()
        {
            InitializeComponent();
            waveformPanel.Paint += WaveformPanel_Paint;
            waveformPanel.MouseDown += WaveformPanel_MouseDown;
            waveformPanel.MouseMove += WaveformPanel_MouseMove;
            waveformPanel.MouseUp += WaveformPanel_MouseUp;
            waveformPanel.MouseWheel += WaveformPanel_MouseWheel;
            waveformPanel.MouseEnter += WaveformPanel_MouseEnter;
            waveformPanel.MouseLeave += WaveformPanel_MouseLeave;
            waveformPanel.KeyDown += WaveformPanel_KeyDown;

            playbackTimer = new Timer();
            playbackTimer.Interval = 30;
            playbackTimer.Tick += PlaybackTimer_Tick;

            this.KeyPreview = true;
            this.KeyDown += AudioEditorForm_KeyDown;

            btnSave.Enabled = false;
            TopMost = true;
        }

        private void WaveformPanel_MouseEnter(object sender, EventArgs e)
        {
            isMouseInWaveform = true;
            waveformPanel.Focus();
        }

        private void WaveformPanel_MouseLeave(object sender, EventArgs e)
        {
            isMouseInWaveform = false;
        }

        private void WaveformPanel_MouseWheel(object sender, MouseEventArgs e)
        {
            if (isMouseInWaveform)
                ZoomWaveform(e);
        }

        private void ZoomWaveform(MouseEventArgs e)
        {
            if (audioSamples == null || audioSamples.Length == 0)
                return;

            int panelWidth = waveformPanel.ClientSize.Width;
            if (panelWidth <= 0)
                return;

            if (viewFrames == 0)
            {
                int totalFrames = audioSamples.Length / waveFormat.Channels;
                viewFrames = totalFrames;
                viewStartFrame = 0;
            }

            float zoomFactor = 1.0f - (e.Delta / 1200.0f);
            if (zoomFactor <= 0) zoomFactor = 0.1f;

            int newViewFrames = (int)(viewFrames * zoomFactor);

            int minFrames = minViewFrames;
            int maxFrames = (audioSamples.Length / waveFormat.Channels) * maxViewFramesFactor;
            if (maxFrames < minFrames) maxFrames = minFrames;

            if (newViewFrames < minFrames) newViewFrames = minFrames;
            if (newViewFrames > maxFrames) newViewFrames = maxFrames;

            if (newViewFrames == viewFrames)
                return;

            double mouseRatio = (double)e.X / panelWidth;
            mouseRatio = Math.Clamp(mouseRatio, 0.0, 1.0);

            long mouseFrame = viewStartFrame + (long)(mouseRatio * viewFrames);
            long newViewStartFrame = mouseFrame - (long)(mouseRatio * newViewFrames);

            if (newViewStartFrame < 0) newViewStartFrame = 0;
            if (newViewStartFrame + newViewFrames > audioSamples.Length / waveFormat.Channels)
                newViewStartFrame = (audioSamples.Length / waveFormat.Channels) - newViewFrames;
            if (newViewStartFrame < 0)
                newViewStartFrame = 0;

            viewStartFrame = (int)newViewStartFrame;
            viewFrames = newViewFrames;

            waveformPanel.Invalidate();
        }

        private void LoadWaveFile(string path)
        {
            if (string.IsNullOrWhiteSpace(path))
                return;

            try
            {
                currentFilePath = path;

                waveOut?.Stop();
                waveOut?.Dispose();
                waveOut = null;
                bufferedWaveProvider = null;

                audioSamples = null;
                selectionStart = -1;
                selectionEnd = -1;

                using (var reader = new AudioFileReader(path))
                {
                    waveFormat = reader.WaveFormat;
                    sampleRate = waveFormat.SampleRate;

                    long totalFloatSamplesLong = reader.Length / 4;

                    if (totalFloatSamplesLong > int.MaxValue)
                        throw new InvalidOperationException("Audio file too large.");

                    int sampleCount = (int)totalFloatSamplesLong;
                    audioSamples = new float[sampleCount];

                    int offset = 0;
                    int samplesRead;
                    var buffer = new float[1024];

                    reader.Position = 0;
                    while ((samplesRead = reader.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        int copyCount = Math.Min(samplesRead, sampleCount - offset);
                        Array.Copy(buffer, 0, audioSamples, offset, copyCount);
                        offset += copyCount;
                    }
                }

                int totalFrames = audioSamples.Length / waveFormat.Channels;

                viewStartFrame = 0;
                viewFrames = totalFrames;

                btnSave.Enabled = true;

                playStartFrame = 0;
                playEndFrame = totalFrames - 1;

                waveOut = new WaveOutEvent();
                waveOut.PlaybackStopped += WaveOut_PlaybackStopped;

                bufferedWaveProvider = new BufferedWaveProvider(waveFormat)
                {
                    BufferLength = audioSamples.Length * waveFormat.BlockAlign
                };
                bufferedWaveProvider.ClearBuffer();

                byte[] byteBuffer = new byte[audioSamples.Length * 4];
                Buffer.BlockCopy(audioSamples, 0, byteBuffer, 0, byteBuffer.Length);
                bufferedWaveProvider.AddSamples(byteBuffer, 0, byteBuffer.Length);

                waveOut.Init(bufferedWaveProvider);

                waveformPanel.Invalidate();
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to load WAV: " + ex.Message);
            }
        }

        private void WaveformPanel_Paint(object sender, PaintEventArgs e)
        {
            e.Graphics.Clear(mainColor);
            if (audioSamples == null || audioSamples.Length == 0)
            {
                using (var brush = new SolidBrush(accentColor))
                    e.Graphics.DrawString("No audio loaded", this.Font, brush, new PointF(10, 10));
                return;
            }

            int w = waveformPanel.ClientSize.Width;
            int h = waveformPanel.ClientSize.Height;

            int totalFrames = audioSamples.Length / waveFormat.Channels;

            using (var pen = new Pen(accentColor))
            {
                if (viewFrames == 0)
                    viewFrames = totalFrames;
                if (viewStartFrame + viewFrames > totalFrames)
                    viewStartFrame = totalFrames - viewFrames;

                if (viewStartFrame < 0) viewStartFrame = 0;
                if (viewFrames <= 0) viewFrames = totalFrames;

                int framesPerPixel = Math.Max(1, viewFrames / w);

                for (int x = 0; x < w; x++)
                {
                    int startFrame = viewStartFrame + x * framesPerPixel;
                    if (startFrame >= totalFrames)
                        break;
                    int endFrame = Math.Min(totalFrames, startFrame + framesPerPixel);

                    float min = 1f;
                    float max = -1f;
                    for (int f = startFrame; f < endFrame; f++)
                    {
                        float sumSample = 0f;
                        for (int c = 0; c < waveFormat.Channels; c++)
                        {
                            float val = audioSamples[f * waveFormat.Channels + c];
                            sumSample += val;
                        }
                        float valAvg = sumSample / waveFormat.Channels;

                        if (valAvg < min) min = valAvg;
                        if (valAvg > max) max = valAvg;
                    }

                    int yMax = (int)((1 - (max + 1) / 2) * h);
                    int yMin = (int)((1 - (min + 1) / 2) * h);

                    e.Graphics.DrawLine(pen, x, yMin, x, yMax);
                }
            }

            // Draw selection rectangle
            if (selectionStart >= 0 && selectionEnd >= 0 && selectionEnd != selectionStart)
            {
                int wFrames = viewFrames;
                int visibleStart = viewStartFrame;
                int x1 = (int)((double)(selectionStart - visibleStart) / wFrames * w);
                int x2 = (int)((double)(selectionEnd - visibleStart) / wFrames * w);

                int rectX = Math.Min(x1, x2);
                int rectW = Math.Abs(x2 - x1);

                if (rectW > 0)
                {
                    using (var selectionBrush = new SolidBrush(Color.FromArgb(100, mainColor)))
                        e.Graphics.FillRectangle(selectionBrush, rectX, 0, rectW, h);

                    using (var pen = new Pen(textColor, 2))
                        e.Graphics.DrawRectangle(pen, rectX, 0, rectW, h - 1);

                    // Draw realtime text anchored centered in the selection rectangle
                    double selectedFrames = Math.Abs(selectionEnd - selectionStart);
                    double selectedSeconds = selectedFrames / (double)sampleRate;
                    string selectionDurationText = $"{selectedSeconds:F2} s selected";

                    using (var font = new Font(this.Font.FontFamily, 10, FontStyle.Bold))
                    using (var brush = new SolidBrush(textColor))
                    {
                        SizeF textSize = e.Graphics.MeasureString(selectionDurationText, font);
                        float textX = rectX + rectW / 2f - textSize.Width / 2f;
                        float textY = h / 2f - textSize.Height / 2f;

                        // Draw text background for better visibility
                        using (var bgBrush = new SolidBrush(Color.FromArgb(160, mainColor)))
                        {
                            RectangleF bgRect = new RectangleF(textX - 4, textY - 2, textSize.Width + 8, textSize.Height + 4);
                            e.Graphics.FillRectangle(bgBrush, bgRect);
                        }

                        e.Graphics.DrawString(selectionDurationText, font, brush, textX, textY);
                    }
                }
            }

            // Draw playback position line
            if (isPlaying && audioSamples != null && waveOut != null)
            {
                int bytesPerSample = sizeof(float);
                int bytesPerFrame = bytesPerSample * waveFormat.Channels;
                long currentFrame = 0;

                try
                {
                    currentFrame = waveOut.GetPosition() / bytesPerFrame + playStartFrame;
                }
                catch
                {
                    currentFrame = 0;
                }

                if (currentFrame > audioSamples.Length / waveFormat.Channels)
                    currentFrame = audioSamples.Length / waveFormat.Channels;

                long segmentLength = playEndFrame - playStartFrame + 1;
                if (segmentLength <= 0) segmentLength = audioSamples.Length / waveFormat.Channels;

                if (currentFrame > playEndFrame)
                    currentFrame = playEndFrame;

                int x = 0;

                if (selectionStart >= 0 && selectionEnd >= 0 && selectionStart != selectionEnd)
                {
                    int wFrames = viewFrames;
                    int visibleStart = viewStartFrame;

                    int sx1 = (int)((double)(selectionStart - visibleStart) / wFrames * w);
                    int sx2 = (int)((double)(selectionEnd - visibleStart) / wFrames * w);

                    int rectX = Math.Min(sx1, sx2);
                    int rectW = Math.Abs(sx2 - sx1);

                    if (segmentLength > 0)
                        x = rectX + (int)((double)(currentFrame - playStartFrame) / segmentLength * rectW);
                    else
                        x = rectX;
                }
                else
                {
                    int wFrames = viewFrames;
                    int visibleStart = viewStartFrame;
                    x = (int)((double)(currentFrame - visibleStart) / wFrames * w);
                }

                if (x >= 0 && x < w)
                {
                    using (var pen = new Pen(textColor, 2))
                        e.Graphics.DrawLine(pen, x, 0, x, h);
                }
            }

            // Draw time stamps below waveform
            DrawTimeStamps(e.Graphics, w, h);
        }

        private void DrawTimeStamps(Graphics g, int width, int height)
        {
            if (waveFormat == null || audioSamples == null || audioSamples.Length == 0)
                return;

            int totalFrames = audioSamples.Length / waveFormat.Channels;
            if (viewFrames <= 0) return;

            int maxLabels = width / 60; // approximate 60px per label minimum
            if (maxLabels < 2) maxLabels = 2;

            int intervalFrames = viewFrames / maxLabels;
            if (intervalFrames <= 0) intervalFrames = 1;

            using (var brush = new SolidBrush(textColor))
            using (var font = new Font(this.Font.FontFamily, 8))
            using (var pen = new Pen(textColor))
            {
                int yOffset = height - 16;
                int tickHeight = 8;

                for (int i = 0; i <= maxLabels; i++)
                {
                    int framePos = viewStartFrame + i * intervalFrames;
                    if (framePos > totalFrames) framePos = totalFrames;

                    double seconds = (double)framePos / sampleRate;
                    int minutesPart = (int)(seconds / 60);
                    int secondsPart = (int)(seconds % 60);

                    string timeLabel = $"{minutesPart:D2}:{secondsPart:D2}";

                    double positionRatio = (double)(framePos - viewStartFrame) / viewFrames;
                    int x = (int)(positionRatio * width);

                    if (x < 0) x = 0;
                    if (x > width) x = width;

                    g.DrawLine(pen, x, height - tickHeight, x, height);

                    SizeF size = g.MeasureString(timeLabel, font);

                    int textX = x - (int)(size.Width / 2);
                    if (textX < 0) textX = 0;
                    if (textX + size.Width > width) textX = width - (int)size.Width;

                    g.DrawString(timeLabel, font, brush, new PointF(textX, yOffset));
                }
            }
        }

        private void WaveformPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (audioSamples == null) return;

            isSelecting = true;
            int w = waveformPanel.ClientSize.Width;

            int frameIndex = viewStartFrame + (int)((double)e.X / w * viewFrames);
            int totalFrames = audioSamples.Length / waveFormat.Channels;
            frameIndex = Math.Clamp(frameIndex, 0, totalFrames - 1);

            selectionStart = frameIndex;
            selectionEnd = frameIndex;
            waveformPanel.Invalidate();
        }

        private void WaveformPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (audioSamples == null || !isSelecting) return;

            int w = waveformPanel.ClientSize.Width;
            int frameIndex = viewStartFrame + (int)((double)e.X / w * viewFrames);
            int totalFrames = audioSamples.Length / waveFormat.Channels;
            frameIndex = Math.Clamp(frameIndex, 0, totalFrames - 1);

            selectionEnd = frameIndex;
            waveformPanel.Invalidate();
        }

        private void WaveformPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (audioSamples == null) return;

            isSelecting = false;

            if (Math.Abs(selectionEnd - selectionStart) == 0)
            {
                selectionStart = -1;
                selectionEnd = -1;
            }
            waveformPanel.Invalidate();
        }

        private void DeleteSelection()
        {
            if (audioSamples == null) return;
            if (selectionStart < 0 || selectionEnd < 0) return;

            int start = Math.Min(selectionStart, selectionEnd);
            int end = Math.Max(selectionStart, selectionEnd);
            if (end == start) return;

            int channels = waveFormat.Channels;
            int deletedFrames = end - start;
            int deletedSamples = deletedFrames * channels;

            int oldViewStartFrame = viewStartFrame;
            int oldViewFrames = viewFrames;

            int newTotalFrames = audioSamples.Length / channels - deletedFrames;
            int newLength = newTotalFrames * channels;
            float[] newSamples = new float[newLength];

            Array.Copy(audioSamples, 0, newSamples, 0, start * channels);
            Array.Copy(audioSamples, end * channels, newSamples, start * channels, (audioSamples.Length - end * channels));

            audioSamples = newSamples;

            selectionStart = -1;
            selectionEnd = -1;

            if (isPlaying)
            {
                waveOut?.Stop();
                isPlaying = false;
                playbackTimer.Stop();
            }

            bufferedWaveProvider = new BufferedWaveProvider(waveFormat)
            {
                BufferLength = audioSamples.Length * waveFormat.BlockAlign
            };
            bufferedWaveProvider.ClearBuffer();

            byte[] byteBuffer = new byte[audioSamples.Length * sizeof(float)];
            Buffer.BlockCopy(audioSamples, 0, byteBuffer, 0, byteBuffer.Length);
            bufferedWaveProvider.AddSamples(byteBuffer, 0, byteBuffer.Length);

            waveOut?.Init(bufferedWaveProvider);

            playStartFrame = 0;
            playEndFrame = (audioSamples.Length / channels) - 1;

            if (oldViewStartFrame >= end)
            {
                viewStartFrame = Math.Max(0, oldViewStartFrame - deletedFrames);
                viewFrames = oldViewFrames;
            }
            else if (oldViewStartFrame < end && (oldViewStartFrame + oldViewFrames) > start)
            {
                int newViewFrames = oldViewFrames - deletedFrames;
                if (newViewFrames < minViewFrames) newViewFrames = Math.Min(minViewFrames, audioSamples.Length / channels);
                if (newViewFrames > audioSamples.Length / channels) newViewFrames = audioSamples.Length / channels;

                viewStartFrame = oldViewStartFrame;
                if (viewStartFrame > (audioSamples.Length / channels) - newViewFrames)
                    viewStartFrame = (audioSamples.Length / channels) - newViewFrames;
                if (viewStartFrame < 0) viewStartFrame = 0;

                viewFrames = newViewFrames;
            }
            else
            {
                viewStartFrame = oldViewStartFrame;
                viewFrames = oldViewFrames;
            }

            if (viewFrames > audioSamples.Length / channels)
            {
                viewFrames = audioSamples.Length / channels;
                viewStartFrame = 0;
            }

            waveformPanel.Invalidate();
        }

        private void WaveformPanel_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Delete)
            {
                if (selectionStart >= 0 && selectionEnd >= 0 && selectionEnd != selectionStart)
                {
                    DeleteSelection();
                    e.Handled = true;
                }
            }
        }

        private void BtnSave_Click(object sender, EventArgs e)
        {
            if (audioSamples == null || string.IsNullOrEmpty(currentFilePath)) return;

            try
            {
                waveOut?.Stop();

                using (var writer = new WaveFileWriter(currentFilePath, waveFormat))
                {
                    if (waveFormat.Encoding == WaveFormatEncoding.IeeeFloat)
                    {
                        writer.WriteSamples(audioSamples, 0, audioSamples.Length);
                    }
                    else if (waveFormat.BitsPerSample == 16)
                    {
                        short[] int16Samples = new short[audioSamples.Length];
                        for (int i = 0; i < audioSamples.Length; i++)
                        {
                            float sample = audioSamples[i];
                            sample = Math.Max(-1f, Math.Min(1f, sample));
                            int16Samples[i] = (short)(sample * short.MaxValue);
                        }
                        byte[] bytes = new byte[int16Samples.Length * 2];
                        Buffer.BlockCopy(int16Samples, 0, bytes, 0, bytes.Length);
                        writer.Write(bytes, 0, bytes.Length);
                    }
                    else
                    {
                        MessageBox.Show("Unsupported bit depth: " + waveFormat.BitsPerSample);
                        return;
                    }
                }
                MessageBox.Show("File saved to " + currentFilePath);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Failed to save WAV: " + ex.Message);
            }
            finally
            {
                if (audioSamples != null && audioSamples.Length > 0 && waveFormat != null)
                {
                    waveOut?.Dispose();

                    waveOut = new WaveOutEvent();
                    waveOut.PlaybackStopped += WaveOut_PlaybackStopped;

                    bufferedWaveProvider = new BufferedWaveProvider(waveFormat)
                    {
                        BufferLength = audioSamples.Length * waveFormat.BlockAlign
                    };
                    bufferedWaveProvider.ClearBuffer();

                    byte[] byteBuffer = new byte[audioSamples.Length * 4];
                    Buffer.BlockCopy(audioSamples, 0, byteBuffer, 0, byteBuffer.Length);
                    bufferedWaveProvider.AddSamples(byteBuffer, 0, byteBuffer.Length);

                    waveOut.Init(bufferedWaveProvider);
                }
            }
        }

        private void TogglePlayPause()
        {
            if (audioSamples == null || waveOut == null || waveFormat == null) return;

            int channels = waveFormat.Channels;

            if (!isPlaying)
            {
                if (selectionStart >= 0 && selectionEnd >= 0 && selectionEnd != selectionStart)
                {
                    playStartFrame = Math.Min(selectionStart, selectionEnd);
                    playEndFrame = Math.Max(selectionStart, selectionEnd);
                }
                else
                {
                    playStartFrame = 0;
                    playEndFrame = (audioSamples.Length / channels) - 1;
                }

                waveOut.Stop();

                long totalFrames = playEndFrame - playStartFrame + 1;
                long totalSamples = totalFrames * channels;

                if (playStartFrame < 0 || playEndFrame >= (audioSamples.Length / channels) || totalFrames <= 0)
                {
                    MessageBox.Show("Invalid selection for playback.");
                    return;
                }

                try
                {
                    int bytesPerSample = sizeof(float);
                    int intLengthBytes = (int)(totalSamples * bytesPerSample);
                    int byteOffset = (int)(playStartFrame * channels * bytesPerSample);

                    if (byteOffset < 0 || (byteOffset + intLengthBytes) > audioSamples.Length * bytesPerSample)
                    {
                        MessageBox.Show("Playback selection range is out of audio sample bounds.");
                        return;
                    }

                    bufferedWaveProvider?.ClearBuffer();

                    bufferedWaveProvider = new BufferedWaveProvider(waveFormat)
                    {
                        BufferLength = intLengthBytes
                    };

                    byte[] byteBuffer = new byte[intLengthBytes];
                    Buffer.BlockCopy(audioSamples, byteOffset, byteBuffer, 0, intLengthBytes);
                    bufferedWaveProvider.AddSamples(byteBuffer, 0, intLengthBytes);

                    waveOut.Init(bufferedWaveProvider);
                    waveOut.Play();

                    isPlaying = true;
                    playbackTimer.Start();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error occurred during playback start: " + ex.Message);
                    return;
                }
            }
            else
            {
                waveOut.Stop();
                isPlaying = false;
                playbackTimer.Stop();
            }

            waveformPanel.Invalidate();
        }

        private void PlaybackTimer_Tick(object sender, EventArgs e)
        {
            if (!isPlaying) return;

            int bytesPerSample = sizeof(float);
            int channels = waveFormat.Channels;
            int bytesPerFrame = bytesPerSample * channels;

            long currentFrame = 0;

            try
            {
                if (bufferedWaveProvider != null && waveOut != null)
                {
                    long position = waveOut.GetPosition();
                    currentFrame = position / bytesPerFrame + playStartFrame;
                }
            }
            catch
            {
                currentFrame = 0;
            }

            if (currentFrame > playEndFrame)
            {
                waveOut.Stop();
                return;
            }

            waveformPanel.Invalidate();
        }

        private void WaveOut_PlaybackStopped(object sender, StoppedEventArgs e)
        {
            isPlaying = false;
            playbackTimer.Stop();
            waveformPanel.Invalidate();
        }

        private void AudioEditorForm_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Space)
            {
                if (!e.Handled)
                {
                    TogglePlayPause();
                    e.Handled = true;
                }
            }
        }

        private void AudioEditorForm_Load(object sender, EventArgs e)
        {
            if (!string.IsNullOrEmpty(SelectionPath))
            {
                LoadWaveFile(SelectionPath);
            }
            else
            {
                btnSave.Enabled = false;
            }
            BackColor = mainColor;
            btnSave.ForeColor = textColor;
            btnSave.BackColor = mainColor;
        }

        private void AudioEditorForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            waveOut?.Stop();
            waveOut?.Dispose();
        }

        private void SaveAsCrop_Click(object sender, EventArgs e)
        {
            //Write me functionality here to save a selection as a seperate file putting it in the same directory but changing its file name to example OriginalName_Cropped_SELECTION TIME HERE_RANDOMNUMBER.EXTENTIONEXAMPLE
            //Return full code also the ending action below leave it it's important.

            //Ending Action.
            DeleteSelection();
        }
    }
}