scheduleToAdd) throws IOException {
+
+ Scanner in = new Scanner(System.in);
+
+ displayMessage("Here you go!");
+ displayMessage("Taking the modules in this order will ensure a prerequisite worry free uni life!");
+ displayMessage("Do you want to add this to your schedule planner? " +
+ "(This will overwrite your current schedule!)");
+ displayMessage("Please input 'Y' or 'N'");
+
+
+
+ String userInput = in.nextLine().replace("\r", "").toUpperCase();
+
+ while (!userInput.equals("N") && !userInput.equals(("Y"))) {
+ displayMessage("Invalid input, please choose Y/N");
+ userInput = in.nextLine().replace("\r", "");
+ }
+
+ if(userInput.equals("N")){
+ displayMessage("Okay, we will not put it in your schedule");
+ return;
+ }
+
+ student.addRecommendedSchedule(scheduleToAdd);
+
+ displayMessage("Here is your schedule planner!");
+
+ Schedule currentSchedule = student.getSchedule();
+
+ displaySchedule(currentSchedule);
+
+ displayMessage("Happy degree planning!");
+
+ saveSchedule(student);
+
+ }
+
+ /**
+ * Retrieves and prints the required modules for a specified major.
+ *
+ * This method initializes a `MajorRequirements` object based on the provided `major`.
+ * It then attempts to print the required modules from a corresponding TXT file.
+ * If the TXT file is not found, an error message is displayed.
+ *
+ * @param major The major for which to retrieve required modules.
+ * @throws NullPointerException If `major` is null.
+ */
+ public static void getRequiredModules(String major) throws NullPointerException {
+ printRequiredModules(major);
+ }
+
+ //@@author SebasFok
+ /**
+ * Asks the user for confirmation to clear their schedule and returns the user's choice.
+ * Displays a message warning that clearing your schedule cannot be undone.
+ *
+ * @return true if the user confirms by entering 'Y', false if 'N'.
+ */
+ public static boolean isConfirmedToClearSchedule() {
+
+ Scanner in = new Scanner(System.in);
+ displayMessage("Are you sure you want to clear your schedule? " +
+ "This action cannot be undone!");
+ displayMessage("Please input 'Y' or 'N'");
+
+ String userInput = in.nextLine().toUpperCase();
+
+ while (!userInput.equals("N") && !userInput.equals(("Y"))) {
+ displayMessage("Invalid input, please choose Y/N");
+ userInput = in.nextLine().toUpperCase();
+ }
+
+ return userInput.equals("Y");
+ }
+
+ //@@author janelleenqi
+ /**
+ * Modifies the timetable for the given student based on user commands.
+ *
+ * This method allows the user to modify the timetable for the current semester
+ * by processing user commands. It enters a loop to continuously accept commands
+ * until the user decides to exit. The modified timetable is saved after each
+ * successful modification. If the timetable view is available, it is printed.
+ * If the timetable view is unavailable, a simple guide is printed to inform the user.
+ *
+ * @param student The student for whom the timetable is to be modified.
+ * Must not be null.
+ * @throws InvalidModifyArgumentException If an invalid argument is provided during modification.
+ */
+ public void modifyTimetable(Student student) throws InvalidModifyArgumentException {
+ Timetable timetable = student.getTimetable();
+ ArrayList currentSemModulesWeekly = timetable.getCurrentSemesterModulesWeekly();
+ //verify accepted timetableuser command
+
+ try {
+ printCurrentSemModules(currentSemModulesWeekly);
+ } catch (TimetableUnavailableException e) {
+ println(e.getMessage());
+ }
+
+ printTTModifyDetailedLessonGuide("Entered Timetable Modify Mode");
+
+ Ui ui = new Ui();
+
+ boolean inTimetableModifyMode = true;
+ while (inTimetableModifyMode) {
+ try {
+ String userInput = ui.getUserCommand("Input timetable modify command here: ");
+
+ TimetableUserCommand currentTimetableCommand = new TimetableUserCommand(student,
+ currentSemModulesWeekly, userInput);
+
+
+ String[] arguments = currentTimetableCommand.getArguments();
+
+ //if exit
+ if (isExitModify(arguments)) {
+ inTimetableModifyMode = false;
+ println("Exited Timetable Modify Mode");
+ continue;
+ }
+
+ currentTimetableCommand.processTimetableCommand(currentSemModulesWeekly);
+ try {
+ saveTimetable(student);
+ } catch (IOException ignored){
+ // GitHub actions cannott save timetable on the directory
+ }
+ if (timetable.timetableViewIsAvailable()) {
+ printTimetable(currentSemModulesWeekly);
+ } else {
+ printTTModifySimpleLessonGuide("Timetable view is unavailable as modules in your " +
+ "current semester have no lessons yet.");
+ }
+
+ } catch (InvalidTimetableUserCommandException e) {
+ println(e.getMessage());
+ }
+ }
+ }
+
+ //@@author janelleenqi
+ /**
+ * Displays the timetable for the current semester based on the provided module weekly data.
+ *
+ * This method displays the timetable for the current semester using the ArrayList of ModuleWeekly objects to the
+ * user.
+ *
+ * @param currentSemesterModuleWeekly The list of ModuleWeekly objects with information about
+ * the timetable for the current semester
+ */
+ public void showTimetable(ArrayList currentSemesterModuleWeekly) {
+ printTimetable(currentSemesterModuleWeekly);
+ }
+}
diff --git a/src/main/java/seedu/duke/models/data/CEGRequirements b/src/main/java/seedu/duke/models/data/CEGRequirements
new file mode 100644
index 0000000000..c65b00d6ce
--- /dev/null
+++ b/src/main/java/seedu/duke/models/data/CEGRequirements
@@ -0,0 +1,39 @@
+*Modular Requirements for CEG - Units
+**Common Curriculum Requirements - 60
+GES1000 (Singapore Studies) - 4
+GEC1000 (Cultures and Connections) - 4
+GEN2000 (Communities and Engagement) - 4
+ES2631 Critique & Communication of Thinking & Design (Critique & Expression) - 4
+CS1010 Programming Methodology (Digital Literacy) - 4
+GEA1000 Quantitative Reasoning with Data (Data Literacy) - 4
+DTK1234 Design Thinking (Design Thinking) - 4
+EG1311 Design and Make (Maker Space) - 4
+IE2141 Systems Thinking and Dynamics (Systems Thinking) - 4
+EE2211 Introduction to Machine Learning (Artificial Intelligence) - 4
+CDE2501 Liveable Cities (Sustainable Futures) - 4
+CDE2000 (Creating Narratives) - 4
+PF1101 Fundamentals of Project Management (Project Management) - 4
+CG4002 Computer Engineering Capstone Project 1 (Integrated Project) - 8
+
+**Programme Requirements - 60
+***Engineering Core - 20
+MA1511 Engineering Calculus - 2
+MA1512 Differential Equations for Engineering - 2
+MA1508E Linear Algebra for Engineering - 4
+EG2401A Engineering Professionalism - 2
+CP3880 Advanced Technology Attachment Programme - 12
+
+***CEG Major - 40
+CG1111A Engineering Principles and Practice I - 4
+CG2111A Engineering Principles and Practice II - 4
+CS1231 Discrete Structures - 4
+CG2023 Signals & Systems - 4
+CG2027 Transistor-level Digital Circuit - 2
+CG2028 Computer Organization - 2
+CG2271 Real-time Operating System - 4
+CS2040C Data Structures and Algorithms - 4
+CS2113 Software Engineering & Object-Oriented Programming - 4
+EE2026 Digital Design - 4
+EE4204 Computer Networks - 4
+
+**Unrestricted Electives - 40
\ No newline at end of file
diff --git a/src/main/java/seedu/duke/models/data/CEGRequirementsModuleCodes b/src/main/java/seedu/duke/models/data/CEGRequirementsModuleCodes
new file mode 100644
index 0000000000..417d7f4ed0
--- /dev/null
+++ b/src/main/java/seedu/duke/models/data/CEGRequirementsModuleCodes
@@ -0,0 +1,31 @@
+CG1111A
+MA1511
+MA1512
+CS1010
+GESS1000
+GEC1000
+GEN2000
+ES2631
+GEA1000
+DTK1234
+EG1311
+IE2141
+EE2211
+EG2501
+CDE2000
+PF1101
+CG4002
+MA1508E
+EG2401A
+CP3880
+CG2111A
+CS1231
+CG2023
+CG2027
+CG2028
+CG2271
+ST2334
+CS2040C
+CS2113
+EE2026
+EE4204
\ No newline at end of file
diff --git a/src/main/java/seedu/duke/models/data/CSRequirements b/src/main/java/seedu/duke/models/data/CSRequirements
new file mode 100644
index 0000000000..9f548b789c
--- /dev/null
+++ b/src/main/java/seedu/duke/models/data/CSRequirements
@@ -0,0 +1,37 @@
+*Modular Requirements for CS - Units
+**Common Curriculum Requirements - 40
+***University Level Requirements: 6 University Pillars - 24
+CS1101S Programming Methodology (Digital Literacy) - 4
+ES2660 Communicating in the Information Age (Critique and Expression) - 4
+GEC1% (Cultures and Connections) - 4
+GEA1000 / BT1101 / ST1131 / DSA1101 (Data Literacy) - 4
+GES1% (Singapore Studies) - 4
+GEN2% (Communities and Engagement) - 4
+
+***Computing Ethics - 4
+IS1108 Digital Ethics and Data Privacy
+
+***Interdisciplinary & Cross-Disciplinary Education - 12
+Interdisciplinary (ID) Courses (at least 2)
+Cross-disciplinary (CD) Courses (no more than 1)
+
+**Programme Requirements - 80
+***Computer Science Foundation - 36
+CS1231S Discrete Structures - 4
+CS2030S Programming Methodology II - 4
+CS2040S Data Structures and Algorithms - 4
+CS2100 Computer Organisation - 4
+CS2101 Effective Communication for Computing Professionals - 4
+CS2103T Software Engineering - 4
+CS2106 Introduction to Operating Systems - 4
+CS2109S Introduction to AI and Machine Learning - 4
+CS3230 Design and Analysis of Algorithms - 4
+
+***Computer Science Breadth and Depth - 32
+
+***Mathematics and Sciences - 12
+MA1521 Calculus for Computing - 4
+MA1522 Linear Algebra for Computing - 4
+ST2334 Probability and Statistics - 4
+
+**Unrestricted Electives - 40
diff --git a/src/main/java/seedu/duke/models/data/CSRequirementsModuleCodes b/src/main/java/seedu/duke/models/data/CSRequirementsModuleCodes
new file mode 100644
index 0000000000..12b60cd9a0
--- /dev/null
+++ b/src/main/java/seedu/duke/models/data/CSRequirementsModuleCodes
@@ -0,0 +1,20 @@
+CS1101S
+ES2660
+IS1108
+CS1231S
+CS2030S
+CS2040S
+CS2100
+CS2101
+CS2103T
+CS2106
+CS2109S
+CS3230
+MA1521
+MA1522
+GEC1000
+GEA1000
+GESS1000
+GEN2000
+ST2334
+CP3880
\ No newline at end of file
diff --git a/src/main/java/seedu/duke/models/data/Duke.txt b/src/main/java/seedu/duke/models/data/Duke.txt
new file mode 100644
index 0000000000..5d8266cc98
--- /dev/null
+++ b/src/main/java/seedu/duke/models/data/Duke.txt
@@ -0,0 +1,2 @@
+CS1010S
+MA1508
\ No newline at end of file
diff --git a/src/main/java/seedu/duke/models/logic/Api.java b/src/main/java/seedu/duke/models/logic/Api.java
new file mode 100644
index 0000000000..2a8f1d3a2d
--- /dev/null
+++ b/src/main/java/seedu/duke/models/logic/Api.java
@@ -0,0 +1,353 @@
+package seedu.duke.models.logic;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.net.HttpURLConnection;
+import java.net.URISyntaxException;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.ArrayList;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+import seedu.duke.utils.exceptions.InvalidModuleCodeException;
+import seedu.duke.utils.exceptions.InvalidModuleException;
+
+import static seedu.duke.utils.errors.HttpError.displaySocketError;
+
+import seedu.duke.utils.Parser;
+import seedu.duke.utils.errors.UserError;
+import seedu.duke.views.ModuleInfoView;
+
+
+public class Api {
+
+ /**
+ * Sends an HTTP GET request to the specified URL and returns the response body as a String.
+ *
+ * @author rohitcube
+ * @param url The URL to which the HTTP GET request is sent.
+ * @return The response body as a String.
+ * @throws ParseException If there is an issue parsing the response.
+ * @throws IOException If an I/O error occurs during the HTTP request.
+ * @throws InterruptedException If the HTTP request is interrupted.
+ * @throws URISyntaxException If the URL is not a valid URI.
+ */
+ private static String sendHttpRequestAndGetResponseBody(String url) throws ParseException,
+ IOException, InterruptedException, URISyntaxException {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(new URI(url))
+ .GET()
+ .build();
+ HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString());
+ return response.body();
+
+ }
+
+ /**
+ * Retrieves detailed module information from an external API based on the module code.
+ *
+ * @author rohitcube
+ * @param moduleCode The module code to retrieve information for.
+ * @return A JSONObject containing module information.
+ *
+ */
+ public static JSONObject getFullModuleInfo(String moduleCode) throws RuntimeException, IOException {
+ try {
+ // Regex pattern to match only letters and numbers
+ String regexPattern = "^[a-zA-Z0-9]+$";
+ if (!moduleCode.matches(regexPattern)) {
+ throw new InvalidModuleException();
+ }
+ String url = "https://api.nusmods.com/v2/2023-2024/modules/" + moduleCode + ".json";
+ URL obj = new URL(url);
+ HttpURLConnection connection = (HttpURLConnection) obj.openConnection();
+ int responseCode = connection.getResponseCode();
+ if (responseCode == HttpURLConnection.HTTP_NOT_FOUND) {
+ throw new InvalidModuleCodeException();
+ }
+ String responseBody = sendHttpRequestAndGetResponseBody(url);
+ JSONParser parser = new JSONParser();
+ return (JSONObject) parser.parse(responseBody);
+ } catch (ParseException e) {
+ //System.out.println("Invalid Module Name");
+ } catch (IOException | InterruptedException e) {
+ throw new IOException(e);
+ } catch (URISyntaxException e) {
+ //to be replaced with more robust error class in the future
+ System.out.println("Sorry, there was an error with" +
+ " the provided URL: " + e.getMessage());
+ } catch (NullPointerException e) {
+ //System.out.println("Invalid Module Name");
+ } catch (InvalidModuleException e) {
+ System.out.println("Invalid Module Code: " + e.getMessage());
+ } catch (InvalidModuleCodeException e) {
+ System.out.println(e.getMessage());
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the name of a module based on its module code.
+ *
+ * @param moduleCode The module code to retrieve the name for.
+ * @return The name of the module.
+ */
+ public static String getModuleName(String moduleCode) {
+ try {
+ JSONObject fullModuleInfo = getFullModuleInfo(moduleCode);
+ assert fullModuleInfo != null;
+ return (String) fullModuleInfo.get("title");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Retrieves the description of a module based on its module code.
+ *
+ * @author rohitcube
+ * @param moduleCode The module code to retrieve the description for.
+ * @return The description of the module.
+ *
+ */
+ public static String getDescription(String moduleCode) throws InvalidModuleException, InvalidModuleCodeException {
+ try {
+ JSONObject moduleInfo = getFullModuleInfo(moduleCode);
+ if (moduleInfo == null) {
+ throw new InvalidModuleCodeException();
+ }
+ return (String) moduleInfo.get("description");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ /**
+ * Wraps a long input string into multiple lines at a specified wrap index.
+ *
+ * This method takes an input string and wraps it into multiple lines by inserting newline
+ * characters at or before the specified wrap index. It ensures that the words are not split,
+ * and the text remains readable.
+ *
+ * @param input The input string to be wrapped.
+ * @param wrapIndex The wrap index, indicating the maximum number of characters per line.
+ * @return A new string with newline characters added for wrapping.
+ */
+ public static String wrapText(String input, int wrapIndex) {
+ if (input == null || input.trim().isEmpty()) {
+ return "";
+ }
+ StringBuilder description = new StringBuilder(input);
+ int currIndex = 0;
+ int markerIndex = 0;
+ while (currIndex < description.length()) {
+ if (markerIndex >= wrapIndex) { //index where string is wrapped
+ if (description.charAt(currIndex) == ' ') {
+ description.insert(currIndex, '\n');
+ markerIndex = 0;
+ continue;
+ }
+ currIndex--;
+ continue;
+ }
+ currIndex++;
+ markerIndex++;
+ }
+ return description.toString();
+ }
+
+ /**
+ * Retrieves the workload information for a module based on its module code.
+ * @author rohitcube
+ * @param moduleCode The module code to retrieve workload information for.
+ * @return A JSONArray containing workload details.
+ *
+ */
+ public static JSONArray getWorkload(String moduleCode) throws InvalidModuleCodeException {
+ try {
+ JSONObject moduleInfo = getFullModuleInfo(moduleCode);
+ if (moduleInfo == null) {
+ throw new InvalidModuleCodeException();
+ }
+ return (JSONArray) moduleInfo.get("workload");
+ } catch (NullPointerException e) {
+ throw new InvalidModuleCodeException();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Retrieves the requirements the module fulfills
+ * @author rohitcube
+ * @param moduleCode The module code to retrieve workload information for.
+ * @return A JSONArray containing workload details.
+ *
+ */
+ public static ArrayList getModuleFulfilledRequirements(String moduleCode) throws IOException {
+ try {
+ JSONObject moduleInfo = getFullModuleInfo(moduleCode);
+ ArrayList fulfilledArray = new ArrayList<>();
+ ArrayList response = (ArrayList) moduleInfo.get("fulfillRequirements");
+ if (response != null) {
+ fulfilledArray = response;
+ }
+
+ return fulfilledArray;
+ } catch (ClassCastException | NullPointerException e) {
+ return new ArrayList();
+ }
+ }
+
+ /**
+ * Checks if a module with the given module code exists in the NUSMods database.
+ *
+ * @param moduleCode The module code to check for existence.
+ * @return `true` if the module exists, `false` if the module does not exist.
+ */
+ public static boolean isValidModule(String moduleCode) {
+ try {
+ JSONObject moduleInfo = getFullModuleInfo(moduleCode);
+ return (!(moduleInfo == null));
+ } catch (IOException e) {
+ displaySocketError();
+ return false;
+ }
+ }
+
+ /**
+ * Retrieves a list of modules from an external API and returns it as a JSONArray.
+ * @author rohitcube
+ * @return A JSONArray containing module information.
+ * @throws RuntimeException If there is an issue with the HTTP request or JSON parsing.
+ *
+ */
+ public static JSONArray listAllModules() {
+ try {
+ String url = "https://api.nusmods.com/v2/2023-2024/moduleList.json";
+ String responseBody = sendHttpRequestAndGetResponseBody(url);
+ JSONParser parser = new JSONParser();
+ return (JSONArray) parser.parse(responseBody);
+ } catch (URISyntaxException e) {
+ System.out.println("Sorry, there was an error with" +
+ " the provided URL: " + e.getMessage());
+ throw new RuntimeException(e);
+ } catch (IOException | InterruptedException e) {
+ displaySocketError();
+ } catch (ParseException e) {
+ //to be replaced with more robust error class in the future
+ System.out.println("Sorry, the JSON object could not be parsed");
+ }
+ return null;
+ }
+
+
+ /**
+ * Executes commands based on user input for module information retrieval.
+ * Supports commands: "description", "workload", "all".
+ * @author rohitcube
+ * @param command The command provided by the user.
+ * @param userInput The user input string containing the command and module code (if applicable).
+ *
+ */
+ public static void infoCommands(String command, String userInput) {
+ try {
+ if (command.equals("description")) {
+ String moduleCode =
+ userInput.substring(userInput.indexOf("description") + 11).trim().toUpperCase();
+ if (!Api.getDescription(moduleCode).isEmpty()) {
+ String description = Api.getDescription(moduleCode);
+ System.out.println(Api.wrapText(description, 100));
+ }
+ } else if (command.equals("workload")) {
+ String moduleCode = userInput.substring(userInput.indexOf("workload") + 8).trim().toUpperCase();
+ if (!Api.getWorkload(moduleCode).isEmpty()) {
+ JSONArray workload = Api.getWorkload(moduleCode);
+ System.out.println(workload);
+ }
+ } else {
+ UserError.invalidCommandforInfoCommand();
+ }
+ } catch (InvalidModuleException e) {
+ // System.out.println("Invalid entry" + e.getMessage());
+ } catch (InvalidModuleCodeException e) {
+ // System.out.println(e.getMessage());
+ }
+ }
+
+ /**
+ * Searches for modules containing a specified keyword in their title within a given module list.
+ * @author rohitcube
+ * @param keyword The keyword to search for.
+ * @param moduleList The list of modules to search within.
+ * @return A JSONArray containing modules matching the keyword.
+ *
+ */
+ public static JSONArray search(String keyword, JSONArray moduleList) {
+ JSONArray modulesContainingKeyword = new JSONArray();
+ if (keyword.isEmpty()) {
+ return new JSONArray();
+ }
+ String[] wordsInKeyword = keyword.split(" ");
+ for (int i = 0; i < wordsInKeyword.length; i++) {
+ wordsInKeyword[i] = capitalizeFirstLetter(wordsInKeyword[i]);
+ }
+ String keywordToSearch = String.join(" ", wordsInKeyword);
+ for (Object moduleObject : moduleList) {
+ JSONObject module = (JSONObject) moduleObject; // Cast to JSONObject
+ String title = (String) module.get("title");
+ if (title.contains(keywordToSearch)) {
+ modulesContainingKeyword.add(module);
+ //not sure how to resolve this yellow line
+ }
+ }
+ return modulesContainingKeyword;
+ }
+
+ /**
+ * Capitalizes the first letter of a given string.
+ * @author rohitcube
+ * @param input The input string.
+ * @return A new string with the first letter capitalized, or the original string if it is null or empty.
+ *
+ */
+ public static String capitalizeFirstLetter(String input) {
+ if (input == null || input.isEmpty()) {
+ return input;
+ }
+ StringBuilder result = new StringBuilder(input.length());
+ result.append(Character.toUpperCase(input.charAt(0)));
+ result.append(input.substring(1));
+ return result.toString();
+ }
+
+ /**
+ * Performs a module search and displays the results.
+ *
+ * This method takes a user input string, extracts keywords from it, performs a search using
+ * the API, and displays the results in a structured format.
+ *
+ * @param userInput The user input string for searching modules.
+ */
+ public static void searchCommand(String userInput) {
+ if (!Parser.isValidKeywordInput(userInput)) {
+ UserError.emptyKeywordforSearchCommand();
+ return;
+ }
+ String keywords = userInput.substring(userInput.indexOf("search") + 6);
+ JSONArray modulesToPrint = Api.search(keywords, Api.listAllModules());
+ if (modulesToPrint.isEmpty()) {
+ UserError.emptyArrayforSearchCommand();
+ return;
+ }
+ ModuleInfoView.searchHeader();
+ ModuleInfoView.printJsonArray(modulesToPrint);
+ }
+}
diff --git a/src/main/java/seedu/duke/models/logic/Prerequisite.java b/src/main/java/seedu/duke/models/logic/Prerequisite.java
new file mode 100644
index 0000000000..353c415aee
--- /dev/null
+++ b/src/main/java/seedu/duke/models/logic/Prerequisite.java
@@ -0,0 +1,377 @@
+package seedu.duke.models.logic;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import seedu.duke.models.schema.ModuleList;
+import seedu.duke.utils.exceptions.InvalidPrereqException;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+
+import static seedu.duke.storage.StorageManager.getRequirements;
+
+public class Prerequisite {
+
+ //@@author SebasFok
+ /**
+ * Recursively checks if each branch of the prereq tree is satisfied by the student.
+ *
+ * @param modulePrereqArray The array of prerequisite modules or conditions to be checked.
+ * @param currRequisite The type of prerequisite condition ("or" or "and").
+ * @param completedModules The list of completed modules by the student.
+ * @return `true` if the student satisfies all prerequisites, `false` otherwise.
+ */
+ private static boolean isPrereqSatisfied(
+ ArrayList modulePrereqArray,
+ String currRequisite,
+ ModuleList completedModules) {
+ try {
+
+ if (currRequisite.equals("or")) {
+ return isOrBranchSatisfied(modulePrereqArray, completedModules);
+ } else {
+ return isAndBranchSatisfied(modulePrereqArray, completedModules);
+ }
+ } catch (ClassCastException e) {
+ System.out.println("Error checking prereq for this module");
+ return false;
+ }
+
+ }
+
+ //@@author SebasFok
+ /**
+ * Checks if the AND branch of a module's prerequisites is satisfied based on completed modules.
+ * Recursively checks the branch if there are nested prerequisite structures in the AND branch
+ *
+ * @param modulePrereqArray The array representing the AND branch of prerequisites.
+ * @param completedModules The list of modules that have been completed.
+ * @return true if the AND branch is satisfied, false otherwise.
+ * @throws RuntimeException If an unexpected exception occurs during prerequisite checking.
+ */
+ private static boolean isAndBranchSatisfied(ArrayList modulePrereqArray, ModuleList completedModules) {
+ for (Object module : modulePrereqArray) {
+ if (module instanceof String) {
+ String formattedModule = ((String) module).replace(":D", "");
+ formattedModule = formattedModule.replace("%", "");
+ try {
+ if (!completedModules.existsByCode(formattedModule)) {
+ return false;
+ }
+ } catch (InvalidObjectException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ JSONObject prereqBranch = (JSONObject) module;
+
+ //for cs, some modules return pre req in this form {"nOf":[2,["MA1511:D","MA1512:D"]]}
+ //have to convert first
+ if (prereqBranch.containsKey("nOf")) {
+ String key = "and";
+ ArrayList> initial =
+ (ArrayList>) prereqBranch.get("nOf");
+ ArrayList formattedInitial = initial.get(1);
+ JSONArray prereqBranchArray = (JSONArray) formattedInitial;
+ if (!isPrereqSatisfied(prereqBranchArray, key, completedModules)){
+ return false;
+ }
+ } else {
+ String key = (String) prereqBranch.keySet().toArray()[0];
+ JSONArray prereqBranchArray = (JSONArray) prereqBranch.get(key);
+ if (!isPrereqSatisfied(prereqBranchArray, key, completedModules)) {
+ return false;
+ }
+ }
+
+ }
+ }
+ return true;
+ }
+
+ //@@author SebasFok
+ /**
+ * Checks if the OR branch of a module's prerequisites is satisfied based on completed modules.
+ * Recursively checks the branch if there are nested prerequisite structures in the OR branch
+ *
+ * @param modulePrereqArray The array representing the OR branch of prerequisites.
+ * @param completedModules The list of modules that have been completed.
+ * @return true if the OR branch is satisfied, false otherwise.
+ * @throws RuntimeException If an unexpected exception occurs during prerequisite checking.
+ */
+ private static boolean isOrBranchSatisfied(ArrayList modulePrereqArray, ModuleList completedModules) {
+ for (Object module : modulePrereqArray) {
+ if (module instanceof String) {
+ String formattedModule = ((String) module).replace(":D", "");
+ formattedModule = formattedModule.replace("%", "");
+ try {
+ if (completedModules.existsByCode(formattedModule)) {
+ return true;
+ }
+ } catch (InvalidObjectException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ JSONObject prereqBranch = (JSONObject) module;
+
+ //for cs, some modules return pre req in this form {"nOf":[2,["MA1511:D","MA1512:D"]]}
+ //have to convert first
+ if (prereqBranch.containsKey("nOf")) {
+ String key = "and";
+ ArrayList> initial =
+ (ArrayList>) prereqBranch.get("nOf");
+ ArrayList formattedInitial = initial.get(1);
+ JSONArray prereqBranchArray = (JSONArray) formattedInitial;
+ if (isPrereqSatisfied(prereqBranchArray, key, completedModules)){
+ return true;
+ }
+
+ } else {
+ String key = (String) prereqBranch.keySet().toArray()[0];
+ JSONArray prereqBranchArray = (JSONArray) prereqBranch.get(key);
+ return isPrereqSatisfied(prereqBranchArray, key, completedModules);
+ }
+
+
+ }
+ }
+ return false;
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Recursively flattens and processes a list of module prerequisites.
+ * More info on the data structure being processed can be found in
+ * the prereqTree key in an example ...
+ * @param major The major or program for which prerequisites are being flattened.
+ * @param prerequisites An ArrayList to store the flattened prerequisites.
+ * @param modulePrereqArray An ArrayList containing the module prerequisites to be processed.
+ * @param courseRequirements An ArrayList containing course requirements.
+ * @param currRequisite The type of the current prerequisite (e.g., "and" or "or").
+ *
+ */
+ private static void flattenPrereq(
+ String major,
+ ArrayList prerequisites,
+ ArrayList modulePrereqArray,
+ ArrayList courseRequirements,
+ String currRequisite) throws ClassCastException {
+ try {
+ int lengthOfModulePreReqArray = modulePrereqArray.size();
+
+ // we keep a counter as if no preclusion is a module requirement for the major
+ // we will take the last module in the list of preclusions
+ int counter = 0;
+
+ for (Object module : modulePrereqArray) {
+ if (module instanceof String) {
+ String formattedModule = ((String) module).split(":")[0];
+ formattedModule = formattedModule.replace("%", "");
+ if (courseRequirements.contains(formattedModule) || currRequisite.equals("and")) {
+ prerequisites.add(formattedModule);
+ if (currRequisite.equals("or")) {
+ return;
+ }
+ }
+
+ //if this is the last item and the module also part of the courseRequirements, we add it anw
+ if (currRequisite.equals("or") && counter == (lengthOfModulePreReqArray - 1)
+ && !courseRequirements.contains((formattedModule))) {
+ prerequisites.add(formattedModule);
+ return;
+ }
+ } else {
+ //item is an object
+ //here, we determine if its 'or' or 'and'
+ JSONObject moduleJSON = (JSONObject) module;
+
+ if (moduleJSON.containsKey("nOf")) {
+ throw new ClassCastException();
+ } else {
+ String key = (String) moduleJSON.keySet().toArray()[0];
+
+ ArrayList initial = (ArrayList) moduleJSON.get(key);
+
+ flattenPrereq(major, prerequisites, initial, courseRequirements, key);
+ }
+
+ }
+ counter += 1;
+ }
+ } catch (ClassCastException e) {
+ throw new ClassCastException();
+
+ }
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Retrieves the prerequisite array for a module specified by its code and also taking into account the degree
+ * requirements of the course.
+ * @param moduleCode The code of the module for which prerequisites are to be retrieved.
+ * @return A JSONObject representing the prerequisite tree for the module or NULL if no prerequisites are specified.
+ *
+ */
+ public static ArrayList getModulePrereqBasedOnCourse(String moduleCode, String major)
+ throws InvalidPrereqException, IOException {
+
+ //Modules that has prerequisites incorrectly identified by NUSMods
+ if (isModuleException(moduleCode)) {
+ return getExemptedPrerequisite(moduleCode);
+ }
+
+ JSONObject modulePrereqTree = getModulePrereqTree(moduleCode);
+
+ if (modulePrereqTree == null) {
+ return null;
+ }
+
+ String key = (String) modulePrereqTree.keySet().toArray()[0];
+
+ ArrayList prerequisites = new ArrayList<>();
+
+ ArrayList initial = (ArrayList) modulePrereqTree.get(key);
+ try {
+ flattenPrereq(major, prerequisites, initial, getRequirements(major), key);
+ } catch (ClassCastException e) {
+ throw new InvalidPrereqException(moduleCode);
+ }
+
+ //As some modules in NUSMods return empty objects, we will return null to standardize
+ if(prerequisites.isEmpty()){
+ return null;
+ }
+
+ return prerequisites;
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Retrieves the prerequisite tree for a module specified by its code.
+ *
+ * @param moduleCode The code of the module for which prerequisites are to be retrieved.
+ * @return A JSON object representing the prerequisite tree for the module. The prerequisite tree can be in one of
+ *
+ */
+ static JSONObject getModulePrereqTree(String moduleCode) throws IOException {
+ JSONObject fullModuleInfo = Api.getFullModuleInfo(moduleCode);
+ if (fullModuleInfo == null) {
+ return null;
+ }
+ //prereqTree can be returned as a string(single pre requisite), null(No pre requisites) or object
+ Object prereqTree = fullModuleInfo.get("prereqTree");
+ if (prereqTree == null) {
+ return null;
+ } else if (prereqTree instanceof String) {
+
+ JSONObject jsonObject = new JSONObject();
+ ArrayList requirementList = new ArrayList<>();
+ requirementList.add((String) prereqTree);
+ jsonObject.put("or", requirementList);
+
+ return jsonObject;
+ }
+
+ return (JSONObject) fullModuleInfo.get("prereqTree");
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Checks if a given module code is exempted from certain requirements.
+ *
+ * @param moduleCode The module code to check.
+ * @return True if the module is exempted, false otherwise.
+ */
+ static boolean isModuleException(String moduleCode) {
+ ArrayList exemptedModules = new ArrayList<>(List.of("CS1231", "CS1231S", "MA1508E", "EE4204",
+ "MA1511", "MA1512", "MA1521", "MA1522", "CS2109S"));
+
+ return exemptedModules.contains(moduleCode);
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Retrieves a list of exempted prerequisites for a given module code.
+ * @param moduleCode The module code to retrieve exempted prerequisites for.
+ * @return An ArrayList of exempted prerequisite module codes.
+ */
+ static ArrayList getExemptedPrerequisite(String moduleCode) {
+ HashMap> map = new HashMap<>();
+ ArrayList list1 = new ArrayList<>();
+ map.put("CS1231", list1);
+
+ ArrayList list2 = new ArrayList<>();
+ list2.add("MA1511");
+ list2.add("MA1512");
+ map.put("MA1508E", list2);
+
+ ArrayList list3 = new ArrayList<>();
+ list3.add("ST2334");
+ map.put("EE4204", list3);
+
+ ArrayList list4 = new ArrayList<>();
+ list4.add("CS2040S");
+ list4.add("CS1231S");
+ list4.add("MA1521");
+ map.put("CS2109S", list4);
+
+ ArrayList emptyList = new ArrayList<>();
+
+ map.put("MA1511", emptyList);
+
+ map.put("CS1231S", emptyList);
+
+ map.put("MA1512", emptyList);
+
+ map.put("MA1521", emptyList);
+
+ map.put("MA1522", emptyList);
+
+
+ return map.get(moduleCode);
+ }
+
+ //@@author SebasFok
+ /**
+ * Checks if a student satisfies all prerequisites for a given module.
+ *
+ * @param moduleCode The code of the module for which prerequisites need to be checked.
+ * @param completedModules The list of completed modules by the student.
+ * @return `true` if the student satisfies all prerequisites for the module, `false` otherwise.
+ * @throws IllegalArgumentException If the module code is invalid.
+ */
+ public static boolean satisfiesAllPrereq(String moduleCode, ModuleList completedModules)
+ throws IllegalArgumentException {
+ try {
+ if (!Api.isValidModule(moduleCode)) {
+ throw new IllegalArgumentException("Invalid module code");
+ }
+
+ JSONObject modulePrereqTree = getModulePrereqTree(moduleCode);
+
+ if (modulePrereqTree == null) {
+ return true;
+ }
+
+ String key = (String) modulePrereqTree.keySet().toArray()[0];
+ ArrayList initial = (ArrayList) modulePrereqTree.get(key);
+
+ //Modules that has prerequisites incorrectly identified by NUSMods
+ if (isModuleException(moduleCode)) {
+ JSONObject exceptionPrereqTree = new JSONObject();
+ ArrayList requirementList = getExemptedPrerequisite(moduleCode);
+ exceptionPrereqTree.put("and", requirementList);
+
+ key = (String) exceptionPrereqTree.keySet().toArray()[0];
+ initial = (ArrayList) exceptionPrereqTree.get(key);
+ }
+
+ return isPrereqSatisfied(initial, key, completedModules);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Command.java b/src/main/java/seedu/duke/models/schema/Command.java
new file mode 100644
index 0000000000..2aa6843434
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Command.java
@@ -0,0 +1,71 @@
+package seedu.duke.models.schema;
+
+/**
+ * Represents a command with its associated information such as command name, arguments, and description.
+ * The class provides methods to retrieve details of the command and to generate a formatted string
+ * for displaying the command's information.
+ */
+public class Command {
+ private static final int JUSTIFY_LENGTH = 30;
+ private final String commandName;
+ private final String description;
+ private final String arguments;
+
+ //@@author janelleenqi
+ /**
+ * Constructs a Command object with the specified command name and description.
+ *
+ * @param commandName The name of the command. Must not be null.
+ * @param description The description of the command. Must not be null.
+ */
+ public Command(String commandName, String description) {
+ this.commandName = commandName;
+ this.description = description;
+ this.arguments = "";
+ }
+
+ /**
+ * Constructs a Command object with the specified command name, arguments, and description.
+ *
+ * @param commandName The name of the command. Must not be null.
+ * @param arguments The arguments of the command. Must not be null.
+ * @param description The description of the command. Must not be null.
+ */
+ public Command(String commandName, String arguments, String description) {
+ this.commandName = commandName;
+ this.arguments = arguments;
+ this.description = description;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Gets the name of the command.
+ *
+ * @return The command name.
+ */
+ public String getCommandName() {
+ return commandName;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Gets the description of the command.
+ *
+ * @return The command description.
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Generates a formatted string representation of the command for display purposes.
+ *
+ * @return The formatted string containing the command name, arguments, and description.
+ */
+ @Override
+ public String toString() {
+ int rightPadding = JUSTIFY_LENGTH - commandName.length() - arguments.length();
+ return commandName + " " + arguments + " ".repeat(rightPadding) + description;
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/CommandManager.java b/src/main/java/seedu/duke/models/schema/CommandManager.java
new file mode 100644
index 0000000000..716fa69ab9
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/CommandManager.java
@@ -0,0 +1,98 @@
+package seedu.duke.models.schema;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class represents the command system for the application.
+ * It stores and provides descriptions for each available command.
+ */
+public class CommandManager {
+ private ArrayList commandArrayList;
+
+ public CommandManager() {
+ commandArrayList = new ArrayList<>();
+ addCurrentCommands();
+ }
+
+ /**
+ * Populates the commandsAndDescription HashMap with the current commands and their descriptions.
+ */
+ private void addCurrentCommands() {
+ commandArrayList.addAll(List.of(
+ new Command("help", "Shows the list of commands."),
+
+ new Command("required", "Displays the full requirements for your major."),
+ new Command("recommend", "Displays a recommended schedule based on a keyword."),
+
+ new Command("search", "KEYWORD",
+ "Searches for modules to take based on keyword"),
+ new Command("info", "COMMAND MODULE_CODE",
+ "Displays information about a specific module."),
+ new Command("prereq", "MODULE_CODE",
+ "Displays the prerequisites for a specific module."),
+
+ new Command("schedule", "Shows schedule planner"),
+ new Command("add", "MODULE_CODE SEMESTER",
+ "Adds module to the schedule planner."),
+ new Command("delete", "MODULE_CODE",
+ "Deletes module from the schedule planner."),
+ new Command("shift", "MODULE_CODE SEMESTER",
+ "Shifts module in the schedule planner."),
+ new Command("clear", "Clears all schedule planner and completion data."),
+
+ new Command("complete", "MODULE_CODE",
+ "Marks a module as complete on schedule planner."),
+ new Command("left", "Displays a list of remaining required modules."),
+ new Command("pace", "[CURRENT_SEMESTER]", "Computes and displays your graduation pace."),
+
+ new Command("timetable", "COMMAND", "Displays a grid containing this semester's classes"),
+
+ new Command("bye", "Saves user's schedule and timetable and exits program.")));
+ }
+
+
+ /*
+ * Retrieves the description for a specific command.
+ *
+ * @param command The command to retrieve the description for.
+ * @return The description of the command, or "Command not recognized" if the command is not in the HashMap.
+ */
+ public String getDescription(String commandName) {
+ for (Command command : commandArrayList) {
+ if (commandName.equals(command.getCommandName())) {
+ return command.getDescription();
+ }
+ }
+ return "Command not recognized";
+ //return commandArrayList.getOrDefault(command, "Command not recognized");
+ }
+
+
+ /**
+ * Retrieves a list of all available commands.
+ *
+ * @return An ArrayList containing all available commands.
+ */
+ public ArrayList getListOfCommandNames() {
+ ArrayList commandNameArrayList = new ArrayList();
+ for (Command command : commandArrayList) {
+ commandNameArrayList.add(command.getCommandName());
+ }
+ return commandNameArrayList;
+ }
+
+
+ /**
+ * Returns a formatted list of all commands and their descriptions.
+ *
+ * @return An ArrayList of strings, where each string represents a command and its description.
+ */
+ public ArrayList printListOfCommands() {
+ ArrayList commandList = new ArrayList<>();
+ for (Command command : commandArrayList) {
+ commandList.add(command.toString());
+ }
+ return commandList;
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Event.java b/src/main/java/seedu/duke/models/schema/Event.java
new file mode 100644
index 0000000000..4dd7f45c23
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Event.java
@@ -0,0 +1,234 @@
+package seedu.duke.models.schema;
+
+import java.util.List;
+
+//@@author janelleenqi
+/**
+ * Represents a scheduled event, with details like the day, start time,
+ * duration, module code, and utility methods for time calculations and comparisons.
+ */
+public class Event {
+ private static final List days = List.of("monday", "tuesday", "wednesday", "thursday", "friday",
+ "saturday", "sunday");
+ private String day;
+ private int startTime;
+ private int duration;
+ private String moduleCode;
+
+ /**
+ * Constructs an Event object with the specified day, start time, duration, and module code.
+ *
+ * @param day The day of the event. Must not be null.
+ * @param startTime The start time of the event.
+ * @param duration The duration of the event.
+ * @param moduleCode The module code associated with the event. Must not be null.
+ */
+ public Event(String day, int startTime, int duration, String moduleCode) {
+ this.day = day;
+ this.startTime = startTime;
+ this.duration = duration;
+ this.moduleCode = moduleCode;
+ }
+
+ /**
+ * Gets the start time of the event.
+ *
+ * @return The start time.
+ */
+ public int getStartTime() {
+ return startTime;
+ }
+
+ /**
+ * Gets the duration of the event.
+ *
+ * @return The duration.
+ */
+ public int getDuration() {
+ return duration;
+ }
+
+ /**
+ * Gets the day of the event as an integer index (0 for Monday, 1 for Tuesday, etc.).
+ *
+ * @return The index of the day.
+ */
+ public int getDayInt() {
+ String lowercaseDay = day.toLowerCase();
+ if (!days.contains(lowercaseDay)) {
+ return -1;
+ }
+
+ return days.indexOf(lowercaseDay);
+ }
+
+ /**
+ * Gets the day of the event.
+ *
+ * @return The day.
+ */
+ public String getDay() {
+ return day;
+ }
+
+ /**
+ * Gets the event type (empty string for general events).
+ *
+ * @return The event type.
+ */
+ public String getEventType() {
+ return "";
+ }
+
+ /**
+ * Gets the module code associated with the event.
+ *
+ * @return The module code.
+ */
+ public String getModuleCode() {
+ return moduleCode;
+ }
+
+ /**
+ * Calculates the time range for a given time period and duration.
+ *
+ * @param timePeriod The starting time period.
+ * @param duration The duration of the event.
+ * @return A string representing the time range.
+ */
+ public static String getTime(int timePeriod, int duration) {
+ String startTime = getTime(timePeriod);
+
+ // time is outside 5am-11pm
+ if (startTime.isEmpty()) {
+ return "";
+ }
+
+ // event has no duration, just return start time
+ if (duration == 0) {
+ return "(" + startTime + ")";
+ }
+
+ String endTime = getTime(timePeriod + duration);
+
+ // time is outside 5am-11pm, just return start time
+ if (endTime.isEmpty()) {
+ return "(" + startTime + ")";
+ }
+
+ return "(" + startTime + "-" + endTime + ")";
+ }
+
+ /**
+ * Gets the time string for the given time period.
+ *
+ * @param timePeriod Index of the time period.
+ * @return A string representing the time.
+ */
+ private static String getTime(int timePeriod) {
+ if (5 <= timePeriod && timePeriod <= 11) {
+ return (timePeriod) + "am";
+ } else if (timePeriod == 12) {
+ return (timePeriod) + "pm";
+ } else if (13 <= timePeriod && timePeriod <= 23) {
+ return (timePeriod - 12) + "pm";
+ } else {
+ // time is outside 5am-11pm
+ return "";
+ }
+ }
+
+ /**
+ * Checks if two events are equal by comparing their day, start time, duration, and module code.
+ *
+ * @param event The event to compare with.
+ * @return true if the events are equal, false otherwise.
+ */
+ public boolean equals(Event event) {
+ if (this.getDayInt() != event.getDayInt()) {
+ return false;
+ }
+
+ if (this.startTime != event.getStartTime()) {
+ return false;
+ }
+
+ if (this.duration != event.getDuration()) {
+ return false;
+ }
+
+ if (!this.moduleCode.equals(event.getModuleCode())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks if the current event is earlier than another event based on start time, duration,
+ * and module code.
+ *
+ * @param event The event to compare with.
+ * @return true if the current event is earlier, false otherwise.
+ */
+ public boolean isEarlierThan(Event event) {
+ // compare startTime
+ if (this.startTime < event.getStartTime()) {
+ return true;
+ }
+ if (this.startTime > event.getStartTime()) {
+ return false;
+ }
+
+ // same startTime, compare duration
+ if (this.duration < event.getDuration()) {
+ return true;
+ }
+ if (this.duration > event.getDuration()) {
+ return false;
+ }
+
+ int currentCharIndex = 0;
+ while (currentCharIndex < this.moduleCode.length() && currentCharIndex < event.getModuleCode().length()) {
+ // same startTime & duration, compare moduleCode characters
+ if (this.moduleCode.charAt(currentCharIndex) < event.getModuleCode().charAt(currentCharIndex)) {
+ return true;
+ }
+ if (this.moduleCode.charAt(currentCharIndex) > event.getModuleCode().charAt(currentCharIndex)) {
+ return false;
+ }
+ currentCharIndex++;
+ }
+
+ // same startTime & duration, compare moduleCode length (shorter is earlier)
+ if (this.moduleCode.length() < event.getModuleCode().length()) {
+ return true;
+ }
+ if (this.moduleCode.length() > event.getModuleCode().length()) {
+ return true;
+ }
+
+ // difference in Lecture, Tutorial, Lab
+ return false; // no swap in bubble sort
+ }
+
+ /**
+ * Generates a string representation of the event, which is the module code.
+ *
+ * @return The string representation of the event.
+ */
+ @Override
+ public String toString() {
+ return moduleCode;
+ }
+
+
+ /**
+ * Generates a string representation of the event for saving purposes, which is the module code.
+ *
+ * @return The string representation of the event for saving.
+ */
+ public String toSave() {
+ return moduleCode;
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Lab.java b/src/main/java/seedu/duke/models/schema/Lab.java
new file mode 100644
index 0000000000..1760b12dfd
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Lab.java
@@ -0,0 +1,74 @@
+package seedu.duke.models.schema;
+
+//@@author janelleenqi
+/**
+ * Represents a Lab that extends the Event class.
+ */
+public class Lab extends Event{
+
+ private static final String EVENT_TYPE = "Lab";
+
+ /**
+ * Constructs a Lab object with the specified day, start time, duration, and module code.
+ *
+ * @param day The day of the lab session. Must not be null.
+ * @param startTime The start time of the lab session.
+ * @param duration The duration of the lab session.
+ * @param moduleCode The module code associated with the lab session. Must not be null.
+ */
+ public Lab(String day, int startTime, int duration, String moduleCode) {
+ super(day, startTime, duration, moduleCode);
+ }
+
+ /**
+ * Gets the type of the event, which is "Lab".
+ *
+ * @return The event type.
+ */
+ @Override
+ public String getEventType() {
+ return EVENT_TYPE;
+ }
+
+ /**
+ * Checks if this Lab object is equal to another Event object by comparing their common attributes.
+ *
+ * @param event The event to compare with.
+ * @return true if the events are equal, false otherwise.
+ */
+ @Override
+ public boolean equals(Event event) {
+ boolean isSameEvent = super.equals(event);
+
+ if (!isSameEvent) {
+ return false;
+ }
+
+ if (!this.getEventType().equals(event.getEventType())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generates a string representation of this Lab, including module code, event type, and time range.
+ *
+ * @return The string representation of this Lab.
+ */
+ @Override
+ public String toString() {
+ return super.toString() + " " + getEventType() + " " + getTime(getStartTime(), getDuration());
+ }
+
+ /**
+ * Generates a string representation of this Lab for saving purposes, including module code,
+ * event type, start time, duration, and day.
+ *
+ * @return The string representation of this Lab for saving.
+ */
+ @Override
+ public String toSave() {
+ return super.toSave() + " " + getEventType() + " " + getStartTime() + " " + getDuration() + " " + getDay();
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Lecture.java b/src/main/java/seedu/duke/models/schema/Lecture.java
new file mode 100644
index 0000000000..48d78982ba
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Lecture.java
@@ -0,0 +1,74 @@
+package seedu.duke.models.schema;
+
+//@@author janelleenqi
+/**
+ * Represents a Lecture that extends the Event class.
+ */
+public class Lecture extends Event{
+
+ private static final String EVENT_TYPE = "Lecture";
+
+ /**
+ * Constructs a Lecture object with the specified day, start time, duration, and module code.
+ *
+ * @param day The day of the lab session. Must not be null.
+ * @param startTime The start time of the lab session.
+ * @param duration The duration of the lab session.
+ * @param moduleCode The module code associated with the lab session. Must not be null.
+ */
+ public Lecture(String day, int startTime, int duration, String moduleCode) {
+ super(day, startTime, duration, moduleCode);
+ }
+
+ /**
+ * Gets the type of the event, which is "Lecture".
+ *
+ * @return The event type.
+ */
+ @Override
+ public String getEventType() {
+ return EVENT_TYPE;
+ }
+
+ /**
+ * Checks if this Lecture object is equal to another Event object by comparing their common attributes.
+ *
+ * @param event The event to compare with.
+ * @return true if the events are equal, false otherwise.
+ */
+ @Override
+ public boolean equals(Event event) {
+ boolean isSameEvent = super.equals(event);
+
+ if (!isSameEvent) {
+ return false;
+ }
+
+ if (!this.getEventType().equals(event.getEventType())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generates a string representation of this Lecture, including module code, event type, and time range.
+ *
+ * @return The string representation of this Lecture.
+ */
+ @Override
+ public String toString() {
+ return super.toString() + " " + getEventType() + " " + getTime(getStartTime(), getDuration());
+ }
+
+ /**
+ * Generates a string representation of this Lecture for saving purposes, including module code,
+ * event type, start time, duration, and day.
+ *
+ * @return The string representation of this Lecture for saving.
+ */
+ @Override
+ public String toSave() {
+ return super.toSave() + " " + getEventType() + " " + getStartTime() + " " + getDuration() + " " + getDay();
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Major.java b/src/main/java/seedu/duke/models/schema/Major.java
new file mode 100644
index 0000000000..ab00f884e1
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Major.java
@@ -0,0 +1,10 @@
+package seedu.duke.models.schema;
+
+//@@author SebasFok
+/**
+ * The `Major` enum represents different majors that can be selected by our application.
+ *
+ */
+public enum Major {
+ CEG, CS
+}
diff --git a/src/main/java/seedu/duke/models/schema/Module.java b/src/main/java/seedu/duke/models/schema/Module.java
new file mode 100644
index 0000000000..dfe4778503
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Module.java
@@ -0,0 +1,109 @@
+package seedu.duke.models.schema;
+
+import org.json.simple.JSONObject;
+
+import java.io.IOException;
+
+import static seedu.duke.models.logic.Api.getFullModuleInfo;
+import static seedu.duke.utils.errors.HttpError.displaySocketError;
+
+public class Module {
+ //module class requires Name
+ private String moduleName;
+ private String moduleDescription;
+ private String moduleCode;
+ private int moduleCredits;
+
+ private boolean isModularCreditsLoaded;
+ private boolean isCompleted;
+
+ /**
+ * Represents a module with the specified module code. This class fetches module information
+ * using the NUSMods API and stores details such as the module description, name, credits, and completion status.
+ *
+ * @param moduleCode The code of the module.
+ */
+ public Module(String moduleCode) throws RuntimeException{
+ if (moduleCode.isEmpty()) {
+ throw new NullPointerException();
+ }
+ this.moduleCode = moduleCode;
+ this.moduleCredits = 4;
+ this.isModularCreditsLoaded = false;
+ }
+
+ /**
+ * Marks this module as completed.
+ */
+ public void markModuleAsCompleted() {
+ this.isCompleted = true;
+ }
+
+ public boolean getCompletionStatus() {
+ return isCompleted;
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Retrieves the modular credits for a module.
+ * This method fetches the modular credits for a module by calling the NUSMods API.
+ * If the modular credits have already been loaded, it returns the cached value.
+ * If not, it makes an API call to get the full module information and extracts the modular credits.
+ * In case of a ClassCastException (edge case when module has no credits), a default value of 4 credits is set.
+ * Handles IOException by displaying a socket error message.
+ *
+ * @return The number of modular credits for the module.
+ *
+ */
+ public int getModuleCredits() {
+ if(this.isModularCreditsLoaded){
+ return this.moduleCredits;
+ }
+ try {
+ JSONObject response = getFullModuleInfo(moduleCode);
+ assert response != null: "Response from NUSMods API is null";
+ assert !response.isEmpty(): "Response Object is empty";
+ this.moduleCredits = Integer.parseInt((String) response.get("moduleCredit"));
+ this.isModularCreditsLoaded = true;
+ }catch (ClassCastException e){
+ System.out.println("Sorry there was issue retrieving the MCs");
+ return -1;
+ }catch (IOException e){
+ displaySocketError();
+ return -1;
+ }
+ return this.moduleCredits;
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Gets the module code.
+ *
+ * @return The code of this module.
+ */
+ public String getModuleCode() {
+ return this.moduleCode;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Checks if this module is equal to another module by comparing their module codes.
+ *
+ * @param module The module to compare with.
+ * @return true if the modules have the same module code, false otherwise.
+ */
+ public boolean equals(Module module) {
+ return this.moduleCode.equals(module.moduleCode);
+ }
+
+ //@@author janelleenqi
+ /**
+ * Generates a string representation of this module, which is its module code.
+ *
+ * @return The string representation of this module.
+ */
+ @Override
+ public String toString() {
+ return this.moduleCode;
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/ModuleList.java b/src/main/java/seedu/duke/models/schema/ModuleList.java
new file mode 100644
index 0000000000..58164809ca
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/ModuleList.java
@@ -0,0 +1,251 @@
+package seedu.duke.models.schema;
+
+import java.io.InvalidObjectException;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+//@@author janelleenqi
+/**
+ * The ModuleList class represents a list of modules and provides various methods for managing modules.
+ * It includes functionality for adding, deleting, and retrieving modules from the list.
+ */
+public class ModuleList {
+ private ArrayList mainModuleList;
+
+ /**
+ * Constructs an empty ModuleList.
+ */
+ public ModuleList() {
+ mainModuleList = new ArrayList();
+ }
+
+ /**
+ * Constructs a ModuleList from a space-separated string of moduleCodes.
+ *
+ * @param moduleCodes A space-separated string of module codes.
+ */
+ public ModuleList(String moduleCodes) {
+ this();
+ if (moduleCodes == null || moduleCodes.isEmpty()) {
+ return;
+ }
+
+ String[] moduleArray = moduleCodes.split(" ");
+
+ for (String moduleCode : moduleArray) {
+ try {
+ mainModuleList.add(new Module(moduleCode));
+
+ } catch (NullPointerException e) {
+ //fail
+ System.out.println("null pointer");
+ }
+ }
+ }
+
+ /**
+ * Retrieves the list of modules.
+ *
+ * @return The ArrayList containing the modules.
+ */
+ public ArrayList getMainModuleList() {
+ assert mainModuleList != null: "null mainModuleList";
+ return mainModuleList;
+ }
+
+ /**
+ * Retrieves the list of module codes from the modules in the mainModuleList.
+ *
+ * @return The ArrayList containing module codes.
+ */
+ public ArrayList getModuleCodes() {
+ ArrayList moduleCodes = new ArrayList<>();
+ for (Module module: mainModuleList){
+ moduleCodes.add(module.getModuleCode());
+ }
+ return moduleCodes;
+ }
+
+ /**
+ * Retrieves the list of module codes for completed modules from the main module list.
+ *
+ * @return The ArrayList containing module codes of completed modules.
+ */
+ public ArrayList getCompletedModuleCodes(){
+ ArrayList completedModuleCodes = new ArrayList<>();
+ for (Module module: mainModuleList){
+ if (module.getCompletionStatus()) {
+ completedModuleCodes.add(module.getModuleCode());
+ }
+ }
+ return completedModuleCodes;
+ }
+
+
+ /**
+ * Creates and returns a new HashMap containing completed modules from the main module list, indexed by module code.
+ *
+ * @return A HashMap containing completed modules indexed by module code.
+ */
+ public HashMap newHashMapOfCompleted(){
+ HashMap completedModules = new HashMap();
+
+ // add modules to HashMap for easy retrieval
+ for (Module module: mainModuleList){
+ if (module.getCompletionStatus()) {
+ completedModules.put(module.getModuleCode(), module);
+ }
+ }
+ return completedModules;
+ }
+
+ /**
+ * Adds a module to the main module list.
+ *
+ * @param module The module to be added.
+ */
+ public void addModule (Module module) {
+ mainModuleList.add(module);
+ }
+
+ /**
+ * Adds a module to the main module list at the specified index.
+ *
+ * @param index The index at which the module should be added.
+ * @param module The module to be added.
+ */
+ public void addModule (int index, Module module) {
+ mainModuleList.add(index, module);
+ }
+
+ public void replaceModule (int index, Module module) {
+ mainModuleList.set(index, module);
+ }
+
+ /**
+ * Deletes a module from the main module list.
+ *
+ * @param module The module to be deleted.
+ */
+ public void deleteModule (Module module) {
+ mainModuleList.remove(module);
+ }
+
+ /**
+ * Deletes a module from the main module list by its module code.
+ *
+ * @param moduleCode The module code of the module to be deleted.
+ */
+ public void deleteModuleByCode (String moduleCode) {
+ try {
+ Module moduleToBeDeleted = getModule(moduleCode);
+ deleteModule(moduleToBeDeleted);
+ } catch (InvalidObjectException e) {
+ return;
+ }
+ }
+
+ /**
+ * Checks if a module exists in the main module list.
+ *
+ * @param module The module to check for existence.
+ * @return true if the module exists, false otherwise.
+ */
+ public boolean exists(Module module) {
+ if (mainModuleList == null) {
+ return false;
+ }
+
+ assert module != null;
+
+ for (Module moduleB : mainModuleList) {
+ if (module.equals(moduleB)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a module with the specified module code exists in the main module list.
+ *
+ * @param moduleCodeA The module code to check for existence.
+ * @return true if the module with the specified code exists, false otherwise.
+ * @throws InvalidObjectException If the main module list is null or the provided module code is null.
+ */
+ public boolean existsByCode(String moduleCodeA) throws InvalidObjectException {
+ if (mainModuleList == null) {
+ throw new InvalidObjectException("Null Module List");
+ }
+
+ if (moduleCodeA == null) {
+ throw new InvalidObjectException("Null Module Code");
+ }
+
+ for (Module moduleB : mainModuleList) {
+ if (moduleCodeA.equals(moduleB.getModuleCode())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves a module from the main module list by its module code.
+ *
+ * @param moduleCode The module code to search for.
+ * @return The module with the specified module code.
+ * @throws InvalidObjectException If the module does not exist.
+ */
+ public Module getModule(String moduleCode) throws InvalidObjectException {
+ for (Module module: mainModuleList) {
+ if (moduleCode.equals(module.getModuleCode())) {
+ return module;
+ }
+ }
+ throw new InvalidObjectException("Module does not exist, please add it in your schedule.");
+ }
+
+
+ /**
+ * Gets the size of the main module list.
+ *
+ * @return The number of modules in the list.
+ */
+ public int size() {
+ return mainModuleList.size();
+ }
+
+
+ /**
+ * Finds the index of a module in the main module list by its module code.
+ *
+ * This method searches for a module in the main module list based on its module code.
+ * If the module code is found in the list, it returns the index at which the module
+ * is located. If the module code is not found, it returns -1 to indicate that the module
+ * is not present in the list.
+ *
+ * @param moduleCode The module code to search for.
+ * @return The index of the module in the list if found, or -1 if not found.
+ */
+ public int getIndexByString(String moduleCode) {
+ int i = 0;
+ for (Module module: mainModuleList){
+ if (moduleCode.equals(module.getModuleCode())) {
+ return i;
+ }
+ i++;
+ }
+ return -1;
+ }
+
+ /**
+ * Retrieves a module from the main module list by its index.
+ *
+ * @param index The index of the module to retrieve.
+ * @return The module at the specified index.
+ */
+ public Module getModuleByIndex(int index) {
+ return this.mainModuleList.get(index);
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/ModuleWeekly.java b/src/main/java/seedu/duke/models/schema/ModuleWeekly.java
new file mode 100644
index 0000000000..34fff88796
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/ModuleWeekly.java
@@ -0,0 +1,166 @@
+package seedu.duke.models.schema;
+
+import seedu.duke.utils.errors.UserError;
+
+import java.util.ArrayList;
+
+public class ModuleWeekly extends Module {
+
+ private String moduleCode;
+ private int lectureTime;
+ private int tutorialTime;
+ private int labTime;
+ private int lectureDuration;
+ private int labDuration;
+ private int tutorialDuration;
+
+ private String day;
+ private ArrayList lessons = new ArrayList();
+
+ public ModuleWeekly(String moduleCode, int lectureTime, int tutorialTime,
+ int labTime) throws NullPointerException, RuntimeException {
+ super(moduleCode);
+ this.lectureTime = lectureTime;
+ this.tutorialTime = tutorialTime;
+ this.labTime = labTime;
+ }
+
+ public ModuleWeekly(String moduleCode) {
+ super(moduleCode);
+ this.moduleCode = moduleCode;
+ this.lectureTime = 8;
+ this.labTime = 7;
+ this.lectureDuration = 1;
+ this.tutorialTime = 1;
+ this.labDuration = 1;
+ }
+
+ public void printModuleWeekly(ModuleWeekly moduleWeekly) {
+ System.out.println("lect time: " + moduleWeekly.getLectureTime());
+ System.out.println("tut time: " + moduleWeekly.getTutorialTime());
+ System.out.println("lab time: "+ moduleWeekly.getLabTime());
+ }
+
+ public String getModuleCode() {
+ return moduleCode;
+ }
+ public int getLectureTime() {
+ return lectureTime;
+ }
+
+ public String getDay() {
+ return day;
+ }
+ public int getTutorialTime() {
+ return tutorialTime;
+ }
+
+ public int getLabTime() {
+ return labTime;
+ }
+
+
+ public void setDay(String day) {
+ this.day = day;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Checks if a specific event exists in this ModuleWeekly.
+ *
+ * @param newEvent The event to check for existence.
+ * @return true if the event exists, false otherwise.
+ */
+ public boolean exists(Event newEvent) {
+ for (Event existingEvent : lessons) {
+ if (newEvent.equals(existingEvent)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if there are any lessons in the weekly schedule.
+ *
+ * @return true if there are lessons, false otherwise.
+ */
+ public boolean haveLessons() {
+ return !lessons.isEmpty();
+ }
+
+ /**
+ * Checks if an event can be added to the timetable and adds it if possible.
+ *
+ * @param event The event to add.
+ * @return true if the event can be added, false otherwise.
+ */
+ public boolean canAddToTimetable(Event event) {
+ if (this.exists(event)) {
+ UserError.displayLessonAlreadyAdded(event);
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Adds a lecture to the weekly timetable.
+ *
+ * @param day The day of the lecture.
+ * @param time The time of the lecture.
+ * @param duration The duration of the lecture.
+ */
+ public void addLecture(String day, int time, int duration) {
+ Event newLecture = new Lecture(day, time, duration, moduleCode);
+ if (canAddToTimetable(newLecture)) {
+ lessons.add(newLecture);
+ }
+ }
+
+ /**
+ * Adds a tutorial to the weekly timetable.
+ *
+ * @param day The day of the tutorial.
+ * @param time The time of the tutorial.
+ * @param duration The duration of the tutorial.
+ */
+ public void addTutorial(String day, int time, int duration) {
+ Event newTutorial = new Tutorial(day, time, duration, moduleCode);
+ if (canAddToTimetable(newTutorial)) {
+ lessons.add(newTutorial);
+ }
+ }
+
+ /**
+ * Adds a lab to the weekly timetable.
+ *
+ * @param day The day of the tutorial.
+ * @param time The time of the tutorial.
+ * @param duration The duration of the tutorial.
+ */
+ public void addLab(String day, int time, int duration) {
+ Event newLab = new Lab(day, time, duration, moduleCode);
+ if (canAddToTimetable(newLab)) {
+ lessons.add(newLab);
+ }
+ }
+
+
+ /**
+ * Clears all lessons from the weekly timetable.
+ */
+ public void clearLessons() {
+ lessons.clear();
+ }
+
+
+ /**
+ * Gets the ArrayList of events representing the weekly timetable.
+ *
+ * @return The ArrayList of events.
+ */
+ public ArrayList getWeeklyTimetable() {
+ return lessons;
+ }
+}
+
diff --git a/src/main/java/seedu/duke/models/schema/Schedule.java b/src/main/java/seedu/duke/models/schema/Schedule.java
new file mode 100644
index 0000000000..c30f2a48b8
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Schedule.java
@@ -0,0 +1,572 @@
+package seedu.duke.models.schema;
+
+import seedu.duke.utils.exceptions.MandatoryPrereqException;
+import seedu.duke.utils.exceptions.FailPrereqException;
+import seedu.duke.utils.exceptions.MissingModuleException;
+import seedu.duke.utils.exceptions.InvalidPrereqException;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Queue;
+import java.util.LinkedList;
+import static seedu.duke.models.logic.Prerequisite.getModulePrereqBasedOnCourse;
+import static seedu.duke.models.logic.Api.getModuleFulfilledRequirements;
+import static seedu.duke.models.logic.Prerequisite.satisfiesAllPrereq;
+import static seedu.duke.storage.StorageManager.getRequirements;
+import static seedu.duke.views.SemesterPlannerView.printSemesterPlanner;
+
+/**
+ * The `Schedule` class represents a student's course schedule.
+ * It allows a student to manage and manipulate their enrolled modules across multiple semesters.
+ */
+public class Schedule {
+
+ private static final int MAXIMUM_SEMESTERS = 8;
+ protected int[] modulesPerSem;
+ private ModuleList modulesPlanned;
+
+ private HashMap completedModules;
+
+ //DO NOT USE PREREQ MAP, ONLY USED FOR OPTIMISING RECOMMENDED SCHEDULE FUNCTION
+ private HashMap> prereqMap;
+
+
+
+ /**
+ * Constructs a Schedule object with the given planned modules and the target distribution of modules per semester.
+ * Initializes the completedModules map to store completed modules.
+ *
+ * @param modulesPlanned The list of modules that are planned for the schedule.
+ * @param modulesPerSem An array representing the target number of modules to be taken in each semester.
+ */
+ public Schedule(ModuleList modulesPlanned, int[] modulesPerSem) {
+ this.modulesPerSem = modulesPerSem;
+ this.modulesPlanned = modulesPlanned;
+ completedModules = new HashMap();
+ }
+
+ /**
+ * Constructs a new `Schedule` with the provided modules and distribution across semesters.
+ *
+ * @param modules A string containing module codes representing the student's schedule.
+ * @param modulesPerSem An array indicating the distribution of modules across semesters.
+ */
+ public Schedule(String modules, int[] modulesPerSem) {
+ this.modulesPerSem = modulesPerSem;
+ modulesPlanned = new ModuleList(modules);
+ completedModules = new HashMap();
+ }
+
+ /**
+ * Constructs a new, empty `Schedule` with no modules and a default semester distribution.
+ */
+ public Schedule() {
+ modulesPerSem = new int[]{0, 0, 0, 0, 0, 0, 0, 0};
+ modulesPlanned = new ModuleList();
+ completedModules = new HashMap();
+ }
+
+ /**
+ * Retrieves the maximum number of semesters allowed in a student's course schedule.
+ *
+ * @return The maximum number of semesters allowed.
+ */
+ public static int getMaximumSemesters() {
+ return MAXIMUM_SEMESTERS;
+ }
+
+ /**
+ * Retrieves the ModuleList for modules planned in the schedule.
+ *
+ * @return The ModuleList containing the planned modules.
+ */
+ public ModuleList getModulesPlanned() {
+ return modulesPlanned;
+ }
+
+ public int[] getModulesPerSem() {
+ return modulesPerSem;
+ };
+
+ //@@author ryanlohyr
+ /**
+ * Adds a recommended schedule list to the current schedule, updating completion statuses if needed.
+ * This method adds a list of recommended schedule modules to the current schedule. You can choose to
+ * either keep or clear the completion statuses of modules. The recommended schedule modules are added
+ * to the schedule, taking into account prerequisites and distributing them across semesters based on
+ * fulfillment of prerequisites.
+ *
+ *
+ * @param scheduleToAdd The list of recommended schedule modules to add.
+ */
+ public void addReccToSchedule(ArrayList scheduleToAdd) {
+
+ final int modsToAddPerSem = 5;
+ int currentIndexOfMod = 0;
+ int currentSem = 1;
+
+ modulesPlanned = new ModuleList();
+ modulesPerSem = new int[]{0, 0, 0, 0, 0, 0, 0, 0};
+
+ for (String module : scheduleToAdd) {
+ // Check if the module fulfill pre req, else we move it to next sem
+ int indexToAdd = 0;
+ for (int i = 1; i < currentSem; i++) {
+ indexToAdd += this.modulesPerSem[i - 1];
+ }
+
+ //Sub list as we only want modules before the current target semester
+ List currentSemestersModules = scheduleToAdd.subList(indexToAdd, indexToAdd + currentIndexOfMod);
+ ArrayList currModulesPrereq = prereqMap.get(module);
+
+ //now we check if the modules prereq is contained on current line
+ for(String currModule:currentSemestersModules){
+ if(currModulesPrereq.contains(currModule)){
+ currentSem += 1;
+ currentIndexOfMod = 0;
+ }
+ }
+
+ try {
+ addModuleWithoutCheckingPrereq(module, currentSem);
+ } catch (InvalidObjectException | IllegalArgumentException e){
+ throw new RuntimeException(e);
+ }
+
+ currentIndexOfMod += 1;
+ if (currentIndexOfMod >= modsToAddPerSem){
+ currentIndexOfMod = 0;
+ currentSem += 1;
+ }
+ if (currentSem > 8) {
+ return;
+ }
+ }
+ }
+
+ //@@author SebasFok
+ /**
+ * Adds a module to the schedule for a specified semester.
+ *
+ * @param moduleCode The module code to be added.
+ * @param targetSem The target semester (an integer from 1 to 8) in which to add the module.
+ * @throws IllegalArgumentException If the provided semester is out of the valid range (1 to 8),
+ * or if the module already exists in the schedule, or if the module is not valid.
+ * @throws InvalidObjectException If the module is null.
+ * @throws FailPrereqException If the prerequisites for the module are not satisfied
+ */
+ public void addModule(String moduleCode, int targetSem) throws IllegalArgumentException, InvalidObjectException,
+ FailPrereqException {
+
+ if (targetSem < 1 || targetSem > MAXIMUM_SEMESTERS) {
+ throw new IllegalArgumentException("Please select an integer from 1 to 8 for semester selection");
+ }
+
+ try {
+ if (modulesPlanned.existsByCode(moduleCode)) {
+ throw new IllegalArgumentException("Module already exists in the schedule");
+ }
+ } catch (InvalidObjectException e) {
+ throw new InvalidObjectException("Module cannot be null");
+ }
+
+ int indexToAdd = 0;
+ int startIndexOfSem = 0;
+ for (int i = 0; i < targetSem; i++) {
+ startIndexOfSem = indexToAdd;
+ indexToAdd += this.modulesPerSem[i];
+ }
+
+ //Sub list as we only want modules before the current target semester
+
+ List partialModulesPlannedArray = modulesPlanned.getModuleCodes().subList(0, (startIndexOfSem));
+ ModuleList partialModulesPlanned = new ModuleList(String.join(" ", partialModulesPlannedArray));
+
+ try {
+ if (satisfiesAllPrereq(moduleCode, partialModulesPlanned)) {
+ //module initialization will be here
+ Module newModule = completedModules.get(moduleCode);
+ if (newModule == null) {
+ newModule = new Module(moduleCode);
+ }
+ modulesPlanned.addModule(indexToAdd, newModule);
+ modulesPerSem[targetSem - 1] += 1;
+
+ return;
+ }
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Please select a valid module");
+ }
+ throw new FailPrereqException("Unable to add module as prerequisites not satisfied for: " + moduleCode);
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Deletes a module from the schedule by its module code.
+ *
+ * @param module The module code to be deleted from the schedule.
+ * @throws MandatoryPrereqException If the module to be deleted is a prerequisite for other modules in the schedule.
+ * @throws MissingModuleException If the provided module code is not valid, the module is not in the schedule
+ */
+ public void deleteModule(String module) throws MandatoryPrereqException, MissingModuleException, IOException {
+
+ int targetIndex = modulesPlanned.getIndexByString(module);
+
+ if (targetIndex == -1) {
+ throw new MissingModuleException("Module does not exist in schedule");
+ }
+
+ int targetSem = 1;
+ int moduleCount = modulesPerSem[0];
+
+ while ((moduleCount - 1) < targetIndex) {
+ moduleCount += modulesPerSem[targetSem];
+ targetSem += 1;
+ }
+
+ ArrayList requirementsFulfilledFromModule = getModuleFulfilledRequirements(module);
+
+ List modulesAheadArray;
+ int lastModuleIndex = modulesPlanned.getMainModuleList().size() - 1;
+ int nextSemStartingIndex = moduleCount;
+
+ try {
+ modulesAheadArray = modulesPlanned.getModuleCodes().subList(nextSemStartingIndex, lastModuleIndex + 1);
+ } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
+ modulesAheadArray = new ArrayList<>();
+ }
+
+ for(String fulfilledModule: requirementsFulfilledFromModule ){
+ //over here we check if the semesters in front of us contain a module in fulfilled module
+ if(modulesAheadArray.contains(fulfilledModule)){
+ throw new MandatoryPrereqException("Unable to delete module. " +
+ "This module is a mandatory prerequisite for " + fulfilledModule);
+ }
+ }
+
+ modulesPlanned.deleteModuleByCode(module);
+
+ modulesPerSem[targetSem - 1] -= 1;
+
+ }
+
+ //@@author SebasFok
+ /**
+ * Shifts a module within the student's planned schedule to a different semester.
+ *
+ * @param module The module code to be shifted.
+ * @param targetSem The target semester to which the module will be shifted.
+ * @throws IllegalArgumentException If the target semester is not within the valid range (1 to 8).
+ * @throws MissingModuleException If the module to be shifted does not exist in the schedule.
+ * @throws IllegalArgumentException If the module is already in the target semester.
+ * @throws FailPrereqException If shifting the module fails due to unsatisfied prerequisites.
+ * @throws MandatoryPrereqException If shifting the module creates a mandatory prerequisite conflict.
+ * @throws InvalidObjectException If the module does not exist in the schedule.
+ * @throws IOException If an IO error occurs during module shifting.
+ */
+ public void shiftModule(String module, int targetSem) throws IllegalArgumentException,
+ FailPrereqException, MandatoryPrereqException, MissingModuleException, IOException {
+
+ if (targetSem < 1 || targetSem > MAXIMUM_SEMESTERS) {
+ throw new IllegalArgumentException("Please select an integer from 1 to 8 for semester selection");
+ }
+
+ int originalIndex = modulesPlanned.getIndexByString(module);
+
+ if (originalIndex == -1) {
+ throw new MissingModuleException("Module does not exist in schedule");
+ }
+
+ int originalSem = 1;
+ int moduleCount = modulesPerSem[0];
+
+ while ((moduleCount - 1) < originalIndex) {
+ moduleCount += modulesPerSem[originalSem];
+ originalSem += 1;
+ }
+
+ int indexToAdd = 0;
+ for (int i = 1; i < targetSem; i++) {
+ indexToAdd += this.modulesPerSem[i - 1];
+ }
+
+ // User input sem that module is already in
+ if (originalSem == targetSem) {
+ throw new IllegalArgumentException("Module is already in semester " + targetSem);
+ }
+
+ // If shifting module earlier
+ if (originalSem > targetSem) {
+
+ shiftModuleEarlier(module, targetSem, indexToAdd, originalSem);
+ return;
+ }
+
+ // If shifting module later
+
+ shiftModuleLater(module, targetSem, indexToAdd, originalSem);
+ }
+
+ //@@author SebasFok
+ /**
+ * Shifts a module later within the student's planned schedule to a different semester.
+ *
+ * @param module The module code to be shifted later.
+ * @param targetSem The target semester to which the module will be shifted.
+ * @param indexToAdd The index at which the module will be added in the schedule.
+ * @param originalSem The original semester in which the module is currently placed.
+ * @throws IOException If an IO error occurs during module shifting.
+ * @throws MandatoryPrereqException If shifting the module later creates a mandatory prerequisite conflict.
+ */
+ private void shiftModuleLater(String module, int targetSem, int indexToAdd, int originalSem) throws IOException,
+ MandatoryPrereqException {
+ ArrayList requirementsFulfilledFromModule = getModuleFulfilledRequirements(module);
+
+ List modulesAheadArray;
+
+ int modulesAheadFromIndex = 0;
+ int modulesAheadToIndex= 0;
+
+ for (int i = 1; i < originalSem + 1; i++) {
+ modulesAheadFromIndex += this.modulesPerSem[i - 1];
+ }
+
+ for (int i = 1; i < targetSem + 1; i++) {
+ modulesAheadToIndex += this.modulesPerSem[i - 1];
+ }
+
+ try {
+ modulesAheadArray = modulesPlanned.getModuleCodes()
+ .subList(modulesAheadFromIndex, modulesAheadToIndex);
+ } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
+ modulesAheadArray = new ArrayList<>();
+ }
+
+ for(String fulfilledModule: requirementsFulfilledFromModule ){
+ //over here we check if the semesters in front of us contain a module in fulfilled module
+ if(modulesAheadArray.contains(fulfilledModule)){
+ throw new MandatoryPrereqException("Unable to shift module. " +
+ "This module is a mandatory prerequisite for " + fulfilledModule);
+ }
+ }
+
+ //module shifting will be here
+
+ Module moduleToBeShifted = getModule(module);
+
+ modulesPlanned.deleteModule(moduleToBeShifted);
+ modulesPerSem[originalSem - 1] -= 1;
+
+ modulesPlanned.addModule(indexToAdd - 1, moduleToBeShifted);
+ modulesPerSem[targetSem - 1] += 1;
+ }
+
+ //@@author SebasFok
+ /**
+ * Shifts a module earlier within the student's planned schedule to a different semester.
+ *
+ * @param module The module code to be shifted earlier.
+ * @param targetSem The target semester to which the module will be shifted.
+ * @param indexToAdd The index at which the module will be added in the schedule.
+ * @param originalSem The original semester in which the module is currently placed.
+ * @throws InvalidObjectException If the module does not exist in the schedule.
+ * @throws FailPrereqException If shifting the module earlier fails due to unsatisfied prerequisites.
+ */
+ private void shiftModuleEarlier(String module, int targetSem, int indexToAdd, int originalSem)
+ throws InvalidObjectException, FailPrereqException {
+ //Sub list as we only want modules before the current target semester
+ List plannedModulesArray = modulesPlanned.getModuleCodes().subList(0, indexToAdd);
+ ModuleList plannedModules = new ModuleList(String.join(" ", plannedModulesArray));
+
+ try {
+ if (satisfiesAllPrereq(module, plannedModules)) {
+ //module shifting will be here
+
+ Module moduleToBeShifted = getModule(module);
+
+ modulesPlanned.deleteModule(moduleToBeShifted);
+ modulesPerSem[originalSem - 1] -= 1;
+
+ modulesPlanned.addModule(indexToAdd, moduleToBeShifted);
+ modulesPerSem[targetSem - 1] += 1;
+ return;
+ }
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Please select a valid module");
+ } catch (InvalidObjectException e) {
+ throw new InvalidObjectException("Module does not exist in the schedule.");
+ }
+ throw new FailPrereqException("Unable to shift module as prerequisites will not be satisfied for: "
+ + module);
+ }
+
+ //@@author janelleenqi
+ /**
+ * Retrieves a specific module from the planned modules based on its module code.
+ *
+ * @param moduleCode The module code of the module to retrieve.
+ * @return The Module object with the specified module code.
+ * @throws InvalidObjectException If the module with the given code is not found.
+ */
+ public Module getModule(String moduleCode) throws InvalidObjectException {
+ return modulesPlanned.getModule(moduleCode);
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Completes the given module, checking prerequisites if applicable.
+ * @param module The module to be completed.
+ * @param modulePrereq The list of prerequisites for the module.
+ * @throws FailPrereqException If prerequisites are not met.
+ * @throws InvalidObjectException If the module is invalid.
+ */
+ public void completeModule(Module module, ArrayList modulePrereq) throws
+ FailPrereqException,
+ InvalidObjectException {
+
+ String moduleCode = module.getModuleCode();
+ int targetIndex = modulesPlanned.getIndexByString(moduleCode);
+ int targetSem = 1;
+ int moduleCount = modulesPerSem[0];
+
+ // finding the index of the target module
+ while ((moduleCount - 1) < targetIndex) {
+ moduleCount += modulesPerSem[targetSem];
+ targetSem += 1;
+ }
+
+ //Array of modules that are before target module
+ List partialModulesPlannedArray = modulesPlanned.getModuleCodes().subList(0, (moduleCount));
+
+ //if there are no prerequisites, we can just mark the module as complicated
+ if(modulePrereq == null){
+ module.markModuleAsCompleted();
+ return;
+ }
+
+ for(String currModule: modulePrereq){
+ if(partialModulesPlannedArray.contains(currModule) && !getModule(currModule).getCompletionStatus()){
+ throw new FailPrereqException(moduleCode);
+ }
+ }
+ module.markModuleAsCompleted();
+ }
+
+ /**
+ * Adds a module to the schedule for a specified semester.
+ *
+ * @param moduleCode The module code to be added.
+ * @param targetSem The target semester (an integer from 1 to 8) in which to add the module.
+ * @throws IllegalArgumentException If the provided semester is out of the valid range (1 to 8),
+ * or if the module already exists in the schedule, or if the module is not valid.
+ * @throws InvalidObjectException If the module is null.
+ * @throws FailPrereqException If the prerequisites for the module are not satisfied
+ */
+ public void addModuleWithoutCheckingPrereq(String moduleCode, int targetSem)
+ throws InvalidObjectException, IllegalArgumentException {
+
+ if (targetSem < 1 || targetSem > MAXIMUM_SEMESTERS) {
+ throw new IllegalArgumentException("Please select an integer from 1 to 8 for semester selection");
+ }
+
+ try {
+ if (modulesPlanned.existsByCode(moduleCode)) {
+ throw new IllegalArgumentException("Module already exists in the schedule");
+ }
+ } catch (InvalidObjectException e) {
+ throw new InvalidObjectException("Module cannot be null");
+ }
+
+ int indexToAdd = 0;
+ for (int i = 1; i < targetSem; i++) {
+ indexToAdd += this.modulesPerSem[i - 1];
+ }
+
+ //reuse module data if existed
+ Module module = completedModules.get(moduleCode);
+ if (module != null) {
+ modulesPlanned.addModule(indexToAdd, module);
+ modulesPerSem[targetSem - 1] += 1;
+ return;
+ }
+
+ modulesPlanned.addModule(indexToAdd, new Module(moduleCode));
+ modulesPerSem[targetSem - 1] += 1;
+ }
+
+ /**
+ * Prints the student's course schedule, displaying modules organized by semesters.
+ */
+
+ public void printMainModuleList() {
+ printSemesterPlanner(modulesPerSem, modulesPlanned);
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Generates a recommended schedule for a given course based on its requirements and prerequisites.
+ *
+ * @param course The course for which to generate a recommended schedule.
+ * @return An ArrayList of strings representing the recommended schedule in order of completion.
+ */
+ public ArrayList generateRecommendedSchedule(String course) throws IOException {
+ ArrayList requirements = getRequirements(course);
+ HashMap degreeMap = new HashMap<>();
+ Queue q = new LinkedList<>();
+ ArrayList schedule = new ArrayList<>();
+ HashMap> adjacencyList = new HashMap<>();
+ this.prereqMap = new HashMap<>();
+ //initialisation
+ for(String requirement: requirements) {
+ adjacencyList.put(requirement, new ArrayList<>());
+ degreeMap.put(requirement, 0);
+ }
+
+ for (String requirement : requirements) {
+ ArrayList prereqArray;
+ try{
+ prereqArray = getModulePrereqBasedOnCourse(requirement, course);
+ } catch (InvalidPrereqException e){
+ prereqArray = new ArrayList<>();
+ }
+ if (prereqArray == null) {
+ prereqArray = new ArrayList<>();
+ }
+
+ prereqMap.put(requirement, prereqArray);
+
+ //we need to create an adjacency list to add all the connections
+ // from pre req -> item
+ for (String s : prereqArray) {
+ adjacencyList.get(s).add(requirement);
+ Integer value = degreeMap.get(requirement) + 1;
+ degreeMap.put(requirement, value);
+ }
+ }
+
+ for (String key : degreeMap.keySet()) {
+ Integer value = degreeMap.get(key);
+ if(value == 0){
+ q.offer(key);
+ }
+ }
+
+ while(!q.isEmpty()){
+ String curr = q.poll();
+ schedule.add(curr);
+ ArrayList currReq = adjacencyList.get(curr);
+ for (String s : currReq) {
+ int num = degreeMap.get(s) - 1;
+ degreeMap.put(s, num);
+ if (num == 0) {
+ q.offer(s);
+ }
+ }
+ }
+
+ return schedule;
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Student.java b/src/main/java/seedu/duke/models/schema/Student.java
new file mode 100644
index 0000000000..04929a8c51
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Student.java
@@ -0,0 +1,508 @@
+package seedu.duke.models.schema;
+
+import seedu.duke.controllers.ModuleServiceController;
+import seedu.duke.utils.exceptions.InvalidModifyArgumentException;
+import seedu.duke.utils.exceptions.FailPrereqException;
+import seedu.duke.utils.exceptions.InvalidPrereqException;
+import seedu.duke.utils.exceptions.MandatoryPrereqException;
+import seedu.duke.utils.exceptions.MissingModuleException;
+import seedu.duke.utils.exceptions.TimetableUnavailableException;
+
+import seedu.duke.utils.Parser;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.util.ArrayList;
+
+import static seedu.duke.models.logic.Prerequisite.getModulePrereqBasedOnCourse;
+import static seedu.duke.storage.StorageManager.getRequirements;
+import static seedu.duke.utils.errors.HttpError.displaySocketError;
+import static seedu.duke.views.CommandLineView.displaySuccessfulCompleteMessage;
+import static seedu.duke.views.TimetableUserGuideView.addOrRecommendGuide;
+
+/**
+ * The Student class represents a student with a name, major, and module schedule.
+ */
+public class Student {
+
+ private static int counter;
+ private String name;
+ private String major;
+ private Schedule schedule;
+ private String year;
+ private int completedModuleCredits;
+ private ArrayList majorModuleCodes;
+ private ModuleList currentSemesterModules;
+ private Timetable timetable;
+
+
+ /**
+ * Constructs a student with a name, major, and module schedule.
+ *
+ * @param name The name of the student.
+ * @param major The major of the student.
+ * @param schedule The module schedule of the student.
+ */
+ public Student(String name, String major, Schedule schedule) {
+ this.name = name;
+ this.major = major;
+ this.schedule = schedule;
+ this.year = null;
+ this.timetable = Timetable.timetable;
+ counter++;
+ }
+
+ /**
+ * Constructs a student with a null name, null major, and an empty module schedule.
+ */
+ public Student() {
+ this.name = null;
+ this.major = null;
+ this.schedule = new Schedule();
+ this.timetable = Timetable.timetable;
+ this.year = null;
+ }
+
+ public static int getNumOfInstances() {
+ return counter;
+ }
+
+
+
+ /**
+ * Retrieves the module schedule of the student.
+ *
+ * @return The module schedule of the student.
+ */
+ public Schedule getSchedule() {
+ return schedule;
+ }
+
+ /**
+ * Sets the class schedule of the student.
+ *
+ * @param schedule The new module schedule.
+ */
+ public void setSchedule(Schedule schedule) {
+ this.schedule = schedule;
+ }
+
+ public int getCurrentModuleCredits() {
+ return completedModuleCredits;
+ }
+
+ /**
+ * Retrieves the name of the student.
+ *
+ * @return The name of the student.
+ */
+ public String getName() {
+ return name;
+ }
+
+ //@@author ryanlohyr
+
+ /**
+ * Sets the name of the student.
+ *
+ * @param name The new name of the student.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Retrieves the major of the student.
+ *
+ * @return The major of the student.
+ * @throws NullPointerException If the major has not been set (i.e., it is `null`).
+ */
+
+ public String getMajor() {
+ return major;
+ }
+
+ /**
+ * Sets the major of the student.
+ *
+ * @param major The new major to set.
+ */
+ public void setMajor(String major) {
+ this.major = major;
+ majorModuleCodes = getRequirements(major);
+ }
+
+ /**
+ * Sets the first major without the major command
+ * @author Isaiah Cerven
+ * @param userInput must be validated in parser as CS or CEG
+ */
+ public void setFirstMajor(String userInput) {
+ try {
+ setMajor(userInput.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ System.out.println(e);
+ }
+ }
+
+ //@@author SebasFok
+ /**
+ * Adds a module to the student's schedule for a specified semester.
+ *
+ * @param moduleCode The code of the module to be added.
+ * @param targetSem The semester in which the module will be added.
+ * @throws IllegalArgumentException If the target semester is not valid.
+ * @throws InvalidObjectException If the module code is invalid or does not exist.
+ * @throws FailPrereqException If the module cannot be added due to unsatisfied prerequisites.
+ */
+ public void addModuleToSchedule(String moduleCode, int targetSem) throws IllegalArgumentException,
+ InvalidObjectException, FailPrereqException {
+ this.schedule.addModule(moduleCode, targetSem);
+ }
+
+ public ArrayList generateRecommendedSchedule() throws IOException {
+ return this.schedule.generateRecommendedSchedule(this.major);
+ }
+
+ public void addRecommendedSchedule(ArrayList schedule){
+ this.schedule.addReccToSchedule(schedule);
+ }
+
+ /**
+ * Completes a module with the specified module code.
+ *
+ * @param moduleCode The code of the module to be completed.
+ */
+ public void completeModuleSchedule(String moduleCode) throws InvalidObjectException,
+ FailPrereqException, InvalidPrereqException {
+ try{
+ Module module = schedule.getModule(moduleCode);
+
+ ArrayList modulePrereq = getModulePrereqBasedOnCourse(moduleCode,this.getMajor());
+
+ schedule.completeModule(module,modulePrereq);
+ this.completedModuleCredits += module.getModuleCredits();
+
+ displaySuccessfulCompleteMessage();
+ } catch (IOException e) {
+ displaySocketError();
+ }
+
+
+ }
+ //@@author ryanlohyr
+ /**
+ * Deletes a module with the specified module code. This method also updates the completed
+ * module credits and removes the module from the planned modules list.
+ *
+ * @param moduleCode The code of the module to be deleted.
+ * @throws MandatoryPrereqException If deleting the module fails due to prerequisite dependencies.
+ */
+ public void deleteModuleFromSchedule(String moduleCode) throws
+ MandatoryPrereqException,
+ MissingModuleException,
+ IOException {
+ try{
+ Module module = schedule.getModule(moduleCode);
+ schedule.deleteModule(moduleCode);
+ if(module.getCompletionStatus()){
+ this.completedModuleCredits -= module.getModuleCredits();
+ }
+
+ }catch (InvalidObjectException e) {
+ throw new MissingModuleException("Module does not exist in schedule");
+ }
+ }
+
+ //@@author SebasFok
+ /**
+ * Shifts a module within the student's schedule to a different semester.
+ *
+ * @param moduleCode The code of the module to be shifted.
+ * @param targetSem The target semester to which the module will be shifted.
+ * @throws IllegalArgumentException If the target semester is not valid.
+ * @throws FailPrereqException If shifting the module fails due to unsatisfied prerequisites.
+ * @throws MissingModuleException If the module to be shifted is missing from the schedule.
+ * @throws IOException If an I/O error occurs during the shift operation.
+ * @throws MandatoryPrereqException If shifting the module violates mandatory prerequisites.
+ */
+ public void shiftModuleInSchedule(String moduleCode, int targetSem) throws IllegalArgumentException,
+ FailPrereqException, MissingModuleException, IOException, MandatoryPrereqException {
+ this.schedule.shiftModule(moduleCode, targetSem);
+ }
+
+ //@@author SebasFok
+ /**
+ * Clears all modules from the student's schedule, resetting it to an empty schedule.
+ * Also resets the completed module credits to zero.
+ *
+ */
+ public void clearAllModulesFromSchedule() {
+ //Replaces current schedule with new schedule
+ this.schedule = new Schedule();
+ this.completedModuleCredits = 0;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Retrieves a module from the schedule planner based on its module code.
+ *
+ * @param moduleCode The module code of the module to retrieve.
+ * @return The Module object with the specified module code.
+ * @throws MissingModuleException If the module with the given code is not found in the schedule.
+ */
+ public Module getModuleFromSchedule(String moduleCode) throws MissingModuleException {
+ try {
+ return schedule.getModule(moduleCode);
+ } catch (InvalidObjectException e) {
+ throw new MissingModuleException(moduleCode + " is not in Modules Planner. " +
+ "Please add the module to your schedule first!");
+ }
+ }
+
+ //@@author
+
+
+ public String getYear() {
+ return year;
+ }
+
+ public void setYear(String year) {
+ this.year = year;
+ }
+
+
+ //@@author janelleenqi
+ /**
+ * Retrieves the module codes of the modules that are left to be completed based on the major's requirements.
+ *
+ * @return An ArrayList of module codes representing the modules that are left to be completed.
+ */
+ public ArrayList getModuleCodesLeft() {
+ ArrayList moduleCodesLeft = new ArrayList();
+ ArrayList completedModuleCodes = schedule.getModulesPlanned().getCompletedModuleCodes();
+
+ for (String moduleCode : majorModuleCodes) {
+ if (!completedModuleCodes.contains(moduleCode)) {
+ moduleCodesLeft.add(moduleCode);
+ }
+ }
+ return moduleCodesLeft;
+ }
+
+ /**
+ * Retrieves the module codes associated with the major's requirements.
+ *
+ * @return An ArrayList of module codes representing the major's module requirements.
+ */
+ public ArrayList getMajorModuleCodes() {
+ return majorModuleCodes;
+ }
+
+ /**
+ * Retrieves the list of modules that are planned for a specific purpose or context.
+ *
+ * @return The ModuleList containing the planned modules.
+ */
+ public ModuleList getModulesPlanned() {
+ return schedule.getModulesPlanned();
+ }
+
+ /**
+ * Prints the schedule, displaying the main module list.
+ */
+ public void printSchedule() {
+ this.schedule.printMainModuleList();
+ }
+
+ //@@author
+
+ /**
+ * Sets the current semester modules for the student based on their year and semester.
+ *
+ * @author @rohitcube
+ */
+ private void setCurrentSemesterModules() {
+ try {
+ int currSem = getCurrentSem();
+
+ int[] modulesPerSem = schedule.getModulesPerSem();
+ // modules planned for all sems
+ ModuleList modulesPlanned = schedule.getModulesPlanned();
+ int numberOfModulesInCurrSem = modulesPerSem[currSem - 1];
+ int numberOfModulesCleared = 0;
+ for (int i = 0; i < currSem - 1; i++) {
+ numberOfModulesCleared += modulesPerSem[i];
+ }
+ int startIndex = numberOfModulesCleared;
+ int endIndex = startIndex + numberOfModulesInCurrSem;
+ currentSemesterModules = new ModuleList();
+ for (int i = startIndex; i < endIndex; i++) {
+ currentSemesterModules.addModule(modulesPlanned.getModuleByIndex(i));
+ }
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.print("why array out of bounds bruh");
+ } catch (NullPointerException e) {
+ System.out.print("null ptr exception");
+ }
+ }
+
+ public int getCurrentSem() {
+ int[] yearAndSem = Parser.parseStudentYear(year);
+ return ((yearAndSem[0] - 1) * 2) + yearAndSem[1];
+ }
+
+ /**
+ * Sets the current semester modules with each module as a ModuleWeekly class.
+ *
+ * @author @rohitcube
+ */
+ private void setCurrentSemesterModulesWeekly() throws TimetableUnavailableException {
+ if (currentSemesterModules == null || currentSemesterModules.getMainModuleList().isEmpty()) {
+ timetable.removeAll();
+ int currentSem = getCurrentSem();
+ throw new TimetableUnavailableException(
+ addOrRecommendGuide("Timetable view is unavailable as your current semester has " +
+ "no modules yet.", currentSem));
+ }
+ ArrayList newCurrentSemModuleList = currentSemesterModules.getMainModuleList();
+ ArrayList currentSemesterModulesWeekly = timetable.getCurrentSemesterModulesWeekly();
+ for (int i = 0; i < currentSemesterModulesWeekly.size(); i++) {
+ ModuleWeekly currModule = currentSemesterModulesWeekly.get(i);
+ String currModuleCode = currModule.getModuleCode();
+ if (isExistInCurrentSemesterModule(currModuleCode, newCurrentSemModuleList)) {
+ continue;
+ }
+ timetable.removeFromCurrentSemesterModulesWeekly(currModule);
+ }
+
+ for (int i = 0; i < newCurrentSemModuleList.size(); i++) {
+ String currModuleCode = newCurrentSemModuleList.get(i).getModuleCode();
+ if (isExistInCurrentSemesterModuleWeekly(currModuleCode, currentSemesterModulesWeekly)) {
+ continue;
+ }
+ ModuleWeekly currModule = new ModuleWeekly(currModuleCode);
+ timetable.addToCurrentSemesterModulesWeekly(currModule);
+ }
+ }
+
+ public void updateTimetable() throws TimetableUnavailableException {
+ this.setCurrentSemesterModules();
+ this.setCurrentSemesterModulesWeekly();
+ }
+
+
+ /**
+ * Executes 'show' or 'modify' subcommands under the timetable command.
+ * @author @rohitcube
+ * @param argument The user input specifying whether to show or modify the timetable.
+ */
+ public void timetableShowOrModify(String argument) {
+ try {
+ this.updateTimetable();
+ ModuleServiceController moduleServiceController = new ModuleServiceController();
+ argument = argument.trim().toUpperCase().replace("\r", "");
+ switch (argument) {
+ case "SHOW": {
+ moduleServiceController.showTimetable(timetable.getCurrentSemesterModulesWeekly());
+ break;
+ }
+ case "MODIFY": {
+
+ moduleServiceController.modifyTimetable(this);
+ break;
+ }
+ default: {
+ System.out.println("Invalid command (not show or modify). Please try again");
+ }
+ }
+ } catch (InvalidModifyArgumentException e) {
+ System.out.println("Invalid argument. Please try again.");
+ } catch (IndexOutOfBoundsException e) {
+ System.out.println("Index out of bounds exception.");
+ } catch (TimetableUnavailableException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+
+
+ /**
+ * Retrieves the ModuleWeekly object for a given module code.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return The ModuleWeekly object corresponding to the provided module code, or null if not found.
+ */
+ public static ModuleWeekly getModuleWeekly(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (int i = 0; i < currentSemesterModulesWeekly.size(); i++) {
+ ModuleWeekly module = currentSemesterModulesWeekly.get(i);
+ if (module.getModuleCode().equals(moduleCode)) {
+ return module;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the index of the ModuleWeekly object for a given module code.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return The index of the ModuleWeekly object corresponding to the provided module code, or -1 if not found.
+ */
+ public static int getIndexOfModuleWeekly(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (int i = 0; i < currentSemesterModulesWeekly.size(); i++) {
+ ModuleWeekly module = currentSemesterModulesWeekly.get(i);
+ if (module.getModuleCode().equals(moduleCode)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Checks if a module with a given module code exists in the current semester 'modules weekly' class.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return true if the module exists, false otherwise.
+ */
+ public static boolean isExistInCurrentSemesterModuleWeekly(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (ModuleWeekly module : currentSemesterModulesWeekly) {
+ if (module.getModuleCode().equals(moduleCode)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks if a module with a given module code exists in the current semester modules.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return true if the module exists, false otherwise.
+ */
+ public static boolean isExistInCurrentSemesterModule(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (Module module : currentSemesterModulesWeekly) {
+ if (module.getModuleCode().equals(moduleCode)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public ModuleList getCurrentSemesterModules() {
+ return currentSemesterModules;
+ }
+
+ public Timetable getTimetable() {
+ return this.timetable;
+ }
+
+}
diff --git a/src/main/java/seedu/duke/models/schema/Timetable.java b/src/main/java/seedu/duke/models/schema/Timetable.java
new file mode 100644
index 0000000000..2122446379
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Timetable.java
@@ -0,0 +1,157 @@
+package seedu.duke.models.schema;
+
+import seedu.duke.views.TimetableView;
+
+import java.util.ArrayList;
+
+public class Timetable {
+
+ public static Timetable timetable = new Timetable();
+
+ private ArrayList currentSemesterModulesWeekly;
+
+
+ private Timetable() {
+ currentSemesterModulesWeekly = new ArrayList<>();
+ }
+
+ public void addToCurrentSemesterModulesWeekly(ModuleWeekly module) {
+ currentSemesterModulesWeekly.add(module);
+ }
+
+ public void removeFromCurrentSemesterModulesWeekly(ModuleWeekly module) {
+ currentSemesterModulesWeekly.remove(module);
+ }
+
+ //@@author janelleenqi
+ /**
+ * Removes all ModuleWeekly from the current semester's weekly schedule.
+ */
+ public void removeAll() {
+ currentSemesterModulesWeekly = new ArrayList<>();
+ }
+
+ //@@author
+
+ public ArrayList getCurrentSemesterModulesWeekly() {
+ return currentSemesterModulesWeekly;
+ }
+
+ public void modify(Student student) {
+
+ }
+ public void printCurrentSemesterModulesWeekly(Student student) {
+ for (ModuleWeekly moduleweekly : currentSemesterModulesWeekly) {
+ System.out.println(moduleweekly.getModuleCode());
+ ArrayList weeklyschedule = moduleweekly.getWeeklyTimetable();
+ if (weeklyschedule.isEmpty()) {
+ System.out.println("aint nothin here");
+ }
+ for (int i = 0; i < weeklyschedule.size(); i++) {
+ System.out.println(weeklyschedule.get(i).getEventType());
+ System.out.println(weeklyschedule.get(i).getStartTime());
+ }
+ }
+ }
+
+ //@@author janelleenqi
+ /**
+ * Checks if there are any lessons in the current semester's weekly schedule, indicating the availability
+ * of a timetable view.
+ *
+ * @return true if there are lessons present, indicating timetable view can be printed, else otherwise.
+ */
+ public boolean timetableViewIsAvailable() {
+ for (ModuleWeekly moduleWeekly : currentSemesterModulesWeekly) {
+ if (moduleWeekly.haveLessons()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ //@@author
+
+
+ /**
+ * Retrieves the ModuleWeekly object for a given module code.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return The ModuleWeekly object corresponding to the provided module code, or null if not found.
+ */
+ public static ModuleWeekly getModuleWeekly(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (int i = 0; i < currentSemesterModulesWeekly.size(); i++) {
+ ModuleWeekly module = currentSemesterModulesWeekly.get(i);
+ if (module.getModuleCode().equals(moduleCode)) {
+ return module;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Retrieves the index of the ModuleWeekly object for a given module code.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return The index of the ModuleWeekly object corresponding to the provided module code, or -1 if not found.
+ */
+ public static int getIndexOfModuleWeekly(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (int i = 0; i < currentSemesterModulesWeekly.size(); i++) {
+ ModuleWeekly module = currentSemesterModulesWeekly.get(i);
+ if (module.getModuleCode().equals(moduleCode)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+
+
+
+
+ public void lessonsController(String lessonType, int indexOfModule, int time, int duration, String day) {
+ //TO BE REFACTORED
+ // parsing of day should be validated in the same as the above ^ if statements. Did not change this
+ //for you yet, but parserDayForModify should be moved up, and only if the day is valid as well,
+ // then you enter the switch statement. done
+ switch (lessonType) {
+ case "LECTURE": {
+ timetable.currentSemesterModulesWeekly.get(indexOfModule).addLecture(day,
+ time, duration);
+ TimetableView.printTimetable(currentSemesterModulesWeekly);
+ return;
+ }
+ case "TUTORIAL": {
+ timetable.currentSemesterModulesWeekly.get(indexOfModule).addTutorial(day,
+ time, duration);
+ TimetableView.printTimetable(currentSemesterModulesWeekly);
+ return;
+ }
+ case "LAB": {
+ timetable.currentSemesterModulesWeekly.get(indexOfModule).addLab(day,
+ time, duration);
+ TimetableView.printTimetable(currentSemesterModulesWeekly);
+ return;
+ }
+ default: {
+ System.out.println("Invalid Command. Please try again!");
+ }
+ }
+
+
+ }
+
+ /**
+ * Retrieves the timetable associated with the current user or context.
+ *
+ * @return The Timetable object representing the timetable.
+ */
+ public Timetable getTimetable() {
+ return timetable;
+ }
+
+}
diff --git a/src/main/java/seedu/duke/models/schema/TimetableUserCommand.java b/src/main/java/seedu/duke/models/schema/TimetableUserCommand.java
new file mode 100644
index 0000000000..a6cfb567c7
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/TimetableUserCommand.java
@@ -0,0 +1,184 @@
+package seedu.duke.models.schema;
+
+import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException;
+
+import java.util.ArrayList;
+
+import static seedu.duke.models.schema.Timetable.getIndexOfModuleWeekly;
+import static seedu.duke.utils.Parser.removeNulls;
+import static seedu.duke.utils.TimetableParser.isModifyValid;
+import static seedu.duke.utils.TimetableParser.parseModuleCode;
+import static seedu.duke.utils.TimetableParser.isModifyClear;
+import static seedu.duke.utils.TimetableParser.parseLessonType;
+import static seedu.duke.utils.TimetableParser.parseTime;
+import static seedu.duke.utils.TimetableParser.parseDuration;
+import static seedu.duke.utils.TimetableParser.parseDay;
+
+//@@author janelleenqi
+/**
+ * The TimetableUserCommand class represents a user command related to the timetable and provides methods to process
+ * and validate the user input.
+ */
+public class TimetableUserCommand {
+
+ private static final String ERROR_INVALID_NUMBER_OF_ARGUMENTS = "Invalid Number of Arguments";
+
+ private static final int NUMBER_OF_ARGUMENTS_EXIT = 1;
+ private static final int NUMBER_OF_ARGUMENTS_CLEAR = 2;
+ private static final int NUMBER_OF_ARGUMENTS_LESSON = 5;
+ private static final String DELIMITER = " ";
+
+ private Student student;
+ private ArrayList currentSemesterModulesWeekly;
+ private String[] arguments;
+
+
+ /**
+ * Constructs a TimetableUserCommand object with the specified student, current semester modules weekly,
+ * and user timetable input.
+ *
+ * @param student The student associated with the command.
+ * @param currentSemesterModulesWeekly The list of current semester modules weekly.
+ * @param userTimetableInput The user input related to the timetable command.
+ * @throws InvalidTimetableUserCommandException If there is an issue with the user command.
+ */
+ public TimetableUserCommand(Student student, ArrayList currentSemesterModulesWeekly,
+ String userTimetableInput)
+ throws InvalidTimetableUserCommandException {
+ this.student = student;
+ this.currentSemesterModulesWeekly = currentSemesterModulesWeekly;
+ arguments = userTimetableInput.split(DELIMITER);
+ cutArguments();
+ clearNullArguments();
+ //cleanArguments();
+ }
+
+ private void cutArguments() throws InvalidTimetableUserCommandException {
+ String[] cutArguments = new String[NUMBER_OF_ARGUMENTS_LESSON];
+
+ int currentIndex = 0;
+ for (int i = 0; i < arguments.length; i++) {
+ arguments[i] = arguments[i].trim();
+ if (arguments[i].isEmpty()) {
+ continue;
+ }
+ try {
+ cutArguments[currentIndex] = arguments[i];
+ currentIndex++;
+ } catch (IndexOutOfBoundsException e) {
+ // too many arguments
+ throw new InvalidTimetableUserCommandException(ERROR_INVALID_NUMBER_OF_ARGUMENTS);
+ }
+ }
+
+ if (currentIndex == NUMBER_OF_ARGUMENTS_EXIT || currentIndex == NUMBER_OF_ARGUMENTS_CLEAR ||
+ currentIndex == NUMBER_OF_ARGUMENTS_LESSON) {
+ // shrink arguments
+
+ String[] newCutArguments = new String[currentIndex];
+ int newCurrentIndex = 0;
+ for (int i = 0; i < cutArguments.length; i++) {
+ if (cutArguments[i] == null || cutArguments[i].isEmpty()) {
+ continue;
+ }
+ newCutArguments[newCurrentIndex] = cutArguments[i];
+ newCurrentIndex++;
+ }
+ arguments = newCutArguments;
+ return;
+ }
+ // invalid number of arguments
+ //System.out.println(currentIndex);
+ throw new InvalidTimetableUserCommandException(ERROR_INVALID_NUMBER_OF_ARGUMENTS);
+ }
+
+ private void clearNullArguments() throws InvalidTimetableUserCommandException {
+ String[] argumentsNotNull = removeNulls(arguments);
+ if (!isModifyValid(arguments, currentSemesterModulesWeekly)) {
+ return;
+ }
+ arguments = argumentsNotNull;
+ }
+
+
+ /**
+ * Processes the timetable command, lessons clear and lessons modification.
+ *
+ * @param currentSemesterModulesWeekly The list of current semester modules weekly.
+ * @throws InvalidTimetableUserCommandException If there is an issue with the user command.
+ */
+ public void processTimetableCommand(ArrayList currentSemesterModulesWeekly)
+ throws InvalidTimetableUserCommandException {
+
+ String moduleCode = parseModuleCode(arguments[0]);
+ int indexOfModuleWeeklyToModify = getIndexOfModuleWeekly(moduleCode, currentSemesterModulesWeekly);
+ if (indexOfModuleWeeklyToModify == -1) {
+ throw new InvalidTimetableUserCommandException(moduleCode + " does not exist in your schedule.");
+ }
+
+ if (isModifyClear(arguments)) {
+ currentSemesterModulesWeekly.get(indexOfModuleWeeklyToModify).clearLessons();
+ System.out.println("All lessons for selected module are cleared.");
+ return;
+ }
+
+ processTimetableCommandLesson(currentSemesterModulesWeekly);
+ }
+
+ /**
+ * Processes the timetable command related to lessons modification.
+ *
+ * @param currentSemesterModulesWeekly The list of current semester modules weekly.
+ * @throws InvalidTimetableUserCommandException If there is an issue with the user command.
+ */
+ public void processTimetableCommandLesson(ArrayList currentSemesterModulesWeekly)
+ throws InvalidTimetableUserCommandException {
+
+ String moduleCode = parseModuleCode(arguments[0]);
+ int indexOfModuleWeeklyToModify = getIndexOfModuleWeekly(moduleCode, currentSemesterModulesWeekly);
+ if (indexOfModuleWeeklyToModify == -1) {
+ throw new InvalidTimetableUserCommandException(moduleCode + " does not exist in your schedule.");
+ }
+
+ String lessonType = parseLessonType(arguments[1]);
+ int time = parseTime(arguments[2]);
+ int duration = parseDuration(arguments[3]);
+ String day = parseDay(arguments[4]);
+
+ switch (lessonType) {
+ case "LECTURE": {
+ currentSemesterModulesWeekly.get(indexOfModuleWeeklyToModify).addLecture(day,
+ time, duration);
+ return;
+ }
+ case "TUTORIAL": {
+ currentSemesterModulesWeekly.get(indexOfModuleWeeklyToModify).addTutorial(day,
+ time, duration);
+ return;
+ }
+ case "LAB": {
+ currentSemesterModulesWeekly.get(indexOfModuleWeeklyToModify).addLab(day,
+ time, duration);
+ return;
+ }
+ default: {
+ System.out.println("Invalid Command. Please try again!");
+ }
+ }
+ }
+
+ /**
+ * Retrieves the arguments of the timetable user command.
+ *
+ * @return The array of arguments.
+ */
+ public String[] getArguments() {
+ return arguments;
+ }
+
+ public void printArguments() {
+ for (String argument : arguments) {
+ System.out.println(argument);
+ }
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/Tutorial.java b/src/main/java/seedu/duke/models/schema/Tutorial.java
new file mode 100644
index 0000000000..fac9aacc40
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/Tutorial.java
@@ -0,0 +1,73 @@
+package seedu.duke.models.schema;
+
+//@@author janelleenqi
+/**
+ * Represents a Tutorial that extends the Event class.
+ */
+public class Tutorial extends Event {
+ private static final String EVENT_TYPE = "Tutorial";
+
+ /**
+ * Constructs a Tutorial object with the specified day, start time, duration, and module code.
+ *
+ * @param day The day of the lab session. Must not be null.
+ * @param startTime The start time of the lab session.
+ * @param duration The duration of the lab session.
+ * @param moduleCode The module code associated with the lab session. Must not be null.
+ */
+ public Tutorial(String day, int startTime, int duration, String moduleCode) {
+ super(day, startTime, duration, moduleCode);
+ }
+
+ /**
+ * Gets the type of the event, which is "Tutorial".
+ *
+ * @return The event type.
+ */
+ @Override
+ public String getEventType() {
+ return EVENT_TYPE;
+ }
+
+ /**
+ * Checks if this Tutorial object is equal to another Event object by comparing their common attributes.
+ *
+ * @param event The event to compare with.
+ * @return true if the events are equal, false otherwise.
+ */
+ @Override
+ public boolean equals(Event event) {
+ boolean isSameEvent = super.equals(event);
+
+ if (!isSameEvent) {
+ return false;
+ }
+
+ if (!this.getEventType().equals(event.getEventType())) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Generates a string representation of this Tutorial, including module code, event type, and time range.
+ *
+ * @return The string representation of this Tutorial.
+ */
+ @Override
+ public String toString() {
+ return super.toString() + " " + getEventType() + " " + getTime(getStartTime(), getDuration());
+ }
+
+ /**
+ * Generates a string representation of this Tutorial for saving purposes, including module code,
+ * event type, start time, duration, and day.
+ *
+ * @return The string representation of this Tutorial for saving.
+ */
+ @Override
+ public String toSave() {
+ return super.toSave() + " " + getEventType() + " " + getStartTime() + " " + getDuration() + " " + getDay();
+ }
+}
diff --git a/src/main/java/seedu/duke/models/schema/UserCommand.java b/src/main/java/seedu/duke/models/schema/UserCommand.java
new file mode 100644
index 0000000000..910c8447ba
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/UserCommand.java
@@ -0,0 +1,203 @@
+package seedu.duke.models.schema;
+
+import seedu.duke.models.logic.Api;
+import seedu.duke.utils.Parser;
+import seedu.duke.utils.errors.UserError;
+
+import static seedu.duke.utils.Parser.parseArguments;
+import static seedu.duke.utils.Parser.parseCommand;
+import static seedu.duke.views.CommandLineView.displayCommands;
+
+import static seedu.duke.controllers.ModuleMethodsController.executePaceCommand;
+import static seedu.duke.controllers.ModuleMethodsController.showModulesLeft;
+import static seedu.duke.controllers.ModuleMethodsController.determinePrereq;
+import static seedu.duke.controllers.ModuleMethodsController.executeRecommendCommand;
+import static seedu.duke.controllers.ModuleMethodsController.executeAddModuleCommand;
+import static seedu.duke.controllers.ModuleMethodsController.executeDeleteModuleCommand;
+import static seedu.duke.controllers.ModuleMethodsController.executeShiftModuleCommand;
+import static seedu.duke.controllers.ModuleMethodsController.executeClearScheduleCommand;
+import static seedu.duke.controllers.ModuleMethodsController.completeModule;
+import static seedu.duke.controllers.ModuleMethodsController.executeGetRequiredModulesForStudent;
+
+/**
+ * The UserCommand class represents a command entered by the user and provides methods to process and execute the
+ * command.
+ */
+public class UserCommand implements UserCommandWord {
+
+ //@@author janelleenqi
+ private final String userInput;
+ private final String commandWord;
+ private final String[] arguments;
+ private final boolean isValid;
+
+ private CommandManager commandManager;
+
+ /**
+ * Constructs a UserCommand object with the specified user input and validates it.
+ *
+ * @param userInput The user input representing the command.
+ */
+ public UserCommand(String userInput) {
+ this.userInput = userInput.trim();
+ this.commandManager = new CommandManager();
+
+ commandWord = parseCommand(this.userInput);
+ arguments = parseArguments(this.userInput);
+
+
+ if (!commandManager.getListOfCommandNames().contains(commandWord)){
+ UserError.displayInvalidInputCommand(commandWord);
+ isValid = false;
+ return;
+ }
+
+ boolean validArgument = Parser.isValidInputForCommand(commandWord, arguments);
+
+ if (!validArgument) {
+ UserError.displayInvalidMethodCommand(commandWord);
+ isValid = false;
+ return;
+ }
+
+ isValid = true;
+ }
+
+ public UserCommand() {
+ userInput = null;
+ commandWord = null;
+ arguments = null;
+ isValid = false;
+ }
+
+ /**
+ * Checks if the UserCommand is valid.
+ *
+ * @return true if the command is valid, false otherwise.
+ */
+ public boolean isValid() {
+ return isValid;
+ }
+
+ public String getUserInput() {
+ return userInput;
+ }
+
+ public String getCommandWord() {
+ return commandWord;
+ }
+
+ public String[] getArguments() {
+ return arguments;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return super.equals(obj);
+ }
+
+ /**
+ * Checks if the command is an close program command.
+ *
+ * @return true if the command is an exit command, false otherwise.
+ */
+ public boolean isBye() {
+ if (commandWord == null) {
+ return false;
+ }
+
+ if (!isValid) {
+ return false;
+ }
+
+ return this.commandWord.equalsIgnoreCase(UserCommandWord.EXIT_COMMAND);
+ }
+
+ //@@author
+
+ /**
+ * Processes and executes the user command based on the command word.
+ *
+ * @param student The student object to perform operations on.
+ */
+ public void processCommand(Student student) {
+ switch (commandWord) {
+ case UserCommandWord.LEFT_COMMAND: {
+ showModulesLeft(student.getModuleCodesLeft());
+ break;
+ }
+ case UserCommandWord.PACE_COMMAND: {
+ executePaceCommand(arguments, student.getCurrentModuleCredits(), student.getYear());
+ break;
+ }
+ case UserCommandWord.PREREQUISITE_COMMAND: {
+ String module = arguments[0];
+ determinePrereq(module.toUpperCase(), student.getMajor()); //to convert "CEG" to dynamic course
+ break;
+ }
+ case UserCommandWord.RECOMMEND_COMMAND: {
+ executeRecommendCommand(student);
+ break;
+ }
+ case UserCommandWord.ADD_MODULE_COMMAND: {
+ String module = arguments[0].toUpperCase();
+ int targetSem = Integer.parseInt(arguments[1]);
+
+ executeAddModuleCommand(module, targetSem, student);
+ break;
+ }
+ case UserCommandWord.DELETE_MODULE_COMMAND: {
+ String module = arguments[0].toUpperCase();
+
+ executeDeleteModuleCommand(module,student);
+ break;
+ }
+ case UserCommandWord.SHIFT_MODULE_COMMAND: {
+ String module = arguments[0].toUpperCase();
+ int targetSem = Integer.parseInt(arguments[1]);
+ executeShiftModuleCommand(module, targetSem, student);
+ break;
+ }
+ case UserCommandWord.VIEW_SCHEDULE_COMMAND: {
+ student.printSchedule();
+ break;
+ }
+ case UserCommandWord.CLEAR_SCHEDULE_COMMAND: {
+ executeClearScheduleCommand(student);
+ break;
+ }
+ case UserCommandWord.COMPLETE_MODULE_COMMAND: {
+ String module = arguments[0].toUpperCase();
+ //to add to user completed module
+ completeModule(student, module);
+
+ break;
+ }
+ case UserCommandWord.REQUIRED_MODULES_COMMAND: {
+ executeGetRequiredModulesForStudent(student.getMajor());
+ break;
+ }
+ case UserCommandWord.INFO_COMMAND: {
+ Api.infoCommands(arguments[0], userInput);
+ break;
+ }
+ case UserCommandWord.SEARCH_MODULE_COMMAND: {
+ Api.searchCommand(userInput);
+ break;
+ }
+ case UserCommandWord.HELP_COMMAND: {
+ displayCommands(commandManager);
+ break;
+ }
+ case UserCommandWord.TIMETABLE_COMMAND: {
+ student.timetableShowOrModify(arguments[0]);
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+
+ }
+
+}
diff --git a/src/main/java/seedu/duke/models/schema/UserCommandWord.java b/src/main/java/seedu/duke/models/schema/UserCommandWord.java
new file mode 100644
index 0000000000..5f9b3eb5a8
--- /dev/null
+++ b/src/main/java/seedu/duke/models/schema/UserCommandWord.java
@@ -0,0 +1,23 @@
+package seedu.duke.models.schema;
+
+public interface UserCommandWord {
+ String EXIT_COMMAND = "Bye";
+ String LEFT_COMMAND = "left";
+ String PACE_COMMAND = "pace";
+ String PREREQUISITE_COMMAND = "prereq";
+ String RECOMMEND_COMMAND = "recommend";
+ String SET_MAJOR_COMMAND = "major";
+ String ADD_MODULE_COMMAND = "add";
+ String DELETE_MODULE_COMMAND = "delete";
+ String SHIFT_MODULE_COMMAND = "shift";
+ String CLEAR_SCHEDULE_COMMAND = "clear";
+ String VIEW_SCHEDULE_COMMAND = "schedule";
+ String COMPLETE_MODULE_COMMAND = "complete";
+ String REQUIRED_MODULES_COMMAND = "required";
+ String INFO_COMMAND = "info";
+ String SEARCH_MODULE_COMMAND = "search";
+ String HELP_COMMAND = "help";
+ String TIMETABLE_COMMAND = "timetable";
+ String BYE_COMMAND = "bye";
+}
+
diff --git a/src/main/java/seedu/duke/storage/FileDecoder.java b/src/main/java/seedu/duke/storage/FileDecoder.java
new file mode 100644
index 0000000000..bf73bf3cce
--- /dev/null
+++ b/src/main/java/seedu/duke/storage/FileDecoder.java
@@ -0,0 +1,307 @@
+package seedu.duke.storage;
+
+import seedu.duke.models.schema.Major;
+import seedu.duke.models.schema.Schedule;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.TimetableUserCommand;
+import seedu.duke.utils.Parser;
+import seedu.duke.utils.exceptions.CorruptedFileException;
+import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException;
+import seedu.duke.utils.exceptions.MissingFileException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class FileDecoder {
+
+ /**
+ * Retrieves a schedule from a specified file path.
+ *
+ * @param scheduleFilePath The file path where the schedule is stored.
+ * @return The retrieved schedule.
+ * @throws MissingFileException If the file is missing.
+ * @throws CorruptedFileException If the file is corrupted or the data format is invalid.
+ */
+ public static Schedule retrieveSchedule(String scheduleFilePath) throws MissingFileException,
+ CorruptedFileException {
+
+
+ if (!isFileExist(scheduleFilePath)) {
+ throw new MissingFileException();
+ }
+
+ Schedule schedule = new Schedule();
+
+ try {
+ // Create a FileReader and BufferedReader to read the file.
+ FileReader fileReader = new FileReader(scheduleFilePath);
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+
+ String line;
+
+ int targetIndex = 0;
+ int[] modulesPerSemArray = new int[]{0, 0, 0, 0, 0, 0, 0, 0};
+
+ // Read lines from the file and add them to the ArrayList.
+ while ((line = bufferedReader.readLine()) != null) {
+
+ String[] splitParts = line.split(" \\| ");
+
+ switch (splitParts[0]) {
+
+ // Happens once on the first line of txt file so that sorting subsequent modules is possible
+ case "ModulesPerSem":
+ String[] modulesPerSemStringArray = splitParts[1].split(",");
+ for (int i = 0; i < modulesPerSemArray.length; i++) {
+ modulesPerSemArray[i] = Integer.parseInt(modulesPerSemStringArray[i]);
+ }
+ break;
+ case "Module":
+ String module = splitParts[1];
+ int targetSemester = 1;
+ int indexOfLastModuleOfSem = modulesPerSemArray[targetSemester - 1] - 1;
+ while (targetIndex > indexOfLastModuleOfSem) {
+ indexOfLastModuleOfSem += modulesPerSemArray[targetSemester];
+ targetSemester += 1;
+ }
+
+ schedule.addModule(module, targetSemester);
+ if (splitParts[2].equals("O")) {
+ schedule.getModule(module).markModuleAsCompleted();
+ }
+ targetIndex += 1;
+ break;
+ default:
+ if (!splitParts[0].trim().isEmpty()) {
+ throw new CorruptedFileException();
+ }
+ }
+ }
+
+ // Close the BufferedReader to release resources.
+ bufferedReader.close();
+ } catch (Exception e) {
+ throw new CorruptedFileException();
+ }
+
+ return schedule;
+
+ }
+
+ /**
+ * Retrieves student details from a specified file path.
+ *
+ * @param studentDetailsFilePath The file path where the student details are stored.
+ * @return The retrieved student details.
+ * @throws MissingFileException If the file is missing.
+ * @throws CorruptedFileException If the file is corrupted or the data format is invalid.
+ */
+ public static ArrayList retrieveStudentDetails(String studentDetailsFilePath) throws MissingFileException,
+ CorruptedFileException {
+
+ if (!isFileExist(studentDetailsFilePath)) {
+ throw new MissingFileException();
+ }
+
+ try {
+ // Create a FileReader and BufferedReader to read the file.
+ FileReader fileReader = new FileReader(studentDetailsFilePath);
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+
+ ArrayList studentDetails = new ArrayList<>(3);
+
+ String line;
+ int lineNumber = 0;
+
+ // to track which line it is supposed to be on
+ HashMap variableMap = new HashMap<>();
+ // Adding key-value pairs
+ variableMap.put("Name", 0);
+ variableMap.put("Major", 1);
+ variableMap.put("Year", 2);
+
+ // Read lines from the file and add them to the ArrayList.
+ while ((line = bufferedReader.readLine()) != null) {
+
+ String[] splitParts = line.split(" \\| ");
+
+ String userAttribute = splitParts[0];
+
+ //validation to see that variables has not been tampered with
+ if(variableMap.get(userAttribute) != lineNumber){
+ throw new CorruptedFileException();
+ }
+
+ switch (splitParts[0]) {
+
+ case "Name":
+ String name = splitParts[1];
+ studentDetails.add(0, name);
+ break;
+ case "Major":
+ String major = splitParts[1];
+
+ // Check if major stored in txt file is valid
+ Major.valueOf(major.toUpperCase());
+
+ studentDetails.add(1, major);
+ break;
+ case "Year":
+ String year = splitParts[1];
+
+ //Check if year stored in txt file is valid
+ if (!Parser.isValidAcademicYear(year)){
+ throw new CorruptedFileException();
+ }
+
+ studentDetails.add(2, year);
+ break;
+ default:
+ if (!splitParts[0].trim().isEmpty()) {
+ throw new CorruptedFileException();
+ }
+ }
+ lineNumber += 1;
+ }
+ // Close the BufferedReader to release resources.
+ bufferedReader.close();
+
+ return studentDetails;
+ } catch (Exception e) {
+ throw new CorruptedFileException();
+ }
+ }
+
+ //@@author janelleenqi
+ /**
+ * Retrieves a timetable from a specified file path.
+ *
+ * @param student The student for whom the timetable is retrieved.
+ * @param timetableFilePath The file path where the timetable is stored.
+ * @return The retrieved timetable as a list of commands.
+ * @throws MissingFileException If the file is missing.
+ * @throws CorruptedFileException If the file is corrupted or the data format is invalid.
+ */
+ public static ArrayList retrieveTimetable(Student student, String timetableFilePath)
+ throws MissingFileException, CorruptedFileException {
+
+ if (!isFileExist(timetableFilePath)) {
+ throw new MissingFileException();
+ }
+
+ ArrayList timetableUserCommands;
+ try {
+ // Create a FileReader and BufferedReader to read the file.
+ FileReader fileReader = new FileReader(timetableFilePath);
+ BufferedReader bufferedReader = new BufferedReader(fileReader);
+
+ String line;
+ if ((line = bufferedReader.readLine()) != null) {
+ if (!line.equals("TimetableForCurrentSem")) {
+ throw new CorruptedFileException();
+ }
+ }
+ timetableUserCommands = new ArrayList<>();
+
+ // Read lines from the file and add them to the ArrayList.
+ while ((line = bufferedReader.readLine()) != null) {
+ try {
+ timetableUserCommands.add(new TimetableUserCommand(student,
+ student.getTimetable().getCurrentSemesterModulesWeekly(), line));
+ } catch (InvalidTimetableUserCommandException e) {
+ //corrupted
+ throw new CorruptedFileException();
+ }
+
+ }
+
+ // Close the BufferedReader to release resources.
+ bufferedReader.close();
+ } catch (Exception e) {
+ throw new CorruptedFileException();
+ }
+
+ return timetableUserCommands;
+
+ }
+
+ //@@author SebasFok
+ /**
+ * Creates "schedule.txt", "studentDetails.txt" and "timetable.txt" files in the data directory to store student
+ * data
+ *
+ * @param storageDirectory location of storage directory to be created
+ */
+ public static void createNewStorageFile(String storageDirectory) {
+
+ createDirectory(storageDirectory);
+
+ createFileInDirectory(storageDirectory, "schedule.txt");
+ createFileInDirectory(storageDirectory, "studentDetails.txt");
+ createFileInDirectory(storageDirectory, "timetable.txt");
+
+ }
+
+ //@@author SebasFok
+ /**
+ * Takes in the location of the file in question and returns whether the file exist
+ *
+ * @param filePath location of the file in question
+ * @return return true if the file exist,return false otherwise
+ */
+ public static boolean isFileExist(String filePath) {
+ Path path = Paths.get(filePath);
+ return Files.exists(path);
+ }
+
+ //@@author SebasFok
+ /**
+ * This method takes in a path and creates a directory at that location. Should the
+ * directory already exist, no new directory will be created.
+ *
+ * @param folderPath the location of where the directory should be created
+ */
+ public static void createDirectory(String folderPath) {
+
+ File folder = new File(folderPath);
+ if (folder.mkdir()) {
+ //System.out.println("Folder created successfully.");
+ } else {
+ //System.out.println("Folder already exists");
+ }
+ }
+
+ //@@author SebasFok
+ /**
+ * This method takes in the path of a directory and creates a file 'fileName' in
+ * the directory. Should the file already exist, no new file will be created.
+ *
+ * @param directoryPath the location of the directory where the file should be created
+ * @param fileName the name of the file to be created
+ */
+ public static void createFileInDirectory(String directoryPath, String fileName) {
+
+ // Create the full path to the text file
+ String filePath = directoryPath + "/" + fileName;
+
+ File file = new File(filePath);
+
+ try {
+ // Create the file
+ if (file.createNewFile()) {
+ //System.out.println("Text file created successfully at: " + filePath);
+ } else {
+ //System.out.println("File already exists.");
+ }
+ } catch (IOException e) {
+ System.out.println("An IOException occurred: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/seedu/duke/storage/ResourceStorage.java b/src/main/java/seedu/duke/storage/ResourceStorage.java
new file mode 100644
index 0000000000..372d44fa41
--- /dev/null
+++ b/src/main/java/seedu/duke/storage/ResourceStorage.java
@@ -0,0 +1,37 @@
+package seedu.duke.storage;
+
+public class ResourceStorage {
+
+
+ /**
+ * Determines the course requirements based on the specified major. Function is used if file is not found
+ *
+ * @param major A string representing the major (e.g., "CEG" for Computer Engineering, "CS" for Computer Science).
+ * @return An array of strings containing the course requirements for the specified major.
+ */
+ static String[] determineRequirements(String major) {
+ String[] courseArray;
+
+ String[] csCourseArray = {
+ "CS1101S", "ES2660", "GEC1000", "GEA1000", "GESS1000",
+ "GEN2000", "IS1108", "CS1231S", "CS2030", "CS2040S",
+ "CS2100", "CS2101", "CS2103T", "CS2106", "CS2109S",
+ "CS3230", "MA1521", "MA1522", "ST2334", "CP3880"
+ };
+ String[] cegCourseArray = {
+ "CG1111A", "MA1511", "MA1512", "CS1010", "GESS1000",
+ "GEC1000", "GEN2000", "ES2631", "GEA1000", "DTK1234",
+ "EG1311", "IE2141", "EE2211", "EG2501", "CDE2000",
+ "PF1101", "CG4002", "MA1508E", "EG2401A", "CP3880",
+ "CG2111A", "CS1231", "CG2023", "CG2027", "CG2028",
+ "CG2271", "ST2334", "CS2040C", "CS2113", "EE2026", "EE4204"
+ };
+
+ if(major.equals("CEG")){
+ courseArray = cegCourseArray;
+ }else{
+ courseArray = csCourseArray;
+ }
+ return courseArray;
+ }
+}
diff --git a/src/main/java/seedu/duke/storage/StorageManager.java b/src/main/java/seedu/duke/storage/StorageManager.java
new file mode 100644
index 0000000000..d48dbb810a
--- /dev/null
+++ b/src/main/java/seedu/duke/storage/StorageManager.java
@@ -0,0 +1,256 @@
+package seedu.duke.storage;
+
+import seedu.duke.models.schema.Event;
+import seedu.duke.models.schema.ModuleList;
+import seedu.duke.models.schema.ModuleWeekly;
+import seedu.duke.models.schema.Schedule;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.TimetableUserCommand;
+import seedu.duke.utils.exceptions.CorruptedFileException;
+import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException;
+import seedu.duke.utils.exceptions.MissingFileException;
+import seedu.duke.utils.exceptions.TimetableUnavailableException;
+
+import java.io.IOException;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import static seedu.duke.storage.FileDecoder.createNewStorageFile;
+import static seedu.duke.storage.FileDecoder.retrieveTimetable;
+import static seedu.duke.storage.FileDecoder.retrieveStudentDetails;
+import static seedu.duke.storage.FileDecoder.retrieveSchedule;
+import static seedu.duke.storage.ResourceStorage.determineRequirements;
+
+
+public class StorageManager {
+
+ private final String userDirectory;
+
+ /**
+ * Constructs a new Storage instance with the specified file path.
+ */
+ public StorageManager() {
+ this.userDirectory = System.getProperty("user.dir");
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Retrieves a list of modules requirements for a specified major.
+ *
+ * @param major The major for which to retrieve requirements.
+ * @return An ArrayList of module codes.
+ * @throws RuntimeException If the specified major requirements file is not found.
+ */
+ public static ArrayList getRequirements(String major) {
+ String[] courseArray = determineRequirements(major);
+ return new ArrayList<>(Arrays.asList(courseArray));
+ }
+
+
+
+ //@@author SebasFok
+ /**
+ * Creates a data directory containing txt folders to store student details, schedule and timetable.
+ */
+ public void createUserStorageFile() {
+ String dataDirectory = userDirectory + "/data";
+ createNewStorageFile(dataDirectory);
+ }
+
+ /**
+ * Loads the student's schedule from the "schedule.txt" file, including modules per semester and individual modules.
+ * Also retains the completion status of each module in the schedule.
+ *
+ * @return A Schedule object representing the loaded schedule.
+ * @throws MissingFileException If the "schedule.txt" file is missing.
+ * @throws CorruptedFileException If the file is corrupted or has unexpected content.
+ */
+ public Schedule loadSchedule() throws MissingFileException, CorruptedFileException {
+ String scheduleFilePath = userDirectory + "/data/schedule.txt";
+ return retrieveSchedule(scheduleFilePath);
+ }
+
+ //@@author SebasFok
+ /**
+ * Loads the student's details (name, major, and year) from the "studentDetails.txt" file.
+ *
+ * @return An ArrayList containing the loaded student details in the order [Name, Major, Year].
+ * @throws MissingFileException If the "studentDetails.txt" file is missing.
+ * @throws CorruptedFileException If the file is corrupted or has unexpected content.
+ */
+ public ArrayList loadStudentDetails() throws MissingFileException, CorruptedFileException {
+
+ String studentDetailsFilePath = userDirectory + "/data/studentDetails.txt";
+
+ return retrieveStudentDetails(studentDetailsFilePath);
+ }
+
+ //@@author janelleenqi
+ /**
+ * Loads timetable user commands from the timetable.txt save file and processes them to update the student's
+ * timetable.
+ *
+ * @param student The student whose timetable is being updated.
+ * @return An ArrayList of TimetableUserCommand objects representing the loaded commands.
+ * @throws MissingFileException If the timetable file is missing.
+ * @throws CorruptedFileException If the timetable file is corrupted or contains invalid commands.
+ */
+ public ArrayList loadTimetable(Student student)
+ throws MissingFileException, CorruptedFileException {
+
+ String timetableFilePath = userDirectory + "/data/timetable.txt";
+
+ return retrieveTimetable(student,timetableFilePath);
+ }
+
+ //@@author janelleenqi
+ /**
+ * Adds events to the student's timetable based on the provided timetable user commands.
+ *
+ * @param timetableUserCommands An ArrayList of TimetableUserCommand objects representing the commands to process.
+ * @param student The student whose timetable is being updated.
+ * @throws CorruptedFileException If the provided timetable user commands are corrupted or contain invalid commands.
+ */
+ public void addEventsToStudentTimetable(ArrayList timetableUserCommands, Student student)
+ throws CorruptedFileException {
+ ArrayList currentSemModulesWeekly = student.getTimetable().getCurrentSemesterModulesWeekly();
+ for (TimetableUserCommand currentTimetableCommand : timetableUserCommands) {
+ //not exit, not clear
+ try {
+ currentTimetableCommand.processTimetableCommandLesson(currentSemModulesWeekly);
+ } catch (InvalidTimetableUserCommandException e) {
+ //corrupted
+ throw new CorruptedFileException();
+ }
+ }
+ }
+
+ //@@author SebasFok
+ /**
+ * Saves the student's details (name, major, and year) to the "studentDetails.txt" file.
+ *
+ * @param student The Student object containing the details to be saved.
+ * @throws IOException If an I/O error occurs while writing to the file.
+ */
+ public void saveStudentDetails (Student student) throws IOException {
+
+ String studentDetailsFilePath = userDirectory + "/data/studentDetails.txt";
+
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(studentDetailsFilePath))) {
+
+ String name = student.getName();
+
+ String major = student.getMajor();
+
+ String year = student.getYear();
+
+ // Write the new content to the file
+ writer.write("Name | " + name);
+ writer.newLine();
+
+ writer.write("Major | " + major);
+ writer.newLine();
+
+ writer.write(("Year | " + year));
+ writer.newLine();
+ }
+ }
+
+ //@@author SebasFok
+ /**
+ * Saves the student's schedule to the "schedule.txt" file.
+ *
+ * @param student The Student object containing the details to be saved.
+ * @throws IOException If an I/O error occurs while writing to the file.
+ */
+ public static void saveSchedule(Student student) throws IOException {
+
+ String scheduleFilePath = System.getProperty("user.dir") + "/data/schedule.txt";
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(scheduleFilePath))) {
+
+ int[] modulesPerSemArray = student.getSchedule().getModulesPerSem();
+
+ StringBuilder modulesPerSemNumbers = new StringBuilder(Integer.toString(modulesPerSemArray[0]));
+ for (int i = 1; i < modulesPerSemArray.length; i++) {
+ modulesPerSemNumbers.append(",").append(modulesPerSemArray[i]);
+ }
+
+ // Write the new content to the file
+ writer.write("ModulesPerSem | " + modulesPerSemNumbers);
+ writer.newLine();
+
+ ModuleList modulesPlanned = student.getSchedule().getModulesPlanned();
+ int numberOfModules = student.getSchedule().getModulesPlanned().getMainModuleList().size();
+ String completionStatus;
+ for (int i = 0; i < numberOfModules; i++) {
+ String moduleCode = modulesPlanned.getModuleByIndex(i).getModuleCode();
+ if (modulesPlanned.getModuleByIndex(i).getCompletionStatus()) {
+ completionStatus = "O";
+ } else {
+ completionStatus = "X";
+ }
+ writer.write("Module | " + moduleCode + " | " + completionStatus);
+ writer.newLine(); // Move to the next line
+ }
+ }
+ }
+
+ public static void saveTimetable(Student student) throws IOException {
+
+ String timetableFilePath = System.getProperty("user.dir") + "/data/timetable.txt";
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(timetableFilePath))) {
+
+ // Write the new content to the file
+ writer.write("TimetableForCurrentSem");
+ writer.newLine();
+
+ //latest info
+ student.updateTimetable();
+
+ ArrayList currentSemesterModules = student.getTimetable().getCurrentSemesterModulesWeekly();
+ for (ModuleWeekly module : currentSemesterModules) {
+ for (Event event : module.getWeeklyTimetable()) {
+ writer.write(event.toSave());
+ writer.newLine();
+ }
+ }
+ } catch (TimetableUnavailableException e) {
+ //no events in timetable, do nothing
+ }
+ }
+
+ // Below this comment are standard file methods
+
+ //@@author SebasFok
+ /**
+ * This method takes in the path of a txt file and adds 'textToAdd' to the last line
+ * of the file
+ *
+ * @param filePath location of the file to be edited
+ * @param textToAdd String to be added to the end of the txt file
+ */
+ public static void addTextToFile(String filePath, String textToAdd) {
+ try {
+ // Create a FileWriter object with the specified file path in append mode (true).
+ FileWriter fileWriter = new FileWriter(filePath, true);
+
+ // Create a BufferedWriter to efficiently write text.
+ BufferedWriter bufferedWriter = new BufferedWriter(fileWriter);
+
+ // Write the text to the file.
+ bufferedWriter.write(textToAdd);
+
+ // Write a newline character to separate lines.
+ bufferedWriter.newLine();
+
+ // Close the BufferedWriter to release resources.
+ bufferedWriter.close();
+
+ //System.out.println("Text added to the file successfully.");
+ } catch (IOException e) {
+ System.out.println("An IOException occurred: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/Parser.java b/src/main/java/seedu/duke/utils/Parser.java
new file mode 100644
index 0000000000..46d2bd11e0
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/Parser.java
@@ -0,0 +1,378 @@
+package seedu.duke.utils;
+
+import seedu.duke.models.schema.Major;
+import seedu.duke.models.schema.UserCommandWord;
+import seedu.duke.utils.errors.UserError;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+public class Parser {
+ public static final String DELIMITER = " ";
+
+ //@@author ryanlohyr
+ /**
+ * Parses a user input string to extract and return the main command.
+ * @param userInput The user input string.
+ * @return The main command from the input string.
+ */
+ public static String parseCommand(String userInput) {
+ if (userInput == null || userInput.isEmpty()) {
+ return null;
+ }
+ String[] keywords = userInput.split(DELIMITER);
+
+ return keywords[0].toLowerCase().trim();
+ }
+
+
+ //@@author ryanlohyr
+ /**
+ * Excludes the command and extracts and returns an array of arguments from a user input string.
+ * @param userInput The user input string.
+ * @return An array of arguments from the input string.
+ */
+ public static String[] parseArguments(String userInput){
+ if (userInput.isEmpty()){
+ return null;
+ }
+ String[] keywords = userInput.split(DELIMITER);
+
+ return Arrays.copyOfRange(keywords, 1, keywords.length);
+ }
+
+
+ //@@author ryanlohyr
+ /**
+ * Checks if the given academic year input is valid.
+ * The academic year should be in the format "Yn/Sx", where 'n' represents the year
+ * and 'x' represents the semester (e.g., Y1/S1, Y2/S2).
+ *
+ *
+ * @param userInput The academic year input to be validated.
+ * @return true if the input is a valid academic year, false otherwise.
+ *
+ * @throws IllegalArgumentException if the input format is incorrect or if the year or semester is invalid.
+ *
+ */
+ public static boolean isValidAcademicYear( String userInput ) {
+ try {
+ String[] parts = userInput.split("/");
+ if(parts.length != 2){
+ throw new IllegalArgumentException("Needs to be in format of Y2/S1");
+ }
+ String year = parts[0].toUpperCase();
+ String semester = parts[1].toUpperCase();
+
+ //last year
+ if(year.equals("Y4") && semester.equals("S2")){
+ throw new IllegalArgumentException("Its your last sem!! A bit too late ya....");
+ }
+ //validate semester
+ if(!semester.equals("S1") && !semester.equals("S2")){
+ throw new IllegalArgumentException("Invalid Semester");
+ }
+
+ //validate year
+ if (!(year.equals("Y1") || year.equals("Y2") || year.equals("Y3") || year.equals("Y4"))) {
+ // The input is not "Y1," "Y2," "Y3," or "Y4"
+ throw new IllegalArgumentException("Invalid Year");
+ }
+ return true;
+ } catch (Exception e) {
+ System.out.println(e.getMessage());
+ return false;
+ }
+ }
+
+ public boolean checkNameInput(String userInput, ArrayList forbiddenCommands) {
+ // Check for non-empty string
+ if (userInput.trim().isEmpty()) {
+ System.out.println("Name cannot be empty. Please enter a valid name.");
+ return false;
+ }
+
+ // Check for length constraints
+ int minLength = 2; // Minimum length for a valid name
+ int maxLength = 50; // Maximum length for a valid name
+ if (userInput.length() < minLength || userInput.length() > maxLength) {
+ System.out.println("Name must be between " + minLength + " and " + maxLength + " characters.");
+ return false;
+ }
+
+ // Check for valid characters
+ if (!userInput.matches("[a-zA-Z- ']+")) {
+ System.out.println("Name can only contain letters, spaces, hyphens, and apostrophes.");
+ return false;
+ }
+
+ // Check for no leading or trailing spaces
+ if (!userInput.equals(userInput.trim())) {
+ System.out.println("Name cannot start or end with a space.");
+ return false;
+ }
+
+
+ if (forbiddenCommands.contains(userInput.trim().toLowerCase())) {
+ System.out.println("Invalid name. This name is reserved for commands. Please enter a different name.");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks the validity of user input based on the provided command and words array.
+ *
+ * @param commandWord The command provided by the user.
+ * @param arguments An array of words parsed from the user input.
+ * @return True if the input is valid, false otherwise.
+ */
+ public static boolean isValidInputForCommand(String commandWord, String[] arguments) {
+ int argumentsCounter = 0;
+
+ //shift forward available arguments
+ if (arguments != null) {
+ for (int i = 0; i < arguments.length; i++) {
+ arguments[i] = arguments[i].trim();
+ if (arguments[i].isEmpty()) {
+ continue;
+ }
+ arguments[argumentsCounter] = arguments[i];
+ argumentsCounter++;
+ }
+ }
+
+ switch (commandWord) {
+ case UserCommandWord.COMPLETE_MODULE_COMMAND:
+ case UserCommandWord.PREREQUISITE_COMMAND: {
+ if (argumentsCounter < 1) {
+ return false;
+ }
+ break;
+ }
+ case UserCommandWord.VIEW_SCHEDULE_COMMAND:
+ case UserCommandWord.CLEAR_SCHEDULE_COMMAND:
+ case UserCommandWord.RECOMMEND_COMMAND: {
+ if (argumentsCounter > 0) {
+ return false;
+ }
+ break;
+ }
+ case UserCommandWord.SET_MAJOR_COMMAND: {
+ if (argumentsCounter == 0) {
+ return true;
+ }
+ if (argumentsCounter > 1) {
+ UserError.invalidMajorFormat();
+ return false;
+ }
+ try {
+ Major.valueOf(arguments[0].toUpperCase());
+ } catch (IllegalArgumentException e) {
+ String availableMajors = Arrays.toString(Major.values());
+ UserError.invalidMajor(availableMajors);
+ return false;
+ }
+ break;
+ }
+ case UserCommandWord.LEFT_COMMAND:
+ case UserCommandWord.REQUIRED_MODULES_COMMAND:
+ case UserCommandWord.HELP_COMMAND:
+ case UserCommandWord.BYE_COMMAND:{
+ if (argumentsCounter == 0) {
+ return true;
+ }
+ return false;
+ }
+ case UserCommandWord.ADD_MODULE_COMMAND: {
+ if (argumentsCounter != 2) {
+ UserError.invalidAddFormat();
+ return false;
+ }
+ try {
+ Integer.parseInt(arguments[1]);
+ } catch (NumberFormatException e) {
+ UserError.invalidSemester();
+ return false;
+ } catch (IndexOutOfBoundsException e) {
+ UserError.invalidAddFormat();
+ return false;
+ }
+ break;
+ }
+ case UserCommandWord.DELETE_MODULE_COMMAND: {
+ if (argumentsCounter != 1) {
+ UserError.invalidDeleteFormat();
+ return false;
+ }
+ break;
+ }
+ case UserCommandWord.SHIFT_MODULE_COMMAND: {
+ if (argumentsCounter != 2) {
+ UserError.invalidShiftFormat();
+ return false;
+ }
+ try {
+ Integer.parseInt(arguments[1]);
+ } catch (NumberFormatException e) {
+ UserError.invalidSemester();
+ return false;
+ } catch (IndexOutOfBoundsException e) {
+ UserError.invalidShiftFormat();
+ return false;
+ }
+ break;
+ }
+ case UserCommandWord.INFO_COMMAND: {
+ if (argumentsCounter < 1) {
+ UserError.emptyInputforInfoCommand();
+ return false;
+ }
+ if (!arguments[0].equals("description")) {
+ UserError.invalidCommandforInfoCommand();
+ return false;
+ }
+
+ break;
+ }
+ case UserCommandWord.TIMETABLE_COMMAND: {
+ if (argumentsCounter < 1) {
+ UserError.emptyInputforTimetableCommand();
+ return false;
+ }
+ break;
+ // add check for modules that are in the current sem
+ // if argument[1] is !show or in currSemModules, return false
+ }
+ default: {
+ return true;
+ }
+
+ }
+ return true;
+ }
+
+ /**
+ * Checks the validity of keyword input for a search command.
+ *
+ * @author rohitcube
+ * @param userInput The user input string containing the search command and keywords.
+ * @return True if the keyword input is valid, false otherwise.
+ */
+ public static boolean isValidKeywordInput(String userInput) {
+ String keywords = userInput.substring(userInput.indexOf("search") + 6);
+ // need to add a function to make search case-insensitive
+ return !keywords.trim().isEmpty();
+ }
+
+
+ /**
+ * Parses the year and semester from the given input string.
+ *
+ * @author rohitcube
+ * @param yearAndSem The input string containing year and semester information.
+ * @return An array containing the parsed year and semester as integers.
+ */
+ public static int[] parseStudentYear(String yearAndSem) {
+ char yearValue = yearAndSem.charAt(1);
+ int year = Character.getNumericValue(yearValue);
+ char semValue = yearAndSem.charAt(4);
+ int sem = Character.getNumericValue(semValue);
+ return new int[]{year, sem};
+ }
+
+ /**
+ * Checks if the given user input is a valid lesson type (lecture, tutorial, or lab).
+ *
+ * @author rohitcube
+ * @param userInput The user input to be validated.
+ * @return true if the input is a valid lesson type, false otherwise.
+ */
+ public static boolean isValidLessonType(String userInput) {
+ String lowerCaseInput = userInput.toLowerCase();
+ return lowerCaseInput.equals("lecture") ||
+ lowerCaseInput.equals("tutorial") ||
+ lowerCaseInput.equals("lab");
+ }
+
+ /**
+ * Checks if the given string can be converted to an integer.
+ *
+ * @author rohitcube
+ * @param input The string to be checked for integer conversion.
+ * @return true if the string can be converted to an integer, false otherwise.
+ */
+ public static boolean isStringInteger(String input) {
+ try {
+ Integer.parseInt(input);
+ return true;
+ } catch (NumberFormatException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the given day is a valid day of the week (case-insensitive).
+ *
+ * @author rohitcube
+ * @param day The day to be validated.
+ * @return true if the day is a valid day of the week, false otherwise.
+ */
+ public static boolean isDayValid(String day) {
+ String uppercaseDay = day.toUpperCase();
+ return uppercaseDay.equals("MONDAY") ||
+ uppercaseDay.equals("TUESDAY") ||
+ uppercaseDay.equals("WEDNESDAY") ||
+ uppercaseDay.equals("THURSDAY") ||
+ uppercaseDay.equals("FRIDAY") ||
+ uppercaseDay.equals("SATURDAY") ||
+ uppercaseDay.equals("SUNDAY");
+ }
+
+ /**
+ * Checks if there are no null elements in the given array.
+ *
+ * @author rohitcube
+ * @param array The array to be checked for null elements.
+ * @return true if there are no null elements, false otherwise.
+ */
+ public static boolean hasNoNulls(Object[] array) {
+ for (Object element : array) {
+ if (element == null) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ /**
+ * Removes null elements from the given string array and returns a new array.
+ *
+ * @author rohitcube
+ * @param inputArray The array from which null elements need to be removed.
+ * @return A new array with null elements removed.
+ */
+ public static String[] removeNulls(String[] inputArray) {
+ int newSize = 0;
+ for (String element : inputArray) {
+ if (element != null) {
+ newSize++;
+ }
+ }
+
+ String[] resultArray = new String[newSize];
+ int index = 0;
+
+ for (String element : inputArray) {
+ if (element != null) {
+ resultArray[index++] = element;
+ }
+ }
+
+ return resultArray;
+ }
+
+
+}
diff --git a/src/main/java/seedu/duke/utils/TimetableParser.java b/src/main/java/seedu/duke/utils/TimetableParser.java
new file mode 100644
index 0000000000..1fbac6610f
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/TimetableParser.java
@@ -0,0 +1,251 @@
+package seedu.duke.utils;
+
+import seedu.duke.models.schema.ModuleWeekly;
+import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException;
+
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static seedu.duke.utils.Parser.removeNulls;
+import static seedu.duke.utils.Parser.hasNoNulls;
+import static seedu.duke.utils.Parser.isStringInteger;
+import static seedu.duke.utils.Parser.isValidLessonType;
+import static seedu.duke.utils.Parser.isDayValid;
+
+public class TimetableParser {
+ private static final String ERROR_INVALID_LESSON_TYPE = "Invalid Lesson Type. Lesson Types available: Lecture, " +
+ "Tutorial, Lab. ";
+
+ private static final String ERROR_TIME_TYPE_IS_WRONG = "Invalid Time. Time should be a integer from 5 to 23. " +
+ "5 represents 5am, 23 represents 11pm";
+
+ private static final String ERROR_DURATION_TYPE_IS_WRONG = "Invalid Duration. Duration should be a integer " +
+ "representing the number of hours.";
+ private static final String ERROR_INVALID_DAY = "Invalid Day. Examples of day: Monday, Tuesday, Wednesday. " +
+ "representing the number of hours.";
+
+ private static final List lessonTypes = List.of("LECTURE", "TUTORIAL", "LAB");
+ private static final List days = List.of("MONDAY", "TUESDAY", "WEDNESDAY", "THURSDAY", "FRIDAY",
+ "SATURDAY", "SUNDAY");
+
+ private static final String DELIMITER = " ";
+
+
+ /**
+ * Checks if the provided array of arguments indicates an "EXIT" command for modifying the timetable.
+ *
+ * @author @rohitcube
+ * @param arguments An array of strings representing user input arguments.
+ * @return true if the command is to exit the modify mode, false otherwise.
+ */
+ public static boolean isExitModify(String[] arguments) {
+ String[] argumentsNoNulls = removeNulls(arguments);
+ if ((argumentsNoNulls.length == 1) && argumentsNoNulls[0].strip().equalsIgnoreCase("EXIT")) {
+ return true;
+ }
+ return false;
+ }
+
+
+ /**
+ * Checks if the provided array of arguments indicates a "clear" command for modifying the timetable.
+ *
+ * @author @rohitcube
+ * @param arguments An array of strings representing user input arguments.
+ * @return true if the command is to clear a modification, false otherwise.
+ */
+ public static boolean isModifyClear(String[] arguments) {
+ try {
+ if (arguments[2] != null || !arguments[2].isEmpty()) {
+ return false;
+ }
+ } catch (ArrayIndexOutOfBoundsException e ) {
+ System.out.println();
+ }
+ if (arguments[1].strip().equalsIgnoreCase("clear")) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if the provided array of arguments is valid for modifying the timetable and throws exceptions if not.
+ *
+ * @author @rohitcube
+ * @param arguments An array of strings representing user input arguments.
+ * @param currentSemesterModulesWeekly A list of ModuleWeekly objects representing modules in the current semester.
+ * @return true if the modification is valid, false otherwise.
+ * @throws InvalidTimetableUserCommandException If the arguments are invalid.
+ */
+ public static boolean isModifyValid(String[] arguments, ArrayList currentSemesterModulesWeekly)
+ throws InvalidTimetableUserCommandException {
+ String[] argumentsNoNulls = removeNulls(arguments);
+ if (!hasNoNulls(arguments)) {
+ throw new InvalidTimetableUserCommandException("Invalid number of arguments");
+ }
+ if (argumentsNoNulls.length == 1) {
+ if (!arguments[0].strip().equalsIgnoreCase("EXIT")) {
+ throw new InvalidTimetableUserCommandException("Invalid argument");
+ }
+ }
+ if (argumentsNoNulls.length == 2) {
+ String moduleCode = arguments[0].toUpperCase();
+ if (!isExistInCurrentSemesterModules(moduleCode, currentSemesterModulesWeekly)) {
+ throw new InvalidTimetableUserCommandException("Module does not exist in current semester.");
+ }
+ if (!argumentsNoNulls[1].strip().equalsIgnoreCase("clear")) {
+ throw new InvalidTimetableUserCommandException("Invalid argument");
+ }
+ return true;
+ }
+
+ if (argumentsNoNulls.length == 5) {
+ String moduleCode = arguments[0].toUpperCase();
+ String lessonType = arguments[1].toUpperCase();
+ String timeString = arguments[2];
+ String durationString = arguments[3];
+ String day = arguments[4].toUpperCase();
+ if (!isExistInCurrentSemesterModules(moduleCode, currentSemesterModulesWeekly)) {
+ throw new InvalidTimetableUserCommandException("Module does not exist in current semester");
+ }
+ if (!isValidLessonType(lessonType)) {
+ throw new InvalidTimetableUserCommandException("Invalid lesson type");
+ }
+ if (!isStringInteger(timeString)) {
+ throw new InvalidTimetableUserCommandException("Input for time is not a valid integer");
+ }
+ int time = Integer.parseInt(timeString);
+ if (time < 5 || time > 23) {
+ throw new InvalidTimetableUserCommandException("Input for time is outside the valid range. " +
+ "Please try again!");
+ }
+ if (!isStringInteger(durationString)) {
+ throw new InvalidTimetableUserCommandException("Input for duration is not a valid integer");
+ }
+ int duration = Integer.parseInt(durationString);
+ if (duration < 0) {
+ throw new InvalidTimetableUserCommandException("Input for duration must be at least 0");
+ }
+ if (duration > 23 - time) {
+ throw new InvalidTimetableUserCommandException("Input for duration exceeds valid hours" +
+ " on the timetable");
+ }
+ if (!isDayValid(day)) {
+ throw new InvalidTimetableUserCommandException("Invalid day");
+ }
+ return true;
+ }
+ return false;
+ }
+
+ //@@author janelleenqi
+ /**
+ * Checks if a module with a given module code exists in the current semester modules.
+ * @author @rohitcube
+ * @param moduleCode The module code to search for.
+ * @param currentSemesterModulesWeekly The list of ModuleWeekly objects for the current semester.
+ * @return true if the module exists, false otherwise.
+ */
+ public static boolean isExistInCurrentSemesterModules(String moduleCode,
+ ArrayList currentSemesterModulesWeekly) {
+ for (ModuleWeekly module : currentSemesterModulesWeekly) {
+ if (module.getModuleCode().equals(moduleCode)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // if return true,
+ public static boolean validateClearCommand(String[] argument,
+ ArrayList currentSemesterModulesWeekly) {
+ if (isExistInCurrentSemesterModules(argument[0].strip().toUpperCase(), currentSemesterModulesWeekly) &&
+ argument[1].strip().equalsIgnoreCase("clear")) {
+ System.out.println(argument[0].strip().toUpperCase());
+ System.out.println(argument[1].strip().toUpperCase());
+ System.out.println("Module does not exist in current semester.");
+ System.out.println("validate clear.");
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Parses the module code by converting it to uppercase.
+ *
+ * @param supposedModuleCode The supposed module code to be parsed.
+ * @return The parsed module code in uppercase.
+ */
+ public static String parseModuleCode(String supposedModuleCode) {
+ supposedModuleCode = supposedModuleCode.toUpperCase();
+
+ return supposedModuleCode;
+ }
+
+ /**
+ * Parses the lesson type by converting it to uppercase and validating its existence in the predefined list.
+ *
+ * @param supposedLesson The supposed lesson type to be parsed.
+ * @return The parsed lesson type in uppercase.
+ * @throws InvalidTimetableUserCommandException If the lesson type is invalid.
+ */
+ public static String parseLessonType(String supposedLesson) throws InvalidTimetableUserCommandException {
+ supposedLesson = supposedLesson.toUpperCase();
+ if (!lessonTypes.contains(supposedLesson)) {
+ throw new InvalidTimetableUserCommandException(ERROR_INVALID_LESSON_TYPE);
+ }
+
+ return supposedLesson;
+ }
+
+ /**
+ * Parses the time by converting it to an integer.
+ *
+ * @param supposedTime The supposed time to be parsed.
+ * @return The parsed time as an integer.
+ * @throws InvalidTimetableUserCommandException If the time format is incorrect.
+ */
+ public static int parseTime(String supposedTime) throws InvalidTimetableUserCommandException {
+ try {
+ return Integer.parseInt(supposedTime);
+ } catch (NumberFormatException e) {
+ throw new InvalidTimetableUserCommandException(ERROR_TIME_TYPE_IS_WRONG);
+ }
+ }
+
+ /**
+ * Parses the duration by converting it to an integer.
+ *
+ * @param supposedDuration The supposed duration to be parsed.
+ * @return The parsed duration as an integer.
+ * @throws InvalidTimetableUserCommandException If the duration format is incorrect.
+ */
+ public static int parseDuration(String supposedDuration) throws InvalidTimetableUserCommandException {
+ try {
+ return Integer.parseInt(supposedDuration);
+ } catch (NumberFormatException e) {
+ throw new InvalidTimetableUserCommandException(ERROR_DURATION_TYPE_IS_WRONG);
+ }
+ }
+
+ /**
+ * Parses the day by converting it to uppercase and validating its existence in the predefined list.
+ *
+ * @param supposedDay The supposed day to be parsed.
+ * @return The parsed day in uppercase.
+ * @throws InvalidTimetableUserCommandException If the day is invalid.
+ */
+ public static String parseDay(String supposedDay) throws InvalidTimetableUserCommandException {
+ supposedDay = supposedDay.toUpperCase();
+ if (!days.contains(supposedDay)) {
+ throw new InvalidTimetableUserCommandException(ERROR_INVALID_DAY);
+ }
+
+ return supposedDay;
+ }
+
+
+
+}
diff --git a/src/main/java/seedu/duke/utils/Utility.java b/src/main/java/seedu/duke/utils/Utility.java
new file mode 100644
index 0000000000..51435e9a5b
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/Utility.java
@@ -0,0 +1,51 @@
+package seedu.duke.utils;
+
+import seedu.duke.storage.StorageManager;
+import seedu.duke.models.schema.Student;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+
+import static seedu.duke.utils.errors.HttpError.displaySocketError;
+import static seedu.duke.views.Ui.displayGoodbye;
+
+/**
+ * Utility class containing reusable functions for various tasks.
+ * This class provides a set of static methods that can be used across different parts of an application.
+ * The methods cover tasks such as checking internet reachability, handling internet connection detection,
+ * and saving student data to storage.
+ *
+ * Note: Some methods in this class may throw IOException in case of errors, and appropriate error
+ * messages are displayed to the console.
+ */
+public class Utility {
+ public static boolean isInternetReachable() {
+ try (Socket socket = new Socket()) {
+ // Try connecting to Google's DNS server on port 53 (DNS)
+ socket.connect(new InetSocketAddress("8.8.8.8", 53), 3000); // 3 seconds timeout
+ return true; // Connection successful
+ } catch (IOException e) {
+ return false; // Unable to connect
+ }
+ }
+
+ public static void detectInternet() throws IOException {
+ if (!Utility.isInternetReachable()) {
+ displaySocketError();
+ displayGoodbye();
+ throw new IOException();
+ }
+ }
+
+ public static void saveStudentData(StorageManager storage, Student student) {
+ try {
+ storage.saveStudentDetails(student);
+ StorageManager.saveSchedule(student);
+ StorageManager.saveTimetable(student);
+ System.out.println("Data successfully saved in save files");
+ } catch (IOException e) {
+ System.out.println("Unable to save data.");
+ }
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/errors/ClassError.java b/src/main/java/seedu/duke/utils/errors/ClassError.java
new file mode 100644
index 0000000000..0be99e86fd
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/errors/ClassError.java
@@ -0,0 +1,7 @@
+package seedu.duke.utils.errors;
+
+public class ClassError {
+ public static void defaultClassError(Object message){
+ System.out.println(message);
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/errors/HttpError.java b/src/main/java/seedu/duke/utils/errors/HttpError.java
new file mode 100644
index 0000000000..08474a458e
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/errors/HttpError.java
@@ -0,0 +1,7 @@
+package seedu.duke.utils.errors;
+
+public class HttpError {
+ public static void displaySocketError(){
+ System.out.println("Please turn on your internet to use NUSDegs!");
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/errors/UserError.java b/src/main/java/seedu/duke/utils/errors/UserError.java
new file mode 100644
index 0000000000..f9e30db2a6
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/errors/UserError.java
@@ -0,0 +1,99 @@
+package seedu.duke.utils.errors;
+
+import seedu.duke.models.schema.Event;
+
+public class UserError {
+ public static void invalidInput(){
+ System.out.println("Invalid Input provided");
+ }
+
+ public static void invalidMajorFormat() {
+ System.out.println("Please select a major using this format: major [major]");
+ System.out.println("To check your current major, type: major");
+ }
+ public static void invalidMajor(String availableMajors) {
+ System.out.println("Please select a major from this list: " + availableMajors);
+ }
+
+ public static void invalidModule(String moduleCode){
+ String response = String.format("Sorry, Module %s does not exist",moduleCode);
+ System.out.println(response);
+ }
+
+ public static void displayInvalidInputCommand(String command){
+ String response = String.format("Invalid command %s. Type help to see the available commands",command);
+
+ if(command == null){
+ response = "Empty commands are not allowed!";
+ }
+
+ System.out.println(response);
+ }
+
+ public static void displayInvalidMethodCommand(String command){
+ String response = String.format("Invalid argument for command %s", command);
+ System.out.println(response);
+ }
+
+ public static void invalidAddFormat() {
+ System.out.println("Please add a module using this format: add [module code] [semester]");
+ }
+
+ public static void invalidSemester() {
+ System.out.println("Please select an integer from 1 to 8 for semester selection");
+ }
+ public static void invalidDeleteFormat() {
+ System.out.println("Please delete a module using this format: delete [module code]");
+ }
+
+ public static void invalidShiftFormat() {
+ System.out.println("Please shift a module using this format: shift [module code] [semester to move to]");
+ }
+
+ public static void emptyInputforInfoCommand() {
+ System.out.println("Empty input detected. Please enter a valid input after the info command." +
+ " (E.g description)");
+ }
+
+ public static void invalidCommandforInfoCommand() {
+ System.out.println("Please enter a valid command after the info command. (E.g description)");
+ }
+
+ public static void emptyModuleForInfoCommand(String infoCommand) {
+ System.out.println("Empty module detected. Please enter a valid module after the info " + infoCommand +
+ " command.");
+ }
+
+ public static void emptyKeywordforSearchCommand() {
+ System.out.println("Empty input detected. Please enter a valid keyword after the search command.");
+ }
+
+ public static void emptyArrayforSearchCommand() {
+ System.out.println("Oops! Your search results came up empty. Please try searching with different keywords.");
+ }
+
+ public static void emptyInputforTimetableCommand() {
+ System.out.println("Empty input detected. Please enter a valid argument after the timetable command. " +
+ "(E.g.timetable show, timetable modify)");
+ }
+ public static void emptyMajor() {
+ System.out.println("Major has not been provided yet.");
+ }
+
+ public static void moduleDoesNotExist(String moduleCode) {
+ System.out.println(moduleCode + "does not exist in your schedule.");
+ }
+
+ public static void invalidModuleCode() {
+ System.out.println("Invalid Module Code. Please try again!");
+ }
+
+ public static void displayModuleAlreadyCompleted(String moduleCode){
+ System.out.printf("%s has already been marked as completed.%n", moduleCode);
+ }
+
+ public static void displayLessonAlreadyAdded(Event event){
+ System.out.printf("%s is already in your timetable.%n", event);
+ }
+
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/CorruptedFileException.java b/src/main/java/seedu/duke/utils/exceptions/CorruptedFileException.java
new file mode 100644
index 0000000000..a017ba1db9
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/CorruptedFileException.java
@@ -0,0 +1,11 @@
+package seedu.duke.utils.exceptions;
+
+//@@author SebasFok
+/**
+ * Custom exception to indicate that a file is corrupted or has unexpected content.
+ * This exception should be thrown when an attempt to read or process a file reveals
+ * that the file's structure or content is not as expected.
+ *
+ */
+public class CorruptedFileException extends Exception{
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/FailPrereqException.java b/src/main/java/seedu/duke/utils/exceptions/FailPrereqException.java
new file mode 100644
index 0000000000..156494647c
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/FailPrereqException.java
@@ -0,0 +1,14 @@
+package seedu.duke.utils.exceptions;
+
+//@@author SebasFok
+/**
+ * Custom exception to indicate that a prerequisite for a certain module is not satisfied.
+ * This exception should be thrown when a required prerequisite for a module is not met,
+ * preventing the successful completion of an operation.
+ *
+ */
+public class FailPrereqException extends Exception{
+ public FailPrereqException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/InvalidModifyArgumentException.java b/src/main/java/seedu/duke/utils/exceptions/InvalidModifyArgumentException.java
new file mode 100644
index 0000000000..bf1b9563b3
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/InvalidModifyArgumentException.java
@@ -0,0 +1,8 @@
+package seedu.duke.utils.exceptions;
+
+public class InvalidModifyArgumentException extends Exception {
+
+ public InvalidModifyArgumentException() {
+ super("Invalid Modify arguments");
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/InvalidModuleCodeException.java b/src/main/java/seedu/duke/utils/exceptions/InvalidModuleCodeException.java
new file mode 100644
index 0000000000..24cef310f1
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/InvalidModuleCodeException.java
@@ -0,0 +1,13 @@
+package seedu.duke.utils.exceptions;
+
+//@@author ryanlohyr
+/**
+ * This class represents a custom exception that is thrown when an invalid module is encountered.
+ * An invalid module that has illegal characters.
+ */
+public class InvalidModuleCodeException extends Exception {
+ public InvalidModuleCodeException() {
+ super("Invalid Module Name");
+ }
+
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/InvalidModuleException.java b/src/main/java/seedu/duke/utils/exceptions/InvalidModuleException.java
new file mode 100644
index 0000000000..526e014dd9
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/InvalidModuleException.java
@@ -0,0 +1,14 @@
+package seedu.duke.utils.exceptions;
+
+//@@author ryanlohyr
+/**
+ * This class represents a custom exception that is thrown when an invalid module is encountered.
+ * An invalid module that has illegal characters.
+ *
+ */
+public class InvalidModuleException extends Exception{
+ public InvalidModuleException() {
+ super("Only alphabets and digits are allowed in module codes!");
+ }
+
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/InvalidPrereqException.java b/src/main/java/seedu/duke/utils/exceptions/InvalidPrereqException.java
new file mode 100644
index 0000000000..34150f2100
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/InvalidPrereqException.java
@@ -0,0 +1,9 @@
+package seedu.duke.utils.exceptions;
+
+public class InvalidPrereqException extends Exception{
+ public InvalidPrereqException(String moduleCode) {
+ super(String.format("Sorry but we could not get the prerequisite for %s as " +
+ "NUSMods API provided it in a invalid format :<",moduleCode));
+ }
+
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/InvalidTimetableUserCommandException.java b/src/main/java/seedu/duke/utils/exceptions/InvalidTimetableUserCommandException.java
new file mode 100644
index 0000000000..dace326f37
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/InvalidTimetableUserCommandException.java
@@ -0,0 +1,19 @@
+package seedu.duke.utils.exceptions;
+
+//@@author janelleenqi
+/**
+ * This exception is thrown to indicate that a timetable user command is invalid.
+ * It may occur due to invalid user input for timetable, missing information, etc.
+ */
+public class InvalidTimetableUserCommandException extends Exception {
+ /**
+ * Constructs a new `InvalidTimetableUserCommandException` with the specified detail message.
+ *
+ * @param message the detail message (which is saved for later retrieval by the `getMessage()` method)
+ */
+ public InvalidTimetableUserCommandException(String message) {
+ super(message + "\nPlease enter in the format: [moduleCode] [lessonType] [startTime] [duration] [day]\n " +
+ "If you wish to clear lessons for a module, enter: [moduleCode] clear\n If you with " +
+ "to exit modify, enter: exit ");
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/MandatoryPrereqException.java b/src/main/java/seedu/duke/utils/exceptions/MandatoryPrereqException.java
new file mode 100644
index 0000000000..ec8e57ccd0
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/MandatoryPrereqException.java
@@ -0,0 +1,14 @@
+package seedu.duke.utils.exceptions;
+
+//@@author SebasFok
+/**
+ * Custom exception to indicate that a module, X, is a mandatory prerequisite for another module, Y.
+ * This exception should be thrown when attempting an action that causes the module X to move to the same
+ * semester or after module Y.
+ *
+ */
+public class MandatoryPrereqException extends Exception{
+ public MandatoryPrereqException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/MissingFileException.java b/src/main/java/seedu/duke/utils/exceptions/MissingFileException.java
new file mode 100644
index 0000000000..091808e30a
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/MissingFileException.java
@@ -0,0 +1,12 @@
+package seedu.duke.utils.exceptions;
+
+//@@author SebasFok
+/**
+ * Custom exception to indicate that a required file is missing.
+ * This exception should be thrown when an operation expects a certain file to be present,
+ * but the file cannot be found or is missing.
+ *
+ */
+public class MissingFileException extends Exception{
+
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/MissingModuleException.java b/src/main/java/seedu/duke/utils/exceptions/MissingModuleException.java
new file mode 100644
index 0000000000..2a681e7956
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/MissingModuleException.java
@@ -0,0 +1,14 @@
+package seedu.duke.utils.exceptions;
+
+/**
+ * Custom exception to indicate that a required module is missing.
+ * This exception should be thrown when an operation expects a certain module to be present,
+ * but the module cannot be found or is missing.
+ *
+ * @author SebasFok
+ */
+public class MissingModuleException extends Exception{
+ public MissingModuleException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/TimetableUnavailableException.java b/src/main/java/seedu/duke/utils/exceptions/TimetableUnavailableException.java
new file mode 100644
index 0000000000..0846101556
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/TimetableUnavailableException.java
@@ -0,0 +1,12 @@
+package seedu.duke.utils.exceptions;
+
+//@@author janelleenqi
+/**
+ * This exception is thrown to indicate that the timetable view is unavailable.
+ */
+public class TimetableUnavailableException extends Exception {
+
+ public TimetableUnavailableException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/duke/utils/exceptions/UnknownCommandException.java b/src/main/java/seedu/duke/utils/exceptions/UnknownCommandException.java
new file mode 100644
index 0000000000..84d30bb0eb
--- /dev/null
+++ b/src/main/java/seedu/duke/utils/exceptions/UnknownCommandException.java
@@ -0,0 +1,7 @@
+package seedu.duke.utils.exceptions;
+
+public class UnknownCommandException extends Exception{
+ public UnknownCommandException(String command) {
+ super("Unknown command: " + command);
+ }
+}
diff --git a/src/main/java/seedu/duke/views/CommandLineView.java b/src/main/java/seedu/duke/views/CommandLineView.java
new file mode 100644
index 0000000000..e95b55df94
--- /dev/null
+++ b/src/main/java/seedu/duke/views/CommandLineView.java
@@ -0,0 +1,97 @@
+package seedu.duke.views;
+
+
+import seedu.duke.models.schema.CommandManager;
+import seedu.duke.utils.exceptions.InvalidPrereqException;
+
+import java.io.IOException;
+
+import static seedu.duke.models.logic.Prerequisite.getModulePrereqBasedOnCourse;
+import static seedu.duke.utils.errors.HttpError.displaySocketError;
+
+/**
+ * The CommandLineView class provides methods to display messages and user prompts in the command line interface.
+ * It also includes a method to show prerequisites for a given module based on the course and major.
+ */
+public class CommandLineView {
+
+ public static void displayReady(){
+ System.out.println("Now you're all set to use NUSDegs to kick start your degree planning!");
+ displayHelp();
+ }
+
+ public static void displayHelp(){
+ System.out.println("Type 'help' to see the available commands");
+ }
+
+
+ public static void displayGetMajor(String name){
+ System.out.println("Welcome " + name + "! What major are you? (Only two available: CEG or CS)");
+ }
+
+ public static void displayGetYear(){
+ System.out.println("What Year and Semester are you? Ex: Y1/S2 for year 1 semester 2");
+ }
+
+ public static void displayCommands(CommandManager commandManager) {
+ Ui.displayMessage("Here are all the commands currently available in NUSDegs!");
+ Ui.displayMessage("- Words in UPPER_CASE are the parameters to be supplied by the user.");
+ Ui.displayMessage("- Parameters in [] are optional.");
+ System.out.println();
+ for (String command : commandManager.printListOfCommands()) {
+ Ui.displayMessage(command);
+ }
+ System.out.println();
+ String userGuideURL = "https://ay2324s1-cs2113-t17-4.github.io/tp/UserGuide.html";
+ Ui.displayMessage("For more information, please read our User Guide at this link:");
+ Ui.displayMessage(userGuideURL);
+ }
+
+ /**
+ * Displays the prerequisites for a given module based on the course and major.
+ *
+ * @param module The module for which prerequisites need to be displayed.
+ * @param major The major or course associated with the module.
+ */
+ public static void showPrereq(String module,String major){
+ try{
+
+ System.out.println("This module's prerequisites are "
+ + getModulePrereqBasedOnCourse(module.toUpperCase(),major));
+ }catch (InvalidPrereqException e){
+ System.out.println(e.getMessage());
+ }catch (IOException e){
+ displaySocketError();
+ }
+ }
+ public static void displaySuccessfulAddMessage() {
+ Ui.displayMessage("Module Successfully Added");
+ }
+
+ public static void displaySuccessfulDeleteMessage() {
+ Ui.displayMessage("Module Successfully Deleted");
+
+ }
+
+ public static void displaySuccessfulShiftMessage() {
+ Ui.displayMessage("Module Successfully Shifted");
+ }
+
+ public static void displaySuccessfulClearMessage() {
+ Ui.displayMessage("Schedule successfully cleared");
+ }
+
+ public static void displayUnsuccessfulClearMessage() {
+ Ui.displayMessage("Clear schedule operation cancelled");
+ }
+ public static void displaySuccessfulCompleteMessage() {
+ Ui.displayMessage("Module Successfully Completed");
+ }
+
+ public static void displayUnsuccessfulCompleteMessage() {
+ Ui.displayMessage("Module cannot be completed as its prereqs have not been completed.");
+ }
+
+
+
+}
diff --git a/src/main/java/seedu/duke/views/MajorRequirementsView.java b/src/main/java/seedu/duke/views/MajorRequirementsView.java
new file mode 100644
index 0000000000..5268159af8
--- /dev/null
+++ b/src/main/java/seedu/duke/views/MajorRequirementsView.java
@@ -0,0 +1,168 @@
+package seedu.duke.views;
+
+//@@author janelleenqi
+/**
+ * The MajorRequirementsView class provides methods to print the required modules for specific majors.
+ */
+public class MajorRequirementsView {
+
+ /**
+ * Prints the required modules for a specified major.
+ *
+ * This method takes a major as input and prints the required modules for that major.
+ *
+ * @param major The major for which to print the required modules.
+ */
+ public static void printRequiredModules(String major) {
+ switch (major) {
+ case "CEG":
+ printRequiredModulesCEG();
+ return;
+ case "CS":
+ printRequiredModulesCS();
+ return;
+ default:
+ // should not be reached, prints nothing
+ displayMessage("");
+ return;
+ }
+ }
+
+ /**
+ * Prints the required modules for the Computer Engineering (CEG) major.
+ */
+ public static void printRequiredModulesCEG() { //60 char
+ displayMessage(
+ "#==========================================================#\n" +
+ "║ Modular Requirements for CEG Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " GES1000 (Singapore Studies) 4\n" +
+ " GEC1000 (Cultures and Connections) 4\n" +
+ " GEN2000 (Communities and Engagement) 4\n" +
+ " ES2631 Critique & Communication of Thinking\n" +
+ " & Design (Critique & Expression) 4\n" +
+ " CS1010 Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " GEA1000 Quantitative Reasoning with Data (Data \n" +
+ " Literacy) 4\n" +
+ " DTK1234 Design Thinking (Design Thinking) 4\n" +
+ " EG1311 Design and Make (Maker Space) 4\n" +
+ " IE2141 Systems Thinking and Dynamics (Systems \n" +
+ " Thinking) 4\n" +
+ " EE2211 Introduction to Machine Learning \n" +
+ " (Artificial Intelligence) 4\n" +
+ " CDE2501 Liveable Cities (Sustainable Futures) 4\n" +
+ " CDE2000 (Creating Narratives) 4\n" +
+ " PF1101 Fundamentals of Project Management \n" +
+ " (Project Management) 4\n" +
+ " CG4002 Computer Engineering Capstone Project 1 \n" +
+ " (Integrated Project) 8\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Engineering Core 20 ~~\n" +
+ "\n" +
+ " MA1511 Engineering Calculus 2\n" +
+ " MA1512 Differential Equations for Engineering 2\n" +
+ " MA1508E Linear Algebra for Engineering 4\n" +
+ " EG2401A Engineering Professionalism 2\n" +
+ " CP3880 Advanced Technology Attachment Programme 12\n" +
+ "\n" +
+ " ~~ CEG Major 40 ~~\n" +
+ "\n" +
+ " CG1111A Engineering Principles and Practice I 4\n" +
+ " CG2111A Engineering Principles and Practice II 4\n" +
+ " CS1231 Discrete Structures 4\n" +
+ " CG2023 Signals & Systems 4\n" +
+ " CG2027 Transistor-level Digital Circuit 2\n" +
+ " CG2028 Computer Organization 2\n" +
+ " CG2271 Real-time Operating System 4\n" +
+ " CS2040C Data Structures and Algorithms 4\n" +
+ " CS2113 Software Engineering & Object-Oriented \n" +
+ " Programming 4\n" +
+ " EE2026 Digital Design 4\n" +
+ " EE4204 Computer Networks 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+\n");
+ }
+
+ /**
+ * Prints the required modules for the Computer Science (CS) major.
+ */
+ public static void printRequiredModulesCS() {
+ displayMessage(
+ "#==========================================================#\n" +
+ "║ Modular Requirements for CS Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 40 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ University Requirements: 6 University Pillars 24 ~~\n" +
+ "\n" +
+ " CS1101S Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " ES2660 Communicating in the Information Age \n" +
+ " (Critique and Expression) 4\n" +
+ " GEC1% (Cultures and Connections) 4\n" +
+ " GEA1000 / BT1101 / ST1131 / DSA1101 (Data \n" +
+ " Literacy) 4\n" +
+ " GES1% (Singapore Studies) 4\n" +
+ " GEN2% (Communities and Engagement) 4\n" +
+ "\n" +
+ " ~~ Computing Ethics 4 ~~\n" +
+ "\n" +
+ " IS1108 Digital Ethics and Data Privacy 4\n" +
+ "\n" +
+ " ~~ Inter & Cross-Disciplinary Education 12 ~~\n" +
+ "\n" +
+ " Interdisciplinary (ID) Courses (at least 2)\n" +
+ " Cross-disciplinary (CD) Courses (no more than 1)\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 80 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Computer Science Foundation 36 ~~\n" +
+ "\n" +
+ " CS1231S Discrete Structures 4\n" +
+ " CS2030S Programming Methodology II 4\n" +
+ " CS2040S Data Structures and Algorithms 4\n" +
+ " CS2100 Computer Organisation 4\n" +
+ " CS2101 Effective Communication for Computing \n" +
+ " Professionals 4\n" +
+ " CS2103T Software Engineering 4\n" +
+ " CS2106 Introduction to Operating Systems 4\n" +
+ " CS2109S Introduction to AI and Machine Learning 4\n" +
+ " CS3230 Design and Analysis of Algorithms 4\n" +
+ "\n" +
+ " ~~ Computer Science Breadth and Depth 32 ~~\n" +
+ "\n" +
+ "\n" +
+ " ~~ Mathematics and Sciences 12 ~~\n" +
+ "\n" +
+ " MA1521 Calculus for Computing 4\n" +
+ " MA1522 Linear Algebra for Computing 4\n" +
+ " ST2334 Probability and Statistics 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+\n");
+ }
+
+ /**
+ * Displays a message to the console.
+ *
+ * This method prints the specified object to the console.
+ *
+ * @param o The object to be displayed as a message.
+ */
+ public static void displayMessage(Object o) {
+ System.out.print(o);
+ }
+
+}
diff --git a/src/main/java/seedu/duke/views/ModuleInfoView.java b/src/main/java/seedu/duke/views/ModuleInfoView.java
new file mode 100644
index 0000000000..98a83f7bcb
--- /dev/null
+++ b/src/main/java/seedu/duke/views/ModuleInfoView.java
@@ -0,0 +1,67 @@
+package seedu.duke.views;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import seedu.duke.models.schema.Module;
+
+import java.util.ArrayList;
+
+public class ModuleInfoView {
+
+ public static void print(String output) {
+ System.out.println(output);
+ }
+
+ public static void printLine() {
+ System.out.println("_________________________________________");
+ }
+ public static void printJsonArray(JSONArray modules) {
+ for (Object module: modules) {
+ JSONObject castedModule = (JSONObject) module;
+ print("Title: " + (String)castedModule.get("title"));
+ print("Module Code: " + (String)castedModule.get("moduleCode"));
+ printLine();
+ }
+ }
+
+ public static void searchHeader() {
+ printLine();
+ print("These are the modules that contain your keyword in the title:");
+ print("");
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Print a list of modules in columns with a specified maximum height.
+ * @param modules An ArrayList of module names to be printed.
+ */
+ public static void printModuleStringArray(ArrayList modules){
+ int maxColumnHeight = 5;
+
+ for (int i = 0; i < modules.size(); i += maxColumnHeight) {
+ for (int j = 0; j < maxColumnHeight && i + j < modules.size(); j++) {
+ String module = (i + j + 1) + ". " + modules.get(i + j);
+ System.out.printf("%-15s", module);
+ }
+ System.out.println();
+ }
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Print a list of modules in columns with a specified maximum height.
+ * @param modules An ArrayList of module names to be printed.
+ */
+ public static void printModuleArray(ArrayList modules){
+ int maxColumnHeight = 5;
+
+ for (int i = 0; i < modules.size(); i += maxColumnHeight) {
+ for (int j = 0; j < maxColumnHeight && i + j < modules.size(); j++) {
+ String module = (i + j + 1) + ". " + modules.get(i + j);
+ System.out.printf("%-15s", module);
+ }
+ System.out.println();
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/duke/views/SemesterPlannerView.java b/src/main/java/seedu/duke/views/SemesterPlannerView.java
new file mode 100644
index 0000000000..658eee8892
--- /dev/null
+++ b/src/main/java/seedu/duke/views/SemesterPlannerView.java
@@ -0,0 +1,84 @@
+package seedu.duke.views;
+
+import seedu.duke.models.schema.Module;
+import seedu.duke.models.schema.ModuleList;
+import seedu.duke.models.schema.Schedule;
+
+
+/**
+ * The SemesterPlannerView class provides methods to display and print the semester planner.
+ */
+public class SemesterPlannerView {
+ private static void print(String output) {
+ System.out.print(output);
+ }
+
+ private static void println() {
+ System.out.println();
+ }
+
+ /**
+ * Displays the schedule based on the provided schedule object.
+ *
+ * @param schedule The Schedule object containing the semester planner information.
+ */
+ public static void displaySchedule (Schedule schedule){
+ int[] modulesPerSem = schedule.getModulesPerSem();
+ ModuleList modulesPlanned = schedule.getModulesPlanned();
+ int moduleCounter = 0;
+ int maxModulesPerColumn = 5;
+ for (int i = 0; i < modulesPerSem.length; i++) {
+ print("Sem " + (i + 1) + ": ");
+ for (int j = 0; j < modulesPerSem[i]; j++) {
+ if(j != 0 && j % maxModulesPerColumn == 0){
+ System.out.println();
+ print(" " + " "+ " ");
+ }
+ Module currentModule = modulesPlanned.getModuleByIndex(moduleCounter);
+ String stringToPrint = getCompletionStatusForPrinting(currentModule) + " " + currentModule + " ";
+ System.out.printf("%-15s", stringToPrint);
+ moduleCounter++;
+ }
+ println();
+ }
+ }
+
+ /**
+ * Prints the semester planner based on the provided modulesPerSem and modulesPlanned.
+ *
+ * @param modulesPerSem The array representing the number of modules per semester.
+ * @param modulesPlanned The ModuleList containing the planned modules.
+ */
+ public static void printSemesterPlanner(int[] modulesPerSem, ModuleList modulesPlanned) {
+ int moduleCounter = 0;
+ int maxModulesPerColumn = 5;
+ for (int i = 0; i < modulesPerSem.length; i++) {
+ print("Sem " + (i + 1) + ": ");
+ for (int j = 0; j < modulesPerSem[i]; j++) {
+ if(j != 0 && j % maxModulesPerColumn == 0){
+ System.out.println();
+ print(" " + " "+ " ");
+ }
+ Module currentModule = modulesPlanned.getModuleByIndex(moduleCounter);
+ String stringToPrint = getCompletionStatusForPrinting(currentModule) + " " + currentModule + " ";
+ System.out.printf("%-15s", stringToPrint);
+ moduleCounter++;
+ }
+ println();
+ }
+ }
+
+ //@@author janelleenqi
+ /**
+ * Retrieves the completion status indicator for a module, 'O' for completed, 'X' for not completed.
+ *
+ * @param module The Module object for which to determine the completion status.
+ * @return The completion status indicator ('O' or 'X').
+ */
+ private static String getCompletionStatusForPrinting(Module module) {
+ if (module.getCompletionStatus()) {
+ return "O";
+ }
+ return "X";
+ }
+}
diff --git a/src/main/java/seedu/duke/views/TimetableUserGuideView.java b/src/main/java/seedu/duke/views/TimetableUserGuideView.java
new file mode 100644
index 0000000000..81fad06a85
--- /dev/null
+++ b/src/main/java/seedu/duke/views/TimetableUserGuideView.java
@@ -0,0 +1,151 @@
+package seedu.duke.views;
+
+import seedu.duke.models.schema.ModuleWeekly;
+import seedu.duke.utils.exceptions.TimetableUnavailableException;
+
+import java.util.ArrayList;
+
+//@@author janelleenqi
+/**
+ * The TimetableUserGuideView class provides methods to display user guides for the timetable-related commands.
+ */
+public class TimetableUserGuideView {
+ private static final int justifyLength = 12;
+
+ private static void print(String output) {
+ System.out.print(output + " ");
+ }
+ private static void println() {
+ System.out.println();
+ }
+ public static void println(String output) {
+ System.out.println(output);
+ }
+
+
+ /**
+ * Prints spaces to justify the output to the given width.
+ *
+ * @param number The width for justification.
+ */
+ private static void printToJustify(int number) {
+ print(String.format("%-" + number + "s", ""));
+ }
+
+ /**
+ * Prints a string with spaces to justify it to the given width.
+ *
+ * @param string The string to be printed.
+ * @param number The width for justification.
+ */
+ private static void printToJustify(String string, int number) {
+ print(String.format("%-" + number + "s", string));
+ }
+
+ /**
+ * Prints the list of modules in the current semester.
+ *
+ * @param currentSemModulesWeekly The list of modules in the current semester.
+ * @throws TimetableUnavailableException If there are no modules in the current semester.
+ */
+ public static void printCurrentSemModules(ArrayList currentSemModulesWeekly)
+ throws TimetableUnavailableException {
+ println("List of modules in current semester: ");
+ if (currentSemModulesWeekly.isEmpty()) {
+ throw new TimetableUnavailableException("There are no modules in your current semester. " +
+ "Please add in modules, or generate using the 'recommend' command.");
+ }
+ for (ModuleWeekly moduleWeekly : currentSemModulesWeekly) {
+ println(moduleWeekly.getModuleCode());
+ }
+ println();
+ }
+
+ public static void addGuide() {
+ println("Please add a module using this format: add [module code] [semester]");
+ }
+
+
+ private static String getTimetableShowGuide() {
+ return "View your timetable using this format: timetable show";
+ }
+
+ private static String getTimetableModifyGuide() {
+ return "Modify your lectures/tutorials/labs in timetable using this format: timetable modify";
+ }
+
+ /**
+ * Generates a guide for adding or recommending modules.
+ *
+ * @param specificContext The specific context for the guide.
+ * @param semester The semester for which to add modules.
+ * @return The guide for adding or recommending modules.
+ */
+ public static String addOrRecommendGuide(String specificContext, int semester) {
+ return (specificContext + "\n" +
+ "Add modules using this format: add [module code] " + semester + "\n" +
+ "Alternatively, get the recommended schedule for your major: recommend");
+ }
+
+ /**
+ * Prints the guide for adding or recommending modules.
+ *
+ * @param specificContext The specific context for the guide.
+ */
+ public static void printAddRecommendGuide(String specificContext) {
+ print(specificContext);
+ println("Add modules to your current semester or get the recommended schedule for your major first.");
+ }
+
+ /**
+ * Prints the guide for modifying the timetable.
+ *
+ * @param specificContext The specific context for the guide.
+ */
+ public static void printTimetableModifyGuide(String specificContext) {
+ println(specificContext);
+ println("Enter Timetable Modify Mode to add lessons: timetable modify");
+ }
+
+ /**
+ * Prints a simple guide for modifying lessons in the timetable.
+ *
+ * @param specificContext The specific context for the guide.
+ */
+ public static void printTTModifySimpleLessonGuide(String specificContext) {
+ println(specificContext);
+ println("To add a lesson for a module, enter: [moduleCode] [lessonType] [startTime] [duration] [day]\n" +
+ "To clear lessons for a module, enter: [moduleCode] clear\n" +
+ "To exit Timetable Modify Mode, enter: EXIT");
+ }
+
+ /**
+ * Prints a detailed guide for modifying lessons in the timetable.
+ *
+ * @param specificContext The specific context for the guide.
+ */
+ public static void printTTModifyDetailedLessonGuide(String specificContext) {
+ println(specificContext);
+ println("To add a lesson to a module: [moduleCode] [lessonType] [startTime] [duration] [day]");
+
+ printToJustify(4);
+ printToJustify("lessonType", justifyLength);
+ println("lecture, tutorial, lab");
+
+ printToJustify(4);
+ printToJustify("startTime", justifyLength);
+ println("integer from 5 to 23 (representing 5am to 11pm)");
+
+ printToJustify(4);
+ printToJustify("duration", justifyLength);
+ println("time in hours");
+
+ printToJustify(4);
+ printToJustify("day", justifyLength);
+ println("eg. monday, tuesday, wednesday");
+
+ println("To clear all lessons for a module: [moduleCode] clear");
+ println("To exit timetable modify: exit");
+ }
+
+}
diff --git a/src/main/java/seedu/duke/views/TimetableView.java b/src/main/java/seedu/duke/views/TimetableView.java
new file mode 100644
index 0000000000..2aacf0aef0
--- /dev/null
+++ b/src/main/java/seedu/duke/views/TimetableView.java
@@ -0,0 +1,310 @@
+package seedu.duke.views;
+
+import seedu.duke.models.schema.Event;
+import seedu.duke.models.schema.ModuleWeekly;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static seedu.duke.views.TimetableUserGuideView.printTimetableModifyGuide;
+import static seedu.duke.views.TimetableUserGuideView.printAddRecommendGuide;
+
+//@@author janelleenqi
+public class TimetableView {
+ private static final int dayColumnWidth = 10;
+ private static final int eventColumnWidth = 45;
+ private static final String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
+
+ /**
+ * Prints the given output without a new line.
+ *
+ * @param output The string to be printed.
+ */
+ private static void print(String output) {
+ System.out.print(output);
+ }
+
+ /**
+ * Prints the given output with a new line.
+ *
+ * @param output The string to be printed.
+ */
+ private static void println(String output) {
+ System.out.println(output);
+ }
+
+ /**
+ * Prints a horizontal line as a separator in the console.
+ */
+ private static void printlnHorizontalLine() {
+ println("------------------------------------------------------------");
+ }
+
+ /**
+ * Prints a vertical line as a separator in the console.
+ */
+ private static void printVerticalLine() {
+ System.out.print("| ");
+ }
+
+ /**
+ * Prints a vertical line followed by a new line.
+ */
+ private static void printlnVerticalLine() {
+ System.out.println("|");
+ }
+
+ /**
+ * Prints spaces to justify the output to the given width.
+ *
+ * @param number The width for justification.
+ */
+ private static void printToJustify(int number) {
+ print(String.format("%-" + number + "s", ""));
+ }
+
+
+ /**
+ * Prints a string with spaces to justify it to the given width.
+ *
+ * @param string The string to be printed.
+ * @param number The width for justification.
+ */
+ private static void printToJustify(String string, int number) {
+ print(String.format("%-" + number + "s", string));
+ }
+
+ /**
+ * Prints the timetable for the given list of ModuleWeekly objects.
+ *
+ * @param currentSemesterModules List of ModuleWeekly objects.
+ */
+ public static void printTimetable(ArrayList currentSemesterModules) {
+ if (currentSemesterModules == null || currentSemesterModules.isEmpty()) {
+ printAddRecommendGuide("Timetable view is unavailable as you have no modules in your current " +
+ "semester.\n");
+ return;
+ }
+
+ // create a List (by days) of EventList (modules, event type, time)
+ List> weeklyTimetableByDay = createDailyEvents(currentSemesterModules);
+
+ if (!eventsExist(weeklyTimetableByDay)) {
+ printTimetableModifyGuide("Timetable view is unavailable as modules in your current " +
+ "semester have no lessons yet.");
+ return;
+ }
+
+ // sort EventList by time
+ for (ArrayList currentDayEvents : weeklyTimetableByDay) {
+ sortByTime(currentDayEvents);
+ }
+
+ // print timetable
+ printTimetableHeader();
+ for (int day = 0; day < days.length; day++) { //8-9am index 0, 7-8pm index 11
+ if (weeklyTimetableByDay.get(day).isEmpty()) {
+ continue;
+ }
+
+ printlnHorizontalLine();
+ printCurrentDayEvents(weeklyTimetableByDay.get(day), day);
+ }
+ printlnHorizontalLine();
+ }
+
+ /**
+ * Creates a list of daily events for the given list of ModuleWeekly objects.
+ *
+ * @param currentSemesterModules List of ModuleWeekly objects.
+ * @return A list of daily events.
+ */
+ private static List> createDailyEvents(ArrayList currentSemesterModules) {
+ // List with 7 empty ArrayList
+ List> weeklyTimetableByDay = initialiseOneDList();
+
+ // Add events as string for all modules to weeklyTimetableByDay
+ for (ModuleWeekly module : currentSemesterModules) {
+ for (Event event : module.getWeeklyTimetable()) {
+ addToWeeklyTimetableByDay(weeklyTimetableByDay, event);
+ }
+ }
+ return weeklyTimetableByDay;
+ }
+
+ /**
+ * Sorts an ArrayList of events in ascending order.
+ * This method uses the bubble sort algorithm to sort the events.
+ *
+ * @param currentDayEvents An ArrayList of Event objects to be sorted.
+ */
+ private static void sortByTime(ArrayList currentDayEvents) {
+ // bubble sort O(n^2)
+ for (int index = 0; index < currentDayEvents.size(); index++) {
+ int bubbleIndex = index;
+ while (bubbleIndex > 0) {
+ Event unsortedEvent = currentDayEvents.get(bubbleIndex);
+ Event sortedEvent = currentDayEvents.get(bubbleIndex - 1);
+ if (unsortedEvent.isEarlierThan(sortedEvent)) {
+ // swap
+ currentDayEvents.set(bubbleIndex, sortedEvent);
+ currentDayEvents.set(bubbleIndex - 1, unsortedEvent);
+ bubbleIndex--;
+ } else {
+ break;
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Prints the timetable header.
+ */
+ private static void printTimetableHeader() {
+ printlnHorizontalLine();
+
+ printVerticalLine();
+ printToJustify("DAY", dayColumnWidth);
+
+ printVerticalLine();
+ printToJustify("TIMETABLE", eventColumnWidth);
+
+ printlnVerticalLine();
+ }
+
+ /**
+ * Prints events for the current day.
+ *
+ * @param currentDayEvents List of events for the current day.
+ * @param day The index of the day.
+ */
+ private static void printCurrentDayEvents(ArrayList currentDayEvents, int day) {
+ // Need to print day for first line
+ boolean isFirstLine = true;
+
+ while (!currentDayEvents.isEmpty()) {
+ Event currentEvent = currentDayEvents.get(0);
+ printCurrentDayOneEvent(currentEvent, day, isFirstLine);
+ currentDayEvents.remove(0);
+ isFirstLine = false;
+ }
+ }
+
+ /**
+ * Prints one line of events for the current day.
+ *
+ * @param currentEvent The current event.
+ * @param day The index of the day.
+ * @param isFirstLine Whether it is the first line.
+ */
+ private static void printCurrentDayOneEvent(Event currentEvent, int day, boolean isFirstLine) {
+ String currentEventDetails = currentEvent.toString();
+ while (!currentEventDetails.isEmpty()) {
+
+ printVerticalLine();
+
+ // print day
+ if (isFirstLine) {
+ print(days[day]);
+ printToJustify(dayColumnWidth - days[day].length());
+
+ isFirstLine = false;
+ } else {
+ printToJustify(dayColumnWidth);
+ }
+
+ printVerticalLine();
+
+ // if currentEvent is too long
+ if (currentEventDetails.length() > eventColumnWidth) {
+ String[] words = currentEventDetails.split(" ");
+ int eventColumnWidthLeft = eventColumnWidth;
+ int currentWordIndex = 0;
+
+ while (eventColumnWidthLeft > words[currentWordIndex].length()) {
+ print(words[currentWordIndex] + " ");
+ eventColumnWidthLeft -= (words[currentWordIndex] + " ").length();
+ currentWordIndex++;
+ }
+ printToJustify(eventColumnWidthLeft);
+
+ String wordNotPrintedYet = words[currentWordIndex];
+ int indexNotPrintedYet = currentEventDetails.indexOf(wordNotPrintedYet);
+ currentEventDetails = currentEventDetails.substring(indexNotPrintedYet);
+ printlnVerticalLine();
+ continue;
+ }
+
+ // currentEvent can be printed
+ print(currentEventDetails);
+ printToJustify(eventColumnWidth - currentEventDetails.length());
+ currentEventDetails = "";
+ printlnVerticalLine();
+ }
+ }
+
+ /**
+ * Fills and sets an object at a specific index in a list, creating intermediate objects if needed.
+ *
+ * @param index Index where the object should be placed.
+ * @param object Object to be placed.
+ * @param list List where the object should be placed.
+ * @param Type of the object.
+ */
+ private static void fillAndSet(int index, T object, List list) {
+ if (index > (list.size() - 1)) {
+ for (int i = list.size(); i < index; i++) {
+ list.add(null);
+ }
+ list.add(object);
+ } else {
+ list.set(index, object);
+ }
+ }
+
+ /**
+ * Initializes a one-dimensional list for daily events.
+ *
+ * @return A list of daily events.
+ */
+ private static List> initialiseOneDList() {
+ List> parentList = new ArrayList<>();
+
+ for (int j = 0; j < 7; j++) { //7 days
+ ArrayList childList = new ArrayList();
+ fillAndSet(j, childList, parentList);
+ parentList.add(childList);
+ }
+
+ return parentList;
+ }
+
+
+ /**
+ * Adds an event to the daily schedule for a specific day, start time, and duration.
+ *
+ * @param list List of daily events.
+ * @param event Event to be added.
+ */
+ private static void addToWeeklyTimetableByDay(List> list, Event event) {
+ ArrayList childList = list.get(event.getDayInt());
+ childList.add(event);
+ }
+
+ /**
+ * Checks if there are any events in the weekly schedule for each day.
+ *
+ * @param weeklyTimetableByDay List of daily events.
+ * @return True if events exist for any day, false otherwise.
+ */
+ private static boolean eventsExist(List> weeklyTimetableByDay) {
+ for (ArrayList currentDayEvents : weeklyTimetableByDay) {
+ if (!currentDayEvents.isEmpty()) {
+ // true if at least 1 event exists in weeklyTimetable
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/seedu/duke/views/Ui.java b/src/main/java/seedu/duke/views/Ui.java
new file mode 100644
index 0000000000..da879dcbbd
--- /dev/null
+++ b/src/main/java/seedu/duke/views/Ui.java
@@ -0,0 +1,166 @@
+package seedu.duke.views;
+import java.util.Scanner;
+import java.io.PrintStream;
+import java.io.InputStream;
+
+/**
+ * Utility class for displaying messages and graphics to welcome and interact with CS and CEG students.
+ * This class provides static methods for displaying welcome messages, logos, and handling user input.
+ * It is designed for use in applications related to Computer Science (CS) and Computer Engineering (CEG).
+ *
+ */
+public class Ui {
+ private static Thread loadingThread;
+ private static final String DIVIDER = "___________________________________________________________";
+ private static final String DIVIDERWithoutPadding = "___________________________________________________________";
+ private final Scanner in;
+ private final PrintStream out;
+
+ public Ui() {
+ this(System.in, System.out);
+ }
+
+ public Ui(InputStream in, PrintStream out) {
+ this.in = new Scanner(in);
+ this.out = out;
+ }
+
+ /**
+ * Display a message to the command line view.
+ *
+ * @param o The object to be displayed.
+ */
+ public static void displayMessage(Object o) {
+ System.out.println(o);
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Displays a welcome message to CS and CEG students.
+ * This method prints a welcome message and a logo to the console, providing a friendly greeting
+ * to Computer Science (CS) and Computer Engineering (CEG) students using the application.
+ */
+ public static void displayWelcome(){
+
+ String logo = " _ _ _ _ ____ ____ \n"
+ + " | \\ | | | | / ___|| _ \\ ___ __ _ ___ \n"
+ + " | \\| | | | \\___ \\| | | |/ _ \\/ _` / __|\n"
+ + " | |\\ | |_| |___) | |_| | __/ (_| \\__ \\\n"
+ + " |_| \\_|\\___/|____/|____/ \\___|\\__, |___/\n"
+ + " |___/ ";
+
+ System.out.println("Hey there CS and CEG Students! Welcome to ");
+ System.out.println(logo);
+ }
+
+ /**
+ * Displays a farewell message to the user.
+ * This method prints a goodbye message to the console, indicating the end of the application or interaction.
+ * It is typically used when the user exits or completes a session with the application.
+ */
+ public static void displayGoodbye(){
+ System.out.println("Goodbye!");
+ }
+
+
+ //@@author ryanlohyr
+ /**
+ * Prompts the user with a message and retrieves a command from the console.
+ * This method displays a prompt message to the user, reads a command from the console,
+ * and returns the entered command as a String.
+ * @param toDisplay The message to display as a prompt before reading the user's input.
+ * @return A String representing the command entered by the user.
+ */
+ public String getUserCommand(String toDisplay) {
+ out.println(DIVIDERWithoutPadding);
+ out.print(toDisplay);
+ return in.nextLine();
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Prints one or more messages surrounded by a divider.
+ * This method prints messages to the console, each on a new line, surrounded by a divider.
+ * It is a utility method for displaying information or messages in a formatted way.
+ *
+ * @param messages The messages to be printed. Each message is printed on a new line.
+ */
+ public void printMessage(String... messages) {
+ out.println();
+ out.println(DIVIDER);
+ for (String m : messages) {
+ out.println(m);
+ }
+ out.println(DIVIDER);
+ }
+
+
+ //@@author ryanlohyr
+ /**
+ * Prints an error message related to data storage issues.
+ * This method prints an error message indicating that there is an issue with retrieving data.
+ * It is typically used when no save file exists, or the existing save file is corrupted.
+ * Users are instructed to continue using the application to create a new save file or overwrite
+ * the corrupted file.
+ *
+ */
+ public void printStorageError() {
+ out.println();
+ out.println(DIVIDER);
+ System.out.println("Unable to retrieve any data. Your save file may be corrupted.\n" +
+ "Please continue using the application to create new save files or overwrite " +
+ "the corrupted files!");
+ out.println("Please check ./data again");
+ stopLoadingAnimation();
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Displays a loading animation in the console.
+ * This method creates a new thread that prints a loading animation sequence to the console.
+ * The animation consists of a series of characters that change to give the appearance of movement.
+ * The loading animation runs until the thread is interrupted.
+ * Note: This method assumes that the console supports carriage return ("\r") for updating the loading animation.
+ * Example animationChars: {"(.O_O.)", "(.o_o.)", "(.<_<.)", "(.^_^.)"}
+ *
+ *
+ */
+ public static void showLoadingAnimation() {
+ String[] animationChars = {"(.O_O.)","(.o_o.)","(.<_<.)","(.>_>.)","(.>_<.)","(.^_^.)"};
+ loadingThread = new Thread(() -> {
+ int i = 0;
+ while (!Thread.currentThread().isInterrupted()) {
+ System.out.print("Loading " + animationChars[i % 6] + "\r");
+ i++;
+ try {
+ Thread.sleep(600);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ System.out.print("\n");
+ });
+ loadingThread.start();
+ }
+
+ //@@author ryanlohyr
+ /**
+ * Stops the loading animation thread if it is currently running.
+ * This method checks if the loading animation thread is active and interrupts it if so.
+ * It is used to halt any ongoing loading animation gracefully.
+ * Note: This method assumes that the loadingThread variable is appropriately managed.
+ * If the loadingThread is null or not alive, no action is taken.
+ */
+ public static void stopLoadingAnimation() {
+ if (loadingThread != null && loadingThread.isAlive()) {
+ loadingThread.interrupt();
+ try {
+ // Wait for the loading thread to finish
+ loadingThread.join();
+ } catch (InterruptedException e) {
+ // Handle the interruption if needed
+ Thread.currentThread().interrupt(); // Restore interrupted status
+ }
+ }
+ }
+}
diff --git a/src/test/java/seedu/duke/ApiTest.java b/src/test/java/seedu/duke/ApiTest.java
new file mode 100644
index 0000000000..780239df6f
--- /dev/null
+++ b/src/test/java/seedu/duke/ApiTest.java
@@ -0,0 +1,138 @@
+package seedu.duke;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import seedu.duke.utils.exceptions.InvalidModuleCodeException;
+import seedu.duke.utils.exceptions.InvalidModuleException;
+import seedu.duke.models.logic.Api;
+
+import java.io.IOException;
+import java.util.Objects;
+
+import org.junit.jupiter.api.Test;
+import seedu.duke.views.ModuleInfoView;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+public class ApiTest {
+ @Test
+ void testGetModuleInfo_shouldReturnTrueForCS2113() throws IOException {
+ String correctModuleInfo = "\"description\":\"This course introduces the necessary skills for systematic " +
+ "and rigorous development of software systems. It covers";
+ String moduleCode = "CS2113";
+ String moduleInfo = null;
+ moduleInfo = Objects.requireNonNull(Api.getFullModuleInfo(moduleCode)).toJSONString();
+ assertNotNull(moduleInfo, "Module info should not be null");
+ assertTrue(moduleInfo.contains(correctModuleInfo), "Module info should contain relevant info");
+ }
+
+ @Test
+ public void getDescription_invalidEntry() {
+ assertThrows(InvalidModuleCodeException.class, () -> Api.getDescription("invalid342432"));
+ }
+
+ @Test
+ public void getDescription_emptyEntry() {
+ assertThrows(InvalidModuleCodeException.class, () -> Api.getDescription(" "));
+ }
+
+ @Test
+ void testGetDescription_shouldReturnEquals() throws InvalidModuleException, InvalidModuleCodeException {
+ String correctDescription = "This course introduces the necessary skills for systematic and " +
+ "rigorous development of software systems. It covers requirements, design, implementation, " +
+ "quality assurance, and project management aspects of small-to-medium size multi-person software" +
+ " projects. The course uses the Object Oriented Programming paradigm. Students of this course will " +
+ "receive hands-on practice of tools commonly used in the industry, such as test automation tools," +
+ " build automation tools, and code revisioning tools will be covered.";
+ String moduleCode = "CS2113";
+ String description = Api.getDescription(moduleCode);
+ assertEquals(correctDescription, description);
+ }
+
+ @Test
+ void testGetWorkload_shouldReturnCorrectValue() throws InvalidModuleCodeException {
+ // uses unchecked or unsafe operations, Note: Recompile with -Xlint:unchecked for details.
+ JSONArray workload = Api.getWorkload("CS2113");
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.add(2);
+ jsonArray.add(1);
+ jsonArray.add(0);
+ jsonArray.add(3);
+ jsonArray.add(4);
+ String jsonString1 = jsonArray.toJSONString();
+ String jsonString2 = workload.toJSONString();
+ assertEquals(jsonString2, jsonString1);
+ }
+
+ @Test
+ void testGetWorkload_invalidEntry() {
+ assertThrows(InvalidModuleCodeException.class, () -> Api.getWorkload("invH3432"));
+ }
+
+ @Test
+ void testWrapTextEmptyInput() {
+ String text = " ";
+ System.out.println(Api.wrapText(text, 60));
+ assertTrue(Api.wrapText(text, 60).trim().isEmpty());
+ }
+ @Test
+ void testWrapTextNullInput() {
+ String text = null;
+ System.out.println(Api.wrapText(text, 60));
+ assertTrue(Api.wrapText(text, 60).trim().isEmpty());
+ }
+
+ @Test
+ void testSearchCommand_invalidInput() {
+ assertTrue(Api.search("1231724738", Api.listAllModules()).isEmpty());
+ }
+
+ @Test
+ void testListAllModules() {
+ Api.listAllModules();
+ }
+ /*
+ @Test
+ void testSearchModules_emptyInput_expectedEmptyJsonArray() {
+ JSONArray modulesToPrint;
+ modulesToPrint = Api.search(" ", Api.listAllModules());
+ assertEquals(0, modulesToPrint.size(), "The JSON array should be empty.");
+ }
+ */
+ @Test
+ void testSearchModules_invalidInput_expectedEmptyJsonArray() {
+ JSONArray modulesToPrint;
+ modulesToPrint = Api.search("bs#4%ggh", Api.listAllModules());
+ assertEquals(0, modulesToPrint.size(), "The JSON array should be empty.");
+ }
+
+ @Test
+ void testSearchModules_validInput_expectedJsonArray() {
+ JSONArray modulesToPrint;
+ modulesToPrint = Api.search("Trustworthy Machine Learning", Api.listAllModules());
+ JSONArray expectedArray = new JSONArray();
+ JSONObject expectedObject = new JSONObject();
+ expectedObject.put("moduleCode", "CS5562");
+ JSONArray semester1 = new JSONArray();
+ semester1.add(1);
+ expectedObject.put("semesters", semester1);
+ expectedObject.put("title", "Trustworthy Machine Learning");
+ expectedArray.add(expectedObject);
+ String expectedOutput = expectedArray.toJSONString();
+ String output = modulesToPrint.toJSONString();
+ assertEquals(expectedOutput, output, "The string should be equal");
+ }
+
+ @Test
+ void testPrintJsonArray() {
+ JSONArray modulesToPrint = Api.search("Machine Learning", Api.listAllModules());
+ ModuleInfoView.printJsonArray(modulesToPrint);
+ }
+
+
+
+}
diff --git a/src/test/java/seedu/duke/controllers/AddFeatureTest.java b/src/test/java/seedu/duke/controllers/AddFeatureTest.java
new file mode 100644
index 0000000000..7a31316e7c
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/AddFeatureTest.java
@@ -0,0 +1,198 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+public class AddFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Sebastian");
+ student.setFirstMajor("CEG");
+ student.setYear("Y2/S1");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void testAddFeature_addValidModuleValidSemester_expectScheduleWithNewModule() {
+ String userInput = "add CS1010 1";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Module Successfully Added\n" +
+ "Sem 1: X CS1010 \n" +
+ "Sem 2: \n" +
+ "Sem 3: \n" +
+ "Sem 4: \n" +
+ "Sem 5: \n" +
+ "Sem 6: \n" +
+ "Sem 7: \n" +
+ "Sem 8:";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testAddFeature_addDuplicateModule_expectErrorMessage() {
+ String userInput = "add CS1010 1";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ //Add again
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Module Successfully Added\n" +
+ "Sem 1: X CS1010 \n" +
+ "Sem 2: \n" +
+ "Sem 3: \n" +
+ "Sem 4: \n" +
+ "Sem 5: \n" +
+ "Sem 6: \n" +
+ "Sem 7: \n" +
+ "Sem 8: \n" +
+ "Module already exists in the schedule";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testAddFeature_addValidModuleInvalidSemester_expectErrorMessage() {
+ String userInput = "add CS1010 9";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Please select an integer from 1 to 8 for semester selection";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testAddFeature_addInvalidModuleValidSemester_expectErrorMessage() {
+ String userInput = "add CS101010 1";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Invalid Module Name\n" +
+ "Please select a valid module";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testAddFeature_noArguments_expectErrorMessage() {
+ String userInput = "add";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Please add a module using this format: add [module code] [semester]\n" +
+ "Invalid argument for command add";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testAddFeature_unsatisfiedPrerequisites_expectErrorMessage() {
+ String userInput = "add CS2040C 1";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "This module's prerequisites are [CS1010]\n" +
+ "Unable to add module as prerequisites not satisfied for: CS2040C";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+}
diff --git a/src/test/java/seedu/duke/controllers/CompleteFeatureTest.java b/src/test/java/seedu/duke/controllers/CompleteFeatureTest.java
new file mode 100644
index 0000000000..3880a9cbb3
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/CompleteFeatureTest.java
@@ -0,0 +1,166 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class CompleteFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+ private String commandToTest = "recommend";
+
+ @BeforeEach
+ public void setUpStreams() throws IOException {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y3/S2");
+ System.setOut(new PrintStream(outputStream));
+ ArrayList recommendedSchedule = student.getSchedule().generateRecommendedSchedule("CEG");
+ student.getSchedule().addReccToSchedule(recommendedSchedule);
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+ //complete a module that is not there
+ //complete a module that pre requisite is not satisfied
+ //complete a module that pre requisite is satisfied
+ //complete a module that pre req is not satisfied, complete the pre req, than complete the module
+ @Test
+ void testCompleteFeature_completeModuleThatPrerequisiteIsSatisfied() {
+ String userInput = "complete CS1010";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Module Successfully Completed";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testCompleteFeature_completeModuleThatPrerequisiteIsNotSatisfied() {
+ String userInput = "complete CS2040C";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Prerequisites not completed for CS2040C\n" +
+ "This module's prerequisites are [CS1010]";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testCompleteFeature_completeModuleAndMakeSureIsSatisfied() {
+ String userInput = "complete CS2040C";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ userInput = "complete CS1010";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ userInput = "complete CS2040C";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Prerequisites not completed for CS2040C\n" +
+ "This module's prerequisites are [CS1010]\n" +
+ "Module Successfully Completed\n" +
+ "Module Successfully Completed";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testCompleteFeature_completeModuleThatPrerequisiteIsNotThere() {
+ String userInput = "complete IS1108";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "IS1108 is not in Modules Planner. Please add the module to your schedule first!";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+}
diff --git a/src/test/java/seedu/duke/controllers/DeleteFeatureTest.java b/src/test/java/seedu/duke/controllers/DeleteFeatureTest.java
new file mode 100644
index 0000000000..3f34043b05
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/DeleteFeatureTest.java
@@ -0,0 +1,162 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Schedule;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeleteFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Sebastian");
+ student.setFirstMajor("CEG");
+ student.setYear("Y2/S1");
+ Schedule schedule = new Schedule("CS1010 CS2040C", new int[]{1, 1, 0, 0, 0, 0, 0, 0});
+ student.setSchedule(schedule);
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void testDeleteFeature_deleteValidModule_expectModuleDeleted() {
+ String userInput = "delete CS2040C";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Module Successfully Deleted\n" +
+ "Sem 1: X CS1010 \n" +
+ "Sem 2: \n" +
+ "Sem 3: \n" +
+ "Sem 4: \n" +
+ "Sem 5: \n" +
+ "Sem 6: \n" +
+ "Sem 7: \n" +
+ "Sem 8:";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testDeleteFeature_deleteModuleWhichIsPrerequisite_expectErrorMessage() {
+ String userInput = "delete CS1010";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Unable to delete module. This module is a mandatory prerequisite for CS2040C";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testDeleteFeature_deleteModuleNotInSchedule_expectErrorMessage() {
+ String userInput = "delete CS1231";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Module does not exist in schedule";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testDeleteFeature_deleteUnknownArgument_expectErrorMessage() {
+ String userInput = "delete faiwefioawlefiuawef";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Module does not exist in schedule";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testDeleteFeature_noArguments_expectErrorMessage() {
+ String userInput = "delete";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Please delete a module using this format: delete [module code]\n" +
+ "Invalid argument for command delete";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+}
diff --git a/src/test/java/seedu/duke/controllers/InfoFeatureTest.java b/src/test/java/seedu/duke/controllers/InfoFeatureTest.java
new file mode 100644
index 0000000000..1c4fafd664
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/InfoFeatureTest.java
@@ -0,0 +1,135 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class InfoFeatureTest {
+
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Rohit");
+ student.setFirstMajor("CEG");
+ student.setYear("Y2/S1");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ public void testInfoFeature_validInput_expectAssertEquals() {
+ String userInput = "info description CS2113";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "This course introduces the necessary skills for systematic and " +
+ "rigorous development of software\n" +
+ " systems. It covers requirements, design, implementation, " +
+ "quality assurance, and project management\n" +
+ " aspects of small-to-medium size multi-person software projects. The course uses the Object\n" +
+ " Oriented Programming paradigm. Students of this course will receive hands-on practice of tools\n" +
+ " commonly used in the industry, such as test automation tools, build automation tools, and code\n" +
+ " revisioning tools will be covered.";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ public void testInfoFeature_noModuleCode_expectErrorMessage() {
+ String userInput = "info description";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "Invalid Module Code: Only alphabets and digits are allowed in module codes!";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ public void testInfoFeature_invalidModuleCode_expectErrorMessage() {
+ String userInput = "info description CS2111113";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "Invalid Module Name";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ public void testInfoFeature_missingDescriptionCommand_expectErrorMessage() {
+ String userInput = "info";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "Empty input detected. Please enter a valid input after the " +
+ "info command. (E.g description)\n" + "Invalid argument for command info";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ public void testInfoFeature_invalidSubcommand_expectErrorMessage() {
+ String userInput = "info workload info";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "Please enter a valid command after the info command. (E.g description)\n" +
+ "Invalid argument for command info";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+}
diff --git a/src/test/java/seedu/duke/controllers/LeftFeatureTest.java b/src/test/java/seedu/duke/controllers/LeftFeatureTest.java
new file mode 100644
index 0000000000..c9fc10b2a1
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/LeftFeatureTest.java
@@ -0,0 +1,111 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class LeftFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Janelle");
+ student.setFirstMajor("CEG");
+ student.setYear("Y2/S1");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void testLeftFeature_noModulesCompleted_expectMajorModuleCodes() {
+ String userInput = "left";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Required Modules Left: \n" +
+ "1. CG1111A 2. MA1511 3. MA1512 4. CS1010 5. GESS1000 \n" +
+ "6. GEC1000 7. GEN2000 8. ES2631 9. GEA1000 10. DTK1234 \n" +
+ "11. EG1311 12. IE2141 13. EE2211 14. EG2501 15. CDE2000 \n" +
+ "16. PF1101 17. CG4002 18. MA1508E 19. EG2401A 20. CP3880 \n" +
+ "21. CG2111A 22. CS1231 23. CG2023 24. CG2027 25. CG2028 \n" +
+ "26. CG2271 27. ST2334 28. CS2040C 29. CS2113 30. EE2026 \n" +
+ "31. EE4204";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testLeftFeature_completedSomeNoPrereqModules_expectPrintLeft() {
+
+ String[] completeUserInputs = {"add cs1010 1", "add dtk1234 2", "complete cs1010", "complete"};
+ int currentIndex = 0;
+ currentUserCommand = new UserCommand(completeUserInputs[currentIndex]);
+ while (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+
+ currentIndex ++;
+ currentUserCommand = new UserCommand(completeUserInputs[currentIndex]);
+ }
+
+ String userInput = "left";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // To exclude printedOutput from add and complete features
+ int indexLeftOutput = printedOutput.indexOf("Modules Left: ");
+ if (indexLeftOutput != -1) {
+ // Extract the text starting from the found index
+ printedOutput = printedOutput.substring(indexLeftOutput);
+ }
+
+ String expectedOutput = "Modules Left: \n" +
+ "1. CG1111A 2. MA1511 3. MA1512 4. GESS1000 5. GEC1000 \n" +
+ "6. GEN2000 7. ES2631 8. GEA1000 9. DTK1234 10. EG1311 \n" +
+ "11. IE2141 12. EE2211 13. EG2501 14. CDE2000 15. PF1101 \n" +
+ "16. CG4002 17. MA1508E 18. EG2401A 19. CP3880 20. CG2111A \n" +
+ "21. CS1231 22. CG2023 23. CG2027 24. CG2028 25. CG2271 \n" +
+ "26. ST2334 27. CS2040C 28. CS2113 29. EE2026 30. EE4204";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+}
diff --git a/src/test/java/seedu/duke/controllers/ModuleMethodsControllerTest.java b/src/test/java/seedu/duke/controllers/ModuleMethodsControllerTest.java
new file mode 100644
index 0000000000..d66789fac9
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/ModuleMethodsControllerTest.java
@@ -0,0 +1,251 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.Test;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+
+import seedu.duke.utils.exceptions.FailPrereqException;
+import seedu.duke.models.schema.Schedule;
+import seedu.duke.models.schema.Student;
+
+import java.io.ByteArrayOutputStream;
+import java.io.InvalidObjectException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static seedu.duke.views.Ui.displayMessage;
+import static seedu.duke.controllers.ModuleMethodsController.completeModule;
+import static seedu.duke.controllers.ModuleMethodsController.showModulesLeft;
+import static seedu.duke.views.CommandLineView.displaySuccessfulAddMessage;
+import static seedu.duke.views.CommandLineView.showPrereq;
+
+
+
+class ModuleMethodsControllerTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y3/S2");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void completeModule_prereqNotCompleted() {
+ ModuleMethodsController.executeAddModuleCommand("CS1010",1,student);
+ ModuleMethodsController.executeAddModuleCommand("CS2040C",2,student);
+ ModuleMethodsController.executeAddModuleCommand("CS2113T",3,student);
+ completeModule(student,"CS2113T");
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Prerequisites not completed for CS2113T\n" +
+ "This module's prerequisites are [CS2040C]";
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ //We need this extra logic as addModule prints text as well
+ int index = printedOutput.indexOf("Prerequisites not completed for");
+ String targetOutputText = "";
+ if (index != -1) {
+ // Extract the text starting from the found index
+ targetOutputText = printedOutput.substring(index);
+ } else {
+ targetOutputText = "invalid";
+ }
+
+ assertEquals(expectedOutput,targetOutputText);
+ }
+
+ @Test
+ void completeModule_prereqSatisfied() {
+ ModuleMethodsController.executeAddModuleCommand("CS1010",1,student);
+ ModuleMethodsController.executeAddModuleCommand("CS2040C",2,student);
+ ModuleMethodsController.executeAddModuleCommand("CS2113T",3,student);
+ completeModule(student,"CS1010");
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Module Successfully Completed";
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ //We need this extra logic as addModule prints text as well
+ int index = printedOutput.indexOf("Module Successfully Completed");
+ String printedText = "";
+ if (index != -1) {
+ // Extract the text starting from the found index
+ printedText = printedOutput.substring(index);
+ } else {
+ printedText = "invalid";
+ }
+
+ assertEquals(expectedOutput,printedText);
+ }
+
+
+
+ @Test
+ void showModulesLeftTest_arrayListModules_expectModulesLeft() {
+ String expectedOutput = "Required Modules Left: \n" +
+ "1. GEA1000 2. MA1521 3. IS1108 4. MA1522 5. CS1231S \n" +
+ "6. ES2660 7. CS2101 8. CS1101S 9. GESS1000 10. GEN2000";
+
+ showModulesLeft(new ArrayList(List.of("GEA1000", "MA1521", "IS1108", "MA1522", "CS1231S", "ES2660",
+ "CS2101", "CS1101S", "GESS1000", "GEN2000")));
+
+ String printedOutput = outputStream.toString().trim();
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testPrereq_addValidModuleToStudent() throws InvalidObjectException {
+
+ String moduleCode = "EG1311";
+ int targetSem = 1;
+ boolean doesModuleExist = false;
+ try {
+ student.addModuleToSchedule(moduleCode, targetSem);
+ displaySuccessfulAddMessage();
+ student.printSchedule();
+ Schedule currentSchedule = student.getSchedule();
+ doesModuleExist = currentSchedule.getModulesPlanned().existsByCode(moduleCode);
+ } catch (InvalidObjectException | IllegalArgumentException e) {
+ displayMessage(e.getMessage());
+ } catch (FailPrereqException f) {
+ showPrereq(moduleCode,student.getMajor());
+ displayMessage(f.getMessage());
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Module Successfully Added\n" +
+ "Sem 1: X EG1311 \n" +
+ "Sem 2: \n" +
+ "Sem 3: \n" +
+ "Sem 4: \n" +
+ "Sem 5: \n" +
+ "Sem 6: \n" +
+ "Sem 7: \n" +
+ "Sem 8:";
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+
+ assertTrue(doesModuleExist);
+ }
+
+ @Test
+ void testPrereq_addInValidModuleToStudent() throws InvalidObjectException {
+ String moduleCode = "eEG1311";
+ int targetSem = 1;
+ boolean doesModuleExist = false;
+ try {
+ student.addModuleToSchedule(moduleCode, targetSem);
+ displaySuccessfulAddMessage();
+ student.printSchedule();
+ Schedule currentSchedule = student.getSchedule();
+ doesModuleExist = currentSchedule.getModulesPlanned().existsByCode(moduleCode);
+ } catch (InvalidObjectException | IllegalArgumentException e) {
+ displayMessage(e.getMessage());
+ } catch (FailPrereqException f) {
+ showPrereq(moduleCode,student.getMajor());
+ displayMessage(f.getMessage());
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Invalid Module Name\n" +
+ "Please select a valid module";
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(printedOutput, expectedOutput);
+ assertFalse(doesModuleExist);
+
+ }
+
+ @Test
+ void testPrereq_addInvalidModuleToStudent() throws InvalidObjectException {
+ String moduleCode = "CS2113";
+ int targetSem = 1;
+ boolean doesModuleExist = false;
+ try {
+ student.addModuleToSchedule(moduleCode, targetSem);
+ displaySuccessfulAddMessage();
+ student.printSchedule();
+ Schedule currentSchedule = student.getSchedule();
+ doesModuleExist = currentSchedule.getModulesPlanned().existsByCode(moduleCode);
+ } catch (InvalidObjectException | IllegalArgumentException e) {
+ displayMessage(e.getMessage());
+ } catch (FailPrereqException f) {
+ showPrereq(moduleCode,student.getMajor());
+ displayMessage(f.getMessage());
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "This module's prerequisites are [CS2040C]\n" +
+ "Unable to add module as prerequisites not satisfied for: CS2113";
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertFalse(doesModuleExist);
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+
+ @Test
+ void addModuleTest() {
+ }
+
+ @Test
+ void deleteModuleTest() {
+ }
+
+ @Test
+ void getRequiredModulesForStudentTest() {
+ }
+}
diff --git a/src/test/java/seedu/duke/controllers/PaceFeatureTest.java b/src/test/java/seedu/duke/controllers/PaceFeatureTest.java
new file mode 100644
index 0000000000..ff4424bb2e
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/PaceFeatureTest.java
@@ -0,0 +1,109 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class PaceFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y1/S2");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void computePaceInvalidArgument() {
+ String userInput = "pace year2 sem1";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ // Assert the printed output matches the expected value
+ String expectedOutput = "Needs to be in format of Y2/S1";
+
+ assertEquals(expectedOutput,printedOutput);
+ }
+
+ @Test
+ void computePaceWithoutArgument() {
+
+ String userInput = "pace";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ // Assert the printed output matches the expected value
+ String expectedOutput = "You have 160MCs for 6 semesters. Recommended Pace: 27MCs per sem until graduation";
+
+ assertEquals(expectedOutput,printedOutput);
+ }
+
+
+ @Test
+ void computePaceInvalidSemester() {
+ String userInput = "pace y2/s10";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ // Assert the printed output matches the expected value
+ assertEquals("Invalid Semester", printedOutput);
+ }
+
+ @Test
+ void computePaceInvalidYear() {
+ String userInput = "pace y0/s1";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ // Assert the printed output matches the expected value
+ assertEquals("Invalid Year", printedOutput);
+ }
+
+ @Test
+ void computePaceValidYear() {
+ String userInput = "pace y3/s2";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ String line = "You have 160MCs for 2 semesters. Recommended Pace: 80MCs per sem until graduation";
+ // Assert the printed output matches the expected value
+ assertEquals(printedOutput, line);
+ }
+
+}
diff --git a/src/test/java/seedu/duke/controllers/PrereqFeatureTest.java b/src/test/java/seedu/duke/controllers/PrereqFeatureTest.java
new file mode 100644
index 0000000000..bc065ec9dd
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/PrereqFeatureTest.java
@@ -0,0 +1,98 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+public class PrereqFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+ private String commandToTest = "prereq";
+
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y3/S2");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void determinePrereq_invalidModuleCode() {
+ String argument = " cs134";
+ currentUserCommand = new UserCommand(commandToTest + argument);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedResponse = "Invalid Module Name";
+ assertEquals(expectedResponse, printedOutput);
+ }
+
+ @Test
+ void determinePrereq_emptyArgument() {
+ currentUserCommand = new UserCommand(commandToTest);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedResponse = "Invalid argument for command prereq";
+ assertEquals(expectedResponse, printedOutput);
+ }
+
+ @Test
+ void determinePrereq_illegalModuleCode() {
+ String argument = " \\cs134```";
+ currentUserCommand = new UserCommand(commandToTest + argument);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedResponse = "Invalid Module Code: Only alphabets and digits are allowed in module codes!";
+ assertEquals(expectedResponse, printedOutput);
+ }
+
+ @Test
+ void determinePrereq_validModuleCodeWithNoPreReq() {
+ String argument = " GEN2061";
+ currentUserCommand = new UserCommand(commandToTest + argument);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedResponse = "Module GEN2061 has no prerequisites.";
+ assertEquals(printedOutput, expectedResponse);
+ }
+
+ @Test
+ void determinePrereq_validModuleCodeWithPreReq() {
+ String argument = " EE2211";
+ currentUserCommand = new UserCommand(commandToTest + argument);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedResponse = "1. CS1010 2. MA1511 3. MA1508E";
+ assertEquals(printedOutput, expectedResponse);
+ }
+
+
+}
diff --git a/src/test/java/seedu/duke/controllers/RecommendedScheduleFeatureTest.java b/src/test/java/seedu/duke/controllers/RecommendedScheduleFeatureTest.java
new file mode 100644
index 0000000000..f678d38c7b
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/RecommendedScheduleFeatureTest.java
@@ -0,0 +1,115 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class RecommendedScheduleFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+ private String commandToTest = "recommend";
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y3/S2");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void testRecommend_generateCEGRecommendedSchedule() throws IOException {
+ ArrayList recommendedSchedule = student.getSchedule().generateRecommendedSchedule("CEG");
+ System.out.println(recommendedSchedule);
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "[GEA1000, MA1511, MA1512, DTK1234, GESS1000, CS1231, CS1010, GEN2000, EG2501," +
+ " EG1311, GEC1000, PF1101, CDE2000, IE2141, CG1111A, EG2401A, ES2631, ST2334, MA1508E, CG2023," +
+ " CG2111A, CS2040C, CG2027, EE2026, EE4204, EE2211, CG2271, CS2113, CG2028, CP3880, CG4002]";
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testRecommend_addCEGRecommendedScheduleToStudent() throws IOException {
+ ArrayList recommendedSchedule = student.getSchedule().generateRecommendedSchedule("CEG");
+ student.getSchedule().addReccToSchedule(recommendedSchedule);
+ student.getSchedule().printMainModuleList();
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Sem 1: X GESS1000 X DTK1234 " +
+ "X MA1512 X MA1511 X GEA1000 \n" +
+ "Sem 2: X EG1311 X EG2501 X GEN2000 X CS1010 X CS1231 \n" +
+ "Sem 3: X CG1111A X IE2141 X CDE2000 X PF1101 X GEC1000 \n" +
+ "Sem 4: X CG2023 X MA1508E X ST2334 X ES2631 X EG2401A \n" +
+ "Sem 5: X EE4204 X EE2026 X CG2027 X CS2040C X CG2111A \n" +
+ "Sem 6: X CG2028 X CS2113 X CG2271 X EE2211 \n" +
+ "Sem 7: X CG4002 X CP3880 \n" +
+ "Sem 8:";
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testRecommend_generateCSRecommendedSchedule() throws IOException {
+ ArrayList recommendedSchedule = student.getSchedule().generateRecommendedSchedule("CS");
+ System.out.println(recommendedSchedule);
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "[GEA1000, MA1521, IS1108, MA1522, CS1231S, ES2660, CS2101, " +
+ "CS1101S, GESS1000, GEN2000," +
+ " GEC1000, ST2334, CS2030, CS2040S, CS2100, CS2103T, CS2109S, CS3230, CS2106, CP3880]";
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testRecommend_addCSRecommendedScheduleToStudent() throws IOException {
+ ArrayList recommendedSchedule = student.getSchedule().generateRecommendedSchedule("CS");
+ student.getSchedule().addReccToSchedule(recommendedSchedule);
+ student.getSchedule().printMainModuleList();
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Sem 1: X CS1231S X MA1522 X IS1108 X MA1521 " +
+ "X GEA1000 \n" +
+ "Sem 2: X GEN2000 X GESS1000 X CS1101S X CS2101 X ES2660 \n" +
+ "Sem 3: X CS2100 X CS2040S X CS2030 X ST2334 X GEC1000 \n" +
+ "Sem 4: X CS2106 X CS3230 X CS2109S X CS2103T \n" +
+ "Sem 5: X CP3880 \n" +
+ "Sem 6: \n" +
+ "Sem 7: \n" +
+ "Sem 8:";
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+}
diff --git a/src/test/java/seedu/duke/controllers/RequiredFeatureTest.java b/src/test/java/seedu/duke/controllers/RequiredFeatureTest.java
new file mode 100644
index 0000000000..6508f26677
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/RequiredFeatureTest.java
@@ -0,0 +1,195 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class RequiredFeatureTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Janelle");
+ student.setYear("Y2/S1");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void testRequiredFeature_ceg_expectRequiredForCEG() throws InvalidTimetableUserCommandException {
+ student.setFirstMajor("CEG");
+
+ String userInput = "required";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "#==========================================================#\n" +
+ "║ Modular Requirements for CEG Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " GES1000 (Singapore Studies) 4\n" +
+ " GEC1000 (Cultures and Connections) 4\n" +
+ " GEN2000 (Communities and Engagement) 4\n" +
+ " ES2631 Critique & Communication of Thinking\n" +
+ " & Design (Critique & Expression) 4\n" +
+ " CS1010 Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " GEA1000 Quantitative Reasoning with Data (Data \n" +
+ " Literacy) 4\n" +
+ " DTK1234 Design Thinking (Design Thinking) 4\n" +
+ " EG1311 Design and Make (Maker Space) 4\n" +
+ " IE2141 Systems Thinking and Dynamics (Systems \n" +
+ " Thinking) 4\n" +
+ " EE2211 Introduction to Machine Learning \n" +
+ " (Artificial Intelligence) 4\n" +
+ " CDE2501 Liveable Cities (Sustainable Futures) 4\n" +
+ " CDE2000 (Creating Narratives) 4\n" +
+ " PF1101 Fundamentals of Project Management \n" +
+ " (Project Management) 4\n" +
+ " CG4002 Computer Engineering Capstone Project 1 \n" +
+ " (Integrated Project) 8\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Engineering Core 20 ~~\n" +
+ "\n" +
+ " MA1511 Engineering Calculus 2\n" +
+ " MA1512 Differential Equations for Engineering 2\n" +
+ " MA1508E Linear Algebra for Engineering 4\n" +
+ " EG2401A Engineering Professionalism 2\n" +
+ " CP3880 Advanced Technology Attachment Programme 12\n" +
+ "\n" +
+ " ~~ CEG Major 40 ~~\n" +
+ "\n" +
+ " CG1111A Engineering Principles and Practice I 4\n" +
+ " CG2111A Engineering Principles and Practice II 4\n" +
+ " CS1231 Discrete Structures 4\n" +
+ " CG2023 Signals & Systems 4\n" +
+ " CG2027 Transistor-level Digital Circuit 2\n" +
+ " CG2028 Computer Organization 2\n" +
+ " CG2271 Real-time Operating System 4\n" +
+ " CS2040C Data Structures and Algorithms 4\n" +
+ " CS2113 Software Engineering & Object-Oriented \n" +
+ " Programming 4\n" +
+ " EE2026 Digital Design 4\n" +
+ " EE4204 Computer Networks 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testRequiredFeature_cs_expectRequiredForCS() {
+ student.setFirstMajor("CS");
+
+ String userInput = "required";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ String expectedOutput = "#==========================================================#\n" +
+ "║ Modular Requirements for CS Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 40 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ University Requirements: 6 University Pillars 24 ~~\n" +
+ "\n" +
+ " CS1101S Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " ES2660 Communicating in the Information Age \n" +
+ " (Critique and Expression) 4\n" +
+ " GEC1% (Cultures and Connections) 4\n" +
+ " GEA1000 / BT1101 / ST1131 / DSA1101 (Data \n" +
+ " Literacy) 4\n" +
+ " GES1% (Singapore Studies) 4\n" +
+ " GEN2% (Communities and Engagement) 4\n" +
+ "\n" +
+ " ~~ Computing Ethics 4 ~~\n" +
+ "\n" +
+ " IS1108 Digital Ethics and Data Privacy 4\n" +
+ "\n" +
+ " ~~ Inter & Cross-Disciplinary Education 12 ~~\n" +
+ "\n" +
+ " Interdisciplinary (ID) Courses (at least 2)\n" +
+ " Cross-disciplinary (CD) Courses (no more than 1)\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 80 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Computer Science Foundation 36 ~~\n" +
+ "\n" +
+ " CS1231S Discrete Structures 4\n" +
+ " CS2030S Programming Methodology II 4\n" +
+ " CS2040S Data Structures and Algorithms 4\n" +
+ " CS2100 Computer Organisation 4\n" +
+ " CS2101 Effective Communication for Computing \n" +
+ " Professionals 4\n" +
+ " CS2103T Software Engineering 4\n" +
+ " CS2106 Introduction to Operating Systems 4\n" +
+ " CS2109S Introduction to AI and Machine Learning 4\n" +
+ " CS3230 Design and Analysis of Algorithms 4\n" +
+ "\n" +
+ " ~~ Computer Science Breadth and Depth 32 ~~\n" +
+ "\n" +
+ "\n" +
+ " ~~ Mathematics and Sciences 12 ~~\n" +
+ "\n" +
+ " MA1521 Calculus for Computing 4\n" +
+ " MA1522 Linear Algebra for Computing 4\n" +
+ " ST2334 Probability and Statistics 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+}
diff --git a/src/test/java/seedu/duke/controllers/ScheduleFeatureTest.java b/src/test/java/seedu/duke/controllers/ScheduleFeatureTest.java
new file mode 100644
index 0000000000..a8d953dcfe
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/ScheduleFeatureTest.java
@@ -0,0 +1,93 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ScheduleFeatureTest {
+
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+ private String commandToTest = "schedule";
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y3/S2");
+ System.setOut(new PrintStream(outputStream));
+
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void testViewScheduleInvalidInput() {
+ String argument = " extra argument";
+ currentUserCommand = new UserCommand(commandToTest + argument);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Invalid argument for command schedule";
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testViewScheduleValidInput() {
+ currentUserCommand = new UserCommand(commandToTest);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Sem 1: \n" +
+ "Sem 2: \n" +
+ "Sem 3: \n" +
+ "Sem 4: \n" +
+ "Sem 5: \n" +
+ "Sem 6: \n" +
+ "Sem 7: \n" +
+ "Sem 8:";
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void testViewScheduleInValidCharacter() {
+ String argument = " ```";
+ currentUserCommand = new UserCommand(commandToTest + argument);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ String expectedOutput = "Invalid argument for command schedule";
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+
+
+
+}
diff --git a/src/test/java/seedu/duke/controllers/SearchFeatureTest.java b/src/test/java/seedu/duke/controllers/SearchFeatureTest.java
new file mode 100644
index 0000000000..20d8a8eead
--- /dev/null
+++ b/src/test/java/seedu/duke/controllers/SearchFeatureTest.java
@@ -0,0 +1,99 @@
+package seedu.duke.controllers;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.Student;
+import seedu.duke.models.schema.UserCommand;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SearchFeatureTest {
+
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Janelle");
+ student.setFirstMajor("CEG");
+ student.setYear("Y2/S1");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ public void testSearchFeature_validKeyword_expectAssertEquals() {
+ String userInput = "search Darwin";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "_________________________________________\n" +
+ "These are the modules that contain your keyword in the title:\n" +
+ "\n" +
+ "Title: Junior Seminar: The Darwinian Revolution\n" +
+ "Module Code: UTC1102B\n" +
+ "_________________________________________";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ public void testSearchFeature_invalidKeyword_expectErrorMessage() {
+ String userInput = "search hehehehe siuuuuuuuu";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "Oops! Your search results came up empty. Please try " +
+ "searching with different keywords.";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ public void testSearchFeature_emptyKeyword_expectErrorMessage() {
+ String userInput = "search ";
+ currentUserCommand = new UserCommand(userInput);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ String expectedOutput = "Empty input detected. Please enter a valid keyword after the search command.";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+
+
+}
diff --git a/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java b/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java
new file mode 100644
index 0000000000..3f76bd827c
--- /dev/null
+++ b/src/test/java/seedu/duke/models/logic/DataRepositoryTest.java
@@ -0,0 +1,18 @@
+package seedu.duke.models.logic;
+
+import org.junit.jupiter.api.Test;
+import seedu.duke.storage.StorageManager;
+
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+
+class DataRepositoryTest {
+ @Test
+ void validRequirementsReturned() {
+ ArrayList cegRequirementArray = StorageManager.getRequirements("CEG");
+ int numberOfRequiredCegMods = 31;
+ assertEquals(numberOfRequiredCegMods,cegRequirementArray.size());
+ }
+}
diff --git a/src/test/java/seedu/duke/models/logic/ScheduleGeneratorTest.java b/src/test/java/seedu/duke/models/logic/ScheduleGeneratorTest.java
new file mode 100644
index 0000000000..fd4745c5d0
--- /dev/null
+++ b/src/test/java/seedu/duke/models/logic/ScheduleGeneratorTest.java
@@ -0,0 +1,12 @@
+package seedu.duke.models.logic;
+
+import org.junit.jupiter.api.Test;
+
+
+class ScheduleGeneratorTest {
+ @Test
+ void validRecommendedSchedule() {
+ // ArrayList cegRequirementArray = generateRecommendedSchedule("CEG");
+ assert(true);
+ }
+}
diff --git a/src/test/java/seedu/duke/models/schema/ModuleListTest.java b/src/test/java/seedu/duke/models/schema/ModuleListTest.java
new file mode 100644
index 0000000000..5b28a3eead
--- /dev/null
+++ b/src/test/java/seedu/duke/models/schema/ModuleListTest.java
@@ -0,0 +1,62 @@
+package seedu.duke.models.schema;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.InvalidObjectException;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ModuleListTest {
+
+ //success scenario 1: 1 input String, 1 ModuleList that contains input String --> true
+ @Test
+ void existsTest_moduleListContainsModule_expectTrue() throws InvalidObjectException {
+ String inputString = "CS1231S";
+ ModuleList ml = new ModuleList("CS1231S CS2030S CS2040S CS2100 CS2101 CS2106 CS2109S CS3230");
+
+ //test
+ boolean result = ml.existsByCode(inputString);
+ assertTrue(result);
+ }
+
+ //success scenario 2: 1 input String, 1 ModuleList that does not contain input String --> false
+ @Test
+ void existsTest_moduleListDoesNotContainModule_expectFalse() throws InvalidObjectException {
+ String inputString = "CS1231S";
+ ModuleList ml = new ModuleList("CS2030S CS2040S CS2100 CS2101 CS2106 CS2109S CS3230");
+
+ //test
+ boolean result = ml.existsByCode(inputString);
+ assertFalse(result);
+ }
+
+ //failure scenario 1: input null string, 1 ModuleList --> throw exception
+ @Test
+ void existsTest_nullInput_expectException() {
+ ModuleList ml = new ModuleList("CS2030S CS2040S CS2100 CS2101 CS2106 CS2109S CS3230");
+
+ //test
+ assertThrows(InvalidObjectException.class, () -> ml.existsByCode(null));
+ }
+
+ //success scenario 3: input string, 1 empty ModuleList --> false
+ @Test
+ void existsTest_nullMainModuleList_expectException() throws InvalidObjectException {
+ String inputString = "CS1231S";
+ ModuleList ml = new ModuleList();
+
+ //test
+ boolean result = ml.existsByCode(inputString);
+ assertFalse(result);
+ }
+
+ @Test
+ void deleteModulebyCodeTest_moduleExists_expectDelete() {
+ ModuleList moduleList = new ModuleList();
+ moduleList.addModule(new Module("CS1231"));
+ moduleList.deleteModuleByCode("CS1231");
+ assertTrue(moduleList.getMainModuleList().isEmpty());
+ }
+}
diff --git a/src/test/java/seedu/duke/models/schema/ModuleTest.java b/src/test/java/seedu/duke/models/schema/ModuleTest.java
new file mode 100644
index 0000000000..f7241b4f26
--- /dev/null
+++ b/src/test/java/seedu/duke/models/schema/ModuleTest.java
@@ -0,0 +1,49 @@
+package seedu.duke.models.schema;
+
+import org.junit.jupiter.api.Test;
+
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class ModuleTest {
+
+ @Test
+ void testModularCreditsGetsUpdated() {
+ // Arrange
+ Module module = new Module("CS1010");
+
+ //Act
+ int credits = module.getModuleCredits();
+
+ //Assert
+ int expectedCredits = 4;
+ assertEquals(expectedCredits,credits);
+ }
+
+ @Test
+ void testTwelveModularCreditsGetsUpdated() {
+ // Arrange
+ Module module = new Module("CP3880");
+
+ //Act
+ int credits = module.getModuleCredits();
+
+ //Assert
+ int expectedCredits = 12;
+ assertEquals(expectedCredits,credits);
+ }
+ @Test
+ void testTw0ModularCreditsGetsUpdated() {
+ // Arrange
+ Module module = new Module("CFG1002");
+
+ //Act
+ int credits = module.getModuleCredits();
+
+ //Assert
+ int expectedCredits = 2;
+ assertEquals(expectedCredits,credits);
+ }
+
+
+}
diff --git a/src/test/java/seedu/duke/models/schema/ScheduleTest.java b/src/test/java/seedu/duke/models/schema/ScheduleTest.java
new file mode 100644
index 0000000000..c06f89e627
--- /dev/null
+++ b/src/test/java/seedu/duke/models/schema/ScheduleTest.java
@@ -0,0 +1,187 @@
+package seedu.duke.models.schema;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import seedu.duke.utils.exceptions.FailPrereqException;
+import seedu.duke.utils.exceptions.MandatoryPrereqException;
+import seedu.duke.utils.exceptions.MissingModuleException;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class ScheduleTest {
+
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ private Student student = new Student();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Ryan Loh");
+ student.setFirstMajor("CEG");
+ student.setYear("Y2/S1");
+ System.setOut(new PrintStream(outputStream));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+
+ //success scenario: String containing valid module, int containing valid targetSem
+ //module satisfies prereqs --> module added
+ @Test
+ void addModuleTest_correctInputsSatisfyPrereqs_expectModuleAdded() throws Exception {
+ Schedule schedule = new Schedule();
+ schedule.addModule("CS1010", 1);
+ ArrayList testArray= new ArrayList<>();
+ testArray.add("CS1010");
+ assertEquals(testArray, schedule.getModulesPlanned().getModuleCodes());
+
+ }
+
+ //failure scenario 1: String containing valid module, int containing valid targetSem
+ //module does not satisfy prereqs --> throw FailPrereqException
+ @Test
+ void addModuleTest_correctInputsDoesNotSatisfyPrereqs_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(FailPrereqException.class, () -> schedule.addModule("CS2040C", 1));
+
+ }
+
+ //failure scenario 2: String containing invalid module, int containing valid targetSem
+ //--> throws IllegalArgumentException
+ @Test
+ void addModuleTest_invalidModule_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(IllegalArgumentException.class, () -> schedule.addModule("wrong", 1));
+ }
+
+ //failure scenario 3: String containing valid module, int containing invalid targetSem
+ //--> throws IllegalArgumentException
+ @Test
+ void addModuleTest_invalidTargetSem_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(IllegalArgumentException.class, () -> schedule.addModule("CS2040C", 1000));
+ }
+
+ //success scenario: String containing valid module, module is in schedule
+ //module is not a prerequisite of modules ahead--> module deleted
+ @Test
+ void deleteModuleTest_correctInputsNotPrereq_expectModuleDeleted() throws Exception {
+ Schedule schedule = new Schedule();
+ schedule.addModule("CS1010", 1);
+ schedule.addModule("CS2040C", 2);
+ schedule.deleteModule("CS2040C");
+ ArrayList testArray= new ArrayList<>();
+ testArray.add("CS1010");
+ assertEquals(testArray, schedule.getModulesPlanned().getModuleCodes());
+
+ }
+
+ //failure scenario 1: String containing invalid module
+ // --> throw MissingModuleException
+ @Test
+ void deleteModuleTest_invalidModule_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(MissingModuleException.class, () -> schedule.deleteModule("wrong"));
+
+ }
+
+ //failure scenario 2: String containing valid module, module not in schedule
+ //--> throws MissingModuleException
+ @Test
+ void deleteModuleTest_missingModule_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(MissingModuleException.class, () -> schedule.deleteModule("CS2040C"));
+ }
+
+ //failure scenario 3: String containing valid module, module in schedule
+ //module is prerequisite of modules ahead--> throws MandatoryPrereqException
+ @Test
+ void deleteModuleTest_mandatoryPrereq_expectException() {
+ Schedule schedule = new Schedule("CS1010 CS2040C", new int[]{1, 1, 0, 0, 0, 0, 0, 0});
+ assertThrows(MandatoryPrereqException.class, () -> schedule.deleteModule("CS1010"));
+ }
+
+ //success scenario 1: String containing valid module, module is in schedule, int containing valid targetSem
+ //shifting earlier does not affect other modules--> module shifted earlier
+ @Test
+ void shiftModuleTest_correctInputsShiftEarlier_expectModuleShifted() throws Exception {
+ Schedule schedule = new Schedule();
+ schedule.addModule("CS1010", 3);
+ schedule.addModule("MA1511", 2);
+ schedule.shiftModule("CS1010", 1);
+ ArrayList testArray= new ArrayList<>();
+ testArray.add("CS1010");
+ testArray.add("MA1511");
+ assertEquals(testArray, schedule.getModulesPlanned().getModuleCodes());
+
+ }
+
+ //success scenario 2: String containing valid module, module is in schedule, int containing valid targetSem
+ //shifting later does not affect other modules--> module shifted later
+ @Test
+ void shiftModuleTest_correctInputsShiftLater_expectModuleShifted() throws Exception {
+ Schedule schedule = new Schedule();
+ schedule.addModule("CS1010", 1);
+ schedule.addModule("MA1511", 2);
+ schedule.shiftModule("CS1010", 3);
+ ArrayList testArray= new ArrayList<>();
+ testArray.add("MA1511");
+ testArray.add("CS1010");
+ assertEquals(testArray, schedule.getModulesPlanned().getModuleCodes());
+
+ }
+
+ //failure scenario 1: String containing valid module, module is in schedule, int containing valid targetSem
+ //shifting earlier affects other modules--> throws FailPrereqException
+ @Test
+ void shiftModuleTest_shiftEarlierAffectsModules_expectException() {
+ Schedule schedule = new Schedule("CS1010 CS2040C", new int[]{1, 1, 0, 0, 0, 0, 0, 0});
+ assertThrows(FailPrereqException.class, () -> schedule.shiftModule("CS2040C", 1));
+
+ }
+
+ //failure scenario 2: String containing valid module, module is in schedule, int containing valid targetSem
+ //shifting later affects other modules--> throws MandatoryPrereqException
+ @Test
+ void shiftModuleTest_shiftLaterAffectsModules_expectException() {
+ Schedule schedule = new Schedule("CS1010 CS2040C", new int[]{1, 1, 0, 0, 0, 0, 0, 0});
+ assertThrows(MandatoryPrereqException.class, () -> schedule.shiftModule("CS1010", 2));
+
+ }
+
+ //failure scenario 3: String containing valid module, int containing invalid targetSem
+ //--> throws IllegalArgumentException
+ @Test
+ void shiftModuleTest_invalidTargetSem_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(IllegalArgumentException.class, () -> schedule.shiftModule("CS1231S", 1000));
+ }
+
+ //failure scenario 4: String containing invalid module
+ // --> throw MissingModuleException
+ @Test
+ void shiftModuleTest_invalidModule_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(MissingModuleException.class, () -> schedule.shiftModule("wrong", 1));
+
+ }
+
+ //failure scenario 5: String containing valid module, module not in schedule
+ //--> throws MissingModuleException
+ @Test
+ void shiftModuleTest_missingModule_expectException() {
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ assertThrows(MissingModuleException.class, () -> schedule.shiftModule("CS2040C", 1));
+ }
+}
diff --git a/src/test/java/seedu/duke/models/schema/StudentTest.java b/src/test/java/seedu/duke/models/schema/StudentTest.java
new file mode 100644
index 0000000000..697d6502ee
--- /dev/null
+++ b/src/test/java/seedu/duke/models/schema/StudentTest.java
@@ -0,0 +1,63 @@
+package seedu.duke.models.schema;
+
+import org.junit.jupiter.api.Test;
+
+import seedu.duke.utils.exceptions.FailPrereqException;
+import seedu.duke.utils.exceptions.MandatoryPrereqException;
+import seedu.duke.utils.exceptions.MissingModuleException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class StudentTest {
+
+ @Test
+ void clearAllModulesFromScheduleTest_expectEmptySchedule() {
+ Student student = new Student();
+ Schedule schedule = new Schedule("CS1231S MA1511", new int[]{2, 0, 0, 0, 0, 0, 0, 0});
+ student.setSchedule(schedule);
+ student.clearAllModulesFromSchedule();
+ assertTrue(student.getSchedule().getModulesPlanned().getMainModuleList().isEmpty());
+ }
+
+ //success scenario: difference (not working)
+ @Test
+ void getModuleCodesLeftTest_majorSet_expectArrayList() {
+ Student student = new Student();
+ student.setMajor("CEG");
+ //actual: student.getModuleCodesLeft();
+
+ //expected:
+ ArrayList expected = new ArrayList<>();
+ ArrayList majorModuleCodes = student.getMajorModuleCodes();
+ ArrayList modulesInPlanner = student.getModulesPlanned().getCompletedModuleCodes();
+ for (String moduleCode : majorModuleCodes) {
+ if (!modulesInPlanner.contains(moduleCode)) {
+ expected.add(moduleCode);
+ }
+ }
+
+ assertEquals(expected, student.getModuleCodesLeft());
+ }
+
+ @Test
+ void deleteModuleScheduleTest_moduleExists_expectDelete()
+ throws IOException, MandatoryPrereqException, MissingModuleException, FailPrereqException {
+ Student student = new Student();
+ student.addModuleToSchedule("CS1010", 1);
+ student.deleteModuleFromSchedule("CS1010");
+
+ assertTrue(student.getModulesPlanned().getMainModuleList().isEmpty());
+ }
+
+ // getModuleCodesLeft_noMajorSet_expectException()
+ // getDifferenceTest_twoModuleList_expectDifference()
+
+ // success scenario 2: 1 empty ModuleList, 1 ModuleList --> difference which is empty
+ // getDifferenceTest_oneEmptyModuleListAnotherModuleList_expectEmptyDifference()
+ // failure scenario 1: null ModuleList input --> throw exception
+ // getDifferenceTest_nullModuleListInput_expectException()
+}
diff --git a/src/test/java/seedu/duke/models/schema/TimetableUserCommandTest.java b/src/test/java/seedu/duke/models/schema/TimetableUserCommandTest.java
new file mode 100644
index 0000000000..f350a2ead6
--- /dev/null
+++ b/src/test/java/seedu/duke/models/schema/TimetableUserCommandTest.java
@@ -0,0 +1,120 @@
+package seedu.duke.models.schema;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.utils.exceptions.InvalidTimetableUserCommandException;
+import seedu.duke.utils.exceptions.TimetableUnavailableException;
+import seedu.duke.views.TimetableView;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static seedu.duke.views.TimetableUserGuideView.printTTModifySimpleLessonGuide;
+
+class TimetableUserCommandTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ private Student student = new Student();
+ private UserCommand currentUserCommand = new UserCommand();
+
+ @BeforeEach
+ public void setUpStreams() {
+ this.student = new Student();
+ student.setName("Janelle");
+ student.setYear("Y2/S1");
+ student.setFirstMajor("CEG");
+ System.setOut(new PrintStream(outputStream));
+
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void partialTestTimetableModify_perfectInputs_expectTimetable() throws InvalidTimetableUserCommandException,
+ TimetableUnavailableException {
+ System.setOut(originalOut);
+ String addUserInputs = "add cs1010 3";
+ currentUserCommand = new UserCommand(addUserInputs);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ System.setOut(new PrintStream(outputStream));
+ student.updateTimetable();
+
+ TimetableUserCommand currentTimetableCommand = new TimetableUserCommand(student,
+ student.getTimetable().getCurrentSemesterModulesWeekly(), "cs1010 lecture 9 2 Monday");
+ currentTimetableCommand.processTimetableCommand(student.getTimetable().getCurrentSemesterModulesWeekly());
+ if (student.getTimetable().timetableViewIsAvailable()) {
+ TimetableView.printTimetable(student.getTimetable().getCurrentSemesterModulesWeekly());
+ } else {
+ printTTModifySimpleLessonGuide("Timetable view is unavailable as modules in your " +
+ "current semester have no lessons yet.");
+ }
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString().trim();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+
+ String expectedOutput = "------------------------------------------------------------\n" +
+ "| DAY | TIMETABLE |\n" +
+ "------------------------------------------------------------\n" +
+ "| Monday | CS1010 Lecture (9am-11am) |\n" +
+ "------------------------------------------------------------";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void partialTestTimetableModify_perfectInput_expectTimetableErrorMessage() throws TimetableUnavailableException {
+ System.setOut(originalOut);
+ String addUserInputs = "add cs1010 3";
+ currentUserCommand = new UserCommand(addUserInputs);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ System.setOut(new PrintStream(outputStream));
+ student.updateTimetable();
+
+ assertThrows(InvalidTimetableUserCommandException.class,
+ () -> badInput("cs1010 lecture 9 2 Mon"));
+ }
+
+
+ @Test
+ void partialTestTimetableModify_badLessonInput_expectTimetableErrorMessage() throws TimetableUnavailableException {
+ System.setOut(originalOut);
+ String addUserInputs = "add cs1010 3";
+ currentUserCommand = new UserCommand(addUserInputs);
+ if (currentUserCommand.isValid() && !currentUserCommand.isBye()) {
+ currentUserCommand.processCommand(student);
+ }
+
+ System.setOut(new PrintStream(outputStream));
+ student.updateTimetable();
+
+ assertThrows(InvalidTimetableUserCommandException.class,
+ () -> badInput("cs1010 lect 9 2 Monday"));
+ }
+
+ public void badInput(String timetableUserInput) throws InvalidTimetableUserCommandException {
+ TimetableUserCommand currentTimetableCommand = new TimetableUserCommand(student,
+ student.getTimetable().getCurrentSemesterModulesWeekly(), timetableUserInput);
+ currentTimetableCommand.processTimetableCommand(student.getTimetable().getCurrentSemesterModulesWeekly());
+ }
+
+}
diff --git a/src/test/java/seedu/duke/utils/UtilityTest.java b/src/test/java/seedu/duke/utils/UtilityTest.java
new file mode 100644
index 0000000000..b293afcbd3
--- /dev/null
+++ b/src/test/java/seedu/duke/utils/UtilityTest.java
@@ -0,0 +1,15 @@
+package seedu.duke.utils;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static seedu.duke.utils.Utility.isInternetReachable;
+
+class UtilityTest {
+ @Test
+ void internetFeature_whenInternetPresent() {
+ boolean isConnectedToInternet = isInternetReachable();
+
+ assertTrue(isConnectedToInternet);
+ }
+}
diff --git a/src/test/java/seedu/duke/views/MajorRequirementsViewTest.java b/src/test/java/seedu/duke/views/MajorRequirementsViewTest.java
new file mode 100644
index 0000000000..5cf16e8564
--- /dev/null
+++ b/src/test/java/seedu/duke/views/MajorRequirementsViewTest.java
@@ -0,0 +1,279 @@
+package seedu.duke.views;
+
+import static seedu.duke.views.MajorRequirementsView.printRequiredModulesCEG;
+import static seedu.duke.views.MajorRequirementsView.printRequiredModules;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class MajorRequirementsViewTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ @BeforeEach
+ public void setUpStreams() {
+ System.setOut(new PrintStream(outputStream));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void printRequiredModulesTest_majorCEG_expectRequiredCEGModulesShown() {
+ printRequiredModules("CEG");
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ String expectedOutput =
+ "#==========================================================#\n" +
+ "║ Modular Requirements for CEG Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " GES1000 (Singapore Studies) 4\n" +
+ " GEC1000 (Cultures and Connections) 4\n" +
+ " GEN2000 (Communities and Engagement) 4\n" +
+ " ES2631 Critique & Communication of Thinking\n" +
+ " & Design (Critique & Expression) 4\n" +
+ " CS1010 Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " GEA1000 Quantitative Reasoning with Data (Data \n" +
+ " Literacy) 4\n" +
+ " DTK1234 Design Thinking (Design Thinking) 4\n" +
+ " EG1311 Design and Make (Maker Space) 4\n" +
+ " IE2141 Systems Thinking and Dynamics (Systems \n" +
+ " Thinking) 4\n" +
+ " EE2211 Introduction to Machine Learning \n" +
+ " (Artificial Intelligence) 4\n" +
+ " CDE2501 Liveable Cities (Sustainable Futures) 4\n" +
+ " CDE2000 (Creating Narratives) 4\n" +
+ " PF1101 Fundamentals of Project Management \n" +
+ " (Project Management) 4\n" +
+ " CG4002 Computer Engineering Capstone Project 1 \n" +
+ " (Integrated Project) 8\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Engineering Core 20 ~~\n" +
+ "\n" +
+ " MA1511 Engineering Calculus 2\n" +
+ " MA1512 Differential Equations for Engineering 2\n" +
+ " MA1508E Linear Algebra for Engineering 4\n" +
+ " EG2401A Engineering Professionalism 2\n" +
+ " CP3880 Advanced Technology Attachment Programme 12\n" +
+ "\n" +
+ " ~~ CEG Major 40 ~~\n" +
+ "\n" +
+ " CG1111A Engineering Principles and Practice I 4\n" +
+ " CG2111A Engineering Principles and Practice II 4\n" +
+ " CS1231 Discrete Structures 4\n" +
+ " CG2023 Signals & Systems 4\n" +
+ " CG2027 Transistor-level Digital Circuit 2\n" +
+ " CG2028 Computer Organization 2\n" +
+ " CG2271 Real-time Operating System 4\n" +
+ " CS2040C Data Structures and Algorithms 4\n" +
+ " CS2113 Software Engineering & Object-Oriented \n" +
+ " Programming 4\n" +
+ " EE2026 Digital Design 4\n" +
+ " EE4204 Computer Networks 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+\n";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ assertEquals(expectedOutput, printedOutput);
+
+ //assertTrue(false);
+ }
+
+
+
+
+ @Test
+ void printRequiredModulesTest_majorCS_expectRequiredCSModulesShown() {
+ printRequiredModules("CS");
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString();
+
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ String expectedOutput = "#==========================================================#\n" +
+ "║ Modular Requirements for CS Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 40 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ University Requirements: 6 University Pillars 24 ~~\n" +
+ "\n" +
+ " CS1101S Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " ES2660 Communicating in the Information Age \n" +
+ " (Critique and Expression) 4\n" +
+ " GEC1% (Cultures and Connections) 4\n" +
+ " GEA1000 / BT1101 / ST1131 / DSA1101 (Data \n" +
+ " Literacy) 4\n" +
+ " GES1% (Singapore Studies) 4\n" +
+ " GEN2% (Communities and Engagement) 4\n" +
+ "\n" +
+ " ~~ Computing Ethics 4 ~~\n" +
+ "\n" +
+ " IS1108 Digital Ethics and Data Privacy 4\n" +
+ "\n" +
+ " ~~ Inter & Cross-Disciplinary Education 12 ~~\n" +
+ "\n" +
+ " Interdisciplinary (ID) Courses (at least 2)\n" +
+ " Cross-disciplinary (CD) Courses (no more than 1)\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 80 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Computer Science Foundation 36 ~~\n" +
+ "\n" +
+ " CS1231S Discrete Structures 4\n" +
+ " CS2030S Programming Methodology II 4\n" +
+ " CS2040S Data Structures and Algorithms 4\n" +
+ " CS2100 Computer Organisation 4\n" +
+ " CS2101 Effective Communication for Computing \n" +
+ " Professionals 4\n" +
+ " CS2103T Software Engineering 4\n" +
+ " CS2106 Introduction to Operating Systems 4\n" +
+ " CS2109S Introduction to AI and Machine Learning 4\n" +
+ " CS3230 Design and Analysis of Algorithms 4\n" +
+ "\n" +
+ " ~~ Computer Science Breadth and Depth 32 ~~\n" +
+ "\n" +
+ "\n" +
+ " ~~ Mathematics and Sciences 12 ~~\n" +
+ "\n" +
+ " MA1521 Calculus for Computing 4\n" +
+ " MA1522 Linear Algebra for Computing 4\n" +
+ " ST2334 Probability and Statistics 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+\n";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ assertEquals(expectedOutput, printedOutput);
+
+ //assertTrue(false);
+ }
+
+ @Test
+ void printRequiredModulesTest_notAMajor_expectNothingShown() {
+ printRequiredModules("");
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ String expectedOutput = "";
+ assertEquals(expectedOutput, printedOutput);
+ }
+
+ @Test
+ void printRequiredModulesCEGTest_expectRequiredCEGModulesShown() {
+ printRequiredModulesCEG();
+
+ // Capture the printed output
+ String printedOutput = outputStream.toString();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ String expectedOutput =
+ "#==========================================================#\n" +
+ "║ Modular Requirements for CEG Units ║\n" +
+ "#==========================================================#\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Common Curriculum Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " GES1000 (Singapore Studies) 4\n" +
+ " GEC1000 (Cultures and Connections) 4\n" +
+ " GEN2000 (Communities and Engagement) 4\n" +
+ " ES2631 Critique & Communication of Thinking\n" +
+ " & Design (Critique & Expression) 4\n" +
+ " CS1010 Programming Methodology (Digital \n" +
+ " Literacy) 4\n" +
+ " GEA1000 Quantitative Reasoning with Data (Data \n" +
+ " Literacy) 4\n" +
+ " DTK1234 Design Thinking (Design Thinking) 4\n" +
+ " EG1311 Design and Make (Maker Space) 4\n" +
+ " IE2141 Systems Thinking and Dynamics (Systems \n" +
+ " Thinking) 4\n" +
+ " EE2211 Introduction to Machine Learning \n" +
+ " (Artificial Intelligence) 4\n" +
+ " CDE2501 Liveable Cities (Sustainable Futures) 4\n" +
+ " CDE2000 (Creating Narratives) 4\n" +
+ " PF1101 Fundamentals of Project Management \n" +
+ " (Project Management) 4\n" +
+ " CG4002 Computer Engineering Capstone Project 1 \n" +
+ " (Integrated Project) 8\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Programme Requirements 60 │\n" +
+ "+----------------------------------------------------------+\n" +
+ " ~~ Engineering Core 20 ~~\n" +
+ "\n" +
+ " MA1511 Engineering Calculus 2\n" +
+ " MA1512 Differential Equations for Engineering 2\n" +
+ " MA1508E Linear Algebra for Engineering 4\n" +
+ " EG2401A Engineering Professionalism 2\n" +
+ " CP3880 Advanced Technology Attachment Programme 12\n" +
+ "\n" +
+ " ~~ CEG Major 40 ~~\n" +
+ "\n" +
+ " CG1111A Engineering Principles and Practice I 4\n" +
+ " CG2111A Engineering Principles and Practice II 4\n" +
+ " CS1231 Discrete Structures 4\n" +
+ " CG2023 Signals & Systems 4\n" +
+ " CG2027 Transistor-level Digital Circuit 2\n" +
+ " CG2028 Computer Organization 2\n" +
+ " CG2271 Real-time Operating System 4\n" +
+ " CS2040C Data Structures and Algorithms 4\n" +
+ " CS2113 Software Engineering & Object-Oriented \n" +
+ " Programming 4\n" +
+ " EE2026 Digital Design 4\n" +
+ " EE4204 Computer Networks 4\n" +
+ "\n" +
+ "+----------------------------------------------------------+\n" +
+ "│ Unrestricted Electives 40 │\n" +
+ "+----------------------------------------------------------+\n";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ // Assert the printed output matches the expected value
+ assertEquals(expectedOutput, printedOutput);
+
+ //assertTrue(false);
+ }
+
+
+}
diff --git a/src/test/java/seedu/duke/views/TimetableViewTest.java b/src/test/java/seedu/duke/views/TimetableViewTest.java
new file mode 100644
index 0000000000..a9a11921c6
--- /dev/null
+++ b/src/test/java/seedu/duke/views/TimetableViewTest.java
@@ -0,0 +1,114 @@
+package seedu.duke.views;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import seedu.duke.models.schema.ModuleWeekly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+
+//import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class TimetableViewTest {
+ private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ @BeforeEach
+ public void setUpStreams() {
+ System.setOut(new PrintStream(outputStream));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ System.setOut(originalOut);
+ }
+
+ @Test
+ void printTimetableTest_cs1231Lessons_expectSortedTimetable() {
+ ArrayList currentSemesterModules = new ArrayList();
+
+ ModuleWeekly firstTestModule = new ModuleWeekly("CS1231");
+ firstTestModule.addLecture("Wednesday", 12, 2);
+ firstTestModule.addTutorial("Thursday", 14, 2);
+
+ ModuleWeekly secondTestModule = new ModuleWeekly("ES2631");
+ secondTestModule.addLecture("sunday", 11, 1);
+ secondTestModule.addTutorial("friday", 14, 2);
+
+ ModuleWeekly thirdTestModule = new ModuleWeekly("EE2026");
+ thirdTestModule.addLecture("thurSDay", 11, 2);
+ thirdTestModule.addTutorial("WEDNESday", 17, 1);
+ thirdTestModule.addLab("wednESDAY", 9, 3);
+
+ ModuleWeekly fourthTestModule = new ModuleWeekly("CS2113");
+ fourthTestModule.addLecture("FRIDAY", 16, 2);
+ fourthTestModule.addTutorial("THURSDAY", 17, 1);
+
+ ModuleWeekly overlappingModule = new ModuleWeekly("CFG1002");
+ overlappingModule.addTutorial("wednesday", 12, 2);
+
+ currentSemesterModules.add(firstTestModule);
+ currentSemesterModules.add(secondTestModule);
+ currentSemesterModules.add(thirdTestModule);
+ currentSemesterModules.add(fourthTestModule);
+ currentSemesterModules.add(overlappingModule);
+
+ TimetableView.printTimetable(currentSemesterModules);
+
+ String printedOutput = outputStream.toString();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ String expectedOutput = "------------------------------------------------------------\n" +
+ "| DAY | TIMETABLE |\n" +
+ "------------------------------------------------------------\n" +
+ "| Wednesday | EE2026 Lab (9am-12pm) |\n" +
+ "| | CFG1002 Tutorial (12pm-2pm) |\n" +
+ "| | CS1231 Lecture (12pm-2pm) |\n" +
+ "| | EE2026 Tutorial (5pm-6pm) |\n" +
+ "------------------------------------------------------------\n" +
+ "| Thursday | EE2026 Lecture (11am-1pm) |\n" +
+ "| | CS1231 Tutorial (2pm-4pm) |\n" +
+ "| | CS2113 Tutorial (5pm-6pm) |\n" +
+ "------------------------------------------------------------\n" +
+ "| Friday | ES2631 Tutorial (2pm-4pm) |\n" +
+ "| | CS2113 Lecture (4pm-6pm) |\n" +
+ "------------------------------------------------------------\n" +
+ "| Sunday | ES2631 Lecture (11am-12pm) |\n" +
+ "------------------------------------------------------------\n";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+
+ //assertTrue(false);
+ }
+ /*
+ @Test
+ void printTimetableTest_noModuleWeekly_expectNothing() {
+ ArrayList currentSemesterModules = new ArrayList();
+ TimetableView.printTimetable(currentSemesterModules);
+
+ String printedOutput = outputStream.toString();
+ printedOutput = printedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ String expectedOutput = "";
+ expectedOutput = expectedOutput
+ .replaceAll("\r\n", "\n")
+ .replaceAll("\r", "\n");
+
+ assertEquals(expectedOutput, printedOutput);
+
+ //assertTrue(false);
+ }
+
+ */
+
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..48ee4b2b45 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,9 @@
Hello from
- ____ _
-| _ \ _ _| | _____
+ ____ _
+| _ \ _ _| | _____
| | | | | | | |/ / _ \
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
-What is your name?
-Hello James Gosling
+Please enter your name:
+Welcome James Gosling! What major are you? (Only two available: CEG or CS)
diff --git a/unused/CompletePreqs.java b/unused/CompletePreqs.java
new file mode 100644
index 0000000000..d955a0b7a7
--- /dev/null
+++ b/unused/CompletePreqs.java
@@ -0,0 +1,160 @@
+package seedu.duke.models.logic;
+import seedu.duke.models.schema.ModuleList;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * CompletePreqs checks which mods are unlocked once you finish a specific module
+ */
+public class CompletePreqs {
+ //Full list of mods with modulesWithPreqs
+ private HashMap> modulesWithPreqs;
+ private ArrayList addToModulesCompleted;
+
+ // To track modules that are already unlocked
+ private Set unlockedModulesSet;
+
+ public CompletePreqs() {
+ modulesWithPreqs = new HashMap<>();
+ unlockedModulesSet = new HashSet<>();
+ addToModulesCompleted = new ArrayList<>();
+ }
+
+ public CompletePreqs(HashMap> inputMods) {
+ modulesWithPreqs = new HashMap<>(inputMods);
+ unlockedModulesSet = new HashSet<>();
+ addToModulesCompleted = new ArrayList<>();
+ }
+
+ /**
+ * Create a list of completed mods, hardcoded from startup.
+ *
+ * @param list
+ */
+
+ public void initializeCompletedMods(ModuleList list) {
+ addToModulesCompleted.addAll(list.getModuleCodes());
+ for (String mod : addToModulesCompleted) {
+ processModuleForUnlockingWithoutPrint(mod);
+ }
+ }
+
+
+
+ /**
+ * Prints what mods have been unlocked after input
+ *
+ * @param moduleCompleted
+ */
+ public void getUnlockedMods(String moduleCompleted) {
+
+
+ // Check prerequisites of the moduleCompleted
+ if (modulesWithPreqs.containsKey(moduleCompleted)) {
+ List unmetPrerequisites = new ArrayList<>();
+ for (String preq : modulesWithPreqs.get(moduleCompleted)) {
+ if (!addToModulesCompleted.contains(preq)) {
+ unmetPrerequisites.add(preq);
+ }
+ }
+ //Stops if a completedMod shouldn't be able to be completed with proper preqs
+ if (!unmetPrerequisites.isEmpty()) {
+ System.out.println(moduleCompleted +
+ " cannot be marked as completed because of uncompleted prerequisites: "
+ + unmetPrerequisites);
+ return;
+ }
+ }
+ //If its not marked as completed, properly add it to the list
+ if (!addToModulesCompleted.contains(moduleCompleted)) {
+ addToModulesCompleted.add(moduleCompleted);
+ }
+
+ System.out.println("Mod completed: " + moduleCompleted);
+ printUnlockedMods(moduleCompleted);
+ }
+
+ /**
+ * This is only used for the first initalization of the mods.
+ * @param moduleCompleted
+ */
+ private void processModuleForUnlockingWithoutPrint(String moduleCompleted) {
+ ArrayList newMods = new ArrayList<>();
+
+
+ for (String key : modulesWithPreqs.keySet()) {
+ //If new unlocked mod isn't marked as complete or unlocked already
+ if (!unlockedModulesSet.contains(key) && !addToModulesCompleted.contains(key)) {
+ boolean allPrerequisitesMet = true;
+ for (String preq : modulesWithPreqs.get(key)) {
+ if (!addToModulesCompleted.contains(preq)) {
+ //Make sure preq isn't already marked as done
+ allPrerequisitesMet = false;
+ break;
+ }
+ }
+ if (allPrerequisitesMet) {
+ newMods.add(key);
+ unlockedModulesSet.add(key);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * @param moduleCompleted
+ */
+ public void printUnlockedMods(String moduleCompleted) {
+ ArrayList newlyUnlockedMods = new ArrayList<>();
+
+ for (String key : modulesWithPreqs.keySet()) {
+ //If new unlocked mod isn't marked as complete or unlocked already
+ if (!unlockedModulesSet.contains(key) && !addToModulesCompleted.contains(key)) {
+ boolean allPrerequisitesMet = true;
+ for (String preq : modulesWithPreqs.get(key)) {
+ if (!addToModulesCompleted.contains(preq)) {
+ //Make sure preq isn't already marked as done
+ allPrerequisitesMet = false;
+ break;
+ }
+ }
+ if (allPrerequisitesMet) {
+ newlyUnlockedMods.add(key);
+ unlockedModulesSet.add(key);
+ }
+ }
+ }
+ for (String mod : newlyUnlockedMods) {
+ System.out.println(mod + " has been unlocked!");
+ }
+
+ }
+
+ public void printModsCompleted(){
+ for (String mod: addToModulesCompleted){
+ System.out.println(mod + "has been completed");
+ }
+ }
+
+ public boolean checkModInput(String[] words, ArrayList majorModuleCodes){
+ if (words.length == 1){
+ if (majorModuleCodes.contains(words[0].toUpperCase())){
+ return true;
+ }
+ System.out.println("Please enter a available mod: ");
+ System.out.println(majorModuleCodes);
+ return false;
+ }
+ System.out.println("Please enter a available mod after the complete keyword");
+ System.out.println(majorModuleCodes);
+ return false;
+ }
+}
+
+
diff --git a/unused/File.java b/unused/File.java
new file mode 100644
index 0000000000..7dc1222b4b
--- /dev/null
+++ b/unused/File.java
@@ -0,0 +1,220 @@
+// if (userInput.equals("Y")) {
+// displayMessage("Do you want to keep your completion statuses?");
+// displayMessage("Please input 'Y' or 'N'");
+//
+// String userInputForCompletion = in.nextLine();
+//
+// while (!userInputForCompletion.equals("N") && !userInputForCompletion.equals(("Y"))) {
+// displayMessage("Invalid input, please choose Y/N");
+// userInputForCompletion = in.nextLine();
+// }
+//
+// displayMessage("Hold on, this may take a while......");
+//
+// Boolean keep;
+// if (userInputForCompletion.equals("Y")) {
+// keep = true;
+// } else {
+// keep = false;
+// }
+// student.getSchedule().addRecommendedScheduleListToSchedule(scheduleToAdd, keep);
+// displayMessage("Here is your schedule planner!");
+// student.getSchedule().printMainModuleList();
+// displayMessage("Happy degree planning!");
+//
+//
+// } else {
+// displayHelp();
+// }
+ /*String logo = " _____ _ __ __ _____ _ _ _ \n"
+ + " | __ \\ | | | \\/ | / ____| | | | | | | \n"
+ + " | | | | ___| |__ _ _ __ _| \\ / |_ _| (___ ___| |__ ___ __| |_ _| | ___ \n"
+ + " | | | |/ _ \\ '_ \\| | | |/ _` | |\\/| | | | |\\___ \\ / __| '_ \\ / _ \\/ _` | | | | |/ _ \\\n"
+ + " | |__| | __/ |_) | |_| | (_| | | | | |_| |____) | (__| | | | __/ (_| | |_| | | __/\n"
+ + " |_____/ \\___|_.__/ \\__,_|\\__, |_| |_|\\__, |_____/ \\___|_| |_|\\___|\\__,_|\\__,_|_|\\___|\n"
+ + " __/ | __/ | \n"
+ // private void processCommand(String command, String[] arguments, String userInput) {
+ // switch (command) {
+ // case UserCommandWord.LEFT_COMMAND: {
+ // showModulesLeft(student.getModuleCodesLeft());
+ // break;
+ // }
+ // case UserCommandWord.PACE_COMMAND: {
+ // computePace(arguments, student.getCurrentModuleCredits(), student.getYear());
+ // break;
+ // }
+ // case UserCommandWord.PREREQUISITE_COMMAND: {
+ // String module = arguments[0];
+ // determinePrereq(module.toUpperCase(), student.getMajor()); //to convert "CEG" to dynamic course
+ // break;
+ // }
+ // case UserCommandWord.RECOMMEND_COMMAND: {
+ // recommendScheduleToStudent(student);
+ // break;
+ // }
+ // case UserCommandWord.ADD_MODULE_COMMAND: {
+ // String module = arguments[0].toUpperCase();
+ // int targetSem = Integer.parseInt(arguments[1]);
+ //
+ // addModule(module, targetSem, student);
+ // break;
+ // }
+ // case UserCommandWord.DELETE_MODULE_COMMAND: {
+ // String module = arguments[0].toUpperCase();
+ //
+ // deleteModule(module,student);
+ // break;
+ // }
+ // case UserCommandWord.SHIFT_MODULE_COMMAND: {
+ // String module = arguments[0].toUpperCase();
+ // int targetSem = Integer.parseInt(arguments[1]);
+ //
+ // shiftModule(module, targetSem, student);
+ // break;
+ // }
+ // case UserCommandWord.VIEW_SCHEDULE_COMMAND: {
+ //// getStudentSchedule();
+ // student.printSchedule();
+ // break;
+ // }
+ // case UserCommandWord.COMPLETE_MODULE_COMMAND: {
+ // String module = arguments[0].toUpperCase();
+ // //to add to user completed module
+ // completeModule(student, module);
+ //
+ // break;
+ // }
+ // case UserCommandWord.REQUIRED_MODULES_COMMAND: {
+ // getRequiredModulesForStudent(student.getMajor());
+ // break;
+ // }
+ // case UserCommandWord.INFO_COMMAND: {
+ // Api.infoCommands(arguments[0], userInput);
+ // break;
+ // }
+ // case UserCommandWord.SEARCH_MODULE_COMMAND: {
+ // Api.searchCommand(userInput);
+ // break;
+ // }
+ // case UserCommandWord.HELP_COMMAND: {
+ // printListOfCommands(commandManager);
+ // break;
+ // }
+ // case UserCommandWord.TIMETABLE_COMMAND: {
+ // student.timetableShowOrModify(student, userInput);
+ // break;
+ // }
+ // default: {
+ // break;
+ // }
+ // }
+ //
+ // } + " |___/ |___/ ";*/
+
+// /**
+// * Processes a course file, extracts relevant information, and returns a list of course codes.
+// *
+// * @param f The file to be processed.
+// * @return An ArrayList of course codes extracted from the file.
+// * @throws FileNotFoundException If the specified file is not found.
+// */
+// private static ArrayList processCourseFile(File f) throws FileNotFoundException {
+// ArrayList currentArray = new ArrayList<>();
+// Scanner s = new Scanner(f);
+// while (s.hasNext()) {
+// String currentLine = s.nextLine();
+//
+// String[] words = currentLine.split(" ");
+//
+// if (!currentLine.isEmpty() && !currentLine.startsWith("*")) { // not empty line, not title
+// currentArray.add(words[0]);
+// }
+// }
+// return currentArray;
+// }
+
+//
+// /**
+// * Add all mods that require prerequisites to a map storing the mod and a set of preqs
+// *
+// * @param list HashMap of ModsWithPreqs
+// * @return HashMap of Mods with their corresponding preqs
+// */
+//
+// private HashMap> addModsWithPreqs(HashMap> list) {
+// //Only two mods don't have preqs MA1511 and CS1231S.
+// // In the future this will be dealt
+// addValue(list, "CS3230", "CS2030S");
+// addValue(list, "CS3230", "CS1231S");
+//
+// addValue(list, "CS2030S", "CS1231S");
+//
+// addValue(list, "CS2040S", "CS1231S");
+//
+// addValue(list, "CS2106", "CS1231S");
+//
+// addValue(list, "CS2109S", "CS1231S");
+//
+// return list;
+// }
+//
+//
+// /**
+// * Adds a value to a HashMap with a list of values associated with a key.
+// * If the key does not exist in the map, it creates a new key-value pair with an empty list.
+// * The value is added to the list associated with the key.
+// *
+// * @param map The HashMap in which the value will be added.
+// * @param key The key to which the value will be associated.
+// * @param value The value to add to the list.
+// */
+// public static void addValue(HashMap> map, String key, String value) {
+// // If the map does not contain the key, put an empty list for that key
+// if (!map.containsKey(key)) {
+// map.put(key, new ArrayList<>());
+// }
+// // Add the value to the list associated with the key
+// map.get(key).add(value);
+// }
+
+// completedModules.deleteModulebyCode(module);
+// int nextSemStartingIndex = moduleCount;
+//
+// int lastModuleIndex = modulesPlanned.getMainModuleList().size() - 1;
+// List completedModulesArray = modulesPlanned.getModuleCodes().subList(0, nextSemStartingIndex);
+// ModuleList completedModules = new ModuleList(String.join(" ", completedModulesArray));
+// completedModules.deleteModulebyCode(module);
+//
+// List modulesAheadArray;
+// try {
+// modulesAheadArray = modulesPlanned.getModuleCodes().subList(nextSemStartingIndex, lastModuleIndex + 1);
+// } catch (IndexOutOfBoundsException | IllegalArgumentException e) {
+// modulesAheadArray = new ArrayList<>();
+// }
+//
+// try {
+// for (String moduleAhead : modulesAheadArray){
+// if (!satisfiesAllPrereq(moduleAhead, completedModules)) {
+// throw new FailPrereqException("Unable to delete module. This module is a prerequisite for "
+// + moduleAhead);
+// }
+// }
+// } catch (IllegalArgumentException e) {
+// // This catch should never occur as it should not be possible to add an invalid module
+// assert false;
+// throw new IllegalArgumentException("Invalid Module in Schedule");
+// }
+
+// modulesPerSem[targetSem - 1] -= 1;
+//
+ /*Sebestians version
+ private static boolean isInternetReachable() {
+ try {
+ // Try connecting to a well-known server (Google's DNS server)
+ InetAddress address = InetAddress.getByName("8.8.8.8");
+ return address.isReachable(3000); // 3 seconds timeout
+ } catch (java.io.IOException e) {
+ return false; // Unable to connect
+ }
+ }
+ */
\ No newline at end of file
diff --git a/unused/WeeklyScheduleView.java b/unused/WeeklyScheduleView.java
new file mode 100644
index 0000000000..24c3608e3f
--- /dev/null
+++ b/unused/WeeklyScheduleView.java
@@ -0,0 +1,294 @@
+package seedu.duke.views;
+
+import seedu.duke.models.schema.Event;
+import seedu.duke.models.schema.ModuleWeekly;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class WeeklyScheduleView {
+ private static final int columnWidth = 11;
+ private static final String[] days = {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"};
+
+ public static void print(String output) {
+ System.out.print(output);
+ }
+
+ public static void println(String output) {
+ System.out.println(output);
+ }
+
+ public static void printlnHorizontalLine() {
+ println("------------------------------------------------------------");
+ }
+
+ public static void printVerticalLine() {
+ System.out.print("| ");
+ }
+
+
+ public static void printlnVerticalLine() {
+ System.out.println("|");
+ }
+
+ public static void printToJustify(int number) {
+ print(String.format("%-" + number + "s", ""));
+ }
+
+ public static void printToJustify(String string, int number) {
+ print(String.format("%-" + number + "s", string));
+ }
+
+ //ideally a function that can be called in Student
+
+ public static void printWeeklySchedule(ArrayList currentSemesterModules) {
+ // 8am to 8pm, Monday to Sunday
+ // Convert current semester modules (ArrayList, ModuleList)
+ // to weeklySchedule, "2D array" of ArrayList of Event (List>>, ArrayList[][])
+ List>> weeklyScheduleByTime = initialiseTwoDList();
+
+ for (ModuleWeekly module : currentSemesterModules) {
+ for (Event event : module.getWeeklyTimetable()) {
+ int day = event.getDay();
+ int timePeriod = event.getStartTime() - 8; //8am index 0
+ int eventDurationLeft = event.getDuration();
+ while (eventDurationLeft > 0) {
+ addToWeeklyScheduleByTime(timePeriod, day,
+ module.getModuleCode() + " " + event.getEventType(), weeklyScheduleByTime);
+ //check if java pass by reference
+
+ timePeriod += 1;
+ eventDurationLeft -= 1;
+ }
+ }
+ }
+
+ printDayHeader();
+ for (int timePeriod = 0; timePeriod < 12; timePeriod++) { //8-9am index 0, 7-8pm index 11
+ printRow(weeklyScheduleByTime.get(timePeriod), timePeriod, timePeriod == 11);
+ }
+ }
+
+ public static void printDayHeader() {
+ printlnHorizontalLine();
+ printVerticalLine();
+ printToJustify(columnWidth);//printblank
+ for (int i = 0; i < 7; i++) { //7 days
+ printVerticalLine();
+
+ String currentDay = days[i];
+ print(currentDay);
+ printToJustify(columnWidth - currentDay.length());
+ }
+ printlnVerticalLine();
+ }
+
+ public static void printRow(List> hourSchedule, int timePeriod, boolean lastLine) {
+ //header & 7 days
+
+ //save a copy
+ //List[] weeklyTask = new List[8];
+ List> weeklyTask = new ArrayList<>();
+ String header = getTime(timePeriod);
+ fillAndSet(0, new ArrayList(List.of(header)), weeklyTask);
+ //weeklyTask.set(0, new ArrayList(List.of(header))); //????
+ for (int i = 0; i < 7; i++) { //7 days
+ ArrayList task = new ArrayList(hourSchedule.get(i));
+ fillAndSet(i + 1, task, weeklyTask);
+ //weeklyTask.set(i + 1, task);
+ }
+
+ boolean tasksPrinted = false;
+ printlnHorizontalLine();
+ boolean firstLine = true;
+ while (!tasksPrinted) { //line, tasks
+
+ for (int i = 0; i <= 7; i++) { //timePeriod + 7 days
+ printVerticalLine();
+ //for (int t = 0; t < weeklyTask[i].size(); t++) { //print limited char
+ //weeklyTask[i] is an ArrayList that contains tasks in that time period
+ if (weeklyTask.get(i).isEmpty()) {
+ printToJustify(columnWidth);
+ } else {
+ String currentTask = weeklyTask.get(i).get(0); //get 1st task
+ if (currentTask.length() < columnWidth) {
+ print(currentTask);
+ printToJustify(columnWidth - currentTask.length());
+ weeklyTask.get(i).remove(0);
+ } else {
+ String[] words = currentTask.split(" ");
+ int columnWidthLeft = columnWidth;
+
+ int j = 0;
+ while (words[j].length() < columnWidthLeft) {
+ print(words[j]);
+ //print(String.valueOf(words[j].length())); //troubleshooting
+ columnWidthLeft -= words[j].length();
+ words[j] = "";
+ j += 1;
+ }
+ //print(String.valueOf(columnWidthLeft)); //troubleshooting
+
+ printToJustify(columnWidthLeft);
+ //currentTask should be updated to start from index j
+ String startingWord = words[j];
+ int startingIndex = currentTask.indexOf(startingWord);
+ weeklyTask.get(i).set(0, currentTask.substring(startingIndex)); //update currentTask
+ }
+ }
+ }
+ printlnVerticalLine();
+
+ boolean thisTaskPrinted = true;
+ for (int i = 0; i <= 7; i++) { //timePeriod + 7 days
+ if (!weeklyTask.get(i).isEmpty()) {
+ thisTaskPrinted = false; //not finished
+ break;
+ }
+ }
+ tasksPrinted = thisTaskPrinted;
+ }
+ if (lastLine) {
+ printlnHorizontalLine();
+ }
+
+ }
+
+ public static String getTime(int timePeriod, int duration) {
+ String startTime = getTime(timePeriod);
+ String endTime = getTime(timePeriod + duration);
+
+ // time is outside 8am-8pm
+ if (startTime.isEmpty() || endTime.isEmpty()) {
+ return "";
+ }
+
+ return "(" + startTime + "-" + endTime + ")";
+ }
+
+ public static String getTime(int timePeriod) {
+ if (8 <= timePeriod && timePeriod <= 11) {
+ return (timePeriod) + "am";
+ } else if (timePeriod == 12) {
+ return (timePeriod) + "pm";
+ } else if (13 <= timePeriod && timePeriod <= 19) {
+ return (timePeriod - 12) + "pm";
+ } else {
+ // time is outside 8am-8pm
+ return "";
+ }
+ }
+
+ public static void fillAndSet(int index, T object, List list) {
+ if (index > (list.size() - 1)) {
+ for (int i = list.size(); i < index; i++) {
+ list.add(null);
+ }
+ list.add(object);
+ } else {
+ list.set(index, object);
+ }
+ }
+
+ public static List>> initialiseTwoDList() {
+ List>> grandparentList = new ArrayList<>();
+ for (int i = 0; i < 12; i++) { //12 time periods
+ List> parentList = new ArrayList<>();
+ for (int j = 0; j < 7; j++) { //7 days
+ ArrayList childList = new ArrayList();
+ fillAndSet(j, childList, parentList);
+ parentList.add(childList);
+ }
+ fillAndSet(i, parentList, grandparentList);
+ grandparentList.add(parentList);
+ }
+ return grandparentList;
+ }
+
+ public static void addToWeeklyScheduleByTime(int indexParent, int indexChild, String eventName,
+ List>> listOfList) {
+ //"2D" array
+ List> parentList = listOfList.get(indexParent);
+ ArrayList childList = parentList.get(indexChild);
+ childList.add(eventName);
+ }
+
+ private static void makeFileReadOnly(String filePath) {
+ try {
+ File file = new File(filePath);
+
+ if (file.exists()) {
+ // Set the file read-only
+ boolean success = file.setReadOnly();
+
+ if (success) {
+ System.out.println("File is now read-only.");
+ } else {
+ System.out.println("Unable to set the file as read-only.");
+ }
+ } else {
+ System.out.println("File does not exist.");
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+
+}
+
+/*
+public ArrayList getByHour() {
+ ArrayList eventByHour = new ArrayList<>();
+ int eventDurationLeft = getDuration();
+ while (eventDurationLeft > 0) {
+ String eventTimeData = getStartTime() + " " + (getStartTime() + 1);
+ eventByHour.add(eventTimeData);
+ eventDurationLeft -= 1;
+ }
+ return eventByHour;
+}
+
+@Override
+public ArrayList getByHour () {
+ ArrayList tutorialByHour = new ArrayList<>();
+
+ int eventDurationLeft = getDuration();
+ while (eventDurationLeft > 0) {
+ String tutorialTimeData = "T " + getStartTime() + " " + (getStartTime() + 1);
+ tutorialByHour.add(tutorialTimeData);
+ eventDurationLeft -= 1;
+ }
+
+ return tutorialByHour;
+}
+
+@Override
+public ArrayList getByHour () {
+ ArrayList tutorialByHour = new ArrayList<>();
+
+ int eventDurationLeft = getDuration();
+ while (eventDurationLeft > 0) {
+ String tutorialTimeData = "T " + getStartTime() + " " + (getStartTime() + 1);
+ tutorialByHour.add(tutorialTimeData);
+ eventDurationLeft -= 1;
+ }
+
+ return tutorialByHour;
+}
+
+
+@Override
+public ArrayList getByHour () {
+ ArrayList tutorialByHour = new ArrayList<>();
+
+ int eventDurationLeft = getDuration();
+ while (eventDurationLeft > 0) {
+ String tutorialTimeData = "T " + getStartTime() + " " + (getStartTime() + 1);
+ tutorialByHour.add(tutorialTimeData);
+ eventDurationLeft -= 1;
+ }
+
+ return tutorialByHour;
+}
+*/