import java.awt.*;
import java.util.LinkedList;
import java.util.Random;
import javax.swing.*;

/**
 * Clase que representa un juego de la serpiente en ASCII-Art utilizando Java Swing.
 */
public class SnakeAsciiArt extends JPanel {
    // Tamaño del área de juego en caracteres
    private static final int WIDTH = 40;
    private static final int HEIGHT = 20;
    
    // Caracteres utilizados en la representación del juego
    private static final char APPLE_CHAR = '@';
    private static final char EMPTY_CHAR = ' ';
    
    private LinkedList<Segment> snake; // Lista de segmentos que representan el cuerpo de la serpiente
    private Point apple; // Posición de la manzana
    private char[][] grid; // Matriz de caracteres que representa el área de juego
    private Timer timer; // Temporizador para actualizar el juego periódicamente
    private String direction; // Dirección actual de la serpiente
    private boolean gameOver; // Estado del juego
    
    /**
     * Clase que representa un segmento de la serpiente.
     */
    private static class Segment {
        Point position;
        String direction;
        
        Segment(Point position, String direction) {
            this.position = position;
            this.direction = direction;
        }
    }
    
    /**
     * Constructor que inicializa el juego.
     */
    public SnakeAsciiArt() {
        snake = new LinkedList<>();
        snake.add(new Segment(new Point(WIDTH / 2, HEIGHT / 2), "RIGHT")); // Posiciona la serpiente en el centro
        direction = "RIGHT"; // Dirección inicial
        gameOver = false; // El juego comienza en estado activo
        spawnApple(); // Genera la primera manzana
        grid = new char[HEIGHT][WIDTH]; // Inicializa la matriz de juego
        
        // Configura el temporizador para actualizar el juego cada 200ms
        timer = new Timer(200, e -> updateGame());
        timer.start();
    }
    
    /**
     * Genera una nueva manzana en una posición aleatoria que no coincida con la serpiente.
     */
    private void spawnApple() {
        Random rand = new Random();
        int x, y;
        do {
            x = rand.nextInt(WIDTH);
            y = rand.nextInt(HEIGHT);
        } while (isSnakeBody(x, y)); // Asegura que la manzana no aparezca dentro de la serpiente
        apple = new Point(x, y);
    }
    
    /**
     * Verifica si una coordenada pertenece al cuerpo de la serpiente.
     */
    private boolean isSnakeBody(int x, int y) {
        for (Segment s : snake) {
            if (s.position.x == x && s.position.y == y) return true;
        }
        return false;
    }
    
    /**
     * Actualiza el estado del juego en cada ciclo del temporizador.
     */
    private void updateGame() {
        if (gameOver) return; // No actualizar si el juego ha terminado
        
        Segment head = snake.getFirst(); // Obtiene la cabeza de la serpiente
        Point newHeadPosition = new Point(head.position);
        
        // Movimiento automático hacia la manzana
        if (apple.x > head.position.x) {
            newHeadPosition.x++;
            direction = "RIGHT";
        } else if (apple.x < head.position.x) {
            newHeadPosition.x--;
            direction = "LEFT";
        } else if (apple.y > head.position.y) {
            newHeadPosition.y++;
            direction = "DOWN";
        } else if (apple.y < head.position.y) {
            newHeadPosition.y--;
            direction = "UP";
        }
        
        // Verifica colisiones con los bordes o con el cuerpo de la serpiente
        if (newHeadPosition.x < 0 || newHeadPosition.x >= WIDTH || newHeadPosition.y < 0 || newHeadPosition.y >= HEIGHT || isSnakeBody(newHeadPosition.x, newHeadPosition.y)) {
            System.out.println("Game Over: La serpiente salió del área de juego o se chocó a sí misma.");
            gameOver = true; // Cambia el estado del juego a terminado
            timer.stop(); // Detiene el juego al colisionar
            repaint(); // Redibuja el juego para mostrar el mensaje de "GAME OVER"
            return;
        }
        
        // Verifica si la serpiente ha comido la manzana
        if (newHeadPosition.equals(apple)) {
            snake.addFirst(new Segment(newHeadPosition, direction)); // La serpiente crece
            spawnApple(); // Se genera una nueva manzana
        } else {
            snake.addFirst(new Segment(newHeadPosition, direction));
            snake.removeLast(); // Elimina la última parte de la serpiente para simular el movimiento
        }
        repaint(); // Redibuja el juego
    }
    
    /**
     * Llena la matriz de juego con los elementos actuales (serpiente y manzana).
     */
    private void renderGrid() {
        for (int y = 0; y < HEIGHT; y++) {
            for (int x = 0; x < WIDTH; x++) {
                grid[y][x] = EMPTY_CHAR;
            }
        }
        for (Segment s : snake) {
            char segmentChar;
            switch (s.direction) {
                case "UP":
                    segmentChar = '|';
                    break;
                case "DOWN":
                    segmentChar = '|';
                    break;
                case "LEFT":
                    segmentChar = '-';
                    break;
                case "RIGHT":
                    segmentChar = '-';
                    break;
                default:
                    segmentChar = '=';
            }
            grid[s.position.y][s.position.x] = segmentChar;
        }
        Segment head = snake.getFirst();
        switch (head.direction) {
            case "UP":
                grid[head.position.y][head.position.x] = '^';
                break;
            case "DOWN":
                grid[head.position.y][head.position.x] = 'v';
                break;
            case "LEFT":
                grid[head.position.y][head.position.x] = '<';
                break;
            case "RIGHT":
                grid[head.position.y][head.position.x] = '>';
                break;
        }
        grid[apple.y][apple.x] = APPLE_CHAR;
    }
    
    /**
     * Dibuja el contenido de la matriz en el panel.
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.setFont(new Font("Monospaced", Font.PLAIN, 14));
        
        if (gameOver) {
            // Dibujar el mensaje de "GAME OVER" estilizado en ASCII
            String[] gameOverMessage = {
                " _ ____  _____      _    ____    _    ____   ___  _ ",
                "(_) ___|| ____|    / \\  / ___|  / \\  | __ ) / _ \\| |",
                "| \\___ \\|  _|     / _ \\| |     / _ \\ |  _ \\| | | | |",
                "| |___) | |___   / ___ \\ |___ / ___ \\| |_) | |_| |_|",
                "|_|____/|_____| /_/   \\_\\____/_/   \\_\\____/ \\___/(_)"
            };
            
            int messageX = (getWidth() - g.getFontMetrics().stringWidth(gameOverMessage[0])) / 2;
            int messageY = getHeight() / 2 - (gameOverMessage.length * 15) / 2;
            
            for (int i = 0; i < gameOverMessage.length; i++) {
                g.drawString(gameOverMessage[i], messageX, messageY + (i * 15));
            }
        } else {
            renderGrid();
            
            // Dibujar el título "SNAKE" estilizado en ASCII
            String[] title = {
                " ____  _____ ____  ____ ___ _____ _   _ _____ _____ ",
                "/ ___|| ____|  _ \\|  _ \\_ _| ____| \\ | |_   _| ____|",
                "\\___ \\|  _| | |_) | |_) | ||  _| |  \\| | | | |  _|  ",
                " ___) | |___|  _ <|  __/| || |___| |\\  | | | | |___ ",
                "|____/|_____|_| \\_\\_|  |___|_____|_| \\_| |_| |_____|"
            };
            
            int titleX = (getWidth() - g.getFontMetrics().stringWidth(title[0])) / 2;
            int titleY = 30; // Ajusta la posición vertical según sea necesario
            
            for (int i = 0; i < title.length; i++) {
                g.drawString(title[i], titleX, titleY + (i * 15));
            }
            
            // Dibujar el contorno del área de juego
            int gridX = 10;
            int gridY = (titleY + title.length * 15) + 10;
            int cellSize = 15;
            
            g.drawRect(gridX - 1, gridY - 1, WIDTH * cellSize + 1, HEIGHT * cellSize + 1);
            
            for (int y = 0; y < HEIGHT; y++) {
                StringBuilder line = new StringBuilder();
                for (int x = 0; x < WIDTH; x++) {
                    line.append(grid[y][x]);
                }
                g.drawString(line.toString(), gridX, gridY + (y * cellSize));
            }
        }
    }
    
    /**
     * Método principal que inicializa la ventana y ejecuta el juego.
     */
    public static void main(String[] args) {
        JFrame frame = new JFrame("ASCII Snake Animation");
        SnakeAsciiArt panel = new SnakeAsciiArt();
        frame.add(panel);
        frame.setSize(620, 450);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}