
// Snake.java
// Rui Afonso Pereira, 2015
// https://github.com/rpereira/lanterna-snake
// Modified by Andrew Davison, ad@coe.psu.ac.th, March 2023

/* Represents the snake in the game.

   The program consists of:
     * SnakeGame.java
     * Snake.java  (this file!)
     * SnakeDirection.java

   Uses Lanterna 3
   https://github.com/mabe02/lanterna
*/

import java.awt.*;
import java.util.*;

import com.googlecode.lanterna.*;
import com.googlecode.lanterna.screen.*;



public class Snake
{
  private final static int START_SIZE = 4;

  // Snake's representation in chars
  private final static String SNAKE_HEAD = "@";
  private final static String SNAKE_BODY = "O";

  private final int gameHeight, gameWidth;

  private static LinkedList<Point> body;
       // the head is in the last position
  private SnakeDirection dir;
  private boolean isAlive;



  public Snake(SnakeDirection d, int gw, int gh)
  {
    gameWidth  = gw;
    gameHeight = gh;

    // Set snake's body. Note that x = 0 represents the wall.
    body = new LinkedList<>();
    for( int i = 0; i < START_SIZE; i++)
      body.add(new Point(i+3, 15));

    dir = d;
    isAlive = true;
  }


  public boolean isAlive()
  {  return isAlive;  }


  public void kill()
  {  isAlive = false;  }



  public LinkedList<Point> getBody()
  {  return body;  }


  public boolean inBody(Point p)
  {  return body.contains(p);  }


  public Point getHead()
  // the head is the last point in the body list
  {  return body.getLast();  }


  public Point getTail()
  // the tail is the first point in the body list
  {  return body.getFirst();  }


  public void increaseSize()
  // occurs when the snake eats food
  {
    Point tail = getTail();
    body.addFirst(new Point(tail.x, tail.y));
  }



  public void move()
  /* The snake "moves" by deleting its first point (its tail) 
     and adding a new point to its front (a new head).
  */
  {
    Point head = getHead();
    body.removeFirst();   // Remove the tail point

    // Determine head's new position based on snake's dir
    switch (dir) {
      case UP:
        head = new Point(head.x, head.y-1);
        break;
	  
      case DOWN:
        head = new Point(head.x, head.y+1);
        break;
	  
      case LEFT:
        head = new Point(head.x-1, head.y);
        break;
	  
      case RIGHT:
        head = new Point(head.x+1, head.y);
        break;
	  
      default:
        break;
    } 

    // add new head to snake
    body.addLast(head);
  } /* move */



  public void draw(Screen scr)
  {
    Point head = getHead();
    for( Point p : body) {
      if (!p.equals(head))
        Utils.putString(scr, p.x, p.y, SNAKE_BODY, TextColor.ANSI.GREEN);
      else
        Utils.putString(scr, p.x, p.y, SNAKE_HEAD, TextColor.ANSI.GREEN);
    }
  } /* drawSnake */



  public void setDir(SnakeDirection d)
  /* Sets a new snake direction, preventing it from turning to its
     opposite direction, otherwise the snake will die.
  */
  {
    switch(d) {
      case UP:
        if (dir != SnakeDirection.DOWN)
          dir = SnakeDirection.UP;
        break;
    
      case DOWN:
        if (dir != SnakeDirection.UP)
          dir = SnakeDirection.DOWN;
        break;
    
      case LEFT:
        if (dir != SnakeDirection.RIGHT)
          dir = SnakeDirection.LEFT;
        break;
    
      case RIGHT:
        if (dir != SnakeDirection.LEFT)
          dir = SnakeDirection.RIGHT;
        break;
    
      default:
        throw new IllegalArgumentException("No such direction");
   } 
  } /* setDir */


  public SnakeDirection getDir()
  {  return dir;  }



  public boolean selfCollided()
  // has the snake's head collided with its body?
  {
    // head is at the end of the list
    Point head = getHead();
    return (body.subList(0, body.size()-1).contains(head));
  }


  public boolean ateFruit(LinkedList<Point> food)
  // is the snake's head at a food position?
  {
    Point head = getHead();
    for(Point f : food)
      if (head.equals(f)) {
        food.remove(f);
        return true;
      }
    return false;
  } /* ateFruit */



  public boolean hitBomb(LinkedList<Point> bombs)
  // is the snake's head at a bomb's position? 
  {
    Point head = getHead();
    for( Point b : bombs)
      if (head.equals(b)) {
        bombs.remove(b);
        return true;
      }
    return false;
  } /* hitBomb */


}  // end of Snake class
