/**
 * Diese Klasse ist das Board von eimem spieler und enthält alle logischen operationen.
 * Sprich ist das Backend Board.
 *
 * @author Peer Ole Wachtel
 */

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

public class Board {

	/**
	 * Alle bisher empfangenen HitResponsen
	 */
	private List<HitResponse> hits;

	/**
	 * Alle Schiffe des Semesters
	 */
	private List<Ship> ships;

	/**
	 * Die größe des Spielfeldes
	 */
	private final int size;


	/**
	 * Erstellt ein neues Board.
	 * setzt die übergebene Spielfeldgröße.
	 * Erstellt die Liste aller Schiffe des Semesters
	 * @param size Die größe des Spielfeldes
	 * @author Peer Ole Wachtel
	 */
	public Board(int size) {
		this.size = size;
		this.ships = new ArrayList<>();
		this.hits = new ArrayList<>();
		this.createShip(size - 13);
	}

	/**
	 * Nimmt einen punkt entgegen und Gibt einen Hitrespons zurück.
	 * @param point auf den geschossen wurde
	 * @return
	 * @author Peer Ole Wachtel
	 */
	public synchronized HitResponse hit (Point point){
		HitResponse response = new HitResponse(HitResponseType.MISS,point);
		for (int i = 0; i < this.ships.size(); i++) {
			HitResponseType type = this.ships.get(i).shootOnShip(point);
			if ( type == HitResponseType.SUNK) {
				for (int ii = 0; ii < this.ships.size(); ii++) {
					if (!this.ships.get(ii).isSunk()) {
						response.setType(type);
						this.addHits(response);
						return response;
					}
				}
				response.setType(HitResponseType.VICTORY);
				this.addHits(response);
				return response;
			} else if (type == HitResponseType.HIT) {
				response.setType(type);
				this.addHits(response);
				return response;
			}
		}

		this.addHits(response);
		return response;
	}

	/**
	 * finds adjacened hit responses and sets their type to SUNK if they are currently HIT
	 * this makes it so that all the points of the ship are marked as SUNK, not just the final hit
	 * @param p the Point from which to propate the SUNK type
	 * @author Luca Conte
	 */
	private void propagateSunk(Point p) {
		HitResponse hit = this.getHitResponseOnPoint(p);

		if (hit == null || hit.getType() != HitResponseType.HIT) return;

		hit.setType(HitResponseType.SUNK);

		propagateSunk(new Point(p.getX() + 1, p.getY()));
		propagateSunk(new Point(p.getX() - 1, p.getY()));
		propagateSunk(new Point(p.getX(), p.getY() + 1));
		propagateSunk(new Point(p.getX(), p.getY() - 1));
	}

	/**
	 * creates all the ships on a board given a certain semester
	 * @param semester the semester to be played in
	 * @author Peer Ole Wachtel
	 */
	private void createShip(int semester){
		List<ShipData> shipData = Ship.semeterList.get(semester -1);
		for (int i = 0; i < shipData.size(); i++) {
			this.ships.add(new Ship(shipData.get(i).size(), shipData.get(i).name()));
		}
	}

	/**
	 * @return a list of all the Ships on the board
	 */
	public List<Ship> getShips() {
		return ships;
	}

	/**
	 * adds a HitResponse to the list of Hits on the board
	 * If a hit response already exists on the same position, the hit response will not be added
	 * If the hit response is of type `HitResponseType.SUNK` it will propagate this hit response type
	 * to all adjacened hit responses with type HIT using `propagateSunk`.
	 * @param hitResponse the HitResponse to be added
	 * @return true when the hit response was added, otherwise false
	 *
	 */
	public synchronized boolean addHits(HitResponse hitResponse) {
		if (this.getHitResponseOnPoint(hitResponse.getPoint()) == null){
			this.hits.add(hitResponse);

			//Propagate sunk for display purposes
			if (hitResponse.getType() == HitResponseType.SUNK) {
				hitResponse.setType(HitResponseType.HIT);
				propagateSunk(hitResponse.getPoint());
			}

			return true;
		}
		return false;
	}

	/**
	 * @param point the position to get the hit response from
	 * @return the hit response at the position `point`
	 * @author Peer Ole Wachtel
	 */
	public synchronized HitResponse getHitResponseOnPoint(Point point) {
		for (int i = 0; i < this.hits.size(); i++){
			if (this.hits.get(i).getPoint().equals(point)){
				return this.hits.get(i);
			}
		}
		return null;
	}

	/**
	 * @return the size of the board
	 */
	public int getSize() {
        return this.size;
    }

}

