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


public class StackGame : MonoBehaviour
{
    // Es. ¡¡ATENCIÓN!! ANTES DE COMENZAR NECESITAS CREAR EL TAG "Block" en el inspector.
    // En. ATTENTION!! BEFORE YOU START YOU NEED TO CREATE THE "Block" TAG in the inspector.
    
    //Es. Ajustes para crear una cuadrícula invisible usada para colocar y monitorear las piezas
    //En. Settings to create an invisible grid used to place and monitor game pieces
    [Header("Grid Settings")]
    public int gridWidth = 10;
    public int gridHeight = 20;
    public float fallInterval = 1f; // Es. Velocidad de caida de las piezas (va de 0 hasta 1) // En. Speed ​​of falling pieces (ranges from 0 to 1)

    //Es. Aquí tu puedes crear las piezas para tu juego
    //En. Here you can create the pieces for your game
    [Header("Pieces")]
    public List<StackPiece> pieces;

    //Es. Aquí colocas las referencias de la interfaz de usuario que vas a utilizar
    //En. Here you can place the references of the user interface that you are going to use
    [Header("UI Settings")]
    public GameObject gameOverPanel;
    public Text gameOverText;
    public Text scoreText;
    
    //Es. Estas son las variables que vamos a usar para crear el juego
    //En. These are the variables that we are going to use to create the game
    private Transform[,] grid;
    private float fallTimer;
    private GameObject currentPieceObject;
    private Vector2Int currentPiecePosition;
    private StackPiece currentPieceData;
    private bool isGameOver = true;
    private int score = 0;
    
    // Es. Cada pieza del juego se comporta como un conjunto de pequeños cubos. Y aquí definimos el nombre de la pieza, su color, cuantos cubos tiene y en que posiciones estan colocados.
    // En. Each game piece behaves like a set of small cubes. And here we define the name of the piece, its color, how many cubes it has and in what positions they are placed.
    [System.Serializable]
    public struct StackPiece
    {
        public string name;
        public Vector2Int[] blocks;
        public Color color;        
    }

    void Start()
    {
        // Es. No ejecutamos algo durante el inicio pero lo dejamos para usarlo después :)
        // En. We don't run something during startup but we leave it to use later :)
        
    }

    // Es. Ejecutamos este método manualmente con un botón externo cuando queremos comenzar a jugar por primera vez.
    // En. We execute this method manually with an external button when we want to start playing for the first time.
    public void LetsPlay()
    {
        isGameOver = false;
        InitializeGame();
    }

    // Es. Este método se usa para comenzar a jugar.
    // En. This method is used to start playing.
    void InitializeGame()
    {
        // Es. Inicializa la cuadrícula invisible con las dimensiones correctas.
        // En. Initializes the invisible grid with the correct dimensions.
        grid = new Transform[gridWidth, gridHeight];
    
        //Es. Borramos los bloques de la partida anterior.
        // En. We delete the blocks from the previous game.
        ClearExistingBlocks();

        // Es. Ocultamos la interfaz de usuario utilizada para mostrar el final del juego y reestablecemos el puntaje.
        // En. We hide the UI used to show the end of the game and reset the score.
        gameOverPanel.SetActive(false);
        isGameOver = false;
        score = 0;
        scoreText.text = $"Score: {score}";
            
        // Es. Asegura que haya piezas definidas en el inspector.
        // En. Ensures that there are defined game pieces in the inspector.
        if (pieces == null || pieces.Count == 0)
        {
            Debug.LogError("¡No hay piezas definidas en el Inspector!"); 
            Debug.LogError("There are no game pieces defined in the Inspector!");
            return;
        }
        
        // Es. Comenzamos colocar las piezas para jugar.
        // En. We begin to place the game pieces to play.
        SpawnPiece();
    }

    // Es. Buscamos cada bloque con el Tag "Block" dentro del juego para despues eliminarlo.
    // En. We look for each block with the Tag "Block" within the game and then eliminate it.
    void ClearExistingBlocks()
    {
        foreach (Transform block in FindObjectsOfType<Transform>())
        {
            if (block.CompareTag("Block")) Destroy(block.gameObject);
        }
    }
    // Es. Se verifica constamente si el juego ya terminó, la velocidad de caida de las piezas y los controles del juego.
    // En. It constantly checks whether the game is over, the speed of falling pieces and the game controls.
    void Update()
    {
        if (isGameOver) return;

        fallTimer += Time.deltaTime;

        if (fallTimer >= fallInterval)
        {
            MovePieceDown();
            fallTimer = 0f;
        }

        HandleInput();
        
    }

    // Es. Aquí vamos a generar las piezas del juego.
    // En. Here we will generate the game pieces.
    void SpawnPiece()
    {
         // Es. Verifica si hay piezas disponibles
         // En. Check if pieces are available.

        if (pieces == null || pieces.Count == 0)
        {
            Debug.LogError("No hay piezas definidas.");
            Debug.LogError("There are no defined pieces.");
            GameOver();
            return;
        }

        // Es. Elige una pieza aleatoria de las que configuramos en el inspector.
        //En. Choose a random piece from those we configured in the inspector.
        StackPiece randomPiece = pieces[Random.Range(0, pieces.Count)];
        currentPieceData = randomPiece;
        currentPiecePosition = new Vector2Int(gridWidth / 2 - 1, gridHeight - 2);

        // Es. Verifica si la posición inicial es válida.
        // En. Check if the initial position is valid.
        if (!IsPositionValid(currentPiecePosition, currentPieceData.blocks))
        {
            GameOver();
            return;
        }

        // Es. Aquí se crea la pieza como un objeto del juego y se le asigna el TAG "Block".
        // En. Here the piece is created as a game object and assigned the TAG "Block".
        currentPieceObject = new GameObject("CurrentPiece");
        currentPieceObject.tag = "Block";
        currentPieceObject.transform.position = new Vector3(currentPiecePosition.x, currentPiecePosition.y, 0);

        // Es. Aquí se generan los bloques que conforman a la pieza como objetos "hijos" de esta.
        // En. Here the blocks that make up the piece are generated as "child" objects of it.
        foreach (Vector2Int offset in currentPieceData.blocks)
        {
            GameObject block = GameObject.CreatePrimitive(PrimitiveType.Cube);
            block.tag = "Block";
            block.transform.parent = currentPieceObject.transform;
            block.transform.localPosition = new Vector3(offset.x, offset.y, 0);
            block.GetComponent<Renderer>().material.color = currentPieceData.color;
        }
    }
    // Es. Aquí están las acciones a ejecutar cuando termina el juego.
    // En. Here are the actions to perform when the game ends.
    void GameOver()
    {
        isGameOver = true;
        gameOverPanel.SetActive(true);
        gameOverText.text = "Game Over!";
                
    }
    // Es. Este método se activa manualmente con el boton de reiniciar. Sirve para reiniciar la partida.
    // En. This method is activated manually with the restart button. It is used to restart the game.
    public void RestartGame()
    {
        InitializeGame();
    }

    // Es. Aquí están definidos los controles del juego.
    // En. Here the game controls are defined.
    void HandleInput()
    {
        if (Input.GetKeyDown(KeyCode.LeftArrow) || Input.GetKeyDown(KeyCode.A) )
        {
            MovePieceHorizontal(-1);
        }
        else if (Input.GetKeyDown(KeyCode.RightArrow) || Input.GetKeyDown(KeyCode.D) )
        {
            MovePieceHorizontal(1);
        }
        else if (Input.GetKeyDown(KeyCode.DownArrow) || Input.GetKeyDown(KeyCode.S) )
        {
            MovePieceDown();
        }
        else if (Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.Space) )
        {
            RotatePiece();
        }
    }

    // Es. Aquí se controla la caida de cada pieza.
    // En. Here the fall of each piece is controlled.
    void MovePieceDown()
    {
        currentPiecePosition.y--;
        if (IsPositionValid(currentPiecePosition, currentPieceData.blocks))
        {
            currentPieceObject.transform.position = new Vector3(currentPiecePosition.x, currentPiecePosition.y, 0);
        }
        else
        {
            currentPiecePosition.y++;
            AddToGrid(currentPieceObject.transform);
            CheckForLines();
            SpawnPiece();
        }
    }

    // Es. Aquí se controla el movimiento lateral de las piezas según lo controle el jugador.
    // En. Here the lateral movement of the pieces is controlled as controlled by the player.
    void MovePieceHorizontal(int direction)
    {
        Vector2Int newPosition = currentPiecePosition + new Vector2Int(direction, 0);
        if (IsPositionValid(newPosition, currentPieceData.blocks))
        {
            currentPiecePosition = newPosition;
            currentPieceObject.transform.position = new Vector3(currentPiecePosition.x, currentPiecePosition.y, 0);
        }
    }

    // Es. Aquí se controla la rotación de las piezas según lo controle el jugador.
    // En. Here the rotation of the pieces is controlled as controlled by the player.
    void RotatePiece()
    {
        
        Vector2Int[] newOffsets = new Vector2Int[currentPieceData.blocks.Length];
        for (int i = 0; i < currentPieceData.blocks.Length; i++)
        {
            Vector2Int original = currentPieceData.blocks[i];
            newOffsets[i] = new Vector2Int(original.y, -original.x);
        }

        
        if (IsPositionValid(currentPiecePosition, newOffsets))
        {
            
            currentPieceData.blocks = newOffsets;
            for (int i = 0; i < currentPieceObject.transform.childCount; i++)
            {
                Transform child = currentPieceObject.transform.GetChild(i);
                child.localPosition = new Vector3(newOffsets[i].x, newOffsets[i].y, 0);
            }
        }
    }

    // Es. Aquí se verifica si la posición la cada pieza es valida, si está dentro de los límites del área de juego y si está en una celda disponible.
    // En. Here we check whether the position of each piece is valid, whether it is within the limits of the playing area and whether it is in an available cell.
    bool IsPositionValid(Vector2Int pivot, Vector2Int[] offsets)
    {
        
        if (offsets == null || offsets.Length == 0)
        {
            return false;
        }

        foreach (Vector2Int offset in offsets)
        {
            Vector2Int gridPos = pivot + offset;

            
            if (gridPos.x < 0 || gridPos.x >= gridWidth || gridPos.y < 0 || gridPos.y >= gridHeight)
            {
                return false;
            }

            
            if (grid[gridPos.x, gridPos.y] != null)
            {
                return false;
            }
        }
        return true;
    }

    // Es. Aquí se verifica si los movimientos de las piezas son válidos.
    // En. This checks whether the pieces' moves are valid.
    bool IsValidMove(Vector2Int position)
    {
        if (position.x < 0 || position.x >= gridWidth || position.y < 0)
        {
            return false;
        }

        if (grid[position.x, position.y] != null)
        {
            return false;
        }

        return true;
    }

    // Es. Aquí se verifica que las piezas estén dentro del área de juego.
    // En. Here you check that the pieces are within the playing area.
    void AddToGrid(Transform piece)
    {
        foreach (Transform child in piece)
        {
            Vector2Int position = new Vector2Int(
                Mathf.RoundToInt(child.position.x),
                Mathf.RoundToInt(child.position.y)
            );

            
            if (position.x >= 0 && position.x < gridWidth && position.y >= 0 && position.y < gridHeight)
            {
                grid[position.x, position.y] = child;
            }
        }
    }

    // Es. Aquí se detectan las líneas completas desde ARRIBA hacia ABAJO. También se calcula el desplazamiento correcto hacia abajo para cada bloque después de que se completa la línea.
    // En. Here the complete lines are detected from TOP to DOWN. The correct downward offset for each block is also calculated after the line is completed.
    void CheckForLines()
    {
        List<int> linesToClear = new List<int>();

        
        for (int y = gridHeight - 1; y >= 0; y--)
        {
            if (IsLineComplete(y))
            {
                linesToClear.Add(y);
            }
        }

        if (linesToClear.Count > 0)
        {
            foreach (int y in linesToClear)
            {
                DeleteLine(y);
            }

            
            MoveAllLinesDown(linesToClear);
            score += 100 * linesToClear.Count;
            UpdateScore();
        }

    }

    // Es. Aquí se corrobora que existan líneas completas.
    // En. Here it is confirmed that complete lines exist.
    bool IsLineComplete(int y)
    {
        for (int x = 0; x < gridWidth; x++)
        {
            if (grid[x, y] == null)
            {
                return false;
            }
        }
        return true;
    }

    // Es. Aquí se borran las líneas que ya están completas.
    // En. Here the lines that are already complete are deleted.
    void DeleteLine(int y)
    {
        for (int x = 0; x < gridWidth; x++)
        {
            Destroy(grid[x, y].gameObject);
            grid[x, y] = null;
        }
    }

   // Es. Aquí se ejecuta el movimiento hacia abajo de las piezas un vez que ya se completó una línea en el juego.
   // En. Here the downward movement of the pieces is executed once a line has been completed in the game. 
    void MoveAllLinesDown(List<int> clearedLines)
    {
        
        Transform[,] newGrid = new Transform[gridWidth, gridHeight];

        
        for (int y = gridHeight - 1; y >= 0; y--)
        {
            for (int x = 0; x < gridWidth; x++)
            {
                Transform block = grid[x, y];
                if (block != null)
                {
                    
                    int linesBelow = 0;
                    foreach (int clearedY in clearedLines)
                    {
                        if (y > clearedY) linesBelow++;
                    }

                    
                    int newY = y - linesBelow;

                    
                    if (newY >= 0)
                    {
                        newGrid[x, newY] = block;
                        block.position = new Vector3(x, newY, 0);
                    }
                }
            }
        }

        
        grid = newGrid;
    }

    // Es. Aquí actualizamos el marcador.
    // En. Here we update the scoreboard.
    void UpdateScore()
    {
        scoreText.text = $"Score: {score}";
        
    }

    // Es. Aquí se activa manualmente la pausa.
    // En. Here you manually activate the pause.
    public void SimplePause() {
        Time.timeScale = 0.0f;
    }

    // Es. Aquí quitamos manualmente la pausa y continuamos con el juego.
    // En. Here we manually remove the pause and continue with the game.
    public void SimpleContinue() {
        Time.timeScale = 1.0f;
    }

    /* Es.
    Esperamos que te diviertas con este juego y que te sea de gran utilidad para tus proyectos.
    Recuerda que este código es de uso libre para tus proyectos tanto gratuitos como comerciales.
    Está prohibido vender este código tal como está ya sea de manera individual o como parte de un paquete o proyecto (en otras palabras, debes modificarlo).
    
    Apóyanos difundiendo este contenido con todas aquellas personas interesadas en el desarrollo de videojuegos.
    Visita nuestra tienda de assets de Unity en https://assetstore.unity.com/publishers/26383
    También visita nuestros juegos en la tienda de Steam https://store.steampowered.com/search/?developer=ACKOSMIC%20Games
    Todo está A MUY BUEN PRECIO (y nos ayudas a seguir con nuestro proyecto).
    "Con cariño "ACKOSMIC Games""
    */
    /* En.
    We hope you have fun with this game and that you find it very useful for your projects.
    Remember that this code is free to use for your projects both free and commercial.
    It is forbidden to sell this code as is either individually or as part of a package or project (in other words, you must modify it).

    Support us by sharing this content to all those interested in game development.
    Visit our Unity asset store at https://assetstore.unity.com/publishers/26383
    Also check out our games on the Steam store https://store.steampowered.com/search/?developer=ACKOSMIC%20Games
    Everything is AT A VERY GOOD PRICE (and you help us continue with our project).
    "With love "ACKOSMIC Games""
    */
}
