
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

record ShipData (int size, String name){}

public class Ship {
	static List<List<ShipData>> semeterList =
	Arrays.asList(
		Arrays.asList(
			new ShipData(2, "PRG 1"),
			new ShipData(2, "GDI"),
			new ShipData(2, "MAT 1"),
			new ShipData(2, "THI"),
			new ShipData(4, "STP"),
			new ShipData(6, "ENG")
		),
		Arrays.asList(
			new ShipData(2, "PRG 2"),
			new ShipData(2, "DBS 1"),
			new ShipData(2, "MAT 2"),
			new ShipData(2, "STA"),
			new ShipData(2, "AUD")
		),
		Arrays.asList(
			new ShipData(2, "PRG 3"),
			new ShipData(2, "DBS 2"),
			new ShipData(2, "MAT 3"),
			new ShipData(2, "BSN 1"),
			new ShipData(4, "PRP"),
			new ShipData(6, "BWL")
		),
		Arrays.asList(
			new ShipData(2, "WEB"),
			new ShipData(2, "SE 1"),
			new ShipData(2, "CG 1"),
			new ShipData(2, "BSN 2"),
			new ShipData(4, "SEM"),
			new ShipData(6, "E BWL")
		),
		Arrays.asList(
			new ShipData(2, "WPF 1"),
			new ShipData(2, "SE 2"),
			new ShipData(2, "CG 2"),
			new ShipData(2, "PXP 1"),
			new ShipData(6, "EF 1")
		),
		Arrays.asList(
			new ShipData(2, "WPF 2"),
			new ShipData(1, "PXP 2"),
			new ShipData(8, "BAA")
		)
	);

	private int size;
	private boolean horizontal;
	private Point position;
	private String name;
	private int hitsOnMe;
	private boolean sunk;

	/**
	 * initialises a Ship with a given size and name
	 * @param size the size of the ship
	 * @param name the name of the ship
	 * @author Peer Ole Wachtel
	 */
	public Ship (int size, String name) {
		this.size = size;
		this.name = name;
		this.horizontal = false; //true = von Punkt aus nach rechts; false = von Punkt aus nach unten
		this.position = null;
		this.hitsOnMe = 0;
		this.sunk = false;
	}
	
	/**
	 * resets the position of this ship
	 * @author Luca Conte
	 */
	public void resetPosition() {
		this.position = null;
	}

	/**
	 * sets the position of this ship, provided it is valid
	 * @param pos the position to move the ship to
	 * @param horizontal whether the ship is horizontal or not
	 * @param shipsList the list of other ships on this board. It will be checked whether the ship is touching any of them
	 * @param boardSize the size of the board the ship is to be placed on
	 * @return true if the position was set successfully. false if the ship is out of the bounds of the board or touches a different ship
	 * @author Luca Conte
	 */
	public boolean setPosition(Point pos, boolean horizontal, List<Ship> shipsList, int boardSize) {
		if (!this.checkValidPlacement(pos, horizontal, shipsList, boardSize)) return false;

	    // kein ueberlappen also setze das Schiff
	    this.position = pos;
	    this.horizontal = horizontal;
	    return true;
	}

	/**
	 * checks whether a position is valid for ship placement
	 * @param pos the position to check the ship placement at
	 * @param horizontal whether the ship is to be placed horizontally or not
	 * @param shipsList the list of other ships on this board. It will be checked whether the ship is touching any of them
	 * @param boardSize the size of the board the ship is to be placed on
	 * @return true if the position is valid. false if the ship is out of the bounds of the board or touches a different ship
	 * @author Florian Hantzschel, Peer Ole Wachtel, Luca Conte
	 */
	public boolean checkValidPlacement(Point pos, boolean horizontal, List<Ship> shipsList, int boardSize) {
		// ueberpruefe boundaries
	    if (pos.getX() < 0 || pos.getY() < 0 || pos.getX() >= boardSize || pos.getY() >= boardSize) {
	        return false;
	    }

	    // bestimme die Endposition anhand der Ausrichtung
	    int endX = pos.getX();
	    int endY = pos.getY();

	    if (horizontal) { // rechts links
	        endX = pos.getX() + this.size - 1;
	        if (endX >= boardSize) {
	            return false;
	        }
	    } else { // oben unten
	        endY = pos.getY() + this.size - 1;
	        if (endY >= boardSize) {
	            return false;
	        }
	    }

	    // Liste an Punkten die das Schiff einnehmen wuerde
	    List<Point> shipPoints = this.getVirtualOccupiedPoints(pos, horizontal);

	    // ueberlappen mit anderen Schiffen pruefen
	    for (Ship otherShip : shipsList) {
	        // eigenes Schiff ueberspringen
	        if (otherShip == this) {
	            continue;
	        }
	        // ueberspringe falls noch nicht gesetzt
	        if (otherShip.position == null) {
	            continue;
	        }
	        // Punkte die das andere Schiff besetzt
	        List<Point> otherShipPoints = otherShip.getOccupiedPoints();
	        // ueberlappen checken
	        for (Point p : shipPoints) {
	        	for (Point otherPoint : otherShipPoints) {
	        		if (otherPoint.neighbours(p)) return false;
	        	}
	        }
	    }
	    return true;
	}

	/**
	 * Returns the Points on the ship if it were to be placed at position `pos` in orientation defined by `horizontal`
	 * @param pos the position where the ship should be placed
	 * @param horizontal whether the ship should be placed horizontally
	 * @return a list of points the ship would occupy, were it placed at position `pos` in orientation `horizontal`
	 * @author Florian Hantzschel, Luca Conte
	 */
	public List<Point> getVirtualOccupiedPoints(Point pos, boolean horizontal) {
		List<Point> points = new ArrayList<>();
	    if (pos == null) {
	        return points;
	    }
	    for (int i = 0; i < this.size; i++) {
	        int x = horizontal ? pos.getX() + i : pos.getX();
	        int y = horizontal ? pos.getY() : pos.getY() + i;
	        points.add(new Point(x, y));
	    }
	    return points;
	}

	/**
	 * Returns the Points the ship occupies
	 * @return a list of points the ship occupies
	 * @author Florian Hantzschel, Luca Conte
	 */
	public List<Point> getOccupiedPoints() {
	    return this.getVirtualOccupiedPoints(this.position, this.horizontal);
	}

	/**
	 * returns the position of this ship
	 * @return the position of this ship
	 * @author Peer Ole Wachte
	 */
	public Point getPosition() {
		return position;
	}

	/**
	 * checks whether the ship occupies a certain point
	 * @param pos the point to be checkd
	 * @return whether the point provided is one of the points occupied by the ship
	 * @author Peer Ole Wachtel, Lucas Bronson
	 */
	public boolean isShipOnPos(Point pos){
		if(this.position == null){
			return false;
		}
		if ((this.horizontal && pos.getY() == this.position.getY() && pos.getX() >= this.position.getX() && pos.getX() < this.position.getX() + size) ||
			(!(this.horizontal) && pos.getX() == this.position.getX() && pos.getY() >= this.position.getY() && pos.getY() < this.position.getY() + size)) {
			return true;
		}
		return false;
	}

	/**
	 * "shoots" this ship.
	 * @return a hit response, depending on whether the ship was hit or not. If the amount of times
	 * the ship was hit is greater or equal to the size of the ship, the ship is considered sunk.
	 * @param pos the point where the ship is shot
	 * @author Peer Ole Wachtel
	 */
	public HitResponseType shootOnShip(Point pos) {
		if (this.isShipOnPos(pos)) {
			hitsOnMe++;
			if (hitsOnMe >= size) {
				this.sunk = true;
				return HitResponseType.SUNK;
			}  else {
				return HitResponseType.HIT;
			}
		} else {
			return HitResponseType.MISS;
		}
	}

	/**
	 * returns whether the ship has been sunk or not
	 * @return whether the ship has been sunk or not
	 * @author Peer Ole Wachtel
	 */
	public boolean isSunk() {
		return sunk;
	}

	/**
	 * sets the orientation of the ship
	 * @param horizontal whether the ship is to be placed in a horizontal orientation
	 * @author Lucas Bronson
	 */
	public void setHorizontal(boolean horizontal) {
		this.horizontal = horizontal;
	}

	/**
	 * returns the size of the ship
	 * @return the size of the ship
	 * @author Lucas Bronson
	 */
	public int getSize() {
		return size;
	}

	/**
	 * returns the name of the ship
	 * @return the name of the ship
	 * @author Lucas Bronson
	 */
	public String getName() {
		return name;
	}

	/**
	 * returns whether the ship has been placed or not
	 * @return whether the ship has been placed or not
	 * @author Lucas Bronson
	 */
	public boolean isPlaced(){
		return this.position != null;
	}
}
