SavesManager.java

package dev.plagarizers.klotski.game.util;

import com.badlogic.gdx.Gdx;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import dev.plagarizers.klotski.game.state.State;

import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

public class SavesManager {
    private static final String LEVEL_DIRECTORY = "levels";
    private static final String COMPLETED_LEVELS_FILE = "completed_levels.json";
    private static final String DATE_FORMAT = "yyyy-MM-dd_HH-mm-ss";
    private final String externalStoragePath;

    /**
     * Constructs a `SavesManager` object with the default external storage path.
     */
    public SavesManager() {
        this.externalStoragePath = "~/klotski/saves/";
    }

    /**
     * Constructs a `SavesManager` object with the specified external storage path.
     *
     * @param externalStoragePath the external storage path
     */
    public SavesManager(String externalStoragePath) {
        this.externalStoragePath = externalStoragePath + "klotski/saves/";
    }

    /**
     * Saves the game state to a file with a specified save name.
     *
     * @param state    the game state to be saved
     * @param saveName the name of the save file
     */
    public void saveState(State state, String saveName) {
        try {
            String json = state.toJson();
            createSaveDirectoryIfNotExists();
            FileWriter fileWriter = new FileWriter(getSaveFilePath(saveName));
            fileWriter.write(json);
            fileWriter.close();

        } catch (IOException e) {
            System.err.println("Error saving state: " + e.getMessage());
        }
    }

    /**
     * Retrieves the saved state paths and the number of moves for each saved state.
     *
     * @return a HashMap containing the saved state paths and the corresponding number of moves
     */
    public HashMap<String, Integer> getSavedStatePaths() {
        HashMap<String, Integer> savedStatePaths = new HashMap<>();
        File saveDirectory = new File(externalStoragePath);
        if (saveDirectory.exists() && saveDirectory.isDirectory()) {
            File[] saveFiles = saveDirectory.listFiles();
            if (saveFiles != null) {
                for (File saveFile : saveFiles) {
                    if (saveFile.isDirectory()) {
                        continue;
                    }
                    if (!saveFile.getName().endsWith(".json")) {
                        continue;
                    }
                    if (saveFile.getName().equals(COMPLETED_LEVELS_FILE)) {
                        continue;
                    }
                    String path = saveFile.getAbsolutePath();
                    int moves = getMoves(path);
                    if (moves < 0) {
                        moves = 0;
                    }
                    savedStatePaths.put(path, moves);
                }
            }
        }
        return savedStatePaths;
    }

    /**
     * Retrieves the number of moves from a saved state file.
     *
     * @param filePath the path to the saved state file
     * @return the number of moves
     */
    public int getMoves(String filePath) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            bufferedReader.close();
            String json = stringBuilder.toString();

            return State.fromJson(json).getMoves();
        } catch (IOException e) {
            System.err.println("Error getting moves: " + e.getMessage());
            return -1;
        }
    }

    /**
     * Loads a saved state by its name.
     *
     * @param name the name of the saved state
     * @return the loaded state
     */
    public State loadStateByName(String name) {
        return loadStateByPath(getSaveFilePath(name));
    }

    /**
     * Loads a saved state by its file path.
     *
     * @param filePath the file path of the saved state
     * @return the loaded state
     */
    public State loadStateByPath(String filePath) {
        try {
            BufferedReader bufferedReader = new BufferedReader(new FileReader(filePath));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            bufferedReader.close();


            String json = stringBuilder.toString();
            return State.fromJson(json);
        } catch (IOException e) {
            System.err.println("Error loading state: " + e.getMessage());
            return null;
        }
    }

    /**
     * Creates the save directory if it does not exist.
     */
    private void createSaveDirectoryIfNotExists() {
        File saveDirectory = new File(externalStoragePath);
        if (!saveDirectory.exists() || !saveDirectory.isDirectory()) {
            boolean created = saveDirectory.mkdirs();
            if (!created) {
                System.err.println("Failed to create save directory: " + externalStoragePath);
            }
        }
    }

    /**
     * Generates a unique filename for the saved state.
     *
     * @return the generated filename
     */
    private String generateFilename() {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
        String timestamp = dateFormat.format(new Date());
        return "save_" + timestamp;
    }

    /**
     * Retrieves the file path for the specified filename.
     *
     * @param filename the filename
     * @return the file path
     */
    private String getSaveFilePath(String filename) {
        return externalStoragePath + filename + ".json";
    }

    /**
     * Loads the game levels from the default path.
     *
     * @return a list of loaded levels
     */
    public List<Level> loadLevelsFromDefaultPath() {
        return loadLevels(Gdx.files.internal(LEVEL_DIRECTORY + File.separator + "levels.json").reader());

    }

    /**
     * Loads the game levels from a given reader.
     *
     * @param levelsReader the reader for the levels file
     * @return a list of loaded levels
     */
    public List<Level> loadLevels(Reader levelsReader) {
        try {
            BufferedReader bufferedReader = new BufferedReader(levelsReader);
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            bufferedReader.close();

            String json = stringBuilder.toString();
            return Level.fromJson(json);
        } catch (IOException e) {
            throw new RuntimeException("Error loading levels: " + e.getMessage());
        }
    }

    /**
     * Deletes a saved state file by its name.
     *
     * @param saveName the name of the saved state file to be deleted
     */
    public void deleteSave(String saveName) {
        String path = getSaveFilePath(saveName);
        File file = new File(path);
        if (file.exists()) {
            file.delete();
        }
    }

    /**
     * Loads the list of completed levels from a file.
     *
     * @return the list of completed levels
     */
    public List<String> loadCompletedLevels() {
        Gson gson = new Gson();
        try {
            File completedLevelsFile = new File(getCompletedLevelsFilePath());

            if (!completedLevelsFile.exists()) {
                // If the file doesn't exist, return an empty list
                createSaveDirectoryIfNotExists();
                boolean created = completedLevelsFile.createNewFile();
                if (!created) {
                    System.err.println("Failed to create completed levels file: " + completedLevelsFile.getAbsolutePath());
                }
                return new ArrayList<>();
            }

            BufferedReader bufferedReader = new BufferedReader(new FileReader(completedLevelsFile));
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            bufferedReader.close();

            String json = stringBuilder.toString();

            return gson.fromJson(json, new TypeToken<List<String>>() {
            }.getType());
        } catch (IOException e) {
            System.err.println("Error loading completed levels: " + e.getMessage());
            return new ArrayList<>();
        }
    }

    /**
     * Saves the list of completed levels to a file.
     *
     * @param completedLevels the list of completed levels
     */
    public void saveCompletedLevels(List<String> completedLevels) {
        Gson gson = new Gson();
        try {
            String json = gson.toJson(completedLevels);
            FileWriter fileWriter = new FileWriter(getCompletedLevelsFilePath());
            fileWriter.write(json);
            fileWriter.close();

        } catch (IOException e) {
            System.err.println("Error saving completed levels: " + e.getMessage());
        }
    }

    /**
     * Adds a level to the list of completed levels.
     *
     * @param levelName the number of the completed level
     */
    public void addCompletedLevel(String levelName) {
        List<String> completedLevels = loadCompletedLevels();
        if (completedLevels == null) {
            completedLevels = new ArrayList<>();
        }
        if (!completedLevels.contains(levelName)) {
            completedLevels.add(levelName);
            saveCompletedLevels(completedLevels);
        }
    }

    /**
     * Retrieves the file path for the completed levels file.
     *
     * @return the file path
     */
    private String getCompletedLevelsFilePath() {
        return externalStoragePath + COMPLETED_LEVELS_FILE;
    }


    /**
     * Retrieves the external storage path.
     *
     * @return the external storage path
     */
    public String getExternalStoragePath() {
        return externalStoragePath;
    }
}