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

namespace BallCollider
{
    public partial class Form1 : Form
    {
        // Ball properties
        private PointF ballPos;
        private PointF ballVel;
        private const float BallRadius = 12f;
        private const float MinSpeed = 6f;
        private const float MaxSpeed = 20f;

        // Shape properties
        private List<List<PointF>> shapes;
        private List<Color> shapeColors;
        private const int MinWalls = 5;
        private const int MaxWalls = 220;
        private const float BaseShapeRadius = 220f;
        private PointF shapeCenter;

        // Game mode
        private bool multiShapeMode = false;
        private int currentShapeIndex = 0;

        // Rendering optimization
        private BufferedGraphicsContext context;
        private BufferedGraphics buffer;
        private readonly SolidBrush ballBrush;
        private readonly List<Pen> shapePens;

        // Game loop
        private readonly WinTimer gameTimer;
        private readonly Random rnd;

        // Collision cooldown to prevent multiple hits
        private int collisionCooldown = 0;

        // Tracer system
        private List<Tracer> tracers;
        private const int MaxTracers = 80;
        private int tracerCooldown = 0;
        private const int TracerInterval = 2;

        // Predictive collision detection
        private PointF predictedPos;
        private bool showPrediction = false;

        public Form1()
        {
            InitializeComponent();

            // Optimize form for rendering
            this.DoubleBuffered = true;
            this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                         ControlStyles.UserPaint |
                         ControlStyles.Opaque, true);

            this.ClientSize = new Size(800, 600);
            this.Text = "balls";
            this.BackColor = Color.Black;

            rnd = new Random();
            ballBrush = new SolidBrush(Color.White);
            shapePens = new List<Pen>();
            tracers = new List<Tracer>();

            // Initialize buffered graphics
            context = BufferedGraphicsManager.Current;
            context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
            buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);

            shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f);
            InitializeGame();

            // High-frequency timer for smooth gameplay
            gameTimer = new WinTimer();
            gameTimer.Interval = 20; // ~60 FPS
            gameTimer.Tick += GameLoop;
            gameTimer.Start();

            this.Resize += (s, e) => RecreateBuffer();
            this.KeyDown += Form1_KeyDown;
            this.KeyPreview = true;
        }

        private void Form1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.M)
            {
                multiShapeMode = !multiShapeMode;
                if (multiShapeMode)
                {
                    InitializeMultiShapeMode();
                }
                else
                {
                    InitializeSingleShapeMode();
                }
            }
            else if (e.KeyCode == Keys.P)
            {
                showPrediction = !showPrediction;
            }
        }

        private void RecreateBuffer()
        {
            if (this.Width > 0 && this.Height > 0)
            {
                buffer?.Dispose();
                context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
                buffer = context.Allocate(this.CreateGraphics(), this.ClientRectangle);
                shapeCenter = new PointF(ClientSize.Width / 2f, ClientSize.Height / 2f);
            }
        }

        private void InitializeGame()
        {
            shapes = new List<List<PointF>>();
            shapeColors = new List<Color>();
            shapePens.Clear();
            tracers.Clear();

            CreateShape(MinWalls, 0);

            // Spawn ball at center
            ballPos = new PointF(shapeCenter.X, shapeCenter.Y);

            // Random initial velocity
            float angle = (float)(rnd.NextDouble() * Math.PI * 2);
            float speed = MinSpeed * 1.5f;
            ballVel = new PointF(
                (float)Math.Cos(angle) * speed,
                (float)Math.Sin(angle) * speed
            );
        }

        private void InitializeSingleShapeMode()
        {
            shapes.Clear();
            shapeColors.Clear();
            shapePens.Clear();
            tracers.Clear();
            CreateShape(MinWalls, 0);
            currentShapeIndex = 0;
        }

        private void InitializeMultiShapeMode()
        {
            shapes.Clear();
            shapeColors.Clear();
            shapePens.Clear();
            tracers.Clear();

            // Create 3 concentric shapes
            for (int i = 0; i < 3; i++)
            {
                CreateShape(MinWalls + i * 2, i);
            }
            currentShapeIndex = 0;
        }

        private void CreateShape(int sides, int shapeIndex)
        {
            while (shapes.Count <= shapeIndex)
            {
                shapes.Add(new List<PointF>());
                shapeColors.Add(Color.White);
                shapePens.Add(new Pen(Color.White, 4f));
            }

            List<PointF> shapePoints = new List<PointF>();

            float angleStep = (float)(2 * Math.PI / sides);
            float startAngle = (float)(rnd.NextDouble() * Math.PI * 2);

            float radius = multiShapeMode ?
                BaseShapeRadius * (1.0f - shapeIndex * 0.2f) :
                BaseShapeRadius;

            for (int i = 0; i < sides; i++)
            {
                float angle = startAngle + i * angleStep;
                shapePoints.Add(new PointF(
                    shapeCenter.X + (float)Math.Cos(angle) * radius,
                    shapeCenter.Y + (float)Math.Sin(angle) * radius
                ));
            }

            shapes[shapeIndex] = shapePoints;

            Color newColor = Color.FromArgb(
                rnd.Next(100, 256),
                rnd.Next(100, 256),
                rnd.Next(100, 256)
            );
            shapeColors[shapeIndex] = newColor;
            shapePens[shapeIndex].Color = newColor;
        }

        private void GameLoop(object sender, EventArgs e)
        {
            UpdateBall();
            UpdateTracers();
            Render();
        }

        private void UpdateBall()
        {
            if (collisionCooldown > 0)
                collisionCooldown--;

            // Store previous position for tracer
            PointF previousPos = ballPos;

            predictedPos = new PointF(ballPos.X + ballVel.X, ballPos.Y + ballVel.Y);

            if (collisionCooldown == 0)
            {
                if (CheckPredictiveCollision())
                {
                    // Predictive collision detected - handle it immediately
                    HandlePredictiveCollision();
                }
                else
                {
                    // No collision predicted - apply normal movement
                    ballPos = predictedPos;
                }
            }
            else
            {
                // Apply normal movement during cooldown
                ballPos = predictedPos;
            }

            // Gradually increase speed for excitement (capped)
            float speed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
            if (speed < MaxSpeed)
            {
                float factor = 1.005f;
                ballVel.X *= factor;
                ballVel.Y *= factor;
            }

            // Add tracer
            if (tracerCooldown <= 0)
            {
                AddTracer(previousPos);
                tracerCooldown = TracerInterval;
            }
            else
            {
                tracerCooldown--;
            }

            // Final boundary enforcement (safety net)
            EnforceBoundaries();
        }

        private bool CheckPredictiveCollision()
        {
            if (shapes.Count == 0) return false;

            List<PointF> currentShape = shapes[currentShapeIndex];

            for (int i = 0; i < currentShape.Count; i++)
            {
                PointF p1 = currentShape[i];
                PointF p2 = currentShape[(i + 1) % currentShape.Count];

                PointF closest = ClosestPointOnSegment(p1, p2, predictedPos);
                float dx = predictedPos.X - closest.X;
                float dy = predictedPos.Y - closest.Y;
                float dist = (float)Math.Sqrt(dx * dx + dy * dy);

                if (dist < BallRadius)
                {
                    return true;
                }
            }

            return false;
        }

        private void HandlePredictiveCollision()
        {
            if (shapes.Count == 0) return;

            List<PointF> currentShape = shapes[currentShapeIndex];
            PointF collisionPoint = PointF.Empty;
            PointF wallNormal = PointF.Empty;
            int wallIndex = -1;
            float minDist = float.MaxValue;

            // Find the closest wall that will be collided with
            for (int i = 0; i < currentShape.Count; i++)
            {
                PointF p1 = currentShape[i];
                PointF p2 = currentShape[(i + 1) % currentShape.Count];

                PointF closest = ClosestPointOnSegment(p1, p2, predictedPos);
                float dx = predictedPos.X - closest.X;
                float dy = predictedPos.Y - closest.Y;
                float dist = (float)Math.Sqrt(dx * dx + dy * dy);

                if (dist < BallRadius && dist < minDist)
                {
                    minDist = dist;
                    collisionPoint = closest;
                    wallIndex = i;

                    // Calculate wall normal
                    float edgeX = p2.X - p1.X;
                    float edgeY = p2.Y - p1.Y;
                    float edgeLen = (float)Math.Sqrt(edgeX * edgeX + edgeY * edgeY);

                    if (edgeLen > 0)
                    {
                        wallNormal.X = -edgeY / edgeLen;
                        wallNormal.Y = edgeX / edgeLen;

                        // Ensure normal points inward
                        float midX = (p1.X + p2.X) / 2f;
                        float midY = (p1.Y + p2.Y) / 2f;
                        float toCenterX = shapeCenter.X - midX;
                        float toCenterY = shapeCenter.Y - midY;

                        if (wallNormal.X * toCenterX + wallNormal.Y * toCenterY < 0)
                        {
                            wallNormal.X = -wallNormal.X;
                            wallNormal.Y = -wallNormal.Y;
                        }
                    }
                }
            }

            if (wallIndex >= 0 && wallNormal != PointF.Empty)
            {
                // Calculate exact collision position (ball surface touching wall)
                float penetration = BallRadius - minDist;
                PointF correctedPos = new PointF(
                    predictedPos.X + wallNormal.X * penetration,
                    predictedPos.Y + wallNormal.Y * penetration
                );

                // Set ball to corrected position
                ballPos = correctedPos;

                // Reflect velocity
                float dotProduct = ballVel.X * wallNormal.X + ballVel.Y * wallNormal.Y;

                if (dotProduct < 0)
                {
                    ballVel.X -= 2 * dotProduct * wallNormal.X;
                    ballVel.Y -= 2 * dotProduct * wallNormal.Y;

                    // Apply boost and clamp speed
                    float boost = 1.05f;
                    ballVel.X *= boost;
                    ballVel.Y *= boost;

                    float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
                    if (currentSpeed > MaxSpeed)
                    {
                        ballVel.X = (ballVel.X / currentSpeed) * MaxSpeed;
                        ballVel.Y = (ballVel.Y / currentSpeed) * MaxSpeed;
                    }

                    PlayHitSound();

                    // Handle shape evolution
                    PointF p1 = currentShape[wallIndex];
                    PointF p2 = currentShape[(wallIndex + 1) % currentShape.Count];

                    if (currentShape.Count < MaxWalls)
                    {
                        // Add vertex to make shape more circular
                        PointF newPoint = new PointF(
                            (p1.X + p2.X) / 2f,
                            (p1.Y + p2.Y) / 2f
                        );

                        float radius = multiShapeMode ?
                            BaseShapeRadius * (1.0f - currentShapeIndex * 0.2f) :
                            BaseShapeRadius;

                        // Project to maintain circular shape
                        float angle = (float)Math.Atan2(newPoint.Y - shapeCenter.Y, newPoint.X - shapeCenter.X);
                        newPoint = new PointF(
                            shapeCenter.X + (float)Math.Cos(angle) * radius,
                            shapeCenter.Y + (float)Math.Sin(angle) * radius
                        );

                        currentShape.Insert(wallIndex + 1, newPoint);
                    }
                    else
                    {
                        if (multiShapeMode && currentShapeIndex < shapes.Count - 1)
                        {
                            currentShapeIndex++;
                            PlayShapeChangeSound();
                        }
                        else
                        {
                            int sides = multiShapeMode ? MinWalls + currentShapeIndex * 2 : MinWalls;
                            CreateShape(sides, currentShapeIndex);
                            PlayShapeChangeSound();
                        }
                    }

                    collisionCooldown = 8;
                }
            }
        }

        private void EnforceBoundaries()
        {
            bool isInsideAnyShape = false;

            foreach (var shape in shapes)
            {
                if (IsPointInPolygon(ballPos, shape))
                {
                    isInsideAnyShape = true;
                    break;
                }
            }

            if (!isInsideAnyShape && shapes.Count > 0)
            {
                PointF closestBoundary = FindClosestBoundaryPoint(ballPos, shapes[0]);

                float pushX = closestBoundary.X - ballPos.X;
                float pushY = closestBoundary.Y - ballPos.Y;
                float pushDist = (float)Math.Sqrt(pushX * pushX + pushY * pushY);

                if (pushDist > 0)
                {
                    float margin = BallRadius + 2f;
                    ballPos.X = closestBoundary.X - (pushX / pushDist) * margin;
                    ballPos.Y = closestBoundary.Y - (pushY / pushDist) * margin;

                    // Emergency bounce
                    float randomAngle = (float)(rnd.NextDouble() * Math.PI * 2);
                    float currentSpeed = (float)Math.Sqrt(ballVel.X * ballVel.X + ballVel.Y * ballVel.Y);
                    ballVel.X = (float)Math.Cos(randomAngle) * currentSpeed;
                    ballVel.Y = (float)Math.Sin(randomAngle) * currentSpeed;
                }
            }
        }

        private void UpdateTracers()
        {
            // Update and remove old tracers
            for (int i = tracers.Count - 1; i >= 0; i--)
            {
                tracers[i].Update();
                if (tracers[i].IsDead)
                {
                    tracers.RemoveAt(i);
                }
            }
        }

        private void AddTracer(PointF position)
        {
            if (tracers.Count >= MaxTracers)
            {
                tracers.RemoveAt(0);
            }

            Color tracerColor = shapes.Count > 0 ?
                Color.FromArgb(150, shapeColors[currentShapeIndex]) :
                Color.FromArgb(150, Color.White);

            tracers.Add(new Tracer(position, tracerColor, BallRadius * 0.7f));
        }

        private PointF FindClosestBoundaryPoint(PointF point, List<PointF> shape)
        {
            PointF closest = shape[0];
            float closestDist = float.MaxValue;

            for (int i = 0; i < shape.Count; i++)
            {
                PointF p1 = shape[i];
                PointF p2 = shape[(i + 1) % shape.Count];

                PointF segmentClosest = ClosestPointOnSegment(p1, p2, point);
                float dist = Distance(point, segmentClosest);

                if (dist < closestDist)
                {
                    closestDist = dist;
                    closest = segmentClosest;
                }
            }

            return closest;
        }

        private float Distance(PointF a, PointF b)
        {
            float dx = a.X - b.X;
            float dy = a.Y - b.Y;
            return (float)Math.Sqrt(dx * dx + dy * dy);
        }

        private bool IsPointInPolygon(PointF point, List<PointF> polygon)
        {
            bool inside = false;
            int j = polygon.Count - 1;

            for (int i = 0; i < polygon.Count; i++)
            {
                if ((polygon[i].Y > point.Y) != (polygon[j].Y > point.Y) &&
                    point.X < (polygon[j].X - polygon[i].X) * (point.Y - polygon[i].Y) /
                    (polygon[j].Y - polygon[i].Y) + polygon[i].X)
                {
                    inside = !inside;
                }
                j = i;
            }

            return inside;
        }

        private PointF ClosestPointOnSegment(PointF a, PointF b, PointF p)
        {
            float dx = b.X - a.X;
            float dy = b.Y - a.Y;

            if (dx == 0 && dy == 0)
                return a;

            float len2 = dx * dx + dy * dy;
            float t = ((p.X - a.X) * dx + (p.Y - a.Y) * dy) / len2;

            t = Math.Max(0, Math.Min(1, t));

            return new PointF(a.X + t * dx, a.Y + t * dy);
        }

        private void PlayHitSound()
        {
            try
            {
                int freq = rnd.Next(400, 1000);
                System.Threading.Tasks.Task.Run(() => Console.Beep(freq, 30));
            }
            catch { }
        }

        private void PlayShapeChangeSound()
        {
            try
            {
                System.Threading.Tasks.Task.Run(() =>
                {
                    Console.Beep(300, 80);
                    System.Threading.Thread.Sleep(50);
                    Console.Beep(500, 80);
                });
            }
            catch { }
        }

        private void Render()
        {
            Graphics g = buffer.Graphics;
            g.SmoothingMode = SmoothingMode.AntiAlias;
            g.Clear(Color.Black);

            // Draw tracers first (behind everything)
            foreach (var tracer in tracers)
            {
                tracer.Draw(g);
            }

            // Draw all shapes
            for (int shapeIndex = 0; shapeIndex < shapes.Count; shapeIndex++)
            {
                var shape = shapes[shapeIndex];
                if (shape.Count >= 2)
                {
                    Color drawColor = shapeColors[shapeIndex];
                    if (multiShapeMode && shapeIndex == currentShapeIndex)
                    {
                        drawColor = Color.FromArgb(255,
                            Math.Min(255, drawColor.R + 50),
                            Math.Min(255, drawColor.G + 50),
                            Math.Min(255, drawColor.B + 50));
                    }

                    using (Pen pen = new Pen(drawColor, shapeIndex == currentShapeIndex ? 5f : 3f))
                    {
                        for (int i = 0; i < shape.Count; i++)
                        {
                            PointF p1 = shape[i];
                            PointF p2 = shape[(i + 1) % shape.Count];
                            g.DrawLine(pen, p1, p2);
                        }
                    }
                }
            }

            // Draw prediction line if enabled
            if (showPrediction)
            {
                using (Pen predictionPen = new Pen(Color.FromArgb(100, Color.Yellow), 1f))
                {
                    g.DrawLine(predictionPen, ballPos, predictedPos);
                }

                // Draw predicted position
                using (Brush predictionBrush = new SolidBrush(Color.FromArgb(100, Color.Red)))
                {
                    g.FillEllipse(predictionBrush,
                        predictedPos.X - BallRadius / 2,
                        predictedPos.Y - BallRadius / 2,
                        BallRadius, BallRadius);
                }
            }

            // Draw ball with glow effect
            using (GraphicsPath path = new GraphicsPath())
            {
                path.AddEllipse(
                    ballPos.X - BallRadius,
                    ballPos.Y - BallRadius,
                    BallRadius * 2,
                    BallRadius * 2
                );

                using (PathGradientBrush brush = new PathGradientBrush(path))
                {
                    Color activeColor = shapes.Count > 0 ? shapeColors[currentShapeIndex] : Color.White;
                    brush.CenterColor = Color.White;
                    brush.SurroundColors = new[] { Color.FromArgb(100, activeColor) };
                    g.FillPath(brush, path);
                }
            }

            // Draw UI info
            using (Font font = new Font("Arial", 14, FontStyle.Bold))
            {
                string sidesText = $"Sides: {shapes[currentShapeIndex].Count}";
                string predictionText = $"Prediction: {(showPrediction ? "ON" : "OFF")} (P)";

                g.DrawString(sidesText, font, Brushes.White, 10, 10);
                g.DrawString(predictionText, font, Brushes.LightGreen, 10, 35);
            }

            buffer.Render();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            if (buffer != null)
            {
                buffer.Render(e.Graphics);
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                gameTimer?.Stop();
                gameTimer?.Dispose();
                buffer?.Dispose();
                context?.Dispose();
                ballBrush?.Dispose();
                foreach (var pen in shapePens)
                    pen?.Dispose();
                components?.Dispose();
            }
            base.Dispose(disposing);
        }
    }

    // Tracer class for ball trail effect
    public class Tracer
    {
        public PointF Position { get; private set; }
        public Color Color { get; private set; }
        public float Radius { get; private set; }
        public float Life { get; private set; }
        public bool IsDead => Life <= 0;

        private readonly float initialRadius;
        private readonly Color initialColor;

        public Tracer(PointF position, Color color, float radius)
        {
            Position = position;
            Color = color;
            Radius = radius;
            initialRadius = radius;
            initialColor = color;
            Life = 1.0f; // Start with full life
        }

        public void Update()
        {
            Life -= 0.03f; // Fade speed
            if (Life < 0) Life = 0;

            // Shrink and fade
            Radius = initialRadius * Life;

            // Fade color
            Color = Color.FromArgb(
                (int)(initialColor.A * Life),
                initialColor.R,
                initialColor.G,
                initialColor.B
            );
        }

        public void Draw(Graphics g)
        {
            if (IsDead) return;

            using (Brush brush = new SolidBrush(Color))
            {
                g.FillEllipse(brush,
                    Position.X - Radius,
                    Position.Y - Radius,
                    Radius * 2,
                    Radius * 2);
            }
        }
    }
}