package hw3;

import api.BodySegment;
import java.util.ArrayList;
import api.Cell;
import api.Direction;
import api.Exit;
import api.ScoreUpdateListener;
import api.ShowDialogListener;
import api.Wall;

/**
 * Class that models a game.
 */
public class LizardGame {
	private int width;
	private int height;
	private Cell[][] grid;
	private ArrayList<Lizard> lizards;
	private ShowDialogListener dialogListener;
	private ScoreUpdateListener scoreListener;

	/**
	 * Constructs a new LizardGame object with given grid dimensions.
	 * 
	 * @param width  number of columns
	 * @param height number of rows
	 */
	public LizardGame(int width, int height) {
		this.width = width;
		this.height = height;
		grid = new Cell[width][height];
		for (int col = 0; col < width; col++) {
			for (int row = 0; row < height; row++) {
				grid[col][row] = new Cell(col, row);
			}
		}
		lizards = new ArrayList<>();
	}

	/**
	 * Get the grid's width.
	 * 
	 * @return width of the grid
	 */
	public int getWidth() {
		return width;
	}

	/**
	 * Get the grid's height.
	 * 
	 * @return height of the grid
	 */
	public int getHeight() {
		return height;
	}

	/**
	 * Adds a wall to the grid.
	 * <p>
	 * Specifically, this method calls placeWall on the Cell object associated with
	 * the wall (see the Wall class for how to get the cell associated with the
	 * wall). This class assumes a cell has already been set on the wall before
	 * being called.
	 * 
	 * @param wall to add
	 */
	public void addWall(Wall wall) {
		Cell cell = wall.getCell();
		cell.placeWall(wall);
	}

	/**
	 * Adds an exit to the grid.
	 * <p>
	 * Specifically, this method calls placeExit on the Cell object associated with
	 * the exit (see the Exit class for how to get the cell associated with the
	 * exit). This class assumes a cell has already been set on the exit before
	 * being called.
	 * 
	 * @param exit to add
	 */
	public void addExit(Exit exit) {
		exit.getCell().placeExit(exit);
	}

	/**
	 * Gets a list of all lizards on the grid. Does not include lizards that have
	 * exited.
	 * 
	 * @return lizards list of lizards
	 */
	public ArrayList<Lizard> getLizards() {
		// TODO: method stub
		return lizards;
	}

	/**
	 * Adds the given lizard to the grid.
	 * <p>
	 * The scoreListener to should be updated with the number of lizards.
	 * 
	 * @param lizard to add
	 */
	public void addLizard(Lizard lizard) {
		lizards.add(lizard);
		if (scoreListener != null) {
			scoreListener.updateScore(lizards.size());
		}
	}

	/**
	 * Removes the given lizard from the grid. Be aware that each cell object knows
	 * about a lizard that is placed on top of it. It is expected that this method
	 * updates all cells that the lizard used to be on, so that they now have no
	 * lizard placed on them.
	 * <p>
	 * The scoreListener to should be updated with the number of lizards using
	 * updateScore().
	 * 
	 * @param lizard to remove
	 */
	public void removeLizard(Lizard lizard) {
		lizards.remove(lizard);
		for (BodySegment segment : lizard.getSegments()) {
			segment.getCell().removeLizard();
		}
		if (scoreListener != null) {
			scoreListener.updateScore(lizards.size());
		}
	}

	/**
	 * Gets the cell for the given column and row.
	 * <p>
	 * If the column or row are outside of the boundaries of the grid the method
	 * returns null.
	 * 
	 * @param col column of the cell
	 * @param row of the cell
	 * @return the cell or null
	 */
	public Cell getCell(int col, int row) {
		if (col < 0 || col >= width || row < 0 || row >= height) {
			return null;
		}
		return grid[col][row];
	}

	/**
	 * Gets the cell that is adjacent to (one over from) the given column and row,
	 * when moving in the given direction. For example (1, 4, UP) returns the cell
	 * at (1, 3).
	 * <p>
	 * If the adjacent cell is outside of the boundaries of the grid, the method
	 * returns null.
	 * 
	 * @param col the given column
	 * @param row the given row
	 * @param dir the direction from the given column and row to the adjacent cell
	 * @return the adjacent cell or null
	 */
	public Cell getAdjacentCell(int col, int row, Direction dir) {
		switch (dir) {
			case UP:
				return getCell(col, row - 1);
			case DOWN:
				return getCell(col, row + 1);
			case LEFT:
				return getCell(col - 1, row);
			case RIGHT:
				return getCell(col + 1, row);
			default:
				return null;
		}
	}

	/**
	 * Resets the grid. After calling this method the game should have a grid of
	 * size width x height containing all empty cells. Empty means cells with no
	 * walls, exits, etc.
	 * <p>
	 * All lizards should also be removed from the grid.
	 * 
	 * @param width  number of columns of the resized grid
	 * @param height number of rows of the resized grid
	 */
	public void resetGrid(int width, int height) {
		this.width = width;
		this.height = height;
		grid = new Cell[width][height];
		for (int col = 0; col < width; col++) {
			for (int row = 0; row < height; row++) {
				grid[col][row] = new Cell(col, row);
			}
		}
		lizards.clear();
	}

	/**
	 * Returns true if a given cell location (col, row) is available for a lizard to
	 * move into. Specifically the cell cannot contain a wall or a lizard. Any other
	 * type of cell, including an exit is available.
	 * 
	 * @param row of the cell being tested
	 * @param col of the cell being tested
	 * @return true if the cell is available, false otherwise
	 */
	public boolean isAvailable(int col, int row) {
		Cell cell = getCell(col, row);
		return cell != null && cell.getWall() == null && cell.getLizard() == null;
	}

	/**
	 * Move the lizard specified by its body segment at the given position (col,
	 * row) one cell in the given direction. The entire body of the lizard must move
	 * in a snake like fashion, in other words, each body segment pushes and pulls
	 * the segments it is connected to forward or backward in the path of the
	 * lizard's body. The given direction may result in the lizard moving its body
	 * either forward or backward by one cell.
	 * <p>
	 * The segments of a lizard's body are linked together and movement must always
	 * be "in-line" with the body. It is allowed to implement movement by either
	 * shifting every body segment one cell over or by creating a new head or tail
	 * segment and removing an existing head or tail segment to achieve the same
	 * effect of movement in the forward or backward direction.
	 * <p>
	 * If any segment of the lizard moves over an exit cell, the lizard should be
	 * removed from the grid.
	 * <p>
	 * If there are no lizards left on the grid the player has won the puzzle the
	 * the dialog listener should be used to display (see showDialog) the message
	 * "You win!".
	 * <p>
	 * It is possible that the given direction is not in-line with the body of the
	 * lizard (as described above), in that case this method should do nothing.
	 * <p>
	 * It is possible that the given column and row are outside the bounds of the
	 * grid, in that case this method should do nothing.
	 * <p>
	 * It is possible that there is no lizard at the given column and row, in that
	 * case this method should do nothing.
	 * <p>
	 * It is possible that the lizard is blocked and cannot move in the requested
	 * direction, in that case this method should do nothing.
	 * <p>
	 * <b>Developer's note: You may have noticed that there are a lot of details
	 * that need to be considered when implement this method method. It is highly
	 * recommend to explore how you can use the public API methods of this class,
	 * Grid and Lizard (hint: there are many helpful methods in those classes that
	 * will simplify your logic here) and also create your own private helper
	 * methods. Break the problem into smaller parts are work on each part
	 * individually.</b>
	 * 
	 * @param col the given column of a selected segment
	 * @param row the given row of a selected segment
	 * @param dir the given direction to move the selected segment
	 */
	public void move(int col, int row, Direction dir) {
		Cell cell = getCell(col, row);
		if (cell == null || cell.getLizard() == null) {
			return; // No lizard at the given position
		}

		Lizard lizard = cell.getLizard();
		Direction headDirection = lizard.getHeadDirection();
		Direction tailDirection = lizard.getTailDirection();
		boolean isForwardMovement = (dir == headDirection);
		boolean isBackwardMovement = (dir == getOppositeDirection(tailDirection));

		if (!isForwardMovement && !isBackwardMovement) {
			return; // Movement direction is not in line with the body
		}

		// Determine the segment to move (head or tail)
		BodySegment segmentToMove = isForwardMovement ? lizard.getHeadSegment() : lizard.getTailSegment();
		Cell nextCell = getAdjacentCell(segmentToMove.getCell().getCol(), segmentToMove.getCell().getRow(), dir);

		if (nextCell != null && isAvailable(nextCell.getCol(), nextCell.getRow())) {
			// Move the lizard to the next cell
			moveLizardSegment(lizard, segmentToMove, nextCell, dir);

			// Check if any segment of the lizard moves over an exit cell
			boolean reachedExit = false;
			for (BodySegment segment : lizard.getSegments()) {
				if (segment.getCell().getExit() != null) {
					reachedExit = true;
					break;
				}
			}

			if (reachedExit) {
				// Remove the lizard from the grid if it reaches an exit
				removeLizard(lizard);
			}

			// Check if all lizards have exited to trigger the win condition
			if (lizards.isEmpty() && dialogListener != null) {
				dialogListener.showDialog("You win!");
			}
		}
	}

	// Helper method to move a specific segment of the lizard
	private void moveLizardSegment(Lizard lizard, BodySegment segmentToMove, Cell nextCell, Direction dir) {
		// Remove the segment from its current cell
		segmentToMove.getCell().removeLizard();

		// Update the segment's position
		segmentToMove.setCell(nextCell);
		nextCell.placeLizard(lizard);

		// Update the positions of the remaining segments
		BodySegment prevSegment = segmentToMove;
		Direction prevDirection = dir;
		for (BodySegment segment : lizard.getSegments()) {
			if (segment != segmentToMove) {
				Cell prevCell = prevSegment.getCell();
				Cell newCell = getAdjacentCell(prevCell.getCol(), prevCell.getRow(), getOppositeDirection(prevDirection));
				if (newCell != null && isAvailable(newCell.getCol(), newCell.getRow())) {
					segment.getCell().removeLizard();
					segment.setCell(newCell);
					newCell.placeLizard(lizard);
				} else {
					break; // Stop moving segments if there's an obstacle
				}
				prevSegment = segment;
				prevDirection = getOppositeDirection(prevDirection);
			}
		}
	}
	

	// Helper method to get the opposite direction
	private Direction getOppositeDirection(Direction dir) {
		switch (dir) {
			case UP:
				return Direction.DOWN;
			case DOWN:
				return Direction.UP;
			case LEFT:
				return Direction.RIGHT;
			case RIGHT:
				return Direction.LEFT;
			default:
				return null;
		}
	}

	/**
	 * Sets callback listeners for game events.
	 * 
	 * @param dialogListener listener for creating a user dialog
	 * @param scoreListener  listener for updating the player's score
	 */
	public void setListeners(ShowDialogListener dialogListener, ScoreUpdateListener scoreListener) {
		this.dialogListener = dialogListener;
		this.scoreListener = scoreListener;
	}

	/**
	 * Load the game from the given file path
	 * 
	 * @param filePath location of file to load
	 */
	public void load(String filePath) {
		GameFileUtil.load(filePath, this);
	}

	@Override
	public String toString() {
		String str = "---------- GRID ----------\n";
		str += "Dimensions:\n";
		str += getWidth() + " " + getHeight() + "\n";
		str += "Layout:\n";
		for (int y = 0; y < getHeight(); y++) {
			if (y > 0) {
				str += "\n";
			}
			for (int x = 0; x < getWidth(); x++) {
				str += getCell(x, y);
			}
		}
		str += "\nLizards:\n";
		for (Lizard l : getLizards()) {
			str += l;
		}
		str += "\n--------------------------\n";
		return str;
	}
}


please fix these issues:
PROBLEM: 
23. LizardGame move head in a forward direction.
Message: 23. LizardGame move head in a forward direction. liz1:
 expected:< (5,2,Ground,Lizard)> but was:< (4,2,Ground,Lizard)>

PROBLEM: 
25. LizardGame move tail in a backward direction.
Message: 25. LizardGame move tail in a backward direction. liz1:
 expected:< (4,2,Ground,Lizard)> but was:< (5,2,Ground,Lizard)>

PROBLEM: 
27. LizardGame move head backward.
Message: 27. LizardGame move head backward. liz1:  expected:<
(4,2,Ground,Lizard)> but was:< (5,2,Ground,Lizard)>

PROBLEM: 
29. LizardGame move tail forward.
Message: 29. LizardGame move tail forward. liz1:  expected:<
(5,2,Ground,Lizard)> but was:< (4,2,Ground,Lizard)>

PROBLEM: 
31. LizardGame move inner segment forward.
Message: 31. LizardGame move inner segment forward. liz1:  expected:<
(5,2,Ground,Lizard)> but was:< (4,2,Ground,Lizard)>

PROBLEM: 
33. LizardGame move inner segment backward.
Message: 33. LizardGame move inner segment backward. liz1: 
expected:< (4,2,Ground,Lizard)> but was:< (5,2,Ground,Lizard)>

PROBLEM: 
38. LizardGame move all lizards to exit.
Message: 38. LizardGame move all lizards to exit. Number of
lizards:  expected:<0> but was:<1>

thank you!