////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////

package pp.battleship.model.dto;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonParseException;
import pp.battleship.message.server.GameDetails;
import pp.battleship.model.Battleship;
import pp.battleship.model.ShipMap;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.List;

/**
 * A class representing data transfer objects of ship maps for JSON serialization and deserialization.
 */
public class ShipMapDTO {
    // Logger instance for logging messages
    static final Logger LOGGER = System.getLogger(ShipMapDTO.class.getName());

    // Width of the ship map
    private int width;

    // Height of the ship map
    private int height;

    // List of ships in the map
    private List<BattleshipDTO> ships;

    /**
     * Constructs a ShipMapDTO object from a ShipMap object.
     *
     * @param map the ShipMap object to be converted
     */
    public ShipMapDTO(ShipMap map) {
        this.width = map.getWidth();
        this.height = map.getHeight();
        this.ships = map.getShips().map(BattleshipDTO::new).toList();
    }

    /**
     * Checks if the current ship map fits the game details provided.
     *
     * @param details the game details to be matched against
     * @return true if the ship map fits the game details, false otherwise
     */
    public boolean fits(GameDetails details) {
        if (width != details.getWidth() || height != details.getHeight())
            return false;
        int numShips = details.getShipNums().values().stream().mapToInt(Integer::intValue).sum();
        if (numShips != ships.size())
            return false;
        for (var e : details.getShipNums().entrySet()) {
            final int shipLength = e.getKey();
            final int requiredNum = e.getValue();
            final int actualNum = (int) ships.stream()
                                             .filter(s -> s.getLength() == shipLength)
                                             .count();
            if (requiredNum != actualNum)
                return false;
        }
        return true;
    }

    /**
     * Returns the ships stored in this DTO.
     *
     * @return the ships stored in this DTO
     */
    public List<Battleship> getShips() {
        return ships.stream().map(BattleshipDTO::toBattleship).toList();
    }

    /**
     * Saves the current ShipMapDTO to a file in JSON format.
     *
     * @param file the file to which the ShipMapDTO will be saved
     * @throws IOException if an I/O error occurs
     */
    public void saveTo(File file) throws IOException {
        try (FileWriter writer = new FileWriter(file)) {
            final Gson gson = new GsonBuilder().setPrettyPrinting().create();
            final String json = gson.toJson(this);
            LOGGER.log(Level.DEBUG, "JSON of map: {0}", json); //NON-NLS
            writer.write(json);
            LOGGER.log(Level.INFO, "JSON written to {0}", file.getAbsolutePath()); //NON-NLS
        }
    }

    /**
     * Loads a ShipMapDTO from a file containing JSON data.
     *
     * @param file the file from which the ShipMapDTO will be loaded
     * @return the loaded ShipMapDTO object
     * @throws IOException if an I/O error occurs or if the JSON is invalid
     */
    public static ShipMapDTO loadFrom(File file) throws IOException {
        try (FileReader reader = new FileReader(file)) {
            final Gson gson = new Gson();
            return gson.fromJson(reader, ShipMapDTO.class);
        }
        catch (JsonParseException e) {
            throw new IOException(e.getLocalizedMessage());
        }
    }
}
