using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class BallSpawner : Spawner
{
    public GameObject basicBallPrefab;

    public GameObject nuclearBallPrefab;

    public GameObject glassBallPrefab;

    public GameObject eyeBallPrefab;

    public GameObject oozeBallPrefab;

    public GameObject fruitBallPrefab;

    public GameObject gameBoard;

    public Player player;

    private static WaitForSeconds _waitForSeconds0_5 = new WaitForSeconds(0.5f);

    private Queue<System.Action> ballQueue = new Queue<System.Action>();

    private Queue<GameObject> balls = new Queue<GameObject>();

    private int basicBallCount = 0;

    // Start is called once before the first execution of Update after the MonoBehaviour is created
    void Start()
    {
        int currentLevel = PlayerPrefs.GetInt("CurrentLevel");

        ballQueue.Enqueue(() => SpawnBall(basicBallPrefab));
        ballQueue.Enqueue(() => SpawnBall(basicBallPrefab));
        basicBallCount = 2;

        StartCoroutine(SpawnBalls());
    }

    public void SpawnBallOfType(string ballType, Vector2? position = null)
    {
        switch (ballType)
        {
            case "BasicBall":
                SpawnBall(basicBallPrefab, position);
                break;
            case "NuclearBall":
                SpawnBall(nuclearBallPrefab, position);
                break;
            case "GlassBall":
                SpawnBall(glassBallPrefab, position);
                break;
            case "EyeBall":
                SpawnBall(eyeBallPrefab, position);
                break;
            case "OozeBall":
                SpawnBall(oozeBallPrefab, position);
                break;
            case "FruitBall":
                SpawnBall(fruitBallPrefab, position);
                break;
            default:
                Debug.LogWarning($"Unknown ball type: {ballType}");
                break;
        }
    }

    public void ResetBalls()
    {
        // Clear all balls.
        int nukeBallCount = 0;
        int glassBallCount = 0;
        int eyeBallCount = 0;
        int oozeBallCount = 0;
        int fruitBallCount = 0;

        foreach (var ball in balls)
        {
            if (ball != null)
            {
                if (ball.CompareTag("NuclearBall"))
                {
                    nukeBallCount++;
                }
                else if (ball.CompareTag("GlassBall"))
                {
                    glassBallCount++;
                }
                else if (ball.CompareTag("EyeBall"))
                {
                    eyeBallCount++;
                }
                else if (ball.CompareTag("OozeBall"))
                {
                    oozeBallCount++;
                }
                else if (ball.CompareTag("FruitBall"))
                {
                    fruitBallCount++;
                }
                Destroy(ball);
            }
        }
        balls.Clear();

        // Always spawn one basic ball more than in the previous level.
        basicBallCount++;

        // At special levels force spawn certain types of balls.
        if (player.level % 5 == 0)
        {
            // One nuclear ball each five levels.
            nukeBallCount++;

            if (player.level >= 10)
            {
                // Glass balls after level 10.
                glassBallCount += Random.Range(1, 3);
            }

            if (player.level >= 15)
            {
                // An extra nuke ball after level 15.
                nukeBallCount++;

                // Eye balls after level 15.
                eyeBallCount++;
            }

            if (player.level >= 20)
            {
                // Ooze balls after level 20.
                oozeBallCount++;
            }

            // No extra basic ball when a special ball spawns for the first time.
            basicBallCount--;
        }

        // Spawn balls.
        for (int i = 0; i < basicBallCount; i++)
        {
            ballQueue.Enqueue(() => SpawnBall(basicBallPrefab));
        }

        for (int i = 0; i < nukeBallCount; i++)
        {
            ballQueue.Enqueue(() => SpawnBall(nuclearBallPrefab));
        }

        for (int i = 0; i < glassBallCount; i++)
        {
            ballQueue.Enqueue(() => SpawnBall(glassBallPrefab));
        }

        for (int i = 0; i < eyeBallCount; i++)
        {
            ballQueue.Enqueue(() => SpawnBall(eyeBallPrefab));
        }

        for (int i = 0; i < oozeBallCount; i++)
        {
            ballQueue.Enqueue(() => SpawnBall(oozeBallPrefab));
        }

        for (int i = 0; i < fruitBallCount; i++)
        {
            ballQueue.Enqueue(() => SpawnBall(fruitBallPrefab));
        }

        StartCoroutine(SpawnBalls());
    }

    private void SpawnBall(GameObject ballPrefab, Vector2? position = null)
    {
        if (ballPrefab == null || gameBoard == null)
        {
            Debug.LogWarning("BallSpawner: Missing ballPrefab or gameBoard reference.");
            return;
        }

        Vector3 spawnPosition = position.HasValue ? 
                                new Vector3(position.Value.x, position.Value.y, gameBoard.transform.position.z) : 
                                GetRandomPositionInsideGameBoard();
        var newBall = Instantiate(ballPrefab, spawnPosition, Quaternion.identity);

        if (ballPrefab == oozeBallPrefab)
        {
            var oozeBallScript = newBall.GetComponent<OozeBall>();
            if (oozeBallScript != null)
            {
                oozeBallScript.spawner = this;
            }
        }

        balls.Enqueue(newBall);

        FixCollisions();
    }

    private Vector3 GetRandomPositionInsideGameBoard()
    {
        Bounds bounds;

        Collider2D collider2D = gameBoard.GetComponent<Collider2D>();
        if (collider2D != null)
        {
            bounds = collider2D.bounds;
        }
        else
        {
            Renderer boardRenderer = gameBoard.GetComponent<Renderer>();
            if (boardRenderer == null)
            {
                Debug.LogWarning("BallSpawner: gameBoard needs a Collider2D or Renderer to read bounds.");
                return gameBoard.transform.position;
            }

            bounds = boardRenderer.bounds;
        }

        const float edgeInset = 0.1f;
        float randomX = Random.Range(bounds.min.x + edgeInset, bounds.max.x - edgeInset);
        float randomY = Random.Range(bounds.min.y + edgeInset, bounds.max.y - edgeInset);

        return new Vector3(randomX, randomY, gameBoard.transform.position.z);
    }

    public void FixCollisions() {
        // Disable collision between existing balls of certain types.
        // Only basic balls and fruit balls will collide with each other, while ooze,
        // nuclear, glass, and eye balls will not collide with any other balls.
        // As an exception, glass balls will collide with other glass balls.
        foreach (var ball in balls)
        {
            if (ball == null) continue;

            foreach (var otherBall in balls)
            {
                if (otherBall == null) continue;
                if (ball == otherBall) continue;

                bool ballIsBasicOrFruit = ball.CompareTag("BasicBall") || ball.CompareTag("FruitBall");
                bool otherIsBasicOrFruit = otherBall.CompareTag("BasicBall") || otherBall.CompareTag("FruitBall");
                bool bothAreGlass = ball.CompareTag("GlassBall") && otherBall.CompareTag("GlassBall");

                bool shouldCollide = (ballIsBasicOrFruit && otherIsBasicOrFruit) || bothAreGlass;

                if (!shouldCollide)
                {
                    Physics2D.IgnoreCollision(
                        ball.GetComponent<Collider2D>(), 
                        otherBall.GetComponent<Collider2D>(), 
                        true
                    );
                }
            }
        }
    }

    private IEnumerator SpawnBalls()
    {
        while (ballQueue.Count > 0)
        {
            System.Action spawnAction = ballQueue.Dequeue();
            spawnAction.Invoke();
            yield return _waitForSeconds0_5;
        }

        FixCollisions();
    }

    public void MagnetActive(bool flipped)
    {
        foreach (var ball in balls)
        {
            if (ball == null) continue;

            var ballScript = ball.GetComponent<BallBase>();
            if (ballScript != null)
            {
                ballScript.MagnetActive(flipped);
            }
        }
    }

    public void MagnetInactive()
    {
        foreach (var ball in balls)
        {
            if (ball == null) continue;

            var ballScript = ball.GetComponent<BallBase>();
            if (ballScript != null)
            {
                ballScript.MagnetInactive();
            }
        }
    }

    public Queue<GameObject> GetBalls()
    {
        return balls;
    }
}
