using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Media;
using System.Windows.Forms;
using Timer = System.Windows.Forms.Timer;

#nullable enable

namespace CubeCollisionSimulator
{
    public class Cube
    {
        public float X { get; set; }
        public float Y { get; set; }
        public float VelocityX { get; set; }
        public float VelocityY { get; set; }
        public float Size { get; set; }
        public float Mass { get; set; }
        public Color Color { get; set; }
        public bool IsDragging { get; set; }

        public Cube(float x, float y, float size, float mass, Color color)
        {
            X = x;
            Y = y;
            Size = size;
            Mass = mass;
            Color = color;
            VelocityX = 0;
            VelocityY = 0;
            IsDragging = false;
        }

        public RectangleF GetBounds()
        {
            return new RectangleF(X, Y, Size, Size);
        }

        public bool Contains(Point point)
        {
            return GetBounds().Contains(point);
        }
    }

    public class SimulatorForm : Form
    {
        private readonly List<Cube> cubes;
        private readonly Timer timer;
        private Cube? draggedCube;
        private Point lastMousePos;

        // Physics parameters
        private float gravity = 0.5f;
        private float restitution = 0.8f;
        private float friction = 0.98f;
        private float forceMultiplier = 0.3f;

        // Sound management
        private DateTime lastSoundTime = DateTime.MinValue;
        private const int MinSoundIntervalMs = 50; // Minimum time between sounds
        private int soundCooldownCounter = 0;
        private CheckBox soundEnabledCheckbox;

        // UI Controls
        private TrackBar? gravitySlider;
        private TrackBar? restitutionSlider;
        private TrackBar? frictionSlider;
        private TrackBar? forceSlider;
        private Label? gravityLabel;
        private Label? restitutionLabel;
        private Label? frictionLabel;
        private Label? forceLabel;
        private Button? addCubeButton;
        private Button? clearButton;
        private Panel? controlPanel;

        public SimulatorForm()
        {
            this.Text = "2D Cube Collision Simulator";
            this.Size = new Size(1200, 800);
            this.DoubleBuffered = true;
            this.BackColor = Color.FromArgb(30, 30, 30);

            cubes = new List<Cube>();
            soundEnabledCheckbox = new CheckBox();

            // Add initial cubes
            Random rand = new Random();
            for (int i = 0; i < 5; i++)
            {
                cubes.Add(new Cube(
                    rand.Next(100, 800),
                    rand.Next(100, 400),
                    50 + rand.Next(30),
                    1 + rand.Next(5),
                    Color.FromArgb(rand.Next(100, 255), rand.Next(100, 255), rand.Next(100, 255))
                ));
            }

            SetupControls();

            timer = new Timer();
            timer.Interval = 16; // ~60 FPS
            timer.Tick += Update;
            timer.Start();

            this.Paint += OnPaint;
            this.MouseDown += OnMouseDown;
            this.MouseMove += OnMouseMove;
            this.MouseUp += OnMouseUp;
        }

        private void SetupControls()
        {
            controlPanel = new Panel
            {
                Dock = DockStyle.Right,
                Width = 250,
                BackColor = Color.FromArgb(45, 45, 45),
                Padding = new Padding(10)
            };

            int yPos = 20;

            // Sound toggle
            soundEnabledCheckbox = new CheckBox
            {
                Text = "Enable Collision Sound",
                Location = new Point(10, yPos),
                Size = new Size(230, 25),
                ForeColor = Color.White,
                Checked = false
            };
            controlPanel.Controls.Add(soundEnabledCheckbox);
            yPos += 40;

            // Gravity control
            gravityLabel = new Label
            {
                Text = $"Gravity: {gravity:F2}",
                Location = new Point(10, yPos),
                Size = new Size(230, 20),
                ForeColor = Color.White
            };
            controlPanel.Controls.Add(gravityLabel);
            yPos += 25;

            gravitySlider = new TrackBar
            {
                Location = new Point(10, yPos),
                Size = new Size(230, 45),
                Minimum = 0,
                Maximum = 100,
                Value = (int)(gravity * 10),
                TickFrequency = 10
            };
            gravitySlider.ValueChanged += (s, e) =>
            {
                gravity = gravitySlider?.Value / 10f ?? 0.5f;
                if (gravityLabel != null)
                    gravityLabel.Text = $"Gravity: {gravity:F2}";
            };
            controlPanel.Controls.Add(gravitySlider);
            yPos += 60;

            // Restitution control
            restitutionLabel = new Label
            {
                Text = $"Bounciness: {restitution:F2}",
                Location = new Point(10, yPos),
                Size = new Size(230, 20),
                ForeColor = Color.White
            };
            controlPanel.Controls.Add(restitutionLabel);
            yPos += 25;

            restitutionSlider = new TrackBar
            {
                Location = new Point(10, yPos),
                Size = new Size(230, 45),
                Minimum = 0,
                Maximum = 100,
                Value = (int)(restitution * 100),
                TickFrequency = 10
            };
            restitutionSlider.ValueChanged += (s, e) =>
            {
                restitution = restitutionSlider?.Value / 100f ?? 0.8f;
                if (restitutionLabel != null)
                    restitutionLabel.Text = $"Bounciness: {restitution:F2}";
            };
            controlPanel.Controls.Add(restitutionSlider);
            yPos += 60;

            // Friction control
            frictionLabel = new Label
            {
                Text = $"Friction: {friction:F2}",
                Location = new Point(10, yPos),
                Size = new Size(230, 20),
                ForeColor = Color.White
            };
            controlPanel.Controls.Add(frictionLabel);
            yPos += 25;

            frictionSlider = new TrackBar
            {
                Location = new Point(10, yPos),
                Size = new Size(230, 45),
                Minimum = 90,
                Maximum = 100,
                Value = (int)(friction * 100),
                TickFrequency = 1
            };
            frictionSlider.ValueChanged += (s, e) =>
            {
                friction = frictionSlider?.Value / 100f ?? 0.98f;
                if (frictionLabel != null)
                    frictionLabel.Text = $"Friction: {friction:F2}";
            };
            controlPanel.Controls.Add(frictionSlider);
            yPos += 60;

            // Force control
            forceLabel = new Label
            {
                Text = $"Drag Force: {forceMultiplier:F2}",
                Location = new Point(10, yPos),
                Size = new Size(230, 20),
                ForeColor = Color.White
            };
            controlPanel.Controls.Add(forceLabel);
            yPos += 25;

            forceSlider = new TrackBar
            {
                Location = new Point(10, yPos),
                Size = new Size(230, 45),
                Minimum = 1,
                Maximum = 50,
                Value = (int)(forceMultiplier * 10),
                TickFrequency = 5
            };
            forceSlider.ValueChanged += (s, e) =>
            {
                forceMultiplier = forceSlider?.Value / 10f ?? 0.3f;
                if (forceLabel != null)
                    forceLabel.Text = $"Drag Force: {forceMultiplier:F2}";
            };
            controlPanel.Controls.Add(forceSlider);
            yPos += 60;

            // Add cube button
            addCubeButton = new Button
            {
                Text = "Add Cube",
                Location = new Point(10, yPos),
                Size = new Size(230, 35),
                BackColor = Color.FromArgb(70, 130, 180),
                ForeColor = Color.White,
                FlatStyle = FlatStyle.Flat
            };
            addCubeButton.Click += (s, e) => AddRandomCube();
            controlPanel.Controls.Add(addCubeButton);
            yPos += 45;

            // Clear button
            clearButton = new Button
            {
                Text = "Clear All",
                Location = new Point(10, yPos),
                Size = new Size(230, 35),
                BackColor = Color.FromArgb(180, 70, 70),
                ForeColor = Color.White,
                FlatStyle = FlatStyle.Flat
            };
            clearButton.Click += (s, e) => cubes.Clear();
            controlPanel.Controls.Add(clearButton);
            yPos += 45;

            // Instructions
            Label instructionsLabel = new Label
            {
                Text = "Instructions:\n\n� Click and drag cubes\n� Throw them by dragging\n� Watch them collide!\n� Adjust physics sliders\n� Add more cubes\n� Toggle sound on/off",
                Location = new Point(10, yPos + 20),
                Size = new Size(230, 200),
                ForeColor = Color.LightGray
            };
            controlPanel.Controls.Add(instructionsLabel);

            this.Controls.Add(controlPanel);
        }

        private void AddRandomCube()
        {
            Random rand = new Random();
            cubes.Add(new Cube(
                rand.Next(100, this.ClientSize.Width - 350),
                rand.Next(100, 300),
                40 + rand.Next(40),
                1 + rand.Next(5),
                Color.FromArgb(rand.Next(100, 255), rand.Next(100, 255), rand.Next(100, 255))
            ));
        }

        private void PlayCollisionSound()
        {
            if (!soundEnabledCheckbox.Checked)
                return;

            // Cooldown to prevent too many sounds
            if (soundCooldownCounter > 0)
            {
                soundCooldownCounter--;
                return;
            }

            DateTime now = DateTime.Now;
            if ((now - lastSoundTime).TotalMilliseconds < MinSoundIntervalMs)
                return;

            lastSoundTime = now;
            soundCooldownCounter = 3; // Skip next 3 collision sounds

            try
            {
                // Shorter, quieter beep
                System.Threading.Tasks.Task.Run(() =>
                {
                    try
                    {
                        Console.Beep(400, 30); // Lower frequency, shorter duration
                    }
                    catch { }
                });
            }
            catch { }
        }

        private void OnMouseDown(object? sender, MouseEventArgs e)
        {
            if (e.X > this.ClientSize.Width - 250) return;

            foreach (var cube in cubes.OrderByDescending(c => c.Size))
            {
                if (cube.Contains(e.Location))
                {
                    draggedCube = cube;
                    draggedCube.IsDragging = true;
                    lastMousePos = e.Location;
                    break;
                }
            }
        }

        private void OnMouseMove(object? sender, MouseEventArgs e)
        {
            if (draggedCube != null && e.Button == MouseButtons.Left)
            {
                float dx = e.X - lastMousePos.X;
                float dy = e.Y - lastMousePos.Y;

                draggedCube.X += dx;
                draggedCube.Y += dy;

                draggedCube.VelocityX = dx * forceMultiplier;
                draggedCube.VelocityY = dy * forceMultiplier;

                lastMousePos = e.Location;
            }
        }

        private void OnMouseUp(object? sender, MouseEventArgs e)
        {
            if (draggedCube != null)
            {
                draggedCube.IsDragging = false;
                draggedCube = null;
            }
        }

        private void Update(object? sender, EventArgs e)
        {
            // Optimized physics loop
            int cubeCount = cubes.Count;

            for (int i = 0; i < cubeCount; i++)
            {
                var cube = cubes[i];

                if (!cube.IsDragging)
                {
                    // Apply gravity
                    cube.VelocityY += gravity;

                    // Apply friction
                    cube.VelocityX *= friction;
                    cube.VelocityY *= friction;

                    // Update position
                    cube.X += cube.VelocityX;
                    cube.Y += cube.VelocityY;

                    // Wall collisions
                    int rightBoundary = this.ClientSize.Width - 250;

                    if (cube.X <= 0)
                    {
                        cube.X = 0;
                        cube.VelocityX = -cube.VelocityX * restitution;
                        PlayCollisionSound();
                    }
                    else if (cube.X + cube.Size >= rightBoundary)
                    {
                        cube.X = rightBoundary - cube.Size;
                        cube.VelocityX = -cube.VelocityX * restitution;
                        PlayCollisionSound();
                    }

                    if (cube.Y <= 0)
                    {
                        cube.Y = 0;
                        cube.VelocityY = -cube.VelocityY * restitution;
                        PlayCollisionSound();
                    }
                    else if (cube.Y + cube.Size >= this.ClientSize.Height)
                    {
                        cube.Y = this.ClientSize.Height - cube.Size;
                        cube.VelocityY = -cube.VelocityY * restitution;
                        PlayCollisionSound();
                    }
                }
            }

            // Optimized cube-to-cube collisions
            for (int i = 0; i < cubeCount; i++)
            {
                for (int j = i + 1; j < cubeCount; j++)
                {
                    if (CheckCollision(cubes[i], cubes[j]))
                    {
                        ResolveCollision(cubes[i], cubes[j]);
                        PlayCollisionSound();
                    }
                }
            }

            this.Invalidate();
        }

        private bool CheckCollision(Cube a, Cube b)
        {
            return a.GetBounds().IntersectsWith(b.GetBounds());
        }

        private void ResolveCollision(Cube a, Cube b)
        {
            // Calculate centers
            float aCenterX = a.X + a.Size / 2;
            float aCenterY = a.Y + a.Size / 2;
            float bCenterX = b.X + b.Size / 2;
            float bCenterY = b.Y + b.Size / 2;

            // Collision normal
            float dx = bCenterX - aCenterX;
            float dy = bCenterY - aCenterY;
            float distance = (float)Math.Sqrt(dx * dx + dy * dy);

            if (distance == 0) return;

            float nx = dx / distance;
            float ny = dy / distance;

            // Separate cubes
            float overlap = (a.Size + b.Size) / 2 - distance;
            if (overlap > 0)
            {
                float separationX = nx * overlap * 0.5f;
                float separationY = ny * overlap * 0.5f;

                a.X -= separationX;
                a.Y -= separationY;
                b.X += separationX;
                b.Y += separationY;
            }

            // Relative velocity
            float dvx = b.VelocityX - a.VelocityX;
            float dvy = b.VelocityY - a.VelocityY;

            // Velocity along normal
            float velAlongNormal = dvx * nx + dvy * ny;

            if (velAlongNormal > 0) return;

            // Calculate impulse
            float impulse = -(1 + restitution) * velAlongNormal;
            impulse /= (1 / a.Mass + 1 / b.Mass);

            // Apply impulse
            float impulseX = impulse * nx;
            float impulseY = impulse * ny;

            a.VelocityX -= impulseX / a.Mass;
            a.VelocityY -= impulseY / a.Mass;
            b.VelocityX += impulseX / b.Mass;
            b.VelocityY += impulseY / b.Mass;
        }

        private void OnPaint(object? sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

            foreach (var cube in cubes)
            {
                using (SolidBrush brush = new SolidBrush(cube.Color))
                {
                    g.FillRectangle(brush, cube.GetBounds());
                }

                if (cube.IsDragging)
                {
                    using (Pen pen = new Pen(Color.Yellow, 3))
                    {
                        g.DrawRectangle(pen, cube.X, cube.Y, cube.Size, cube.Size);
                    }
                }

                // Draw mass indicator
                using (Font font = new Font("Arial", 8))
                using (SolidBrush textBrush = new SolidBrush(Color.White))
                {
                    string massText = $"M:{cube.Mass}";
                    g.DrawString(massText, font, textBrush, cube.X + 5, cube.Y + 5);
                }
            }
        }

        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new SimulatorForm());
        }
    }
}

