diff --git a/.gitignore b/.gitignore
index f69985ef1f..4c7693aae1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
/.idea/
/out/
/*.iml
+/META-INF/
# Gradle build files
/.gradle/
@@ -13,5 +14,17 @@ src/main/resources/docs/
*.iml
bin/
+# Exclude storage files
+/data
+
/text-ui-test/ACTUAL.txt
text-ui-test/EXPECTED-UNIX.TXT
+
+# log files
+*.log
+
+# data files
+/data/
+
+# manifest files
+*.MF
\ No newline at end of file
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000000..c741881743
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index b0c5528fb5..ead6732068 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,8 +10,12 @@ repositories {
}
dependencies {
+ // https://mvnrepository.com/artifact/org.fusesource.jansi/jansi
+ implementation group: 'org.fusesource.jansi', name: 'jansi', version: '2.4.0'
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'
+ // https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple
+ implementation group: 'com.googlecode.json-simple', name: 'json-simple', version: '1.1.1'
}
test {
@@ -25,15 +29,18 @@ test {
showCauses true
showStackTraces true
showStandardStreams = false
+
+ systemProperty("junit.platform.output.capture.stdout", true);
+ systemProperty("junit.platform.output.capture.stderr", true);
}
}
application {
- mainClassName = "seedu.duke.Duke"
+ mainClassName = "seedu.cardli.CardLI"
}
shadowJar {
- archiveBaseName = "duke"
+ archiveBaseName = "cardli"
archiveClassifier = null
}
@@ -43,4 +50,5 @@ checkstyle {
run{
standardInput = System.in
+ enableAssertions = true
}
diff --git a/docs/AboutUs.md b/docs/AboutUs.md
index 0f072953ea..02f01bc81d 100644
--- a/docs/AboutUs.md
+++ b/docs/AboutUs.md
@@ -1,9 +1,10 @@
# About us
-Display | Name | Github Profile | Portfolio
---------|:----:|:--------------:|:---------:
- | John Doe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | Don Joe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | Ron John | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | John Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
- | Don Roe | [Github](https://github.com/) | [Portfolio](docs/team/johndoe.md)
+|Display | Name | Github Profile | Portfolio|
+|--------|----|--------------|---------|
+ | Koh Meng Kiat, Kenneth | [Github](https://github.com/xRossKoh) | [Portfolio](team/xrosskoh.md)|
+ | Lim Chang Quan Thaddeus | [Github](https://github.com/ThaddeusLim99) | [Portfolio](team/thaddeuslim99.md)|
+ | Joanne Wong Wei Yin | [Github](https://github.com/JWweiyin) | [Portfolio](team/jwweiyin.md)|
+ | Jiang Xing Kai | [Github](https://github.com/xkisxk) | [Portfolio](/team/xkisxk.md)|
+ | Jin Minyue | [Github](https://github.com/astralum) | [Portfolio](team/astralum.md)|
+
diff --git a/docs/DeveloperGuide.md b/docs/DeveloperGuide.md
index 64e1f0ed2b..fd7de579be 100644
--- a/docs/DeveloperGuide.md
+++ b/docs/DeveloperGuide.md
@@ -1,38 +1,802 @@
# Developer Guide
-## Acknowledgements
+
-{list here sources of all reused/adapted ideas, code, documentation, and third-party libraries -- include links to the original source as well}
+
+## Content
-## Design & implementation
+1. [Introduction](#1-introduction)
+2. [Acknowledgements](#2-acknowledgements)
+3. [Design](#3-design)
+ 3.1. [Model Component](#31-model-component)
+ 3.2. [UI Component](#32-ui-component)
+ 3.3. [Logic Component](#33-logic-component)
+ 3.4. [Storage Component](#34-storage-component)
+4. [Implementation](#4-implementation)
+ 4.1. [Edit](#41-edit)
+ 4.1.1 [EditDeckCommand](#411-editdeckcommand)
+ 4.1.2 [EditCardCommand](#412-editcardcommand)
+ 4.2. [Move](#42-move)
+ 4.3. [Find](#43-find)
+ 4.4. [Test Feature](#44-test-feature)
+ 4.4.1 [Test Setup](#441-test-setup)
+ 4.4.2 [Testing Process](#442-testing-process)
+ 4.5. [Storage](#45-storage)
+ 4.5.1. [Writing to JSON files](#451-writing-to-json-files)
+ 4.5.2. [Reading from JSON files](#452-reading-from-json-files)
+ 4.5.2.1. [ReadCardsFromFile](#4521-readcardsfromfile)
+ 4.5.2.2. [ReadTestFromFile](#4522-readtestfromfile)
+5. [Product Scope](#5-product-scope)
+ 5.1. [Target User Profile](#51-target-user-profile)
+ 5.2. [Value Proposition](#52-value-proposition)
+6. [User Stories](#6-user-stories)
+7. [Non-Functional Requirements](#7-non-functional-requirements)
+8. [Glossary](#8-glossary)
+9. [Instructions for Manual Testing](#9-instructions-for-manual-testing)
+ 9.1. [Main Menu](#91-main-menu)
+ 9.2. [Deck Mode](#92-deck-mode)
-{Describe the design and implementation of the product. Use UML diagrams and short code snippets where applicable.}
+
+## [1. Introduction](#content)
+CardLI is a Command Line Interface (CLI) desktop app that helps students manage their flashcards. It also allows
+students to test and review their knowledge. All of this in one single platform.
-## Product scope
-### Target user profile
+
+## [2. Acknowledgements](#content)
-{Describe the target user profile}
+* [__AB3:__](https://se-education.org/addressbook-level3/) For reference regarding the user guide and developer guide
+* [__Jansi:__](https://mvnrepository.com/artifact/org.fusesource.jansi/jansi) For its ANSI escape sequence library,
+ currently used in flashcard testing
+* [__JSON.simple__](https://mvnrepository.com/artifact/com.googlecode.json-simple/json-simple) For saving and storing
+ JSON objects.
-### Value proposition
+
+## [3. Design](#content)
-{Describe the value proposition: what problem does it solve?}
+CardLi has one main component, ```Main```, consisting of one class `CardLi`. It is responsible for:
-## User Stories
+
At app launch: initializing the components in the correct sequence, and connecting them up with each other
+
+
+
+The rest of the App consists of the following components:
+
+* ```Model```: Holds the data of the App in memory.
+* ```UI```: The UI of the App
+* ```Logic```: The command executor
+* ```Storage```: Reads and writes data from and to external files
+
+Each component is explained in the sections below.
+
+### [3.1. Model Component](#content)
+
+
+
+The `Model` component consists of two packages, `flashcard` and `testing`.
+
+The `flashcard` package contains:
+
DeckManager
+
Deck
+
FlashCard
+
+
+The `testing` package contains:
+
Answer
+
TestManager
+
TestHistory
+
TestManager
+
Countdown
+
+A `Deck` contains a private ArrayList of `FlashCards`, and `DeckManager` contains a private ArrayList of `Decks`.
+
+An `AnswerList` contains a private ArrayList of `Answers`, which is created by `TestManager` when the commands for the
+test functions are executed (`ReviewCommand`, `TestCommand`).
+
+The `Countdown` class creates a timer that displays the time left for a question during a Test or Review.
+
+### [3.2. UI Component](#content)
+
+The UI component consists of two classes, ```CardLiUi``` and ```TestUi```.
+
+` CardLiUi` outputs greeting messages, exit messages, help messages and command results to the user on command. It takes
+in input from the user when the program is not in Test or Review.
+
+`TestUi` handles the input and output for the Test and Review functions. More details are explained in the
+implementation section for Test.
+
+### [3.3. Logic Component](#content)
+
+
+
+> ℹ️ `XYZCommand` is a placeholder name for subclasses of the abstract `Command` class (e.g. `AddCardsCommand`, `DeleteDeckCommand`, `TestCommand`, etc.)
+
+The above diagram is a partial architecture diagram of the `Logic` component. It executes user commands via Command
+classes with the appropriate arguments as given by the Parser classes.
+
+CardLi’s user commands operate on a 2-tier structure: a Systemwide level and a Deck level. The Systemwide level commands
+execute commands related to the management of decks, testing and reviewing, while the Deck level commands execute
+commands related to flashcards in a specific deck. The specific implementations are elaborated on in the *
+Implementation* section.
+
+How the `Logic` component works:
+
+1. When `Main` is called upon to execute a command, `Inner Parser` or `Outer Parser` parses the user input to get the
+ command word.
+2. According to the command word, a subclass of the `Command` object (e.g. `AddCardsCommand`, `DeleteDeckCommand`, etc.)
+ is created.
+3. The command communicates with the `Model` when it is executed (e.g. to add a flashcard).
+4. The result of the command execution is encapsulated as a `CommandResult` object which is returned to the `UI`
+ from `Logic` to be processed.
+
+
+
+The architecture diagram above contains the parser classes in `Logic` that are used for parsing a user command.
+
+How the parsing works:
+
+`Inner Parser` or `Outer Parser` creates a `XYZCommand` which in turn creates its corresponding `XYZCommandParser`
+(e.g. when `InnerParser` creates a `EditCardCommand`, `EditCardCommand` creates a `EditCardParser`.)
+
+All `XYZCommandParser` classes implement the `CommandArgumentParser` interface.
+
+### [3.4. Storage Component](#content)
+
+The `Storage` component:
+
+* Saves all the decks
+* Saves all the flashcards
+* Saves all the flashcard scores
+* Remembers which deck each flashcard belongs to
+* Saves the results of each test
+
+All app data is saved as JSON files.
+
+
+
+## [4. Implementation](#content)
+
+> ℹ️ Note: In the sequence diagrams below, the lifeline for objects should end at the destroy marker (X) but due
+> to a limitation of PLANTUML, the lifeline reaches the end of the diagram.
+
+### [4.1. Edit](#content)
+
+This subsection provides details on the implementation of the commands that enable the editing of the `Deck`
+and `FlashCard` objects.
+
+#### [4.1.1. `EditDeckCommand`](#content)
+
+Given below is the sequence diagram for `edit` (Deck):
+
+
+
+The `EditDeckCommand` allows the changing of the name of the `Deck`. The user can only edit the `name` attribute of the
+`Deck` object, which represents the name of the deck.
+
+By entering the `edit` command in the `OuterParser` class, an `EditDeckCommand` object is created and its constructor is
+called. This object is returned to `CardLi` class, which then calls the `execute()` method of the `EditDeckCommand`
+object.
+
+The `execute()` method in the `EditDeckCommand` class self-invokes the `prepareEditDeckCommand()` method, which helps
+the handling of edge cases as well as format the method arguments. In turn, `prepareEditDeckCommand()` self-invokes the
+`prepareDeckIndex` method, which handles the formatting of the deck index specified by the user. `prepareDeckIndex()`
+returns `deck`, of string type, which represents the index of the deck to be edited. `prepareEditDeckCommand()` will
+then return a string array, `preparedArguments`, which represents the arguments for the next method call.
+
+The `execute()` method will then call the `editDeck()` method of the `DeckManager` class, which in turn calls the
+`setName()` method of the `Deck` class. Once `editDeck()` is completed, a message of string type is returned to the
+`execute()` method. The message is stored in a `CommandResult` class, which is then returned to `CardLi`.
+
+`CardLi` then calls upon the `printResult()` method of the `CardLiUi` class to print the message to the user.
+
+#### [4.1.2. `EditCardCommand`](#content)
+
+
+
+The `EditCardCommand` allows the changing of the content of the `FlashCard`. The user can decide to change either
+the `front`
+or `back` attributes of the `FlashCard`object, which represents the front and back side of the card.
+
+By entering the edit command in the `InnerParser` class, an `EditCardCommand` object is created and its constructor is
+called. This object is returned to `CardLi` class, which then calls the `execute()` method of the `EditCardCommand`
+object.
+
+The `execute()` method in the `EditCardCommand` class self-invokes the `prepareEditCardCommand()` method, which helps
+the handling of edge cases as well as format the method arguments. In turn, `prepareEditCardCommand()` self-invokes the
+`prepareCardIndex` method, which handles the formatting of the card index specified by the user. `prepareCardIndex()`
+returns the index of the card to be edited as a string. `prepareEditCardCommand()` will then return a string array,
+`preparedArguments`, which represents the arguments for the next method call.
+
+The `execute()` method will then call the `editCard()` method of the `Deck` class, which in turn calls the
+`setFront()` or `setBack()` method of the `FlashCard` class. Once `editCard()` is completed, a message of string type is
+returned to the `execute()` method. The message is stored in a `CommandResult` class, which is then returned to
+`CardLi`.
+
+`CardLi` then calls upon the `printResult()` method of the `CardLiUi` class to print the message to the user.
+
+### [4.2. Move](#content)
+
+
+
+This subsection provides details on the implementation of the `moveCardCommand`. This command enables moving of a card
+in a deck the user is currently in to another deck.
+
+By entering the move command in the `InnerParser` class, an `MoveCardCommand` object is created and its constructor is
+called. This object is returned to `CardLi` class, which then calls the `execute()` method of the `MoveCardCommand`
+object.
+
+The `execute()` method in the `MoveCardCommand` class self-invokes the `prepareMoveCardCommand()` method, which helps
+the handling of edge cases as well as format the method arguments. In turn, `prepareMoveCardCommand()` self-invokes the
+`prepareCardIndex` method, which handles the formatting of the card index specified by the user. After this,
+`prepareMoveCardCommand()` self invokes the `prepareDeckIndex` method, which handles the formatting of the deck index
+specified by the user.`prepareCardIndex()` returns `card`, of string type, which represents the index of the card to be
+edited. `prepareDeckIndex()`returns `deck`, of string type, which represents the index of the deck to be edited.
+`prepareMoveCommand()` will then return a string array, `preparedArguments`, which represents the arguments for the next
+method call.
+
+The `execute()` method will then call the `moveCard()` method of the `DeckManager` class, which in turn calls the
+`getCard()` method of the `Deck` class to get a copy of the card to be moved. `DeckManager` then calls the
+`addFlashCard()` method of the `Deck` class to add the card to the deck specified by the user. Next, `DeckManager` calls
+the `deleteFlashCard()` method of the `Deck` class to delete the card from the deck it was from. Once `moveCard()` is
+completed, a message of string type is returned to the `execute()` method. The message is stored in a `CommandResult`
+class, which is then returned to `CardLi`.
+
+`CardLi` then calls upon the `printResult()` method of the `CardLiUi` class to print the message to the user.
+
+
+### [4.3. Find](#content)
+
+
+
+Given above is the sequence diagram of the `find` function. This feature allows users of CardLI to find a
+`FlashCard` by providing a search term to the input following the command term `find`. By invoking this function the
+user can view specific `FlashCards` matching the search term from the main menu, instead of entering each `Deck` and
+manually looking through the list of `FlashCards` for the desired ones.
+
+Currently, `find` is implemented at the Systemwide level. After the `CardLiUi` handles the user input,
+`OuterParser` formats the user input and creates a `FindCardsCommand` object which is returned.
+`CardLi` calls the `execute()` method of the `FindCardsCommand` object. The `FindCardsParser` extracts the search term(
+s) and passes them to `CommandResult` which invokes the `findCards()`
+method of `DeckManager` that repeatedly calls the `returnMatchingFlashCards()` method that iterates once for each
+instance of a `Deck`.
+
+`returnMatchingFlashCards()` is implemented by creating a stream that consists of all the `FlashCards` from one deck,
+and filters them based on whether they contain the search term given. This method is not case-sensitive. Finally, all
+the `FlashCards` that contain the search term are collected in an ArrayList and their console outputs are returned in
+string format for `CardLiUi` to display to the user.
+
+
+### [4.4. Test Feature](#content)
+
+
+
+Currently, test feature is implemented at the systemwide level and is handled by `TestManager`.
+`TestManager` will call on `TestUi` and `TestParser` to handle the inputs and outputs with the user and the parsing
+respectively during the test.
+
+#### [4.4.1. Test Setup](#content)
+
+
+
+As shown in the sequence diagram above, to enter into test mode, the user needs to enter `test` in the main menu from
+which the program will prompt the user to input a number (done in `startTest()`) corresponding to the index of the deck
+that they want to be tested on or "all" to test all decks.
+
+
+
+In both cases, `TestManager` will create an `AnswerList` using a `Deck` that it creates or gets from
+`DeckManager` depending on the condition which is shown by the sequence diagram above. If the user decides to test all
+decks, the program will compile all `FlashCards` into a `Deck`. If the user decides to test a single deck, the program
+will get that deck instance from `DeckManager`. The `AnswerList` consists of an ArrayList of `Answers` and is where the
+user's test responses are stored. The `Deck` to be tested is stored within `AnswerList`.
+
+
+
+After constructing the `AnswerList`, the preparation begins. The `Deck` that is attached to the `AnswerList` gets
+duplicated, then shuffled. Afterwards, for each question in the test, an `Answer` is initialised with an empty string
+and added to `AnswerList`.
+
+#### [4.4.2. Testing Process](#content)
+
+
+
+This is where the actual test starts. The user is given a total time of 15s x number of questions to answer all the
+questions. The test will keep looping until every card in the `Deck` to test is answered, or until the
+`Countdown` timer expires. And there is another loop within that loops until the
+`currentQuestion`, which is an `int` representing the question number, goes out of bounds, or until the `Countdown`
+timer expires. Inside the inner loop, `testCard` is called to test an individual card. The resulting `nextQuestionFlag`
+decides whether to proceed to the next question (if it equals to '0') or go back to a previous question
+(if it equals to '1'). If this results in `currentQuestion` going out of bounds and if every question is not answered,
+currentQuestion will get reset to either the lowest or highest question number that is not answered.
+
+
+
+How the `Countdown` class works is shown in the diagram above. When the `Countdown` class is created, it will create a
+nested class `CountdownTimerTask` initialised with the `startValue`, or value of time to count down from, and the
+`timesUpMessage` that will be printed when the time runs out. Once `Countdown` has been started by calling `start()`,
+every second, the time remaining will be printed, then decremented, and the current printed line will be erased and
+replaced with the new time remaining. Note, however, that the displaying of the first time remaining will be delayed by
+0.1s to allow for the question to be printed first. The loop occurs until the time runs out or `Countdown` has been
+stopped by calling `stop()`, which will internally call `cancel()` in `CountdownTimerTask`. If the time runs out,
+`CountdownTimerTask` will call the `stop()` method of `Countdown`, which will likewise terminate it.
+
+
+
+
+The question is printed for the user to answer. The user's answer is then parsed and checked if it is `/Next` or `/Back`
+. If it is neither, the user's answer is added into `AnswerList`. If it is
+`/Next`, nextQuestionFlag is set to 0 and if it is `/Back`, nextQuestionFlag is set to 1. If the user has not input an
+answer before the countdown timer runs out, the answer for the current question can still be input. However, the answer
+stored by the system will be an empty answer and thus will not be counted even if it is correct.
+
+> ! The question will be printed on a new screen. However, if the user scrolls up far enough, the previous inputs can be seen.
+
+
+
+After all cards have been tested, the marking process begins as shown by the sequence diagram above. The program prints
+the question, followed by the actual answer, followed by the user's answer.
+
+For every correct answer, the `userScore`
+and `totalScore` for the `FlashCard` and the `userScore` for the `AnswerList` increments; `TestUi` prints a correct
+answer message.
+
+For every wrong answer, the `userScore` for the `FlashCard` and `AnswerList` does not change but the `totalScore` for
+the `FlashCard` increments; `TestUi` prints an incorrect answer message.
+
+After all the questions are marked, the user's results are printed and saved in `TestHistory`. This concludes the
+entire `startTest()` process.
+
+The `startReview()` process is similar to `startTest()`, except the `getTestDeck()` function has an additional condition
+in which only `FlashCards` with `userScore` being strictly less than 50% of `totalScore` are added to the `Deck` to be
+reviewed.
+
+
+Remark
+
+> `TestCommand` and `ReviewCommand` is executed quite differently compared to the other features.
+> The other commands return the resulting string of the `execute()` method but ` test` and `review` only return the end
+> test/review message.
+> This is because, unlike the others, `test` and `review` need to constantly interact with the user,
+> which means that the feature needs to print out a response message after the user's input. Furthermore, it needs to
+> also update the timer live, which makes storing the entire process as a string
+> when `Command.execute()` is called not really feasible.
+
+
+
+
+### [4.5. Storage](#content)
+
+This feature allows users of CardLI to save data on their current `Decks` of `FlashCards` as well as the tests that they
+have completed thus far. This will also allow users to re-access the data when they re-enter the application. This way,
+they will not have to keep re-adding the same flashcards, while also being able to review tests that they had previously
+done on the application.
+
+This feature is implemented by saving the user’s data into two separate JSON files, which is saved into a new `data`
+directory created upon first start up of the application if it does not yet exist. This new directory is created within
+the current directory from which the `CardLI.jar` file is run in the CLI. The file paths of the two JSON files are hard
+coded as `Cards_CardLI.json` and `Tests_CardLI.json`, which will save data on the user's decks of flashcards and test
+history respectively. Whenever the user inputs a new command, the application will execute the save functions after the
+actions corresponding to the command are completed. This is done in order to maintain data integrity and accuracy in
+case of technical malfunctions that may cause the application to terminate prematurely. When the user restarts or
+re-enters the application, the application will parse the JSON files and convert them into the relevant data. The format
+of how the data is saved into the JSON files are specified during the development process in order to reduce the risk of
+bugs arising when they are being parsed, which will be explained in the following paragraphs.
+
+The `Storage` class contains all the methods to execute the save and parse functions to and from the relevant JSON
+files. This `Storage` object is created upon first starting up the application to handle all the method calls. The
+respective methods will be explained in more detail in the following paragraphs.
+
+#### [4.5.1. Writing to JSON files](#content)
+
+Two separate methods are used for this feature, namely the ` writeCardsToFile()` and the `writeTestsToFile()` methods.
+The two methods save the user's `Decks` of `FlashCards` and `TestHistory` to the JSON files respectively. Since the two
+methods are effectively similar in their implementation, the sequence diagram below will suffice in depicting their
+execution. For all labels that include a "/", the text before the "/" applies for the `writeCardsToFile()` method while
+the text after the "/" applies for the
+`writeTestsToFile()` method.
+
+
+
+`writeCardsToFile(ArrayList decks)`
+
+For the saving of the user's `Decks` of `FlashCards`, the method call will expect an `ArrayList` of `Deck` objects.
+The `toJSONObject()` methods within the `Deck`and `FlashCard` classes have been written as per the specified format of
+saving the `Decks` of `FlashCards` to the `json` file. Each of the `toJSONObject()` methods returns a formatted JSON
+Object containing information about the attributes in each instance of each class, in the form of a map. A combination
+of `JSONObject` and `JSONArray` instances are used to format each of the individual attributes, before they are combined
+into a single `JSONObject` instance and returned from the method. An example of the format of the `Cards_CardLI.json`
+where the `Decks` of `FlashCards` are saved is shown in the image below.
+
+
+
+`writeTestsToFile(ArrayList testHistory)`
+
+For the saving of the user's test history, the method call will expect an `ArrayList` of `AnswerList` objects.
+The `toJSONObject()` methods within the `AnswerList`and `Answer` classes have been written as per the specified format
+of saving the `TestHistory` to the `json` file. The concept behind the formatting of the returned JSON Object is the
+same as that explained under the `writeCardsToFile()` method. An example of the format of the `Tests_CardLI.json` where
+the test data is saved is shown in the screenshot below.
+
+
+
+#### [4.5.2. Reading from JSON files](#content)
+
+The methods for reading from the JSON files are executed once upon each startup of the CardLI application. The methods
+use an instance of the `Scanner` class to parse through the JSON files, before using an instance of the
+`JSONParser` class to parse through the scanned `String` instances and convert them into the relevant `JSONObject`
+instances. As per the saving format explained in the "Writing to JSON files" section above, the methods then reverse
+engineer the process to convert and add the user's stored data into the application before any commands are given from
+the user. Since the implementation of the two methods, namely
+`readCardsFromFile()` and `readTestsFromFile()`, are effectively similar in their implementation, the sequence diagram
+below will suffice in depicting their execution. For all labels that include a "/", the text before the "/" applies for
+the `readCardsFromFile()` method while the text after the "/" applies for the
+`readTestsFromFile()` method. The extension to the "ref" box within the sequence diagram will be shown in a separate
+sequence diagram in the following sections.
+
+
+
+##### [4.5.2.1 `readCardsFromFile`](#content)
+
+The `readCardsFromFile()` method reads from the `Cards_CardLI.json` file. As per the sequence diagram under the
+"Reading from JSON files" header, this method calls the `parseDeck(JSONObject jsonDeck)` method iteratively to convert
+the saved data into individual `Deck` instances to be added into the application. The sequence diagram depicting the
+exact implementation and execute of the `parseDeck(JSONObject jsonDeck)` method is shown below.
+
+
+
+##### [4.5.2.2 `readTestFromFile`](#content)
+
+The `readTestsFromFile()` method reads from the `Tests_CardLI.json` file. As per the sequence diagram under the
+"Reading from JSON files" header, this method calls the `parseAnswerList(JSONObject jsonTestHistory)` method iteratively
+to convert the saved data into individual `AnswerList` instances to be added into the application. The sequence diagram
+depicting the exact implementation and execute of the `parseDeck(JSONObject jsonTestHistory)` method is shown below.
+
+
+
+
+
+## [5. Product scope](#content)
+
+### [5.1. Target user profile](#content)
+
+* Pre-University/University/Polytechnic students
+* Reasonably comfortable using CLI apps
+* Types fast
+* Prefers to store their information digitally rather than physically
+* Has a lot of flashcards
+
+### [5.2. Value Proposition](#content)
+
+CardLI provides a:
+
User-friendly
+
Storage efficient
+
Internet connection independent flashcard experience.
+
+## [6. User Stories](#content)
|Version| As a ... | I want to ... | So that I can ...|
|--------|----------|---------------|------------------|
|v1.0|new user|see usage instructions|refer to them when I forget how to use the application|
-|v2.0|user|find a to-do item by name|locate a to-do without having to go through the entire list|
+| |user| add flashcards|
+| |user| delete flashcards|
+| |user| view my flashcards|know what cards I currently have in the deck|
+| |user| test myself with my flashcards|know if I have memorised the flashcards correctly|
+| |student preparing for their exam|see a list of the flashcards I got wrong|know which concepts I do not know|
+|v2.0|user|shuffle my flashcards|test myself with a different order of flashcards each time|
+| |student in a hurry|edit my flashcards|change the front or back of a flashcard without deleting and adding it again
+| |student who studies multiple subjects|organise my flashcards into different decks|keep flashcards with related topics in the same set|
+| |student preparing for their exam|see a timer when in test mode|keep track of how much time I have spent on each question
+| |student who wants to test themself|to see the percentage of correct answers for each flashcard|know which cards I am less proficient in
+| |student in a hurry|test myself with the flashcards I have answered wrongly for more than half the time|get more proficient at the concepts I am not familiar with
+| |student with many flashcards and decks|find a flashcard by searching for a term matching it|find the flashcard without looking through all my decks
+| |student with little time|save my flashcards|I do not have to add my flashcards to the app every time I use it
+
+## [7. Non-Functional Requirements](#content)
+
+
The app should be usable by someone who is reasonably comfortable using Command Line Interface.
+
The app should be able to handle at least 25 flashcard decks of at least 25 cards each.
+
The app should work on a computer that has Java 11 or above installed.
+
The app should store data in a format that is readable by humans, and easy for machines to parse and generate.
+
+
+## [8. Glossary](#content)
+
+* *CLI* - Command Line Interface
+* *JSON* - JavaScript Object Notation, a lightweight data-interchange format.
+
+
+## [9. Instructions for Manual Testing](#content)
+
+### [9.1. Main Menu](#content)
+
+> ℹ️ Note: In order to view the JSON files as per the screenshots shown under this section, the respective files
+> should be opened on Mozilla Firefox, since it is the only browser that supports the reading and displaying of JSON
+> files in the format shown by default. Alternatives to using Mozilla Firefox can also be found [here](https://www.javatpoint.com/how-to-open-json-file).
+> It is also possible to open the JSON file using the Notepad application on Windows, or any other Notepad equivalent
+> applications on other Operating Systems. However, doing so may make it difficult to read the contents of the file
+> as the information will be displayed in a single line.
+
+#### Adding a deck
+
+1. Test case: `add`
+
+ Expected: No deck is added. Error message is printed to prompt user to input the name of the deck after "add".
+2. Test case: `add ExampleDeck1`
+
+ Expected: No deck is added. Error message is shown to inform user that the deck name is already in use by another
+ deck.
+3. Test case: `add ExampleDeck4`
+
+ Expected: Deck is added with the name "ExampleDeck4". Success message is shown.
+
+ Expected JSON output:
+
+ 
+
+
+#### Editing a Deck
+
+1. Test case: `edit /d /d /n /n`
+
+ Expected: Error is thrown. Error message indicating that you should not be using command flags as part of the command
+arguments is printed.
+
+2. Test case: `edit /d two /n ExampleDeck5`
+
+ Expected: Error is thrown. Error message indicating that you should only be using positive integer to indicate the deck
+to edit is printed.
+
+3. Test case: `edit /d 2 /n ExampleDeck5`
+
+ Expected: Message is printed, stating the new name of the edited deck and the index of the edited deck
+
+ Expected JSON Output:
+
+ 
+
+
+#### Deleting a deck
+
+1. Test case: `delete`
+
+ Expected: Error is thrown. Error message is printed to system output, indicating that an argument for deck index is
+expected for this command.
+2. Test case: `delete 10`
+
+ Expected: Error is thrown. Error message is printed to system output, informing the user that the specified deck
+index does not exist.
+3. Test case: `delete ExampleDeck3`/`delete -1`
+
+ Expected: Error is thrown. Error message is printed to system output, prompting the user for a positive integer as
+a valid argument for this command.
+4. Test case: `delete 3`
+
+ Expected: Command is executed successfully and deck 3 is deleted from the user's current decks.
+
+ Expected JSON output:
+
+ 
+
+
+#### Entering a deck
+
+1. Test case: `enter`
+
+ Expected: No deck is entered. Error message is shown to prompt user to input the index of the deck after "enter".
+2. Test case: `enter 7`
+
+ Expected: No deck is entered. Error message is shown to inform user that there is no deck with that index.
+3. Test case: `enter one`
+
+ Expected: No deck is entered. Error message is shown to inform user that the index of deck must be a positive integer
+ smaller than 2147483647.
+4. Test case: `enter 1`
+
+ Expected: Deck 1 is entered. Success message is shown.
+
+
+#### Testing
+
+> ❗️ Please delete and reimport Cards_CardLI.json and Tests_CardLI.json after each step.
+> ❗️ An arrow "->" represents a multistep instruction. Progress on to the next step by pressing ENTER on your keyboard.
+> ❗️ You may enter CTRL+C anytime to stop the execution of the program. When a test is running, pressing CTRL+C will not
+> save your results.
+
+**Entering Test Mode**
+1. Test case: `test` -> `1`
+
+ Expected: Test mode is entered. All cards in deck 1 are tested.
+2. Test case: `test` -> `0` or `all`
+
+ Expected: Test mode is entered. All cards in all decks are tested.
+3. Test case: `test` -> `-1`
+
+ Expected: Error is thrown. Error message indicating that you should only be using non-negative integers or "all"
+to indicate the deck to test is printed.
+
+4. Test case: `test` -> `3`
+
+ Expected: Error is thrown. Error message indicating that there are no cards to test is printed.
+
+**Taking Test**
+1. Test case: `test` -> `1` -> answer the questions, within the time limit, as per below:
+ * Question: Card1
+
+ Answer: CardBack1
+
+ * Question: Card2
+
+ Answer: Wrong answer
+
+ Expected: A timer of 30s starts counting down and runs continuously throughout the duration of the test.
+Answering a question clears the screen and prints the next question.
+At the end of the test, a screen similar to the one below appears.
+The questions may appear in a different order. The user is then returned to the main menu.
+
+ 
+
+*For the next two steps, enter deck 1 and add a new flashcard by entering `enter 1` -> `add /f Card5 /b CardBack5`.
+Exit deck mode by entering `exit`.*
+
+2. Test case: `test` -> `1` -> `/NEXT` or `/BACK`
+
+ Expected: The test progresses onto the next unanswered question upon entering `/NEXT` and moves to the
+previous unanswered question upon entering `/BACK`. If at the last question, upon entering `/NEXT`, the test will wrap
+around to the first unanswered question. If at the first question, upon entering `/BACK`, the test will wrap around to
+the last unanswered question.
+
+3. Test case: `test` -> `1` -> answer the questions according to the following instructions:
+ 1. Answer the first question with any answer that is not blank.
+ 2. Input an answer for the second question but do not submit it.
+ 3. Wait for the time to run out.
+ 4. Once the time has run out, press ENTER.
+
+ Expected: Only the answer to the first question is graded. The third question is skipped and does not display.
+At the end of the test, a screen similar to the one below appears.
+The questions may appear in a different order. The user is then returned to the main menu.
+
+ 
+
+#### Reviewing
+> ❗️ Please delete and reimport Cards_CardLI.json and Tests_CardLI.json.
+
+1. Test case: `review` -> `2` or `0` or `all`
+
+ Expected: Only one card is tested.
+
+2. Test case: `review` -> `1`
+
+ Expected: Message indicating that there are no low scoring cards to review is printed.
+
+
+#### Viewing Test and FlashCard Statistics
+
+> ❗️ Please delete and reimport Cards_CardLI.json and Tests_CardLI.json.
+
+1. Test Case: `viewtest invalid`
+
+ Expected: Nothing is displayed. Error message is shown to prompt the user to input a positive integer
+ or "all".
+2. Test Case: `viewtest all`
+
+ Expected:
+ 
+3. Test Case: `viewtest 5`
+
+ Expected: Nothing is displayed. Error message is shown to inform the user that there is no test
+ at that index
+
+4. Test Case: `viewfc all`
+
+ Expected: No flashcards are displayed. Error message is shown to inform the user that the command
+ does not take in any arguments
+5. Test Case: `viewfc`
+
+ Expected:
+ 
+
+
+#### Finding flashcards
+
+1. Test Case: `find`
+
+ Expected: No flashcards are displayed. Error message is shown to prompt user to input a search term after "find".
+2. Test Case: `find 1`
+
+ Expected: Flashcards should be displayed in the manner shown in the below screenshot.
+
+ 
+
+3. Test Case: `find 0`
+
+ Expected: No flashcards are displayed. Message is shown to inform user that there are no cards matching the search
+ term.
+
+### [9.2. Deck Mode](#content)
+
+#### Adding a flashcard
+
+1. Prerequisites: Run the command `enter 1`.
+
+2. Test case: `add /f /b`
+
+ Expected: No card is added. Error message is shown to inform user that fields cannot be left empty.
+3. Test case: `add /f Card4 /b CardBack4`
+
+ Expected: Card is added with the front "Card4" and back "CardBack4". Success message is shown.
+
+ Expected JSON output:
+
+ 
+
+4. Test case: `add /f Card1 /b CardBack1`
+
+ Expected: No card is added. Error message is printed to inform user that there already exists a card with the same
+ content on its front. 5.Test case: `add /f card1 /b CardBack1a`
+
+ Expected: Card is added with the front "card1" and back "CardBack1a". Success message is shown.
+
+ Expected JSON output:
+
+ 
+
+
+#### Editing a flashcard
+
+1. Test case: `edit /d 1 /j front /e EditedCard1`
+
+ Expected: Error is thrown. Error message indicating that there are missing flags is printed.
+
+2. Test case: `edit /c 1 /s middle /i EditedCard1`
+
+ Expected: Error is thrown. Error message indicating that only "front" or "back" can be entered into the `/s`
+argument.
+
+3. Test case: `edit /c 1 /s front /i EditedCard1`
+
+ Expected: Message is printed, stating the new name of the edited card, the index of the edited card as well as the
+side changed
+
+ Expected JSON Output:
+
+ 
+
+
+#### Deleting a flashcard
+
+1. Prerequisites: Run the command `enter 1`.
+
+2. Test case: `delete`
+
+ Expected: No card is deleted. Error message is shown to inform user that fields cannot be empty.
+3. Test case: `delete one`
+
+ Expected: No card is deleted. Error message is shown to inform user that index of card must be a positive integer
+ smaller than 2147483647.
+4. Test case: `delete 2`
+
+ Expected: The second card in the deck has been deleted. Success message is shown.
+
+ Expected JSON output:
+
+ 
+
+
+#### Moving a flashcard
+
+1. Test case: `move /c 1 /d -2`
-## Non-Functional Requirements
+ Expected: Error is thrown. Error message indicating that only positive integers are allowed is printed.
-{Give non-functional requirements}
+2. Test case: `move /c /d `
-## Glossary
+ Expected: Error is thrown. Error message indicating that arguments is empty is printed. The command format is printed
+as well.
+> ❗️ JSON output is based on the edit flashcard test
+3. Test case: `move /c 1 /d 2`
-* *glossary item* - Definition
+ Expected: Message is printed, stating which card was moved to which deck.
-## Instructions for manual testing
+ Expected JSON Output:
-{Give instructions on how to do a manual product testing e.g., how to load sample data to be used for testing}
+ 
diff --git a/docs/README.md b/docs/README.md
index bbcc99c1e7..904b1db77b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,6 +1,8 @@
-# Duke
+
-{Give product intro here}
+CardLI is a Command Line Interface (CLI) desktop app designed to help students manage their flashcards. CardLI can help
+students keep track of all their flashcards. It also allows students to test their knowledge. All of this in one
+single platform.
Useful links:
* [User Guide](UserGuide.md)
diff --git a/docs/UserGuide.md b/docs/UserGuide.md
index abd9fbe891..533b4b6c72 100644
--- a/docs/UserGuide.md
+++ b/docs/UserGuide.md
@@ -1,42 +1,421 @@
# User Guide
+
+
+
## Introduction
-{Give a product intro}
+CardLI is a desktop app that helps you create, organise, and review flashcards via a Command Line Interface. As students
+ourselves, we find that studying through the use of flashcards can greatly increase the efficacy of our study sessions
+as they break down large chapters and concepts into easy-to-understand segments. Hence, CardLI was designed in order to
+help students study better through the use of flashcards, while also reducing the paper waste that goes into writing
+physical flashcards. The interfacing within the application is designed to be interactive and functional, while the
+commands are designed to be intuitive to use.
-## Quick Start
+
+## How To Use
-{Give steps to get started quickly}
+This guide helps you to get familiarised with all the features of CardLI. The [Quick Start](#quick-start)
+section tells you how to download and get CardLI set up. The [Features](#features) section provides an in-depth
+description of all available features, which you can refer to for the exact definitions.
+The [Command Summary](#4-command-summarycontent) section gives you a brief overview of the format of all commands in
+table format to let you check the format of commands at glance. To make it easier for you to navigate,
+the [contents](#content) is linked to their respective sections.
+
+
+## Quick Start
1. Ensure that you have Java 11 or above installed.
-1. Down the latest version of `Duke` from [here](http://link.to/duke).
+2. Download the latest version of `CardLI.jar`
+ from [here](https://github.com/AY2122S1-CS2113T-F12-1/tp/releases/tag/v2.1).
+3. Take note of the full filepath of directory in which `CardLI.jar` is saved.
+4. Open the command prompt by searching `cmd` in the search bar.
+5. Navigate to the file directory containing `CardLI.jar` using the command `cd `.
+6. Start up CardLI using the command``java -jar CardLI.jar`.
+
+> ❗ The above steps are for users who are running CardLI on a Windows device. If you are using a macOS or Linux device, you will have to open the command prompt equivalent on your operating system in step 4. Subsequently, you will also have to use the equivalent command on your operating system for navigation to a file directory in step 5. The remaining steps are the same across all operating systems.
+>
+> ❗ The timer feature is not supported on macOS or other environments that do not support ANSI.
+
+The expected output upon startup of the app is shown below:
+
+
+
+## Features
+
+The current version of CardLI has two menus: main and deck. In the main menu, you are working with all the decks, so
+commands only apply to the decks and not the individual flashcards. Enter the deck menu to work with the flashcards.
+
+### Content
+
+1. [Main Menu](#1-main-menu)
+ 1.1 [Add Deck](#11-adding-a-deck-add)
+ 1.2 [Edit Deck](#12-editing-a-deck-edit)
+ 1.3 [Delete Deck](#13-deleting-a-deck-delete)
+ 1.4 [Enter Deck](#14-entering-a-deck-enter)
+ 1.5 [Test](#15-test-flashcards-test)
+ 1.6 [Review](#16-review-flashcards-review)
+ 1.7 [View Decks](#17-viewing-all-decks-view)
+ 1.8 [View Flashcard Stats](#18-view-flashcard-statistics-viewfc)
+ 1.9 [View Test Stats](#19-view-test-statistics-viewtest)
+ 1.10 [Find Flashcard](#110-finding-a-flashcard-find)
+ 1.11 [Help](#111-listing-all-commands-help)
+ 1.12 [Exit App](#112-exiting-the-app-bye)
+2. [Deck Menu](#2-deck-menu)
+ 2.1 [Add Flashcard](#21-adding-a-flashcard-add)
+ 2.2 [Edit Flashcard](#22-editing-a-flashcard-edit)
+ 2.3 [Delete Flashcard](#23-deleting-a-flashcard-delete)
+ 2.4 [Move Flashcard](#24-moving-a-flashcard-move)
+ 2.5 [View Flashcard](#25-viewing-flashcards-view)
+ 2.6 [Help](#26-listing-all-commands-in-deck-mode-help)
+ 2.7 [Exit Deck Mode](#27-exiting-deck-mode-exit)
+3. [FAQ](#3-faqs)
+4. [Command Summary](#4-command-summary)
+ 4.1 [Main Menu](#41-main-menu)
+ 4.2 [Deck Menu](#42-deck-menu)
+
+---
+
+
+## [1. Main Menu](#content)
+
+> 💾 Your decks are saved after each command.
+
+### [1.1 Adding a deck: `add`](#content)
+
+Begin your CardLI journey with `add`. With `add`, create a new deck with the given name, if it does not already exist.
+> ❗ Names of decks are case-sensitive.
+
+Format: `add `
+
+Example of Usage:
+
+`add English vocab`
+
+Expected outcome:
+
+
+
+
+### [1.2 Editing a deck: `edit`](#content)
+
+If you ever find a better name for your deck, `edit` allows you to modify the name of your deck. A deck can be chosen
+through its index.
+
+In the command, `/d` denotes the index of the deck to be edited and `/n` denotes the new name of the deck.
+
+Format: `edit /d /n `
+
+Example of Usage:
+
+`edit /d 1 /n mathematics`
+
+Expected outcome:
+
+
+
+
+### [1.3 Deleting a deck: `delete`](#content)
+
+If you no longer need a deck, `delete` allows you to delete the deck indicated by the index.
+
+Format: `delete `
+
+Example of Usage:
+
+`delete 1`
+
+Expected outcome:
+
+
+
+
+### [1.4 Entering a deck: `enter`](#content)
+
+If you wish to add, delete, edit or view flashcards, `enter` allows you to select a deck to perform these actions on.
+Details on Deck Mode can be found [here](#2-deck-menu).
+
+Format:
+`enter `
+
+Expected outcome:
+
+
+
+
+### [1.5 Test flashcards: `test`](#content)
+
+If you wish to test your knowledge on your flashcards, use `test` to enter test mode. The program will ask you to input
+the index for the deck that is to be tested. The word to be tested will be displayed in the console. A timer will start
+running, which will start from the time 15s * number of flashcards tested. Typing the exact character-for-character
+definition for the word will result in a correct answer. Otherwise, the response will be marked as incorrect. Regardless
+of whether the answer is correct or incorrect, the console will then display the next word to be tested. If the time
+runs out, your answer for the current question will not be saved, and all the other questions will be skipped. When all
+cards in the deck have been tested or the time has run out, the percentage of correct answers will be displayed in the
+console, as well as the cards which received incorrect responses.
+
+If you do not know the answer to the current question being tested, and you want to skip to another question, you can do
+so by typing `/NEXT` or `/BACK` when prompted with the test question.
+
+`/NEXT` will skip to the next question while `/BACK` will go back to the previous question tested.
+> ❗ Test data is not saved until a test is complete. If the program crashes mid-test, the data for that test will not be saved.
+
+Format: `test`
+
+Expected outcome:
+
+
+
+
+### [1.6 Review flashcards: `review`](#content)
+
+If you wish to review the flashcards you are scoring wrongly more often, use `review` to enter review mode, which is the
+same as test mode except that the cards tested will be the cards that you got wrong on more than 50% of the tests.
+
+Format: `review`
+
+Expected outcome:
+
+
+
+
+### [1.7 Viewing all decks: `view`](#content)
+
+If you wish to see all your decks at a glance, use `view` to display the names of all decks.
+
+Format: `view`
+
+Expected outcome:
+
+
+
+
+### [1.8 View flashcard statistics: `viewfc`](#content)
+
+If you wish to know the overall scores of your flashcards, `viewfc` displays all flashcards that have been added up to
+this point, including the cumulative score of all tests done for each of the flashcards.
+
+Format: `viewfc`
+
+Expected outcome:
+
+
+
+### [1.9 View test statistics: `viewtest`](#content)
+
+If you wish to know how well you did on each of your tests, `viewtest` displays the results for a particular test index
+or for all tests, depending on the argument that follows the `viewtest`command.
+
+Format: `viewtest ` or `viewtest all`
+
+Expected outcome:
+
+
+
+
+
+### [1.10 Finding a flashcard: `find`](#content)
+
+If you wish to find a certain flashcard, `find` returns all flashcards with descriptions containing the search terms are
+displayed on the screen.
+
+> ℹ️ Search terms are not case-sensitive.
+
+Format: `find `
+
+Expected outcome:
+
+
+
+### [1.11 Listing all commands: `help`](#content)
+
+If you wish to check the format of a command, `help` lists all commands within the main menu.
+
+Format: `help`
+
+Expected outcome:
+
+
+
+
+
+### [1.12 Exiting the app: `bye`](#content)
+
+When you are done using CardLI, `bye` exits the CardLI application within the Command Line Interface.
+
+Format: `bye`
+
+Expected outcome:
+
+
+---
+
+## [2. Deck Menu](#content)
+
+> 💾 Your flashcards are saved after each command.
+
+### [2.1 Adding a flashcard `add`](#content)
+
+Begin your flashcard journey with `add`. Adds a flashcard with the given front and back to the current deck.
+
+> ❗ You cannot add a flashcard if the content on its front matches the front of an existing flashcard anywhere in the
+> app character-for-character.
+>
+> ❗ Everything between and after the first instance of /f and /b is treated as a string.
+
+Format: `add /f /b `
+
+Example of usage:
+` add /f glycerol /b C3H8O3`
+
+Expected outcome:
+
+
+
+
+### [2.2 Editing a flashcard: `edit`](#content)
+
+Did you accidentally make a mistake in the content of a flashcard? `edit` allows you to modify either the front or back
+side of a flashcard. This is so that you do not have to resort to deleting and adding a new flashcard.
+
+In the `edit` command, `/c` denotes the card index, `/s` denotes the side of the flashcard to edit and`/i` denotes the
+content you want to change the flashcard to.
+
+> ❗ You cannot edit the front of a flashcard if the resulting flashcard front matches the front of an existing flashcard anywhere in the app character-for-character.
+
+Format: `edit /c /s /i `
+
+Example of usage:
+
+`edit /c 1 /s front /i 1 + 1`
+
+Expected outcome:
+
+
+
+
+### [2.3 Deleting a flashcard: `delete`](#content)
+
+No longer need a flashcard? `delete` removes the flashcard indicated by the index.
+
+Format: `delete `
+
+Example of usage:
+
+`delete 1`
+
+Expected outcome:
+
+
+
+
+### [2.4 Moving a flashcard: `move`](#content)
+
+`move` is useful when you accidentally added a flashcard to the wrong deck. You can transfer the card to another deck
+without going through the hassle of deleting the card and adding the same card to another deck.
+
+`move ` moves a flashcard from the deck you are currently in to a deck of your choice, if it exists.
+
+In this command, `/c` denotes the card index and `/d` denotes the deck index or the name of the deck you want to move
+the card to.
+
+Format: `move /c /d `
+
+Example of usage:
+
+`move /c 1 /d 2`
+
+Expected outcome:
+
+* Deck 2 before moving
+ 
+
+* Deck 1 before and after moving; Deck 2 after moving
+ 
+
+
+### [2.5 Viewing flashcards: `view`](#content)
+
+`view` lets you look at all the flashcards in the current deck at a glance. Displays all the flashcards in the current
+deck.
+
+Format: `view`
+
+Expected outcome:
+
+
+
+
+### [2.6 Listing all commands in deck mode: `help`](#content)
+
+Did you forget the format of a command? `help` lists all commands within Deck Mode.
+
+Format: `help`
+
+Expected outcome:
+
+
+
+
+### [2.7 Exiting deck mode: `exit`](#content)
+
+When you are done taking actions within a deck, `exit` lets you exit deck mode and returns you to the main menu.
-## Features
+Format: ``exit``
-{Give detailed description of each feature}
+Expected outcome:
-### Adding a todo: `todo`
-Adds a new item to the list of todo items.
+
-Format: `todo n/TODO_NAME d/DEADLINE`
+
+## [3. FAQs](#content)
-* The `DEADLINE` can be in a natural language format.
-* The `TODO_NAME` cannot contain punctuation.
+**Q**: How do I transfer my data to another computer?
-Example of usage:
+**A**: In order to transfer your app data to another computer, simply transfer the `data` file directory to your other
+computer, and download the latest version of CardLI there. The `data` file directory should contain both
+the `Cards_CardLI.json` and `Tests_CardLI.json` files. Ensure that both the `data` file directory and `CardLI.jar` file
+are in the **same directory** before running the CardLI application in the command prompt as instructed under the Quick
+Start section.
-`todo n/Write the rest of the User Guide d/next week`
+**Q**: Can I directly edit the storage JSON files to make changes to my saved data?
-`todo n/Refactor the User Guide to remove passive voice d/13/04/2020`
+**A**: We highly recommend that users who are unfamiliar with the workings of JSON and the format of the saved data do
+not attempt to directly edit the storage JSON files. This is because the CardLI application has been programmed to read
+a specified format from the JSON files. Any inaccuracies arising from the direct editing of the files will likely lead
+to undesirable consequences such as bugs and possible loss of your saved data if the files cannot be reverted to their
+original format. Nonetheless, it is still possible to directly make edits to the storage JSON files if you are confident
+in maintaining the format integrity of the files.
-## FAQ
+
+## [4. Command Summary](#content)
-**Q**: How do I transfer my data to another computer?
+### [4.1 Main Menu:](#content)
-**A**: {your answer here}
+|Action|Format|
+|-------|------|
+|add deck|`add `|
+|edit deck|`edit /d /n `|
+|delete deck|`delete `|
+|enter deck|`enter `|
+|test|`test`|
+|review|`review`|
+|view decks|`view`|
+|view overall statistics for flashcards|`viewfc`|
+|view test statistics|`viewtest ` prints the result of the test indicated by the index. `viewtest all` prints the results for all tests.|
+|find flashcard|`find `|
+|lists all commands in main menu|`help`|
+|exiting program|`bye`|
-## Command Summary
+### [4.2 Deck Menu:](#content)
-{Give a 'cheat sheet' of commands here}
+|Action|Format|
+|------|------|
+|add flashcard|`add /f /b `|
+|editing a flashcard|`edit /c /s /i `|
+|deleting a flashcard|`delete `|
+|moving a flashcard| `move /c /d `|
+|viewing flashcards|`view`|
+|lists all commands in deck mode|`help`|
+|exiting deck mode|`exit`|
-* Add todo `todo n/TODO_NAME d/DEADLINE`
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..c741881743
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-slate
\ No newline at end of file
diff --git a/docs/assets/Cards_CardLI Example.png b/docs/assets/Cards_CardLI Example.png
new file mode 100644
index 0000000000..6737483bd0
Binary files /dev/null and b/docs/assets/Cards_CardLI Example.png differ
diff --git a/docs/assets/Tests_CardLI Example.png b/docs/assets/Tests_CardLI Example.png
new file mode 100644
index 0000000000..07057c47d4
Binary files /dev/null and b/docs/assets/Tests_CardLI Example.png differ
diff --git a/docs/assets/dg diagrams/EditCardCommandSeqDiagram2.png b/docs/assets/dg diagrams/EditCardCommandSeqDiagram2.png
new file mode 100644
index 0000000000..03c6b33f60
Binary files /dev/null and b/docs/assets/dg diagrams/EditCardCommandSeqDiagram2.png differ
diff --git a/docs/assets/dg diagrams/EditDeckCommandSeqDiagram2.png b/docs/assets/dg diagrams/EditDeckCommandSeqDiagram2.png
new file mode 100644
index 0000000000..2e04eca6bd
Binary files /dev/null and b/docs/assets/dg diagrams/EditDeckCommandSeqDiagram2.png differ
diff --git a/docs/assets/dg diagrams/MoveCommandSeqDiagram2.png b/docs/assets/dg diagrams/MoveCommandSeqDiagram2.png
new file mode 100644
index 0000000000..f140ff4ed1
Binary files /dev/null and b/docs/assets/dg diagrams/MoveCommandSeqDiagram2.png differ
diff --git a/docs/assets/dg diagrams/TestSequenceDiagram.png b/docs/assets/dg diagrams/TestSequenceDiagram.png
new file mode 100644
index 0000000000..3e7b3267b8
Binary files /dev/null and b/docs/assets/dg diagrams/TestSequenceDiagram.png differ
diff --git a/docs/assets/dg diagrams/Tests_CardLI.txt Example.png b/docs/assets/dg diagrams/Tests_CardLI.txt Example.png
new file mode 100644
index 0000000000..58353b11cb
Binary files /dev/null and b/docs/assets/dg diagrams/Tests_CardLI.txt Example.png differ
diff --git a/docs/assets/dg diagrams/countdownSeqDiagram.png b/docs/assets/dg diagrams/countdownSeqDiagram.png
new file mode 100644
index 0000000000..4002be490b
Binary files /dev/null and b/docs/assets/dg diagrams/countdownSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/editCardCommandSeqDiagram.png b/docs/assets/dg diagrams/editCardCommandSeqDiagram.png
new file mode 100644
index 0000000000..102853c723
Binary files /dev/null and b/docs/assets/dg diagrams/editCardCommandSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/editDeckCommandSeqDiagram.png b/docs/assets/dg diagrams/editDeckCommandSeqDiagram.png
new file mode 100644
index 0000000000..bf3df446d3
Binary files /dev/null and b/docs/assets/dg diagrams/editDeckCommandSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/findSeqDiagram.png b/docs/assets/dg diagrams/findSeqDiagram.png
new file mode 100644
index 0000000000..aec44ecd0f
Binary files /dev/null and b/docs/assets/dg diagrams/findSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/findSeqDiagram2.png b/docs/assets/dg diagrams/findSeqDiagram2.png
new file mode 100644
index 0000000000..6d72b65670
Binary files /dev/null and b/docs/assets/dg diagrams/findSeqDiagram2.png differ
diff --git a/docs/assets/dg diagrams/getTestDeckSequenceDiagram.png b/docs/assets/dg diagrams/getTestDeckSequenceDiagram.png
new file mode 100644
index 0000000000..83391bfb2c
Binary files /dev/null and b/docs/assets/dg diagrams/getTestDeckSequenceDiagram.png differ
diff --git a/docs/assets/dg diagrams/logicArchitectureDiagram.png b/docs/assets/dg diagrams/logicArchitectureDiagram.png
new file mode 100644
index 0000000000..f29d1cd2ce
Binary files /dev/null and b/docs/assets/dg diagrams/logicArchitectureDiagram.png differ
diff --git a/docs/assets/dg diagrams/markTestSequenceDiagram.png b/docs/assets/dg diagrams/markTestSequenceDiagram.png
new file mode 100644
index 0000000000..e6e2236e48
Binary files /dev/null and b/docs/assets/dg diagrams/markTestSequenceDiagram.png differ
diff --git a/docs/assets/dg diagrams/modelArchitectureDiagram.png b/docs/assets/dg diagrams/modelArchitectureDiagram.png
new file mode 100644
index 0000000000..c64dc4dd8e
Binary files /dev/null and b/docs/assets/dg diagrams/modelArchitectureDiagram.png differ
diff --git a/docs/assets/dg diagrams/modelArchitectureDiagram2.png b/docs/assets/dg diagrams/modelArchitectureDiagram2.png
new file mode 100644
index 0000000000..ff57127d14
Binary files /dev/null and b/docs/assets/dg diagrams/modelArchitectureDiagram2.png differ
diff --git a/docs/assets/dg diagrams/moveCardCommandSeqDiagram.png b/docs/assets/dg diagrams/moveCardCommandSeqDiagram.png
new file mode 100644
index 0000000000..dc98cbcb6e
Binary files /dev/null and b/docs/assets/dg diagrams/moveCardCommandSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/overallArchitectureDiagram.png b/docs/assets/dg diagrams/overallArchitectureDiagram.png
new file mode 100644
index 0000000000..4192984141
Binary files /dev/null and b/docs/assets/dg diagrams/overallArchitectureDiagram.png differ
diff --git a/docs/assets/dg diagrams/parseAnswerListSeqDiagram.png b/docs/assets/dg diagrams/parseAnswerListSeqDiagram.png
new file mode 100644
index 0000000000..5c2e955a8f
Binary files /dev/null and b/docs/assets/dg diagrams/parseAnswerListSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/parseDeckSeqDiagram.png b/docs/assets/dg diagrams/parseDeckSeqDiagram.png
new file mode 100644
index 0000000000..1ed36f5764
Binary files /dev/null and b/docs/assets/dg diagrams/parseDeckSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/parserArchitectureDiagram.png b/docs/assets/dg diagrams/parserArchitectureDiagram.png
new file mode 100644
index 0000000000..d9625cb304
Binary files /dev/null and b/docs/assets/dg diagrams/parserArchitectureDiagram.png differ
diff --git a/docs/assets/dg diagrams/prepareTestDeckSeqDiagram.png b/docs/assets/dg diagrams/prepareTestDeckSeqDiagram.png
new file mode 100644
index 0000000000..bbfbcb3263
Binary files /dev/null and b/docs/assets/dg diagrams/prepareTestDeckSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/readFromFileSeqDiagram.png b/docs/assets/dg diagrams/readFromFileSeqDiagram.png
new file mode 100644
index 0000000000..e3867c4357
Binary files /dev/null and b/docs/assets/dg diagrams/readFromFileSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/testCardSeqDiagram.png b/docs/assets/dg diagrams/testCardSeqDiagram.png
new file mode 100644
index 0000000000..514f61915f
Binary files /dev/null and b/docs/assets/dg diagrams/testCardSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/testClassDiagram.png b/docs/assets/dg diagrams/testClassDiagram.png
new file mode 100644
index 0000000000..939479fcf2
Binary files /dev/null and b/docs/assets/dg diagrams/testClassDiagram.png differ
diff --git a/docs/assets/dg diagrams/testInProgressSeqDiagram.png b/docs/assets/dg diagrams/testInProgressSeqDiagram.png
new file mode 100644
index 0000000000..4c4fc5a6bc
Binary files /dev/null and b/docs/assets/dg diagrams/testInProgressSeqDiagram.png differ
diff --git a/docs/assets/dg diagrams/writeToFileSeqDiagram.png b/docs/assets/dg diagrams/writeToFileSeqDiagram.png
new file mode 100644
index 0000000000..81370c57e8
Binary files /dev/null and b/docs/assets/dg diagrams/writeToFileSeqDiagram.png differ
diff --git a/docs/assets/dg manual testing/addCard4.jpg b/docs/assets/dg manual testing/addCard4.jpg
new file mode 100644
index 0000000000..f24ba0aff3
Binary files /dev/null and b/docs/assets/dg manual testing/addCard4.jpg differ
diff --git a/docs/assets/dg manual testing/addExampleDeck4.jpg b/docs/assets/dg manual testing/addExampleDeck4.jpg
new file mode 100644
index 0000000000..606104d5c8
Binary files /dev/null and b/docs/assets/dg manual testing/addExampleDeck4.jpg differ
diff --git a/docs/assets/dg manual testing/addcard1.jpg b/docs/assets/dg manual testing/addcard1.jpg
new file mode 100644
index 0000000000..4488f3ec0c
Binary files /dev/null and b/docs/assets/dg manual testing/addcard1.jpg differ
diff --git a/docs/assets/dg manual testing/deleteCard2.jpg b/docs/assets/dg manual testing/deleteCard2.jpg
new file mode 100644
index 0000000000..54144b9035
Binary files /dev/null and b/docs/assets/dg manual testing/deleteCard2.jpg differ
diff --git a/docs/assets/dg manual testing/deleteDeck3.jpg b/docs/assets/dg manual testing/deleteDeck3.jpg
new file mode 100644
index 0000000000..a57169d1cb
Binary files /dev/null and b/docs/assets/dg manual testing/deleteDeck3.jpg differ
diff --git a/docs/assets/dg manual testing/editCard1name.jpg b/docs/assets/dg manual testing/editCard1name.jpg
new file mode 100644
index 0000000000..b356fa9f33
Binary files /dev/null and b/docs/assets/dg manual testing/editCard1name.jpg differ
diff --git a/docs/assets/dg manual testing/editNameOfDeck.jpg b/docs/assets/dg manual testing/editNameOfDeck.jpg
new file mode 100644
index 0000000000..3f37e2c820
Binary files /dev/null and b/docs/assets/dg manual testing/editNameOfDeck.jpg differ
diff --git a/docs/assets/dg manual testing/find1.png b/docs/assets/dg manual testing/find1.png
new file mode 100644
index 0000000000..1ca3e2614b
Binary files /dev/null and b/docs/assets/dg manual testing/find1.png differ
diff --git a/docs/assets/dg manual testing/moveEditedCard1.jpg b/docs/assets/dg manual testing/moveEditedCard1.jpg
new file mode 100644
index 0000000000..d91d182c5f
Binary files /dev/null and b/docs/assets/dg manual testing/moveEditedCard1.jpg differ
diff --git a/docs/assets/dg manual testing/test1.png b/docs/assets/dg manual testing/test1.png
new file mode 100644
index 0000000000..54ff0dbb2a
Binary files /dev/null and b/docs/assets/dg manual testing/test1.png differ
diff --git a/docs/assets/dg manual testing/test2.png b/docs/assets/dg manual testing/test2.png
new file mode 100644
index 0000000000..84a7b2dea3
Binary files /dev/null and b/docs/assets/dg manual testing/test2.png differ
diff --git a/docs/assets/dg manual testing/viewfc_expected.png b/docs/assets/dg manual testing/viewfc_expected.png
new file mode 100644
index 0000000000..dc54544a19
Binary files /dev/null and b/docs/assets/dg manual testing/viewfc_expected.png differ
diff --git a/docs/assets/dg manual testing/viewtest_expected.png b/docs/assets/dg manual testing/viewtest_expected.png
new file mode 100644
index 0000000000..df5503187d
Binary files /dev/null and b/docs/assets/dg manual testing/viewtest_expected.png differ
diff --git a/docs/assets/logo.png b/docs/assets/logo.png
new file mode 100644
index 0000000000..3d6e13e532
Binary files /dev/null and b/docs/assets/logo.png differ
diff --git a/docs/assets/ug/CardLiStartup.png b/docs/assets/ug/CardLiStartup.png
new file mode 100644
index 0000000000..bd04344999
Binary files /dev/null and b/docs/assets/ug/CardLiStartup.png differ
diff --git a/docs/assets/ug/adddeck.png b/docs/assets/ug/adddeck.png
new file mode 100644
index 0000000000..2ed5869e1f
Binary files /dev/null and b/docs/assets/ug/adddeck.png differ
diff --git a/docs/assets/ug/addflash.png b/docs/assets/ug/addflash.png
new file mode 100644
index 0000000000..5a6cd41736
Binary files /dev/null and b/docs/assets/ug/addflash.png differ
diff --git a/docs/assets/ug/bye.png b/docs/assets/ug/bye.png
new file mode 100644
index 0000000000..de86d5a2b7
Binary files /dev/null and b/docs/assets/ug/bye.png differ
diff --git a/docs/assets/ug/deleteDeck.png b/docs/assets/ug/deleteDeck.png
new file mode 100644
index 0000000000..f6b50fcbda
Binary files /dev/null and b/docs/assets/ug/deleteDeck.png differ
diff --git a/docs/assets/ug/deleteflash.png b/docs/assets/ug/deleteflash.png
new file mode 100644
index 0000000000..8d6f0b302e
Binary files /dev/null and b/docs/assets/ug/deleteflash.png differ
diff --git a/docs/assets/ug/deleteindexflash.png b/docs/assets/ug/deleteindexflash.png
new file mode 100644
index 0000000000..ff9586d501
Binary files /dev/null and b/docs/assets/ug/deleteindexflash.png differ
diff --git a/docs/assets/ug/editcard.png b/docs/assets/ug/editcard.png
new file mode 100644
index 0000000000..e1e56a114a
Binary files /dev/null and b/docs/assets/ug/editcard.png differ
diff --git a/docs/assets/ug/editdeck.png b/docs/assets/ug/editdeck.png
new file mode 100644
index 0000000000..010df00fb9
Binary files /dev/null and b/docs/assets/ug/editdeck.png differ
diff --git a/docs/assets/ug/enter.png b/docs/assets/ug/enter.png
new file mode 100644
index 0000000000..909d6a14f7
Binary files /dev/null and b/docs/assets/ug/enter.png differ
diff --git a/docs/assets/ug/exit.png b/docs/assets/ug/exit.png
new file mode 100644
index 0000000000..f61a20b3ce
Binary files /dev/null and b/docs/assets/ug/exit.png differ
diff --git a/docs/assets/ug/find.png b/docs/assets/ug/find.png
new file mode 100644
index 0000000000..6bedf70e4b
Binary files /dev/null and b/docs/assets/ug/find.png differ
diff --git a/docs/assets/ug/help1.png b/docs/assets/ug/help1.png
new file mode 100644
index 0000000000..004bb63712
Binary files /dev/null and b/docs/assets/ug/help1.png differ
diff --git a/docs/assets/ug/help2.png b/docs/assets/ug/help2.png
new file mode 100644
index 0000000000..5db8f3afed
Binary files /dev/null and b/docs/assets/ug/help2.png differ
diff --git a/docs/assets/ug/helpdeck.png b/docs/assets/ug/helpdeck.png
new file mode 100644
index 0000000000..8a63d6bb17
Binary files /dev/null and b/docs/assets/ug/helpdeck.png differ
diff --git a/docs/assets/ug/move2.png b/docs/assets/ug/move2.png
new file mode 100644
index 0000000000..f0cb3363fb
Binary files /dev/null and b/docs/assets/ug/move2.png differ
diff --git a/docs/assets/ug/move4.png b/docs/assets/ug/move4.png
new file mode 100644
index 0000000000..5568a76500
Binary files /dev/null and b/docs/assets/ug/move4.png differ
diff --git a/docs/assets/ug/review.png b/docs/assets/ug/review.png
new file mode 100644
index 0000000000..5b90933056
Binary files /dev/null and b/docs/assets/ug/review.png differ
diff --git a/docs/assets/ug/test.png b/docs/assets/ug/test.png
new file mode 100644
index 0000000000..833b8205cb
Binary files /dev/null and b/docs/assets/ug/test.png differ
diff --git a/docs/assets/ug/viewcard.png b/docs/assets/ug/viewcard.png
new file mode 100644
index 0000000000..8d34493e37
Binary files /dev/null and b/docs/assets/ug/viewcard.png differ
diff --git a/docs/assets/ug/viewdeck.png b/docs/assets/ug/viewdeck.png
new file mode 100644
index 0000000000..0badc85b52
Binary files /dev/null and b/docs/assets/ug/viewdeck.png differ
diff --git a/docs/assets/ug/viewfc.png b/docs/assets/ug/viewfc.png
new file mode 100644
index 0000000000..ca16bfd544
Binary files /dev/null and b/docs/assets/ug/viewfc.png differ
diff --git a/docs/assets/ug/viewtestAll.png b/docs/assets/ug/viewtestAll.png
new file mode 100644
index 0000000000..d63bfc55f8
Binary files /dev/null and b/docs/assets/ug/viewtestAll.png differ
diff --git a/docs/assets/ug/viewtestIndex.png b/docs/assets/ug/viewtestIndex.png
new file mode 100644
index 0000000000..3c99ed7bd9
Binary files /dev/null and b/docs/assets/ug/viewtestIndex.png differ
diff --git a/docs/img.png b/docs/img.png
new file mode 100644
index 0000000000..561908ddd0
Binary files /dev/null and b/docs/img.png differ
diff --git a/docs/team/astralum.md b/docs/team/astralum.md
new file mode 100644
index 0000000000..21be5e49b2
--- /dev/null
+++ b/docs/team/astralum.md
@@ -0,0 +1,74 @@
+# Jin Minyue - Project Portfolio Page
+
+## Project: CardLI
+CardLI is a desktop Command Line Interface (CLI) application that allows users to create, organise, test and
+review their flashcards. It is written in java and has about 4kLoC.
+
+Below are my contributions to the project.
+
+### Summary of Contributions
+
+#### Features Implemented
+* **View flashcards**
+ * What it does: Prints all the flashcards in the current deck to the standard output. Each flashcard is formatted
+ such that the front and back are printed side by side, with centred-justification and line-wrapping.
+ * Justification: This feature is essential so that users can know which cards they have in their decks.
+ * Highlights: It required the creation of several helper methods to split the lines of the front and back, pad
+ the one with fewer lines with empty lines, pad the last line with whitespaces to center it, and finally join each
+ line of front and back to be displayed on the same line.
+* **Countdown timer**
+ * What it does: Prints a countdown timer to the standard output, starting from the given time. When used while
+ testing, ends the test prematurely if the user hasn't finished it, and will reject the answer for the current
+ question the user is at when the timer runs out.
+ * Justification: It simulates the time-limited testing conditions users undergo when preparing for tests.
+ * Highlights: It was difficult and complicated to implement. It relies on the relative position of the user's cursor
+ to clear the current timer line and print the new timer line with the updated time. Since it is updated and prints in
+ the background asynchronously, it might not always appear in the correct location. A workaround had to be implemented
+ to allow it to start printing after a certain amount of time has elapsed so that the other synchronous printing tasks
+ will be completed first.
+ * Credits: Jansi, a library that enables ANSI escape sequences to be used
+
+#### Code Contributed
+[tP Code Dashboard](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=astralum&tabRepo=AY2122S1-CS2113T-F12-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+#### Enhancements Implemented
+* Implemented timer feature in the testing mode, including ending the test when the time is up
+
+#### Others
+* Refactored code to use `Command` classes that upon calling `execute()` would return a `CommandResult` to be printed.
+ Each type of command had its own `XYZCommand` that inherited from `Command` and implemented an `execute()` that was
+ specific to it. [#69](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/69)
+ * Justification: The code was clunky and the storage-related methods had to be implemented in the same
+ class as the Logic or even Model components. Abstracting out the `Command` classes helped make the code simpler,
+ more understandable and easier to use.
+ * Highlights: It was difficult and extremely time-consuming as changing attributes to be non-static meant many
+ methods that depended on static methods were broken. In addition, many existing methods that handled logic
+ had to be moved to the created `Command` classes, and many existing methods that handled parsing input had to be moved
+ to the new `Parser` classes created. The original `Parser` class was also split into two so that one, the `InnerParser`,
+ handled commands occurring in the main menu and the other, the `OuterParser`, handled commands occurring in the deck menu.
+
+#### Contributions to the UG
+
+* Added the features, adding a deck and viewing a deck: [#85](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/85)
+* Added explanation of timer during a test: [#208](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/208)
+
+#### Contributions to the DG
+
+* Added acknowledgements: [#82](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/82), [#189](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/189)
+* Wrote the original Design: [#71](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/71)
+* Added implementation details of `Countdown`: [#194](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/194)
+ * Added sequence diagram of how `Countdown` works
+* Added explanation of how the test interacts with the timer: [#194](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/194)
+* Added instructions for manual testing of the features test and review: [#208](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/208)
+
+#### Contributions to team-based tasks
+* Added Javadoc to many classes: [#194](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/194), etc.
+* Added Jansi to build.gradle: [#93](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/93)
+* Tested the programme on Linux
+
+#### Reviewing/mentoring contributions
+* Helped teammates better use the "Tell, Don't Ask" principle
+
+#### Contributions beyond the project team
+* Tested and [reported bugs](https://github.com/astralum/ped/issues) in the software of another team
+* [Reviewed and commented](https://github.com/nus-cs2113-AY2122S1/tp/pull/34#pullrequestreview-792610303) on the Developer Guide of another team
diff --git a/docs/team/johndoe.md b/docs/team/johndoe.md
deleted file mode 100644
index ab75b391b8..0000000000
--- a/docs/team/johndoe.md
+++ /dev/null
@@ -1,6 +0,0 @@
-# John Doe - Project Portfolio Page
-
-## Overview
-
-
-### Summary of Contributions
diff --git a/docs/team/jwweiyin.md b/docs/team/jwweiyin.md
new file mode 100644
index 0000000000..be4085ce1b
--- /dev/null
+++ b/docs/team/jwweiyin.md
@@ -0,0 +1,64 @@
+# Joanne Wong - Project Portfolio Page
+
+## Overview
+
+CardLI is a Command Line Interface app that helps students manage their flashcards. Students can organise their
+flashcards and test their knowledge of them with CardLI.
+
+### Summary of Contributions
+
+#### Code Contributed
+
+My RepoSense Link can be
+found [here](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=jwweiyin&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=JWweiyin&tabRepo=AY2122S1-CS2113T-F12-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false)
+
+#### Enhancements Implemented
+
+1. Adding and deleting of flashcards (Pull requests [#7](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/7)
+ , [#19](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/19))
+These functions add and delete flashcards from the deck.
+Initially, we allowed the user to delete flashcards by providing the **index of card** or the **content on the front of
+the flashcard**. However, this led to unexpected outcomes when users added cards which front only contained a positive
+integer. Hence we discarded the delete by content method as there was insufficient time to design an elegant
+implementation to handle such cases.
+
+2. Adding of decks (Pull requests [#49](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/49))
+This function allows multiple decks to be managed by the program.
+
+3. Inner Parser (Pull requests [#60](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/60)
+ , [#61](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/61))
+The inner parser handles all commands in deck mode, separate from the parser handling the commands in the main menu.
+Initially, both deck mode and main menu commands were handled within one parser, but doing so required more commands and
+flags (*add* was used to add flashcards to a deck, *adddeck* was used to add a deck). To make the usage of the app more
+intuitive, the deck mode commands were extracted and placed in their own parser.
+
+4. Flashcard search (Pull request [#81](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/81))
+This function searches for and displays flashcards matching the search term input by the user. It allows the user to
+find certain flashcards without entering each deck and manually looking through all the flashcards. The function is not
+case-sensitive.
+
+5. JUnit Testing
+Wrote tests for JUnit for add, edit and delete flashcard functions.
+
+6. Bug fixes for v2.1 (Pull requests [#164](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/164)
+ , [#172](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/172) )
+
+#### Contributions to the UG
+
+Added screenshots to show sample output of the commands. Wrote `find`, `help` and `bye` sections. Wrote command summary.
+Edited descriptions and format of commands for
+consistency. ([#179](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/179))
+
+#### Contributions to the DG
+
+Wrote and constructed architecture diagrams for overall architecture, model component, logic component. Wrote design
+details and constructed sequence diagram under `find`. Contributed to the manual testing section for adding decks,
+finding flashcards and adding flashcards.
+
+#### Contributions to team-based tasks
+
+Documented user stories and non-functional requirements in DG. Managed merge conflicts in all team meetings. Edited and
+rephrased UG and DG for clarity and consistency.
+
+Tested and reported bugs for other team's apps. (Examples: [1](https://github.com/JWweiyin/ped/issues/4)
+, [2](https://github.com/JWweiyin/ped/issues/5), [3](https://github.com/JWweiyin/ped/issues/7))
diff --git a/docs/team/thaddeuslim99.md b/docs/team/thaddeuslim99.md
new file mode 100644
index 0000000000..fc79cf5689
--- /dev/null
+++ b/docs/team/thaddeuslim99.md
@@ -0,0 +1,73 @@
+# Thaddeus Lim - Project Portfolio Page
+
+
+### Project: CardLI
+
+CardLI is a desktop app that helps you create, organise, and review flashcards via a Command Line
+Interface. It is written in Java.
+
+Given below are my contributions to the project.
+
+* **New Feature**: Added the ability to edit card content and deck name.
+ * What it does: Allows the user to change the front and back of a card, as well as the name of decks.
+ * Justification: This feature improves the product significantly because a user can make mistakes in commands
+ and the app should provide a convenient way to rectify them.
+
+* **New Feature**: Added the ability to move a card from one deck to another.
+ * What it does: Allows the user to transfer a card from one deck to another.
+ * Justification: This feature improves the product significantly because a user can make mistakes in placing a card in
+ a certain deck and may wish to move it to another deck in a convenient way rather than having to delete the card and
+ add the card in another deck.
+ * Comments: This feature was slightly harder to implement compared to the `edit` feature as it needed access to
+ both the system level and the deck level, whereas `edit` either used the system level or the deck level but not both
+ at the same time
+
+* **New Feature**: FlashCard Testing.
+ * What it does: Through creating the initial Answer, AnswerList and TestManager classes, users can test his/her
+ knowledge with the flashcards.
+ * Justification: This feature is essential to the product as the purpose of flashcards is to test oneself with them.
+
+* **New Feature**: Capability to skip questions during tests.
+ * What it does: Added the ability to skip a question during tests.
+ * Justification: This feature improves the product significantly as a user may not know the answer to a flashcard
+ and may want to come back to the question later.
+ * Comments: This feature was more of a challenge than initially thought. There were many bugs to take care of and the
+ underlying method of testing had to be changed to enable this feature. Instead of a for loop testing all the
+ flashcards in a deck, the use of a counter variable to keep track of the current question being tested and a flag
+ to check if a question has already been answered had to be developed.
+
+* **New Feature**: Added help command.
+ * What it does: Added the ability to ask for the list of commands.
+ * Justification: This feature improves the product significantly as a user may not want to refer to the user guide
+ for instructions.
+
+* **Code contributed**: [RepoSense link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=true&checkedFileTypes=docs~functional-code~test-code~other&since=2021-09-25&tabOpen=true&tabType=authorship&tabAuthor=ThaddeusLim99&tabRepo=AY2122S1-CS2113T-F12-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code&authorshipIsBinaryFileTypeChecked=false&zFR=false)
+
+* **Project management**:
+ * Managed milestone V1.0 and V2.1
+ * Ensured tracker progress for V2.0 and V2.1
+ * Reviewed team's PR. Example: [\#188](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/188)
+
+* **Enhancements to existing features**:
+ * Created CardLI logo
+ * Fixed some PED bugs and wrote additional tests for existing features to increase test coverage: [\#173](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/173), [\#176](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/176/commits/4a65edacd11031aa9171799af420bb412d415ddb), [\#210](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/210)
+
+* **Documentation**:
+ * User Guide:
+ * Added logo: [\#176](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/176/commits/4a65edacd11031aa9171799af420bb412d415ddb)
+ * Added documentation for the features `edit` (both card and deck): [\#76](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/76/commits/3b7d831011e154f6b354cf6f130344fb7f947b60), [\#87](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/87/commits/be81fd470d6bb00ae978f801be4b194aa8dffeb7)
+ * Including Sequence diagrams for `edit`
+ * Added documentation for the features `move`: [\#92](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/92/commits/c22db9da9d6b8532e5f78b5759969e74bf468a86)
+ * Including Sequence diagrams for `move`
+ * Developer Guide:
+ * Added logo: [\#176](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/176/commits/4a65edacd11031aa9171799af420bb412d415ddb)
+ * Added implementation details of the `edit` feature (for both cards and deck): [\#65](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/65/commits/f996afecdada4c5118fba93f1a43893fed89c2ed)
+ * Added implementation details of the `move` feature: [\#92](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/92/commits/7580984d8ec14af1bca8d4f213ff4e6b30e79074)
+ * Added table of content and enabled anchor links: [\#191](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/191)
+ * Added manual testing instructions for `edit` and `move`: [\#199](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/199/commits/720dc0808b9bb05d96f607781850e9f4efc29cfc)
+
+* **Community**:
+ * Reported bugs and suggestions for other teams' [developer guide](https://github.com/nus-cs2113-AY2122S1/tp/pull/33#pullrequestreview-792605786)
+ * Tested and reported bugs for other team's program.
+ Examples: [1](https://github.com/ThaddeusLim99/ped/issues/1) ,[5](https://github.com/ThaddeusLim99/ped/issues/5)
+ , [6](https://github.com/ThaddeusLim99/ped/issues/6)
diff --git a/docs/team/xkisxk.md b/docs/team/xkisxk.md
new file mode 100644
index 0000000000..29a6d53bf6
--- /dev/null
+++ b/docs/team/xkisxk.md
@@ -0,0 +1,66 @@
+# Jiang Xing Kai - Project Portfolio Page
+
+## Project: CardLi
+
+CardLI is a Command Line Interface (CLI) desktop app designed to help users manage and test their flashcards. CardLI can
+help users keep track of all their flashcards and to test their knowledge. It is written in Java and contains about
+6kLoC.
+
+Given below are my contributions to the project.
+
+## Summary of Contributions
+
+---
+
+### Features Implemented
+
+- Review Feature:
+ - Tests all cards that scored less than 50% across all tests combined.
+ - This feature allows users to test themselves on cards that they need help with, increasing the efficiency of their
+ studying.
+ - The implementation was challenging due to the need of keeping track of the user score and total score of each
+ flashcard, which led to the addition of view flashcard statistics feature. Another reason is that the review
+ feature, like test feature, works quite differently compared to the other features, so the implementation of the
+ command needed multiple revisions to get it working.
+
+
+- View Statistics Feature:
+ - Allows users to view a single of all previous test scores and all accumulated flashcard scores.
+ - This feature allows users to keep track of their previous tests, and focus on the tests that require more work.
+ - This required an `AnswerList` and `TestHistory` class that keeps track of the compiled answers for each test, and
+ as mentioned in the Review Feature, expanded the `Flashcard` class to keep track of the user score and total
+ score.
+
+### Feature Enhancements:
+
+- Wrote Junit tests to existing features, mainly `TestManager`, `TestUi` and `TestHistory`
+- Allow testing for individual decks or all decks, and repackaged it
+
+### Code Contributed:
+
+Code contribution can be found at the
+[RepoSense Link](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=xkisxk&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=xkisxk&tabRepo=AY2122S1-CS2113T-F12-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false)
+
+### Documentation:
+
+- User Guide:
+ - Added documentation features for features `viewtest`, `viewfc`, `review`, `delete`
+ [#75](https://github.com/AY2122S1-CS2113T-F12-1/tp/commit/a1d5f600678dce14bbf3438fbfbe8e78641ef377)
+ - Added anchor and How To Use section
+ links [#188](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/188/commits/9914206a06c69f1d155fb8045015280d199e1ee7)
+- Developer Guide:
+ - Wrote implementation details of `test` feature including the UML diagrams.
+ [#89](https://github.com/AY2122S1-CS2113T-F12-1/tp/commit/1c3adf0706446b7a775c1ba744394abfbf5edba8)
+ - Wrote manual user testing for `view test`
+ features. [#207](https://github.com/AY2122S1-CS2113T-F12-1/tp/commit/052ae91db88e401c109b33c6ebee83f14977b0ce)
+
+### Community:
+
+- Reported bugs and suggestions to improve other team's
+ [developer guide](https://github.com/nus-cs2113-AY2122S1/tp/pull/1/files)
+- Tested and reported bugs for other team's program: [1](https://github.com/xkisxk/ped/issues/1)
+ , [2](https://github.com/xkisxk/ped/issues/2),
+ [3](https://github.com/xkisxk/ped/issues/3)
+- Reviewed teammate's code: [1](https://github.com/AY2122S1-CS2113T-F12-1/tp/pull/191)
+
+
diff --git a/docs/team/xrosskoh.md b/docs/team/xrosskoh.md
new file mode 100644
index 0000000000..d713bbeaf7
--- /dev/null
+++ b/docs/team/xrosskoh.md
@@ -0,0 +1,70 @@
+# Koh Meng Kiat, Kenneth - Project Portfolio Page
+
+## Overview
+CardLI is a desktop app that helps you create, organise, and review flashcards via a Command Line
+Interface. It is written in Java.
+
+### Summary of Contributions
+
+#### Implemented features
+**Added marking and scoring of tests**
+
+This feature marks a user's test and outputs the test score to the system output.
+This feature will let users know the questions on which they have made errors, and use
+the test score to gauge their overall competency with the content on the flashcards.
+
+**Test order randomizer**
+
+This feature randomizes the order in which a user's flashcards are tested.
+The feature prevents users from memorizing the answers to the test in
+the order of the flashcards within the deck. It aims to simulate an examination situation
+where the user would not know the order of the questions beforehand, making the test feature
+a good gauge of the user's grasp on the concepts tested.
+
+**Flashcard storage**
+
+This feature saves a user's decks of flashcards into an external `json` file after
+each command. It also reads from the `json` file upon each startup of CardLI to
+initialize the user's saved deck of flashcards.
+This feature provides users with access to decks of flashcards added in earlier
+sessions without having to re-add then upon every startup of the application.
+
+**Test history storage**
+
+This feature saves a user's test history into an external `json` file after each
+command. It also reads from the same `json` file upon each startup of CardLI to
+initialize the user's test history.
+This feature allows users to view tests that they had previously done, including
+tests that were completed during an earlier session. This can help them revise
+better by learning from mistakes made in past tests.
+
+#### Feature enhancements
+
+1. Determined and implemented the format of the saved data within the `json` files.
+2. Added JUnit tests all for `toString()` and `toJSONObject()` methods.
+
+#### Code Contributions
+
+My RepoSense Link can be found [here](https://nus-cs2113-ay2122s1.github.io/tp-dashboard/?search=&sort=groupTitle&sortWithin=title&since=2021-09-25&timeframe=commit&mergegroup=&groupSelect=groupByRepos&breakdown=false&tabOpen=true&tabType=authorship&tabAuthor=xRossKoh&tabRepo=AY2122S1-CS2113T-F12-1%2Ftp%5Bmaster%5D&authorshipIsMergeGroup=false&authorshipFileTypes=docs~functional-code~test-code~other&authorshipIsBinaryFileTypeChecked=false).
+
+#### Documentation Contributions
+
+**User Guide**
+
+Contributed to the `Introduction`, `Quick Start` and `FAQs` sections.
+
+**Developer Guide**
+
+1. Contributed the implementation details and diagrams under the `Storage` section.
+2. Contributed the `Deleting a deck` subsection under the `Instructions for Manual Testing` section.
+3. Contributed the screenshots of the JSON files.
+
+#### Project Management
+
+1. Managed PRs for milestone v1.0
+2. Managed GitHub issues for milestone v2.0
+
+### Community
+1. Reviewed and gave suggestions on other team's [Developer Guide](https://github.com/nus-cs2113-AY2122S1/tp/pull/10).
+2. Tested and reported bugs on other team's application in PE-DryRun.
+
diff --git a/docs/uml/EditCardCommandSeqDiagram.puml b/docs/uml/EditCardCommandSeqDiagram.puml
new file mode 100644
index 0000000000..48d9ad69d5
--- /dev/null
+++ b/docs/uml/EditCardCommandSeqDiagram.puml
@@ -0,0 +1,63 @@
+@startuml
+!include style.puml
+
+hide footbox
+autonumber
+
+participant ":CardLI" as CardLI mainCOLOUR
+participant "innerParser:\nInnerParser" as InnerParser logicCOLOUR
+participant "command:\nEditCardCommand" as EditCardCommand logicCOLOUR
+participant ":Deck" as Deck modelCOLOUR
+participant ":FlashCard" as FlashCard modelCOLOUR
+participant "ui:\nCardLiUI" as CardLiUI uiCOLOUR
+actor "User" as User
+
+CardLI -> InnerParser: parseCommand\n(input)
+activate InnerParser
+
+ref over InnerParser
+getCommandArguments(commandType,input)
+end ref
+
+InnerParser -> EditCardCommand** : new EditCardCommand\n(arguments)
+activate EditCardCommand
+InnerParser <-- EditCardCommand : command
+deactivate EditCardCommand
+InnerParser --> CardLI: command
+deactivate InnerParser
+
+CardLI -> EditCardCommand : execute()
+activate EditCardCommand
+
+ref over EditCardCommand
+ prepareEditCardCommand()
+end ref
+
+EditCardCommand -> Deck: editCard\n(parameters)
+activate Deck
+
+alt front
+ Deck -> FlashCard: setFront\n(changeTo)
+ activate FlashCard
+ deactivate FlashCard
+else back
+ Deck -> FlashCard: setBack\n(changeTo)
+ activate FlashCard
+ deactivate FlashCard
+end
+
+
+Deck --> EditCardCommand: message
+deactivate Deck
+
+CardLI<-- EditCardCommand: result
+deactivate EditCardCommand
+destroy EditCardCommand
+
+CardLI -> CardLiUI: printResult(result)
+activate CardLiUI
+
+CardLiUI -> User
+
+deactivate CardLiUI
+@enduml
\ No newline at end of file
diff --git a/docs/uml/EditDeckCommandSeqDiagram.puml b/docs/uml/EditDeckCommandSeqDiagram.puml
new file mode 100644
index 0000000000..3314d63a35
--- /dev/null
+++ b/docs/uml/EditDeckCommandSeqDiagram.puml
@@ -0,0 +1,56 @@
+@startuml
+!include style.puml
+
+hide footbox
+autonumber
+
+participant ":CardLI" as CardLI mainCOLOUR
+participant "outerParser:OuterParser" as OuterParser logicCOLOUR
+participant "command:\nEditDeckCommand" as EditDeckCommand logicCOLOUR
+participant "deckManager:\nDeckManager" as DeckManager modelCOLOUR
+participant ":Deck" as Deck modelCOLOUR
+participant "ui:CardLiUI" as CardLiUI uiCOLOUR
+actor "User" as User
+
+CardLI-> OuterParser : parseCommand\n(input)
+activate OuterParser
+
+ref over OuterParser
+getCommandArguments(commandType,input)
+end ref
+
+OuterParser -> EditDeckCommand**: new EditDeckCommand\n(arguments)
+activate EditDeckCommand
+EditDeckCommand --> OuterParser: command
+deactivate EditDeckCommand
+OuterParser --> CardLI: command
+deactivate OuterParser
+
+CardLI ->EditDeckCommand : execute()
+activate EditDeckCommand
+
+ref over EditDeckCommand
+prepareEditDeckCommand()
+end ref
+
+EditDeckCommand -> DeckManager: editDeck\n(parameters)
+activate DeckManager
+
+DeckManager -> Deck: setName\n(deckName)
+activate Deck
+deactivate Deck
+
+DeckManager --> EditDeckCommand: message
+deactivate DeckManager
+
+CardLI<-- EditDeckCommand: result
+deactivate EditDeckCommand
+destroy EditDeckCommand
+
+CardLI-> CardLiUI: printResult(result)
+activate CardLiUI
+
+CardLiUI -> User
+
+deactivate CardLiUI
+@enduml
\ No newline at end of file
diff --git a/docs/uml/MoveCommandSeqDiagram.puml b/docs/uml/MoveCommandSeqDiagram.puml
new file mode 100644
index 0000000000..52e61bf27d
--- /dev/null
+++ b/docs/uml/MoveCommandSeqDiagram.puml
@@ -0,0 +1,66 @@
+@startuml
+!include style.puml
+
+hide footbox
+autonumber
+
+participant ":CardLI" as CardLI mainCOLOUR
+participant "innerParser:\nInnerParser" as InnerParser logicCOLOUR
+participant "command:\nMoveCardCommand" as MoveCardCommand logicCOLOUR
+participant "deckManager:\nDeckManager" as DeckManager modelCOLOUR
+participant ":Deck" as Deck modelCOLOUR
+participant "ui:\nCardLiUI" as CardLiUI uiCOLOUR
+actor "User" as User
+
+CardLI -> InnerParser: parseCommand(input)
+activate InnerParser
+
+ref over InnerParser
+getCommandArguments\n(commandType,input)
+end ref
+
+InnerParser -> MoveCardCommand**: new MoveCardCommand\n(arguments)
+activate MoveCardCommand
+MoveCardCommand --> InnerParser: "command:\nMoveCardCommand
+deactivate MoveCardCommand
+InnerParser --> CardLI: command
+deactivate InnerParser
+
+CardLI->MoveCardCommand : execute()
+activate MoveCardCommand
+
+ref over MoveCardCommand
+prepareMoveCardCommand()
+end ref
+
+MoveCardCommand -> DeckManager: moveCard\n(parameters)
+activate DeckManager
+
+DeckManager -> Deck: getCard\n(cardIndex)
+activate Deck
+
+Deck --> DeckManager: cardCopy
+deactivate Deck
+
+DeckManager -> Deck: addFlashCard\n(cardCopy)
+activate Deck
+deactivate Deck
+
+DeckManager -> Deck: deleteFlashCard\n(enteredCardIndex)
+activate Deck
+deactivate Deck
+
+DeckManager --> MoveCardCommand: message
+deactivate DeckManager
+
+CardLI<-- MoveCardCommand: result
+deactivate MoveCardCommand
+destroy MoveCardCommand
+
+CardLI-> CardLiUI: printResult\n(result)
+activate CardLiUI
+
+CardLiUI -> User
+
+deactivate CardLiUI
+@enduml
\ No newline at end of file
diff --git a/docs/uml/TestSequenceDiagram.puml b/docs/uml/TestSequenceDiagram.puml
new file mode 100644
index 0000000000..2c298ee3cf
--- /dev/null
+++ b/docs/uml/TestSequenceDiagram.puml
@@ -0,0 +1,29 @@
+@startuml
+!include style.puml
+autonumber
+hide footbox
+
+actor "User" as User
+participant ":Ui" as Ui uiCOLOUR
+participant ":Logic" as Logic logicCOLOUR
+participant ":Model" as Model modelCOLOUR
+
+User -> Ui : "test"
+activate Ui
+
+Ui -> Logic : execute()
+activate Logic
+
+Logic -> Model : startTest()
+activate Model
+
+Model --> Logic
+deactivate Model
+
+Logic --> Ui
+deactivate Logic
+
+Ui --> User : endTestMessage
+deactivate Ui
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/countdownSeqDiagram.puml b/docs/uml/countdownSeqDiagram.puml
new file mode 100644
index 0000000000..2f5fc94dd8
--- /dev/null
+++ b/docs/uml/countdownSeqDiagram.puml
@@ -0,0 +1,34 @@
+@startuml
+!include style.puml
+hide footbox
+autonumber
+
+participant ":Countdown" as Countdown modelCOLOUR
+participant ":CountdownTimerTask" as CountdownTimerTask modelCOLOUR
+
+[->Countdown: Countdown(startValue,\n timesUpMessage)
+activate Countdown
+Countdown->CountdownTimerTask: CountdownTimerTask(startValue,\n timesUpMessage)
+activate CountdownTimerTask
+[->Countdown: start()
+Countdown->CountdownTimerTask: scheduleAtFixedRate(timerTask,\n delay, period)
+
+loop until time is up or Countdown is stopped
+[<--CountdownTimerTask: display()
+CountdownTimerTask->CountdownTimerTask: countDown()
+ opt
+ [->Countdown: stop()
+ Countdown->CountdownTimerTask: cancel()
+ end
+end
+
+alt isRunning()
+CountdownTimerTask->Countdown: stop()
+Countdown->CountdownTimerTask: cancel()
+end
+
+deactivate CountdownTimerTask
+destroy CountdownTimerTask
+deactivate Countdown
+destroy Countdown
+@enduml
\ No newline at end of file
diff --git a/docs/uml/findSeqDiagram.puml b/docs/uml/findSeqDiagram.puml
new file mode 100644
index 0000000000..4ec26aaaf8
--- /dev/null
+++ b/docs/uml/findSeqDiagram.puml
@@ -0,0 +1,66 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+
+!include style.puml
+
+participant ":OuterParser" as Parser logicCOLOUR
+participant "FindCardsCommand" as FindCardsCommand logicCOLOUR
+participant "FindCardsParser" as FindCardsParser logicCOLOUR
+participant "CommandResult" as CommandResult logicCOLOUR
+participant ":DeckManager" as Dm modelCOLOUR
+participant ":Deck" as deck modelCOLOUR
+autonumber
+
+hide footbox
+
+
+ [-> Parser: parseCommand\n(input)
+ activate Parser
+ ref over Parser
+ parsing
+ arguments
+ end ref
+ create FindCardsCommand
+ Parser -> FindCardsCommand
+
+ activate FindCardsCommand
+ create FindCardsParser
+ FindCardsCommand->FindCardsParser
+ activate FindCardsParser
+ FindCardsParser --> FindCardsCommand
+ deactivate FindCardsParser
+ Parser <--FindCardsCommand : command:Command
+ deactivate FindCardsCommand
+ [<--Parser : command:Command
+ deactivate Parser
+ [-> FindCardsCommand: execute()
+ activate FindCardsCommand
+ FindCardsCommand->FindCardsParser: parseArguments\n(arguments)
+ activate FindCardsParser
+ FindCardsParser-->FindCardsCommand: parameters:String[]
+ deactivate FindCardsParser
+ destroy FindCardsParser
+
+ alt !findInput.isEmpty()
+ create CommandResult
+ FindCardsCommand -> CommandResult
+ activate CommandResult
+ CommandResult -> Dm: findCards\n(searchInput)
+ activate Dm
+ loop all decks
+ Dm -> deck: returnMatchingFlashCards\n(searchInput)
+ activate deck
+ deck --> Dm: result:String
+ deactivate deck
+ end
+ Dm --> CommandResult: result:String
+ deactivate Dm
+ CommandResult --> FindCardsCommand: result:CommandResult
+ deactivate CommandResult
+ destroy CommandResult
+ end
+ [<--FindCardsCommand: result: CommandResult
+ deactivate FindCardsCommand
+ destroy FindCardsCommand
+@enduml
\ No newline at end of file
diff --git a/docs/uml/getTestDeckSequenceDiagram.puml b/docs/uml/getTestDeckSequenceDiagram.puml
new file mode 100644
index 0000000000..e8ea7d381a
--- /dev/null
+++ b/docs/uml/getTestDeckSequenceDiagram.puml
@@ -0,0 +1,45 @@
+@startuml
+!include style.puml
+autonumber
+hide footbox
+
+participant ":TestManager" as TestManager modelCOLOUR
+participant ":AnswerList" as AnswerList modelCOLOUR
+participant ":DeckManager" as DeckManager modelCOLOUR
+participant "deckToTest:Deck" as Deck modelCOLOUR
+
+activate TestManager
+
+TestManager -> DeckManager : getTestDeck(deckIndex:int)
+activate DeckManager
+
+alt test all decks
+DeckManager -> Deck **: Deck()
+activate Deck
+loop every Deck in DeckManager
+loop every FlashCard in Deck
+
+Deck -> Deck : addFlashCard()
+activate Deck
+Deck --> Deck
+deactivate Deck
+
+end
+end
+Deck --> DeckManager
+deactivate Deck
+DeckManager --> TestManager : deckToTest
+
+else test single deck
+TestManager -> DeckManager : get(deckIndex)
+DeckManager --> TestManager : deckToTest
+end
+deactivate DeckManager
+
+TestManager -> AnswerList **: AnswerList(deckToTest)
+activate AnswerList
+
+TestManager <-- AnswerList : userAnswers
+deactivate AnswerList
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/logicArchitecture.puml b/docs/uml/logicArchitecture.puml
new file mode 100644
index 0000000000..efce2b354b
--- /dev/null
+++ b/docs/uml/logicArchitecture.puml
@@ -0,0 +1,56 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+!define logicCOLOUR #a6f0ff
+!define boxCOLOUR #86b1db
+!define uiCOLOUR #88fca7
+!define storageCOLOUR #fc9e88
+!define modelCOLOUR #c088fc
+!define mainCOLOUR #b5b5b5
+
+
+
+
+rectangle Main as Main mainCOLOUR
+rectangle logic as "Logic" boxCOLOUR{
+
+
+rectangle InnerParser as iParser logicCOLOUR
+rectangle OuterParser as oParser logicCOLOUR
+rectangle CommandResult as CommandResult logicCOLOUR
+rectangle "{abstract}\nCommand " as Command logicCOLOUR
+
+rectangle XYZCommand as XYZCommand logicCOLOUR
+}
+ model <-[dashed]- XYZCommand
+User -[dashed]-> UI
+Main --> "1"iParser
+Main --> "1"oParser
+iParser -[dashed]->XYZCommand: > creates
+oParser -[dashed]->XYZCommand: > creates
+XYZCommand --|> Command
+
+
+Command -[dashed]->CommandResult: > produces
+Main -[dashed]->CommandResult
+Main --> storage
+
+UI <-[dashed]-> Main
+
+rectangle storage as "Storage" storageCOLOUR {
+
+}
+
+rectangle model as "Model" modelCOLOUR {
+
+}
+
+rectangle UI as "UI" uiCOLOUR {
+
+}
+
+
+
+
+
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/markTestSequenceDiagram.puml b/docs/uml/markTestSequenceDiagram.puml
new file mode 100644
index 0000000000..97603bd2da
--- /dev/null
+++ b/docs/uml/markTestSequenceDiagram.puml
@@ -0,0 +1,90 @@
+@startuml
+!include style.puml
+autonumber
+hide footbox
+
+participant ":TestManager" as TestManager modelCOLOUR
+participant ":AnswerList" as AnswerList modelCOLOUR
+participant ":Deck" as Deck modelCOLOUR
+participant ":TestHistory" as TestHistory modelCOLOUR
+participant ":TestUi" as TestUi uiCOLOUR
+
+activate TestManager
+
+TestManager -> TestManager : markTest(userAnswers)
+activate TestManager
+
+loop every answer in (userAnswers)
+
+TestManager -> TestManager : markQuestion(userAnswers, answer)
+activate TestManager
+
+TestManager -> TestUi : printQuestion()
+activate TestUi
+TestUi --> TestManager
+deactivate TestUi
+
+TestManager -> TestUi : printCorrectAnswer()
+activate TestUi
+TestUi --> TestManager
+deactivate TestUi
+
+TestManager -> TestUi : printUserAnswer()
+activate TestUi
+TestUi --> TestManager
+deactivate TestUi
+
+alt answer is correct
+TestManager -> AnswerList : incrementUserScore()
+activate AnswerList
+AnswerList --> TestManager
+deactivate AnswerList
+
+TestManager -> AnswerList : getDeck()
+activate AnswerList
+AnswerList -> Deck : incrementUserScore()
+activate Deck
+Deck --> AnswerList
+deactivate Deck
+AnswerList --> TestManager
+deactivate AnswerList
+
+TestManager -> TestUi : printCorrectAnsMessage()
+activate TestUi
+TestUi --> TestManager
+deactivate TestUi
+
+else answer is wrong
+TestManager -> TestUi : printIncorrectAnsMessage()
+activate TestUi
+TestUi --> TestManager
+deactivate TestUi
+end
+
+TestManager -> AnswerList : getDeck()
+activate AnswerList
+AnswerList -> Deck : incrementTotalScore()
+activate Deck
+Deck --> AnswerList
+deactivate Deck
+AnswerList --> TestManager
+deactivate AnswerList
+
+deactivate TestManager
+end
+
+TestManager -> TestUi : printResult(score, totalScore)
+activate TestUi
+TestUi --> TestManager
+deactivate TestUi
+
+
+deactivate TestManager
+
+TestManager -> TestHistory : addAnswerList(userAnswers)
+activate TestHistory
+TestHistory --> TestManager
+deactivate TestHistory
+deactivate TestManager
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/modelArchitecture.puml b/docs/uml/modelArchitecture.puml
new file mode 100644
index 0000000000..472756d0f0
--- /dev/null
+++ b/docs/uml/modelArchitecture.puml
@@ -0,0 +1,60 @@
+@startuml
+'https://plantuml.com/deployment-diagram
+!define logicCOLOUR #a6f0ff
+!define storageCOLOUR #fc9e88
+!define modelCOLOUR #c088fc
+!define boxCOLOUR #a08dc7
+!define boxCOLOUR1 #dac0eb
+!define mainCOLOUR #b5b5b5
+!define uiCOLOUR #88fca7
+!define dataCOLOUR #e34668
+
+rectangle "Main" as main mainCOLOUR
+rectangle "Storage" as storage storageCOLOUR
+rectangle "XYZCommand" as command logicCOLOUR
+rectangle "TestUi" as tui uiCOLOUR
+
+rectangle "Model" as model boxCOLOUR {
+rectangle "flashcard" boxCOLOUR1 {
+
+ rectangle "Deck" as deck modelCOLOUR
+ rectangle "DeckManager" as dm modelCOLOUR
+ rectangle "FlashCard" as fc modelCOLOUR
+
+}
+
+rectangle "testing" boxCOLOUR1 {
+
+ rectangle "Answer" as answer modelCOLOUR
+ rectangle "AnswerList" as al modelCOLOUR
+
+ rectangle "TestHistory" as th modelCOLOUR
+ rectangle "TestManager" as tm modelCOLOUR
+ rectangle "Countdown" as cd modelCOLOUR
+
+}
+}
+
+main -> storage
+main -[dashed]-> command: > calls execute()
+main --> "1" dm
+main --> "1" th
+dm --> "*" deck
+deck --> "*" fc
+command -[dashed]-> dm: > accesses
+command --[dashed]-> deck: > accesses
+command -[dashed]-> tm: > accesses
+command -[dashed]-> th : > accesses
+tm -left-> "1" tui
+tm --> dm
+tm -> th
+
+al -> "*" answer
+al -> "1" deck
+tm -[dashed]-> al: > creates
+
+tm -[dashed]-> cd
+
+
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/overallArchitectureDiagram.puml b/docs/uml/overallArchitectureDiagram.puml
new file mode 100644
index 0000000000..1eef41e192
--- /dev/null
+++ b/docs/uml/overallArchitectureDiagram.puml
@@ -0,0 +1,31 @@
+@startuml
+'https://plantuml.com/deployment-diagram
+
+!define logicCOLOUR #a6f0ff
+!define storageCOLOUR #fc9e88
+!define modelCOLOUR #c088fc
+!define mainCOLOUR #b5b5b5
+!define uiCOLOUR #88fca7
+!define dataCOLOUR #e34668
+
+actor "User" as user
+file "data/CardLi.txt" as data dataCOLOUR
+
+rectangle "CardLI" as CLI {
+rectangle "UI" as UI uiCOLOUR
+rectangle "Main" as main mainCOLOUR
+rectangle "Logic" as logic logicCOLOUR
+rectangle "Storage" as storage storageCOLOUR
+rectangle "Model" as model modelCOLOUR
+}
+
+user -down-> UI
+main -right-> UI
+main -down-> logic
+main -down-> model
+main -left-> storage
+storage -left-> data
+storage -[dashed]-> model
+logic -> model
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/parseAnswerListSeqDiagram.puml b/docs/uml/parseAnswerListSeqDiagram.puml
new file mode 100644
index 0000000000..f51a76ff65
--- /dev/null
+++ b/docs/uml/parseAnswerListSeqDiagram.puml
@@ -0,0 +1,86 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+!include style.puml
+participant ": Storage" as Store storageCOLOUR
+participant "jsonAnswerList: JSONObject" as jsonAL
+participant "newAnswerList: AnswerList" as AL modelCOLOUR
+participant "jsonAnswer: JSONObject" as jsonAns
+participant "newAnswer: Answer" as Ans modelCOLOUR
+autonumber
+hide footbox
+
+mainframe **ref** parseAnswerList
+activate Store
+
+Store->Store: parseAnswerList(jsonAnswerList: JSONObject)
+activate Store
+
+Store->jsonAL: get("deck")
+activate jsonAL
+
+Store<--jsonAL: jsonDeck: Object
+deactivate jsonAL
+
+Store->Store: parseDeck\n((JSONObject) jsonDeck)
+activate Store
+
+Store-->Store: newDeck: Deck
+deactivate Store
+
+Store->AL**:new AnswerList(newDeck)
+activate AL
+
+Store<--AL
+deactivate AL
+
+Store->jsonAL: get("answerList")
+activate jsonAL
+
+Store<--jsonAL: JSONArray jsonAnswers\n = ((JSONArray) answerList:\n Object)
+deactivate jsonAL
+
+loop (JSONObject) jsonAnswer in jsonAnswers
+
+Store->jsonAns: get("answer")
+activate jsonAns
+
+Store<--jsonAns: answer: Object
+deactivate jsonAns
+
+Store->jsonAns: get("questionIndex")
+activate jsonAns
+
+Store<--jsonAns: questionIndex: Object
+deactivate jsonAns
+
+Store->Ans**: new Answer((String) answer, (int)(long) questionIndex)
+activate Ans
+
+Store<--Ans
+deactivate Ans
+
+Store->AL: add(newAnswer)
+activate AL
+
+Store<--AL
+deactivate AL
+
+end
+
+Store->jsonAL: get("userScore")
+activate jsonAL
+
+Store<--jsonAL: userScore: Object
+deactivate jsonAL
+
+Store->AL: setUserScore((int)(long) userScore)
+activate AL
+
+Store<--AL
+deactivate AL
+
+Store-->Store: newAnswerList
+deactivate Store
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/parseDeckSeqDiagram.puml b/docs/uml/parseDeckSeqDiagram.puml
new file mode 100644
index 0000000000..b8cbe59ab4
--- /dev/null
+++ b/docs/uml/parseDeckSeqDiagram.puml
@@ -0,0 +1,79 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+!include style.puml
+participant ": Storage" as Store storageCOLOUR
+participant "jsonDeck: JSONObject" as jsonDeck
+participant "newDeck: Deck" as Deck modelCOLOUR
+participant "jsonCard: JSONObject" as jsonCard
+participant "newFlashCard: FlashCard" as Card modelCOLOUR
+autonumber
+hide footbox
+
+mainframe **ref** parseDeck
+activate Store
+
+Store->Store: parseDeck(jsonDeck: JSONObject)
+activate Store
+
+Store->jsonDeck: get("deckName")
+activate jsonDeck
+
+Store<--jsonDeck: deckName: Object
+deactivate jsonDeck
+
+Store->Deck**: new Deck((String) deckName)
+activate Deck
+
+Store<--Deck
+deactivate Deck
+
+Store->jsonDeck: get("cards")
+activate jsonDeck
+
+Store<--jsonDeck: JSONArray jsonCards\n = ((JSONArray) cards:\n Object)
+deactivate jsonDeck
+
+loop (JSONObject) jsonCard in jsonCards
+
+Store->jsonCard: get("front")
+activate jsonCard
+
+Store<--jsonCard: front: Object
+deactivate jsonCard
+
+Store->jsonCard: get("back")
+activate jsonCard
+
+Store<--jsonCard: back: Object
+deactivate jsonCard
+Store->jsonCard: get("userScore")
+activate jsonCard
+
+Store<--jsonCard: userScore: Object
+deactivate jsonCard
+
+Store->jsonCard: get("totalScore")
+activate jsonCard
+
+Store<--jsonCard: totalScore: Object
+deactivate jsonCard
+
+Store->Card**: new FlashCard((String) front, (String) back, (int)(long) userScore, (int)(long) totalScore)
+activate Card
+
+Store<--Card
+deactivate Card
+
+Store->Deck: addFlashCard(newFlashCard)
+activate Deck
+
+Store<--Deck
+deactivate Deck
+
+end
+
+Store-->Store: newDeck
+deactivate Store
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/parserArchitecture.puml b/docs/uml/parserArchitecture.puml
new file mode 100644
index 0000000000..f1bd8994ac
--- /dev/null
+++ b/docs/uml/parserArchitecture.puml
@@ -0,0 +1,28 @@
+@startuml
+'https://plantuml.com/deployment-diagram
+!define logicCOLOUR #a6f0ff
+!define boxCOLOUR #86b1db
+!define mainCOLOUR #b5b5b5
+
+rectangle parser as "Parser" boxCOLOUR{
+rectangle "InnerParser" as iParser logicCOLOUR
+rectangle "OuterParser" as oParser logicCOLOUR
+rectangle "<>\nCommandArgumentParser" as CAP logicCOLOUR
+rectangle "XYZCommandParser" as XYZCP logicCOLOUR
+
+}
+
+rectangle "{abstract}\nCommand" as Command logicCOLOUR
+rectangle "XYZCommand" as XYZCommand logicCOLOUR
+
+rectangle "Main" as Main mainCOLOUR
+
+XYZCP -up-|> CAP
+iParser -[dashed]->XYZCommand: > creates
+oParser -[dashed]->XYZCommand: > creates
+XYZCommand-[dashed]->XYZCP: > creates
+XYZCommand-up-|>Command
+
+Main -down-> iParser
+Main -down-> oParser
+@enduml
\ No newline at end of file
diff --git a/docs/uml/prepareTestDeckSeqDiagram.puml b/docs/uml/prepareTestDeckSeqDiagram.puml
new file mode 100644
index 0000000000..689ad0d18e
--- /dev/null
+++ b/docs/uml/prepareTestDeckSeqDiagram.puml
@@ -0,0 +1,40 @@
+@startuml
+!include style.puml
+autonumber
+hide footbox
+
+participant ":TestManager" as TestManager modelCOLOUR
+participant ":AnswerList" as AnswerList modelCOLOUR
+participant ":Collections" as Collections
+participant "ShuffledDeck:Deck" as Deck modelCOLOUR
+
+activate TestManager
+
+TestManager -> TestManager : prepareTestDeck(userAnswers)
+activate TestManager
+
+TestManager -> Collections : shuffle(deckToTest)
+activate Collections
+
+Collections --> TestManager : ShuffledDeck
+deactivate Collections
+
+loop every card in deckToTest
+TestManager -> AnswerList : getDeck()
+activate AnswerList
+AnswerList -> Deck : getQuestionNumber(question:FlashCard)
+activate Deck
+Deck --> AnswerList : questionNumber:Int
+deactivate Deck
+AnswerList --> TestManager : questionNumber:Int
+deactivate AnswerList
+
+TestManager -> AnswerList : addAnswer("NIL", questionNumber)
+activate AnswerList
+AnswerList --> TestManager
+deactivate AnswerList
+end
+
+TestManager --> TestManager
+deactivate TestManager
+@enduml
diff --git a/docs/uml/readFromFileSeqDiagram.puml b/docs/uml/readFromFileSeqDiagram.puml
new file mode 100644
index 0000000000..6c2ee7c98f
--- /dev/null
+++ b/docs/uml/readFromFileSeqDiagram.puml
@@ -0,0 +1,68 @@
+@startuml
+'https://plantuml.com/sequence-diagram
+
+!include style.puml
+hide footbox
+autonumber
+participant ": Storage" as Store storageCOLOUR
+participant "decks: ArrayList /\ntestHistory: ArrayList" as ArrayList modelCOLOUR
+participant "s: Scanner" as Scan uiCOLOUR
+participant "parser: JSONParser" as Parser
+
+[->Store: readCardsFromFile() /\nreadTestsFromFile()
+activate Store
+
+Store->ArrayList**: new ArrayList<>()
+activate ArrayList
+
+Store<--ArrayList
+deactivate ArrayList
+
+Store->Scan**: new Scanner(filepath: String)
+activate Scan
+
+Store<--Scan
+deactivate Scan
+
+Store->Parser**: new JSONParser()
+activate Parser
+
+Store<--Parser
+deactivate Parser
+
+Store->Scan: nextLine()
+activate Scan
+
+Store<--Scan: scannedString: String
+destroy Scan
+
+Store->Parser: parse(scannedString)
+activate Parser
+
+Store<--Parser: JSONArray jsonDecks / jsonTestHistory\n = ((JSONArray) scannedObject: Object)
+deactivate Parser
+
+loop (JSONObject) object in jsonDecks / jsonTestHistory
+
+Store->Store: parseDeck / parseAnswerList(object)
+activate Store
+
+ref over Store
+parseDeck / parseAnswerList
+end ref
+
+Store-->Store: deck: Deck / answerList: AnswerList
+deactivate Store
+
+Store->ArrayList: add(deck / answerList)
+activate ArrayList
+
+Store<--ArrayList
+deactivate ArrayList
+
+end
+
+[<--Store: decks / testHistory
+destroy Store
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/style.puml b/docs/uml/style.puml
new file mode 100644
index 0000000000..41f11cbe5c
--- /dev/null
+++ b/docs/uml/style.puml
@@ -0,0 +1,6 @@
+!define logicCOLOUR #a6f0ff
+!define storageCOLOUR #fc9e88
+!define modelCOLOUR #c088fc
+!define mainCOLOUR #b5b5b5
+!define uiCOLOUR #88fca7
+!define dataCOLOUR #e34668
diff --git a/docs/uml/testCardSeqDiagram.puml b/docs/uml/testCardSeqDiagram.puml
new file mode 100644
index 0000000000..d32238e8c3
--- /dev/null
+++ b/docs/uml/testCardSeqDiagram.puml
@@ -0,0 +1,49 @@
+@startuml
+!include style.puml
+autonumber
+hide footbox
+
+participant ":TestManager" as TestManager modelCOLOUR
+participant ":AnswerList" as AnswerList modelCOLOUR
+participant "TestParser" as TestParser modelCOLOUR
+participant ":Answer" as Answer modelCOLOUR
+participant ":TestUi" as TestUi uiCOLOUR
+
+activate TestManager
+TestManager -> TestManager : testCard(userAnswers, card)
+activate TestManager
+
+TestManager -> TestUi : getUserMessage()
+activate TestUi
+TestUi --> TestManager : userResponse
+deactivate TestUi
+
+TestManager -> TestParser : parseUserResponse(userResponse)
+activate TestParser
+TestParser --> TestManager : parsedUserResponse
+deactivate TestParser
+
+opt parsedUserResponse != /Next && parsedUserResponse != /Back
+TestManager -> AnswerList : setQuestionAnswer(questionNumber, parsedUserResponse)
+activate AnswerList
+AnswerList --> TestManager
+deactivate AnswerList
+
+TestManager -> AnswerList : getAnswer(questionNumber)
+activate AnswerList
+AnswerList -> Answer : setIsAnswered()
+activate Answer
+Answer --> AnswerList
+deactivate Answer
+AnswerList --> TestManager
+deactivate AnswerList
+
+TestManager -> AnswerList : addAnswer(parsedUserResponse, questionNumber)
+activate AnswerList
+AnswerList --> TestManager
+deactivate AnswerList
+
+TestManager --> TestManager : nextQuestionFlag:Int
+deactivate TestManager
+end
+@enduml
\ No newline at end of file
diff --git a/docs/uml/testClassDiagram.puml b/docs/uml/testClassDiagram.puml
new file mode 100644
index 0000000000..9bc7d74e4d
--- /dev/null
+++ b/docs/uml/testClassDiagram.puml
@@ -0,0 +1,47 @@
+@startuml
+hide circle
+!include style.puml
+
+!define modelBoxCOLOUR #a08dc7
+!define parserBoxCOLOUR #86b1db
+!define uiBoxCOLOUR #40fca7
+
+skinparam classAttributeIconSize 0
+package testing modelBoxCOLOUR {
+ class TestManager modelCOLOUR
+ class AnswerList modelCOLOUR
+ class Answer modelCOLOUR
+ class TestHistory modelCOLOUR
+ class Countdown modelCOLOUR
+}
+
+package parser parserBoxCOLOUR {
+ class TestParser logicCOLOUR
+}
+
+package ui uiBoxCOLOUR {
+ class TestUi uiCOLOUR
+}
+
+package flashcard modelBoxCOLOUR {
+ class Deck modelCOLOUR
+ class FlashCard modelCOLOUR
+}
+
+TestManager -left> "1" TestUi
+TestManager -> "1" Countdown
+TestManager .right> "1" TestParser
+TestManager --> "1" AnswerList : creates
+TestManager .left> "1" TestHistory
+TestManager .> "1" Deck
+
+TestHistory --> "*" AnswerList : stores
+
+AnswerList "1" --> "*" Answer : contains
+AnswerList -right-> "1" Deck
+
+Deck --> "*" FlashCard
+
+Answer .> "1" FlashCard
+
+@enduml
diff --git a/docs/uml/testInProgressSeqDiagram.puml b/docs/uml/testInProgressSeqDiagram.puml
new file mode 100644
index 0000000000..c4f4b92a49
--- /dev/null
+++ b/docs/uml/testInProgressSeqDiagram.puml
@@ -0,0 +1,39 @@
+@startuml
+!include style.puml
+autonumber
+hide footbox
+
+participant ":TestManager" as TestManager modelCOLOUR
+participant "countdown:Countdown" as Countdown modelCOLOUR
+activate TestManager
+
+
+TestManager -> TestManager : testInProgress(shuffledDeckToTest, userAnswers)
+activate TestManager
+
+TestManager -> Countdown **: Countdown(timer:Int, TIMES_UP_MESSAGE:String)
+activate Countdown
+
+Countdown --> TestManager
+deactivate Countdown
+
+loop until every card in deckToTest is answered && countdown.isRunning()
+loop currentQuestion >= 0 && currentQuestion < deckReplicate.size() && countdown.isRunning()
+opt current question is not answered
+TestManager -> TestManager : testCard(userAnswer, currentQuestion:FlashCard, countdown)
+activate TestManager
+TestManager --> TestManager : nextQuestionFlag:Int
+deactivate TestManager
+end
+end
+end
+opt countdown.isRunning
+TestManager -> Countdown : stop()
+activate Countdown
+deactivate Countdown
+end
+TestManager --> TestManager
+deactivate TestManager
+destroy Countdown
+
+@enduml
\ No newline at end of file
diff --git a/docs/uml/writeToFileSeqDiagram.puml b/docs/uml/writeToFileSeqDiagram.puml
new file mode 100644
index 0000000000..c0d15e1d03
--- /dev/null
+++ b/docs/uml/writeToFileSeqDiagram.puml
@@ -0,0 +1,55 @@
+@startuml
+
+!include style.puml
+hide footbox
+autonumber
+participant ": Storage" as Storage storageCOLOUR
+participant "fileWriter: FileWriter" as File storageCOLOUR
+participant "jsonDecks / jsonTestHistory:\n JSONArray" as JSON
+participant "deck: Deck /\nanswerList: AnswerList" as Object
+[-> Storage: writeCardsToFile\n(decks: ArrayList) /\nwriteTestsToFile\n(testHistory: ArrayList)
+activate Storage
+
+Storage -> File **: FileWriter(filepath: String, append: boolean)
+activate File
+
+File --> Storage
+deactivate File
+
+Storage->JSON**: new JSONArray()
+activate JSON
+
+Storage<--JSON
+deactivate JSON
+
+loop deck in decks/answerList in testHistory
+
+Storage->Object: toJSONObject()
+activate Object
+
+Storage<--Object: jsonDeck / jsonAnswerList: JSONObject
+destroy Object
+
+Storage->JSON: add(jsonDeck / jsonAnswerList)
+activate JSON
+
+Storage<--JSON
+destroy JSON
+
+end
+
+Storage->File: write(jsonDecks / jsonTestHistory)
+activate File
+
+File --> Storage
+deactivate File
+
+Storage->File: close()
+activate File
+
+File --> Storage
+destroy File
+
+[<-- Storage
+destroy Storage
+@enduml
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/CardLI.java b/src/main/java/seedu/cardli/CardLI.java
new file mode 100644
index 0000000000..54f40a127a
--- /dev/null
+++ b/src/main/java/seedu/cardli/CardLI.java
@@ -0,0 +1,84 @@
+package seedu.cardli;
+
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+import org.fusesource.jansi.AnsiConsole;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.InnerParser;
+import seedu.cardli.parser.OuterParser;
+import seedu.cardli.storage.Storage;
+import seedu.cardli.testing.TestHistory;
+import seedu.cardli.testing.TestManager;
+import seedu.cardli.ui.CardLiUi;
+
+import java.util.ArrayList;
+
+/**
+ * Represents CardLI application.
+ */
+public class CardLI {
+ private static final CardLiUi ui = new CardLiUi();
+
+ private ArrayList decks;
+ private Storage storage;
+ private DeckManager deckManager;
+ private TestManager testManager;
+ private InnerParser innerParser;
+ private OuterParser outerParser;
+ private TestHistory testHistory;
+
+ private CardLI() {
+ this.storage = new Storage();
+ this.decks = storage.readCardsFromFile();
+ this.deckManager = new DeckManager(decks);
+ this.testHistory = new TestHistory(deckManager, storage.readTestsFromFile());
+ this.testManager = new TestManager(testHistory, deckManager);
+ this.innerParser = new InnerParser();
+ this.outerParser = new OuterParser(deckManager, innerParser, testHistory, testManager);
+ AnsiConsole.systemInstall();
+ }
+
+ /**
+ * Main function that runs the java.duke.Duke application.
+ */
+ public void run() {
+
+ ui.printGreetingMessage();
+ boolean exitProgram = false;
+ boolean inDeck;
+ boolean inReview;
+
+
+ while (!exitProgram) {
+ String input = ui.getUserMessage();
+ Command command = outerParser.parseCommand(input);
+ CommandResult result = command.execute();
+ ui.printResult(result);
+ exitProgram = result.isExit();
+ inDeck = result.isEnter();
+ while (inDeck) {
+ input = ui.getUserMessage();
+ command = innerParser.parseCommand(input);
+ result = command.execute();
+ ui.printResult(result);
+ inDeck = !result.isExit();
+ storage.writeCardsToFile(deckManager.getDecks());
+ }
+ storage.writeCardsToFile(deckManager.getDecks());
+ storage.writeTestsToFile(testHistory.getTestHistory());
+ }
+ ui.printByeMessage();
+ }
+
+ /**
+ * Entry point to the java.duke.Duke application.
+ * @param args user's input
+ */
+ public static void main(String[] args) {
+ new CardLI().run();
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/Command.java b/src/main/java/seedu/cardli/commands/Command.java
new file mode 100644
index 0000000000..fda1c27f11
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/Command.java
@@ -0,0 +1,38 @@
+package seedu.cardli.commands;
+
+/**
+ * Implements the Command class, which contains the
+ * type of command and the arguments to be executed.
+ */
+public abstract class Command {
+ protected String name;
+ protected String arguments;
+
+ /**
+ * Constructs a {@code Command} with the specified fields.
+ */
+ public Command(String name, String arguments) {
+ this.name = name;
+ this.arguments = arguments;
+ }
+
+ /**
+ * Constructs a {@code Command} with the given name and no arguments.
+ * @param name name of command
+ */
+ public Command(String name) {
+ this.name = name;
+ this.arguments = null;
+ }
+
+ /**
+ * Executes the command and returns the result as a {@code CommandResult}.
+ * @return a {@code CommandResult} containing the result of the executed command
+ */
+ public abstract CommandResult execute();
+
+ @Override
+ public String toString() {
+ return this.name;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/CommandResult.java b/src/main/java/seedu/cardli/commands/CommandResult.java
new file mode 100644
index 0000000000..1c65538475
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/CommandResult.java
@@ -0,0 +1,54 @@
+package seedu.cardli.commands;
+
+/**
+ * Implements the class CommandResult, which contains the result of the
+ * executed command and some flags.
+ */
+public class CommandResult {
+
+ private final String result;
+ private final boolean exit;
+ private final boolean enter;
+
+ /**
+ * Constructs a {@code CommandResult} with the specified result.
+ */
+ public CommandResult(String result) {
+ this.result = result;
+ this.exit = false;
+ this.enter = false;
+ }
+
+ /**
+ * Constructs a {@code CommandResult} with the specified fields.
+ * @param result the result of the {@code Command} execution
+ * @param exit whether to exit the programme or return to the main menu
+ * @param enter whether to enter a deck
+ */
+ public CommandResult(String result, boolean exit, boolean enter) {
+ this.result = result;
+ this.exit = exit;
+ this.enter = enter;
+ }
+
+ /**
+ * Getter for result.
+ */
+ public String getResult() {
+ return this.result;
+ }
+
+ /**
+ * Getter for exit.
+ */
+ public boolean isExit() {
+ return this.exit;
+ }
+
+ /**
+ * Getter for enter.
+ */
+ public boolean isEnter() {
+ return this.enter;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/InvalidCommand.java b/src/main/java/seedu/cardli/commands/InvalidCommand.java
new file mode 100644
index 0000000000..b8d4d80fa2
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/InvalidCommand.java
@@ -0,0 +1,13 @@
+package seedu.cardli.commands;
+
+public class InvalidCommand extends Command {
+
+ public InvalidCommand() {
+ super("InvalidCommand");
+ }
+
+ @Override
+ public CommandResult execute() {
+ return new CommandResult("\tThat's not a command.");
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/deck/AddCardCommand.java b/src/main/java/seedu/cardli/commands/deck/AddCardCommand.java
new file mode 100644
index 0000000000..0f4714f604
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/AddCardCommand.java
@@ -0,0 +1,67 @@
+package seedu.cardli.commands.deck;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.deck.AddCardParser;
+
+/**
+ * Implements the AddCardCommand class, which adds a card with the
+ * specified front and back to the given deck.
+ */
+public class AddCardCommand extends Command {
+
+ private static final String FIELD_EMPTY_ERROR_MESSAGE = "You cannot leave any field empty! "
+ + "Format should be\n add /f /b ";
+ private static final String WRONG_ORDER_ERROR_MESSAGE = "Incorrect add command! Format should be\n"
+ + "add /f /b ";
+
+ private AddCardParser parser;
+ private Deck deck;
+ private DeckManager deckManager;
+
+ public AddCardCommand(String arguments, Deck deck, DeckManager deckManager) {
+ super("AddCardCommand", arguments);
+ this.deck = deck;
+ this.deckManager = deckManager;
+ this.parser = new AddCardParser();
+ }
+
+ //@@author JWweiyin
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ try {
+ if (!arguments.toLowerCase().contains("/f") || !arguments.toLowerCase().contains("/b")) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+ String[] rawParameters = parser.parseArguments(super.arguments);
+
+ if (rawParameters.length < 2) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ String front = rawParameters[0].trim();
+ String back = rawParameters[1].trim();
+
+ if (front.isEmpty() || back.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+ String deckWithSameNameCard = deckManager.cardHasSameName(front);
+ if (!deckWithSameNameCard.isEmpty()) {
+ throw new CardLiException("There is already a card with " + front + " on the front in deck "
+ + deckWithSameNameCard + ".");
+
+ }
+
+ String[] parameters = {front, back};
+ result = new CommandResult(deck.prepareToAddFlashCard(parameters));
+ } catch (CardLiException e) {
+ result = new CommandResult(e.getMessage());
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/deck/DeleteCardCommand.java b/src/main/java/seedu/cardli/commands/deck/DeleteCardCommand.java
new file mode 100644
index 0000000000..74f8791c42
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/DeleteCardCommand.java
@@ -0,0 +1,54 @@
+package seedu.cardli.commands.deck;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.parser.Parser;
+import seedu.cardli.parser.deck.DeleteCardParser;
+
+/**
+ * Implements the DeleteCard class, which deletes a card with the
+ * specified index from the given deck.
+ */
+public class DeleteCardCommand extends Command {
+
+ private static final String FIELD_EMPTY_ERROR_MESSAGE = "You cannot leave any field empty! "
+ + "Format should be\n delete ";
+ public static final String CARD_INDEX_TOO_BIG_MESSAGE = "Card index must be a positive integer "
+ + "smaller than 2147483647.";
+
+ private DeleteCardParser parser;
+ private Deck deck;
+
+ public DeleteCardCommand(String arguments, Deck deck) {
+ super("DeleteCardCommand", arguments);
+ this.parser = new DeleteCardParser();
+ this.deck = deck;
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+
+ try {
+ String[] parameters = parser.parseArguments(super.arguments);
+ String enterInput = parameters[0];
+ if (enterInput.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ if (Parser.isInteger(enterInput)) {
+ result = new CommandResult(deck.deleteFlashCardByIndex(enterInput));
+ } else {
+ throw new CardLiException(CARD_INDEX_TOO_BIG_MESSAGE);
+ }
+ } catch (CardLiException e) {
+ result = new CommandResult(e.getMessage());
+ } catch (NumberFormatException e) {
+ result = new CommandResult(CARD_INDEX_TOO_BIG_MESSAGE);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/deck/EditCardCommand.java b/src/main/java/seedu/cardli/commands/deck/EditCardCommand.java
new file mode 100644
index 0000000000..be5ac9e88c
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/EditCardCommand.java
@@ -0,0 +1,167 @@
+//@@author ThaddeusLim99
+
+package seedu.cardli.commands.deck;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.exceptions.InvalidCommandFormatException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+import seedu.cardli.parser.deck.EditCardParser;
+import seedu.cardli.testing.TestManager;
+
+/**
+ * Implements the EditCardCommand class, which edits the specified side of
+ * the card with the specified index in the given deck.
+ */
+public class EditCardCommand extends Command {
+
+ private static final String FIELD_EMPTY_ERROR_MESSAGE = "You cannot leave any field empty! "
+ + "Format should be\nedit /c /s /i ";
+ private static final String WRONG_ORDER_ERROR_MESSAGE = "/c should come before /s, which should come before /i!"
+ + " Format should be\nedit /c /s /i ";
+ private static final String INVALID_INDEX_ERROR_MESSAGE = "Incorrect index for Card!";
+ private static final String INVALID_SIDE_ERROR_MESSAGE = "What side is this? It's only either front or back";
+ private static final String LARGE_INTEGER_ERROR_MESSAGE = "Card index must be smaller than 2147483647.";
+ private static final String ARGUMENT_TYPE_ERROR_MESSAGE = "You can only input the index of the card, which is "
+ + "a positive integer!";
+ private static final String INVALID_ARGUMENTS_MESSAGE = "Please use the correct flags and in the correct order! "
+ + "\nFormat should be edit /c /s /i ";
+ private static final String FLAG_ARGUMENT_ERROR_MESSAGE = "You should not use this command's flag as your argument";
+ private static final String MISSING_FLAG_MESSAGE = "You are missing the relevant flag/flags";
+
+ private EditCardParser parser;
+ private Deck deck;
+ private DeckManager deckManager;
+ private static Logger logger = Logger.getLogger(TestManager.class.getName());
+
+ public EditCardCommand(String arguments, Deck deck, DeckManager deckManager) {
+ super("EditCardCommand", arguments);
+ this.deck = deck;
+ this.parser = new EditCardParser();
+ this.deckManager = deckManager;
+ }
+
+ /**
+ * Returns the card index for prepareEditCardCommand as a string.
+ *
+ * @return card index.
+ * @throws NumberFormatException If an integer above 2147483647 is entered by the user as the card index.
+ * @throws CardLiException If a non-integer is given as index, if the card index given is out of bounds.
+ */
+ public static String prepareCardIndex(String card, Deck deck) throws CardLiException, NumberFormatException {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "preparing Card Index");
+ int cardIndex = 0;
+ if (Parser.isInteger(card)) {
+ logger.log(Level.INFO, "checking if integer cardIndex is out of bounds");
+ //card is an index
+ cardIndex = Integer.parseInt(card) - 1;
+ if (!(cardIndex >= 0 && cardIndex < deck.getCards().size())) {
+ throw new CardLiException(INVALID_INDEX_ERROR_MESSAGE);
+ }
+ } else {
+ throw new CardLiException(ARGUMENT_TYPE_ERROR_MESSAGE);
+ }
+
+ return card;
+ }
+
+ /**
+ * Returns the arguments for EditCardCommand if accepted.
+ *
+ * @return accepted arguments.
+ * @throws FieldEmptyException If arguments or flags are empty.
+ * @throws InvalidCommandFormatException If flags are in the wrong position.
+ * @throws NumberFormatException If an integer above 2147483647 is entered by the user as the card index.
+ * @throws CardLiException If flags are used as arguments, if a non-integer is given as index,if front or side are
+ * not entered as a side.
+ */
+ public String[] prepareEditCardCommand() throws CardLiException, NumberFormatException {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "preparing EditCardCommand");
+
+ if (arguments.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if input contains /c, /s and /i");
+ if (!arguments.contains("/c") || !arguments.contains("/s")
+ || !arguments.contains("/i")) {
+ throw new FieldEmptyException(MISSING_FLAG_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if /c,/s and /i are in the right order");
+ if (!(arguments.indexOf("/c") < arguments.indexOf("/s")
+ && arguments.indexOf("/s") < arguments.indexOf("/i"))) {
+ throw new InvalidCommandFormatException(WRONG_ORDER_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Splitting the input up");
+ // "", card, side, input ///c <> /s <> /i <>
+ String[] rawParameters = parser.parseArguments(super.arguments);
+
+ logger.log(Level.INFO, "Checking if there is enough arguments");
+ if (rawParameters.length != 6) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if /c,/s and /i are in the right order");
+ if (!(rawParameters[0].trim().equals("/c") && rawParameters[2].trim().equals("/s")
+ && rawParameters[4].trim().equals("/i"))) {
+ throw new InvalidCommandFormatException(INVALID_ARGUMENTS_MESSAGE);
+ }
+
+ String card = rawParameters[1].trim();
+ String side = rawParameters[3].trim();
+ String input = rawParameters[5].trim();
+
+ if (side.equalsIgnoreCase("front")) {
+ String deckWithSameNameCard = deckManager.cardHasSameName(input);
+ if (!deckWithSameNameCard.isEmpty()) {
+ throw new CardLiException("There is already a card with " + input + " on the front in deck "
+ + deckWithSameNameCard + ".");
+
+ }
+ }
+
+ logger.log(Level.INFO, "Checking if any field is empty");
+ if (card.isEmpty() || side.isEmpty() || input.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ if (input.equalsIgnoreCase("/c") || input.equalsIgnoreCase("/s") || input.equalsIgnoreCase("/i")) {
+ throw new CardLiException(FLAG_ARGUMENT_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "checking only front and back inputted to /s");
+ if (!(side.equalsIgnoreCase("front") | side.equalsIgnoreCase("back"))) {
+ throw new CardLiException(INVALID_SIDE_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "preparing CardIndex");
+ card = prepareCardIndex(card, deck);
+
+ String[] preparedArguments = {card, side, input};
+ return preparedArguments;
+ }
+
+ @Override
+ public CommandResult execute() { //edit /c /s /i
+ CommandResult result;
+ try {
+ String[] parameters = prepareEditCardCommand();
+ result = new CommandResult(deck.editCard(parameters));
+ } catch (CardLiException e) {
+ result = new CommandResult(e.getMessage());
+ } catch (NumberFormatException e) {
+ result = new CommandResult(LARGE_INTEGER_ERROR_MESSAGE);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/deck/ExitDeckCommand.java b/src/main/java/seedu/cardli/commands/deck/ExitDeckCommand.java
new file mode 100644
index 0000000000..a650b3fe65
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/ExitDeckCommand.java
@@ -0,0 +1,23 @@
+package seedu.cardli.commands.deck;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+
+/**
+ * Implements the ExitDeckCommand class, which exits the deck and
+ * returns the user to the main menu.
+ */
+public class ExitDeckCommand extends Command {
+ public ExitDeckCommand(String arguments) {
+ super("ExitDeckCommand", arguments);
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ CommandResult result = new CommandResult("Exiting to main menu.", true, false);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/commands/deck/HelpInDeckCommand.java b/src/main/java/seedu/cardli/commands/deck/HelpInDeckCommand.java
new file mode 100644
index 0000000000..d628df635e
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/HelpInDeckCommand.java
@@ -0,0 +1,26 @@
+//@@author astralum
+
+package seedu.cardli.commands.deck;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.ui.CardLiUi;
+
+/**
+ * Implements the HelpInDeck class, which returns a help message with a
+ * list of commands the user can input at the deck level.
+ */
+public class HelpInDeckCommand extends Command {
+ public HelpInDeckCommand(String arguments) {
+ super("HelpInDeckCommand", arguments);
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ CommandResult result = new CommandResult(CardLiUi.returnHelpInDeckMessage());
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/commands/deck/MoveCardCommand.java b/src/main/java/seedu/cardli/commands/deck/MoveCardCommand.java
new file mode 100644
index 0000000000..0fa51ed199
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/MoveCardCommand.java
@@ -0,0 +1,134 @@
+package seedu.cardli.commands.deck;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.commands.system.EditDeckCommand;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.exceptions.InvalidCommandFormatException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.deck.MoveCardParser;
+import seedu.cardli.testing.TestManager;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements the MoveCardCommand class, which moves the specified card in
+ * the current deck to another deck.
+ */
+public class MoveCardCommand extends Command {
+
+ private static final String FIELD_EMPTY_ERROR_MESSAGE = "You cannot leave any field empty! "
+ + "Format should be\nmove /c /d ";
+ private static final String WRONG_ORDER_ERROR_MESSAGE = "/c should come before /d! Format should be\n"
+ + "move /c /d ";
+ private static final String SAME_DESTINATION_ERROR_MESSAGE = "Your card is already in the deck specified!";
+ private static final String LARGE_INTEGER_ERROR_MESSAGE = "Card and Deck index must be both "
+ + "smaller than 2147483647.";
+ private static final String INVALID_ARGUMENTS_MESSAGE = "Please use the correct flags and in the correct order! "
+ + "\nFormat should be move /c /d ";
+ private static final String FLAG_ARGUMENT_ERROR_MESSAGE = "You should not use this command's flag as your argument";
+ private static final String MISSING_FLAG_MESSAGE = "You are missing the relevant flag/flags";
+
+ private MoveCardParser parser;
+ private Deck deck;
+ private DeckManager deckManager;
+ private static Logger logger = Logger.getLogger(TestManager.class.getName());
+
+ public MoveCardCommand(String arguments, Deck deck, DeckManager deckManager) {
+ super("MoveCardCommand", arguments);
+ this.deck = deck;
+ this.deckManager = deckManager;
+ this.parser = new MoveCardParser();
+ }
+
+ /**
+ * Returns the arguments for MoveCardCommand if accepted.
+ *
+ * @return accepted arguments.
+ * @throws FieldEmptyException If arguments or flags are empty.
+ * @throws InvalidCommandFormatException If flags are in the wrong position.
+ * @throws seedu.cardli.exceptions.DeckNotExistException If the deck index given is out of bounds.
+ * @throws NumberFormatException If an integer above 2147483647 is entered by the user as the card or deck index.
+ * @throws CardLiException If flags are used as arguments,if a card is being sent to the deck it is currently in,
+ * if a non-integer is given as index, if the card index given is out of bounds.
+ */
+ public String[] prepareMoveCardCommand() throws CardLiException {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "preparing MoveCommand");
+
+ if (arguments.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if input contains /d and /c");
+ if (!arguments.contains("/c") || !arguments.contains("/d")) {
+ throw new FieldEmptyException(MISSING_FLAG_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if /d and /c are in the right order");
+ if (!(arguments.indexOf("/c") < arguments.indexOf("/d"))) {
+ throw new InvalidCommandFormatException(WRONG_ORDER_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Splitting the input up");
+ String[] rawParameters = parser.parseArguments(super.arguments);
+
+ logger.log(Level.INFO, "Checking if there is enough arguments");
+ if (rawParameters.length != 4) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if /c,/d are in the right order");
+ if (!(rawParameters[0].trim().equals("/c") && rawParameters[2].trim().equals("/d"))) {
+ throw new InvalidCommandFormatException(INVALID_ARGUMENTS_MESSAGE);
+ }
+
+ String cardInput = rawParameters[1].trim();
+ String deckInput = rawParameters[3].trim();
+
+ logger.log(Level.INFO, "Checking if any field is empty");
+ if (cardInput.isEmpty() || deckInput.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ if (cardInput.equalsIgnoreCase("/c") || cardInput.equalsIgnoreCase("/d")
+ || deckInput.equalsIgnoreCase("/c") || deckInput.equalsIgnoreCase("/d")) {
+ throw new CardLiException(FLAG_ARGUMENT_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "preparing CardIndex and DeckIndex");
+ cardInput = EditCardCommand.prepareCardIndex(cardInput, deck);
+ deckInput = EditDeckCommand.prepareDeckIndex(deckInput, deckManager);
+
+ logger.log(Level.INFO, "checking if user is already in specified deck");
+ if ((Integer.parseInt(deckInput) - 1) == deckManager.getDeckIndex(deck)) {
+ throw new CardLiException(SAME_DESTINATION_ERROR_MESSAGE);
+ }
+
+ String sourceDeckIndex = String.valueOf(deckManager.getDeckIndex(deck));
+
+ String[] preparedArguments = {sourceDeckIndex, cardInput, deckInput};
+
+ return preparedArguments;
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ try {
+ String[] parameters = prepareMoveCardCommand();
+ result = new CommandResult(deckManager.moveCard(parameters));
+ } catch (CardLiException e) {
+ result = new CommandResult(e.getMessage());
+ } catch (ArrayIndexOutOfBoundsException e) {
+ result = new CommandResult("You are moving the card to a deck that does not exist.");
+ } catch (NumberFormatException e) {
+ result = new CommandResult(LARGE_INTEGER_ERROR_MESSAGE);
+ }
+ return result;
+ }
+}
+
diff --git a/src/main/java/seedu/cardli/commands/deck/ViewCardsCommand.java b/src/main/java/seedu/cardli/commands/deck/ViewCardsCommand.java
new file mode 100644
index 0000000000..d94651de61
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/deck/ViewCardsCommand.java
@@ -0,0 +1,28 @@
+package seedu.cardli.commands.deck;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.Deck;
+
+/**
+ * Implements the ViewCardsCommand class, which returns a formatted list
+ * of all flashcards in the current deck.
+ */
+public class ViewCardsCommand extends Command {
+
+ private Deck deck;
+
+ public ViewCardsCommand(Deck deck, String arguments) {
+ super("ViewCardCommand", arguments);
+ this.deck = deck;
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ CommandResult result = new CommandResult(this.deck.returnAllFlashCards());
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/AddDeckCommand.java b/src/main/java/seedu/cardli/commands/system/AddDeckCommand.java
new file mode 100644
index 0000000000..f98b58e532
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/AddDeckCommand.java
@@ -0,0 +1,40 @@
+//@@author JWweiyin
+
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.system.AddDeckParser;
+
+public class AddDeckCommand extends Command {
+
+ public static final String FIELD_EMPTY_ERROR_MESSAGE = "Please input the name of the deck after \"add\".";
+
+ private AddDeckParser parser;
+ private DeckManager deckManager;
+
+ public AddDeckCommand(String arguments, DeckManager deckManager) {
+ super("AddDeckCommand", arguments);
+ this.parser = new AddDeckParser();
+ this.deckManager = deckManager;
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ try {
+ String[] parameters = parser.parseArguments(super.arguments);
+ String deckName = parameters[0];
+
+ if (deckName.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+ result = new CommandResult(deckManager.prepareToAddDeck(deckName));
+ } catch (FieldEmptyException e) {
+ result = new CommandResult(e.getMessage());
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/DeleteDeckCommand.java b/src/main/java/seedu/cardli/commands/system/DeleteDeckCommand.java
new file mode 100644
index 0000000000..48cc8cc440
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/DeleteDeckCommand.java
@@ -0,0 +1,45 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+import seedu.cardli.parser.system.DeleteDeckParser;
+
+public class DeleteDeckCommand extends Command {
+
+ private DeleteDeckParser parser;
+ private DeckManager deckManager;
+
+ public DeleteDeckCommand(String arguments, DeckManager deckManager) {
+ super("DeleteDeckCommand", arguments);
+ this.parser = new DeleteDeckParser();
+ this.deckManager = deckManager;
+ }
+
+ @Override
+ public CommandResult execute() {
+ String[] parameters = parser.parseArguments(super.arguments);
+ String enterInput = parameters[0];
+
+ CommandResult result;
+
+ try {
+ if (Parser.isInteger(enterInput)) {
+ int deckIndex = Integer.parseInt(enterInput) - 1;
+ return new CommandResult(deckManager.deleteDeck(deckIndex));
+ } else {
+ throw new DeckNotExistException("Please input a positive integer.");
+ }
+
+ } catch (IndexOutOfBoundsException e) {
+ result = new CommandResult("This deck does not exist.");
+ } catch (DeckNotExistException e) {
+ result = new CommandResult(e.getMessage());
+ } catch (NumberFormatException e) {
+ result = new CommandResult("Deck index must be smaller than 2147483647.");
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/EditDeckCommand.java b/src/main/java/seedu/cardli/commands/system/EditDeckCommand.java
new file mode 100644
index 0000000000..0f8818cec4
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/EditDeckCommand.java
@@ -0,0 +1,142 @@
+package seedu.cardli.commands.system;
+
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.exceptions.InvalidCommandFormatException;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+import seedu.cardli.parser.system.EditDeckParser;
+import seedu.cardli.testing.TestManager;
+
+public class EditDeckCommand extends Command {
+
+ private static final String FIELD_EMPTY_ERROR_MESSAGE = "You cannot leave any field empty! "
+ + "Format should be\nedit /d /n ";
+ private static final String WRONG_ORDER_ERROR_MESSAGE = "Incorrect edit command! /n should come after /d. Format "
+ + "should be\nedit /d /n ";
+ private static final String INVALID_INDEX_ERROR_MESSAGE = "Incorrect index for deck!";
+ private static final String ARGUMENT_TYPE_ERROR_MESSAGE = "You can only input the index of the deck, which is "
+ + "a positive integer!";
+ private static final String LARGE_INTEGER_ERROR_MESSAGE = "Deck index must be smaller than 2147483647.";
+ private static final String INVALID_ARGUMENTS_MESSAGE = "Please use the correct flags and in the correct order! "
+ + "\nFormat + should be edit /d /n ";
+ private static final String FLAG_ARGUMENT_ERROR_MESSAGE = "You should not use this command's flag as your argument";
+ private static final String MISSING_FLAG_MESSAGE = "You are missing the relevant flag/flags";
+
+ private EditDeckParser parser;
+ private DeckManager deckManager;
+ private static Logger logger = Logger.getLogger(TestManager.class.getName());
+
+ public EditDeckCommand(String arguments, DeckManager deckManager) {
+ super("EditDeckCommand", arguments);
+ this.parser = new EditDeckParser();
+ this.deckManager = deckManager;
+ }
+
+ /**
+ * Returns the deck index for prepareEditDeckCommand as a string.
+ *
+ * @return deck index.
+ * @throws DeckNotExistException If the deck index given is out of bounds.
+ * @throws NumberFormatException If an integer above 2147483647 is entered by the user as the deck index.
+ * @throws CardLiException If a non-integer is given as index.
+ */
+ public static String prepareDeckIndex(String deck, DeckManager deckManager) throws CardLiException,
+ NumberFormatException {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "preparing Deck Index");
+ int deckIndex = 0;
+ if (Parser.isInteger(deck)) {
+ logger.log(Level.INFO, "checking if integer deckIndex is out of bounds");
+ //deck is an index
+ deckIndex = Integer.parseInt(deck) - 1;
+ if (!(deckIndex >= 0 && deckIndex < deckManager.getDecksSize())) {
+ throw new DeckNotExistException(INVALID_INDEX_ERROR_MESSAGE);
+ }
+ } else {
+ throw new CardLiException(ARGUMENT_TYPE_ERROR_MESSAGE);
+ }
+
+ return deck;
+ }
+
+ /**
+ * Returns the arguments for EditDeckCommand if accepted.
+ *
+ * @return accepted arguments.
+ * @throws FieldEmptyException If arguments or flags are empty.
+ * @throws InvalidCommandFormatException If flags are in the wrong position.
+ * @throws DeckNotExistException If the deck index given is out of bounds.
+ * @throws NumberFormatException If an integer above 2147483647 is entered by the user as the deck index.
+ * @throws CardLiException If flags are used as arguments, if a non-integer is given as index.
+ */
+ public String[] prepareEditDeckCommand() throws CardLiException, NumberFormatException {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "preparing EditDeckCommand");
+
+ if (arguments.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if input contains /d and /n");
+ if (!arguments.contains("/d") || !arguments.contains("/n")) {
+ throw new FieldEmptyException(MISSING_FLAG_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if /d and /n are in the right order");
+ if (!(arguments.indexOf("/d") < arguments.indexOf("/n"))) {
+ throw new InvalidCommandFormatException(WRONG_ORDER_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Splitting the input up");
+ String[] parameters = parser.parseArguments(super.arguments);
+
+ logger.log(Level.INFO, "Checking if there is enough arguments");
+ if (parameters.length != 4) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "Checking if /d and /n are in the right order");
+ if (!(parameters[0].trim().equals("/d") && parameters[2].trim().equals("/n"))) {
+ throw new InvalidCommandFormatException(INVALID_ARGUMENTS_MESSAGE);
+ }
+
+ String deck = parameters[1].trim();
+ String input = parameters[3].trim();
+
+ logger.log(Level.INFO, "Checking if any field is empty");
+ if (deck.isEmpty() || input.isEmpty()) {
+ throw new FieldEmptyException(FIELD_EMPTY_ERROR_MESSAGE);
+ }
+ if (input.equalsIgnoreCase("/d") || input.equalsIgnoreCase("/n")) {
+ throw new CardLiException(FLAG_ARGUMENT_ERROR_MESSAGE);
+ }
+
+ logger.log(Level.INFO, "preparing deckIndex");
+ deck = prepareDeckIndex(deck, deckManager);
+
+ String[] preparedArguments = {deck, input};
+ return preparedArguments;
+ }
+
+ @Override
+ public CommandResult execute() { //edit /d /n
+ CommandResult result;
+ try {
+ String[] parameters = prepareEditDeckCommand();
+ result = new CommandResult(this.deckManager.editDeck(parameters));
+ } catch (CardLiException e) {
+ result = new CommandResult(e.getMessage());
+ } catch (NumberFormatException e) {
+ result = new CommandResult(LARGE_INTEGER_ERROR_MESSAGE);
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/EnterDeckCommand.java b/src/main/java/seedu/cardli/commands/system/EnterDeckCommand.java
new file mode 100644
index 0000000000..603446af7d
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/EnterDeckCommand.java
@@ -0,0 +1,66 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.InnerParser;
+import seedu.cardli.parser.Parser;
+import seedu.cardli.parser.system.EnterDeckParser;
+
+public class EnterDeckCommand extends Command {
+
+ private EnterDeckParser parser;
+ private DeckManager deckManager;
+ private InnerParser innerParser;
+
+
+ public EnterDeckCommand(String arguments, DeckManager deckManager, InnerParser innerParser) {
+ super("EnterDeckCommand", arguments);
+ this.parser = new EnterDeckParser();
+ this.deckManager = deckManager;
+ this.innerParser = innerParser;
+ }
+
+ //@@author JWweiyin
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ try {
+ String[] parameters = parser.parseArguments(super.arguments);
+ String enterInput = parameters[0];
+
+ if (enterInput.isEmpty()) {
+ throw new CardLiException("Invalid input. Please input deck index after \"enter\".");
+ }
+
+ if (enterInput.contains("-")) {
+ throw new DeckNotExistException("Deck index must be a positive integer smaller than 2147483647.");
+ }
+
+ if (!Parser.isInteger(enterInput)) {
+ throw new NumberFormatException("That is not a number.");
+ }
+
+ int deckIndex = Integer.parseInt(enterInput) - 1;
+
+ if (deckIndex >= deckManager.getDecks().size()) {
+ throw new DeckNotExistException("That deck doesn't exist. Please input a valid deck index.");
+ }
+
+ Deck currDeck = deckManager.getDeck(deckIndex);
+ this.innerParser.setCurrDeck(currDeck);
+ this.innerParser.setDeckManager(deckManager);
+ result = new CommandResult("You are now in deck " + enterInput
+ + ". Type \"help\" for more commands.", false, true);
+ } catch (CardLiException e) {
+ result = new CommandResult(e.getMessage());
+ } catch (NumberFormatException e) {
+ result = new CommandResult("Deck index must be a positive integer smaller than 2147483647.");
+ }
+
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/ExitProgrammeCommand.java b/src/main/java/seedu/cardli/commands/system/ExitProgrammeCommand.java
new file mode 100644
index 0000000000..0821d7c252
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/ExitProgrammeCommand.java
@@ -0,0 +1,19 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+
+public class ExitProgrammeCommand extends Command {
+ public ExitProgrammeCommand(String arguments) {
+ super("ExitProgrammeCommand", arguments);
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ CommandResult result = new CommandResult("Exiting", true, false);
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/FindCardsCommand.java b/src/main/java/seedu/cardli/commands/system/FindCardsCommand.java
new file mode 100644
index 0000000000..788d62751c
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/FindCardsCommand.java
@@ -0,0 +1,38 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.system.FindCardsParser;
+
+public class FindCardsCommand extends Command {
+
+ public static final String NO_SEARCH_TERM_ERROR = "You did not input a search term after \"find\".";
+ private FindCardsParser parser;
+ private DeckManager deckManager;
+
+
+ public FindCardsCommand(String arguments, DeckManager deckManager) {
+ super("FindCardsCommand", arguments);
+ this.deckManager = deckManager;
+ this.parser = new FindCardsParser();
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ try {
+ String[] parameters = parser.parseArguments(super.arguments);
+ String findInput = parameters[0];
+ if (findInput.isEmpty()) {
+ throw new FieldEmptyException(NO_SEARCH_TERM_ERROR);
+ }
+ result = new CommandResult(deckManager.findCards(findInput));
+ } catch (FieldEmptyException e) {
+ result = new CommandResult(e.getMessage());
+ }
+ return result;
+ }
+
+}
diff --git a/src/main/java/seedu/cardli/commands/system/HelpCommand.java b/src/main/java/seedu/cardli/commands/system/HelpCommand.java
new file mode 100644
index 0000000000..f9d342146c
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/HelpCommand.java
@@ -0,0 +1,22 @@
+//@@author astralum
+
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.ui.CardLiUi;
+
+public class HelpCommand extends Command {
+ public HelpCommand(String arguments) {
+ super("HelpCommand", arguments);
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ CommandResult result = new CommandResult(CardLiUi.returnHelpMessage());
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/commands/system/ReviewCommand.java b/src/main/java/seedu/cardli/commands/system/ReviewCommand.java
new file mode 100644
index 0000000000..fdf0dc6cea
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/ReviewCommand.java
@@ -0,0 +1,25 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.testing.TestManager;
+
+public class ReviewCommand extends Command {
+
+ private final TestManager testManager;
+
+ public ReviewCommand(String arguments, TestManager testManager) {
+ super("ReviewCommand", arguments);
+ this.testManager = testManager;
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ result = new CommandResult(testManager.startReview(), false, false);
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/TestCommand.java b/src/main/java/seedu/cardli/commands/system/TestCommand.java
new file mode 100644
index 0000000000..23382164ca
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/TestCommand.java
@@ -0,0 +1,25 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.testing.TestManager;
+
+public class TestCommand extends Command {
+
+ private final TestManager testManager;
+
+ public TestCommand(String arguments, TestManager testManager) {
+ super("TestCommand", arguments);
+ this.testManager = testManager;
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ result = new CommandResult(testManager.startTest(), false, false);
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/ViewDecksCommand.java b/src/main/java/seedu/cardli/commands/system/ViewDecksCommand.java
new file mode 100644
index 0000000000..f9c9084af3
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/ViewDecksCommand.java
@@ -0,0 +1,24 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+
+public class ViewDecksCommand extends Command {
+
+ private DeckManager deckManager;
+
+ public ViewDecksCommand(DeckManager deckManager, String arguments) {
+ super("ViewDecksCommand", arguments);
+ this.deckManager = deckManager;
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ CommandResult result = new CommandResult(this.deckManager.viewDecks());
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/ViewFlashCardStatsCommand.java b/src/main/java/seedu/cardli/commands/system/ViewFlashCardStatsCommand.java
new file mode 100644
index 0000000000..5a698e9e4a
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/ViewFlashCardStatsCommand.java
@@ -0,0 +1,23 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.testing.TestHistory;
+
+public class ViewFlashCardStatsCommand extends Command {
+
+ private final TestHistory testHistory;
+
+ public ViewFlashCardStatsCommand(String arguments, TestHistory testHistory) {
+ super("ViewFlashCardStatsCommand", arguments);
+ this.testHistory = testHistory;
+ }
+
+ @Override
+ public CommandResult execute() {
+ if (arguments.length() > 0) {
+ return new CommandResult("There should not be any arguments.");
+ }
+ return new CommandResult(testHistory.viewAllFlashcardStats());
+ }
+}
diff --git a/src/main/java/seedu/cardli/commands/system/ViewTestCommand.java b/src/main/java/seedu/cardli/commands/system/ViewTestCommand.java
new file mode 100644
index 0000000000..67ef8d17b0
--- /dev/null
+++ b/src/main/java/seedu/cardli/commands/system/ViewTestCommand.java
@@ -0,0 +1,33 @@
+package seedu.cardli.commands.system;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.parser.TestParser;
+import seedu.cardli.testing.TestHistory;
+
+public class ViewTestCommand extends Command {
+
+ private final TestHistory testHistory;
+
+ public ViewTestCommand(String arguments, TestHistory testHistory) {
+ super("ViewTestCommand", arguments);
+ this.testHistory = testHistory;
+ }
+
+ @Override
+ public CommandResult execute() {
+ CommandResult result;
+ try {
+ int index = TestParser.toInt(super.arguments.trim());
+ result = new CommandResult(testHistory.prepareViewTest(index));
+ } catch (NumberFormatException e) {
+ result = new CommandResult("Input a positive integer or \"all\" after viewtest.");
+ } catch (IndexOutOfBoundsException e) {
+ result = new CommandResult("There is no test at that index.");
+ } catch (DeckNotExistException e) {
+ result = new CommandResult("You have not taken any tests.");
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/seedu/cardli/exceptions/CardLiException.java b/src/main/java/seedu/cardli/exceptions/CardLiException.java
new file mode 100644
index 0000000000..34dd9692fd
--- /dev/null
+++ b/src/main/java/seedu/cardli/exceptions/CardLiException.java
@@ -0,0 +1,11 @@
+package seedu.cardli.exceptions;
+
+public class CardLiException extends Exception {
+ public CardLiException() {
+ super();
+ }
+
+ public CardLiException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/cardli/exceptions/DeckNotExistException.java b/src/main/java/seedu/cardli/exceptions/DeckNotExistException.java
new file mode 100644
index 0000000000..420ec8ae1d
--- /dev/null
+++ b/src/main/java/seedu/cardli/exceptions/DeckNotExistException.java
@@ -0,0 +1,11 @@
+package seedu.cardli.exceptions;
+
+public class DeckNotExistException extends CardLiException {
+ public DeckNotExistException() {
+ super();
+ }
+
+ public DeckNotExistException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/cardli/exceptions/EmptyDeckException.java b/src/main/java/seedu/cardli/exceptions/EmptyDeckException.java
new file mode 100644
index 0000000000..88492c3570
--- /dev/null
+++ b/src/main/java/seedu/cardli/exceptions/EmptyDeckException.java
@@ -0,0 +1,11 @@
+package seedu.cardli.exceptions;
+
+public class EmptyDeckException extends CardLiException {
+ public EmptyDeckException() {
+ super();
+ }
+
+ public EmptyDeckException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/cardli/exceptions/FieldEmptyException.java b/src/main/java/seedu/cardli/exceptions/FieldEmptyException.java
new file mode 100644
index 0000000000..bf8bf06584
--- /dev/null
+++ b/src/main/java/seedu/cardli/exceptions/FieldEmptyException.java
@@ -0,0 +1,14 @@
+package seedu.cardli.exceptions;
+
+/**
+ * Represents exception thrown when a field that is expected to be filled is empty.
+ */
+public class FieldEmptyException extends CardLiException {
+ public FieldEmptyException() {
+ super();
+ }
+
+ public FieldEmptyException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/cardli/exceptions/InvalidCommandFormatException.java b/src/main/java/seedu/cardli/exceptions/InvalidCommandFormatException.java
new file mode 100644
index 0000000000..47972fd6b4
--- /dev/null
+++ b/src/main/java/seedu/cardli/exceptions/InvalidCommandFormatException.java
@@ -0,0 +1,14 @@
+package seedu.cardli.exceptions;
+
+/**
+ * Represents exception thrown when a field that is expected to be filled is empty.
+ */
+public class InvalidCommandFormatException extends CardLiException {
+ public InvalidCommandFormatException() {
+ super();
+ }
+
+ public InvalidCommandFormatException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/cardli/exceptions/NoSlashException.java b/src/main/java/seedu/cardli/exceptions/NoSlashException.java
new file mode 100644
index 0000000000..a6c5ac75e9
--- /dev/null
+++ b/src/main/java/seedu/cardli/exceptions/NoSlashException.java
@@ -0,0 +1,14 @@
+package seedu.cardli.exceptions;
+
+/**
+ * Represents exception thrown when user input does not contain '/' character when invoking certain commands.
+ */
+public class NoSlashException extends CardLiException {
+ public NoSlashException() {
+ super();
+ }
+
+ public NoSlashException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/seedu/cardli/flashcard/Deck.java b/src/main/java/seedu/cardli/flashcard/Deck.java
new file mode 100644
index 0000000000..62dc974b06
--- /dev/null
+++ b/src/main/java/seedu/cardli/flashcard/Deck.java
@@ -0,0 +1,275 @@
+package seedu.cardli.flashcard;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.exceptions.NoSlashException;
+import seedu.cardli.parser.Parser;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
+
+/**
+ * Implements the class Deck, which contains all the FlashCards and
+ * methods to operate on them, such as add, delete, edit, view, etc.
+ */
+public class Deck {
+
+ private static final String CARD_DOES_NOT_EXIST_ERROR_MESSAGE =
+ "\tThe card you are trying to delete does not exist.";
+
+ private final ArrayList cards = new ArrayList();
+ private String name;
+ private static final Logger logger = Logger.getLogger(Deck.class.getName());
+
+ public Deck(String name) {
+ this.name = name;
+ }
+
+ public Deck() {
+ this.name = "Untitled";
+ }
+
+ public boolean hasSameName(String input) {
+ return name.trim().equals(input.trim());
+ }
+
+ //@@author ThaddeusLim99
+ /**
+ * Returns the details of the edit that took place as a message.
+ *
+ * @param parameters string array containing the card index, side and input.
+ * @return the details of the transfer that took place as a message.
+ */
+ public String editCard(String[] parameters) {
+ String enteredCardIndex = parameters[0];
+ int cardIndex = Integer.parseInt(enteredCardIndex) - 1;
+ String side = parameters[1];
+ boolean isFront = side.equalsIgnoreCase("front");
+ String changeTo = parameters[2];
+
+ if (isFront) {
+ cards.get(cardIndex).setFront(changeTo);
+ } else {
+ cards.get(cardIndex).setBack(changeTo);
+ }
+ return ("Changed " + side + " of card " + enteredCardIndex + " to " + changeTo);
+ }
+
+ //@@author JWweiyin
+ private String returnNewFlashCard(String front, String back) {
+ String result = "\tAdded card:" + System.lineSeparator()
+ + returnCardInfo(front, back);
+ if (getDeckSize() == 1) {
+ result = result.concat("\tYou have " + getDeckSize()
+ + " card in your card deck." + System.lineSeparator());
+ } else {
+ result = result.concat("\tYou have " + getDeckSize()
+ + " cards in your card deck." + System.lineSeparator());
+
+ }
+ return result;
+ }
+
+ private String returnCardInfo(String front, String back) {
+ return "\t\tFront: " + front + System.lineSeparator()
+ + "\t\tBack: " + back + System.lineSeparator();
+ }
+
+ private String returnDeletedFlashCardMessage(String front, String back) {
+ String result = "\tDeleted card:" + System.lineSeparator();
+ result = result.concat(returnCardInfo(front, back));
+ return result;
+ }
+
+ /**
+ * Adds a new flashcard and returns the message printed after the addition of the flashcard.
+ *
+ * @param input An array containing the content for front of the flashcard and the back of the flashcard
+ * @return Message printed after adding of flashcard
+ */
+ public String prepareToAddFlashCard(String[] input) {
+ //String[] flashCardWords = trimStrings(input);
+ addFlashCard(input[0], input[1]);
+ return returnNewFlashCard(input[0], input[1]);
+ }
+
+ //@@author xkisxk
+ /**
+ * Deletes the flashcard with the given input.
+ *
+ * @param input description of the card to delete
+ * @throws CardLiException if card does not exist
+ */
+ public String deleteFlashCard(String input) throws CardLiException {
+ logger.setLevel(Level.WARNING);
+ if (cards.isEmpty()) {
+ throw new CardLiException();
+ }
+ assert getDeckSize() > 0 : "cards.size() should be greater than 0";
+ logger.log(Level.INFO, "Detecting the type of input, ie word/phrase or index");
+ if (Parser.isInteger(input)) {
+ return deleteFlashCardByIndex(input);
+ } else {
+ throw new CardLiException("Please enter a positive number.");
+ }
+ }
+
+ //@@author JWweiyin
+ /**
+ * Deletes the flashcard with the given index.
+ *
+ * @param index user's input (index of the card to be deleted)
+ * @throws CardLiException if the index of the card exceeds the number of flashcards in cards
+ * or index of card is less than 1
+ */
+ public String deleteFlashCardByIndex(String index) throws CardLiException {
+ logger.setLevel(Level.WARNING);
+ int indexToBeRemoved = Integer.parseInt(index) - 1;
+ if (!((indexToBeRemoved < getDeckSize()) && (indexToBeRemoved >= 0))) {
+ throw new CardLiException("Please enter a valid deck index.");
+ }
+ assert getDeckSize() > 0 : "cards.size() should be greater than 0";
+ logger.log(Level.INFO, "Detecting the type of input, ie word/phrase or index");
+
+ FlashCard card = cards.get(indexToBeRemoved);
+ cards.remove(card);
+ return returnDeletedFlashCardMessage(card.getFront(), card.getBack());
+ }
+
+ /**
+ * Checks if there are flashcards within the deck with fronts that match the query term exactly.
+ *
+ * @param query String to be checked.
+ * @return true if there is a card with front that matches the query exactly, false otherwise
+ */
+ public boolean hasCardWithSameName(String query) {
+ for (FlashCard f : cards) {
+ if (f.getFront().equals(query.trim())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void addFlashCard(String front, String back) {
+ cards.add(new FlashCard(front, back));
+ }
+
+ public void addFlashCard(FlashCard card) {
+ cards.add(card);
+ }
+
+ public void addFlashCard(String front, String back, int userScore, int totalScore) {
+ cards.add(new FlashCard(front, back, userScore, totalScore));
+ }
+
+ //@@author astralum
+ public String returnAllFlashCards() { // TODO: throw exception if no cards
+ String result = "";
+ if (getDeckSize() > 0) {
+ for (int i = 0; i < getDeckSize(); i++) {
+ result = result.concat("Card " + (i + 1) + ":" + System.lineSeparator());
+ FlashCard card = cards.get(i);
+ result = result.concat(card.returnFlashCard());
+ }
+ } else {
+ result = "This deck has no cards." + System.lineSeparator();
+ }
+ return result;
+ }
+
+ public void viewAllFlashCards() {
+ String result = returnAllFlashCards();
+ System.out.println(result);
+ }
+
+ //@@author JWweiyin
+ /**
+ * Returns all matching flashcards which fronts or backs match the search terms. Not case sensitive.
+ * @param searchInput The search terms input by the user
+ * @return All matching flashcards, returns an empty string if there are no matching flashcards
+ */
+ public String returnMatchingFlashCards(String searchInput) {
+ String result = "";
+ ArrayList matchingCards = (ArrayList) cards.stream()
+ .filter((f) -> f.getFront().toLowerCase().contains(searchInput.toLowerCase())
+ || f.getBack().toLowerCase().contains(searchInput.toLowerCase()))
+ .collect(Collectors.toList());
+ if (matchingCards.size() > 0) {
+ result = result.concat("Cards in " + getName() + " that contain the term " + searchInput + ":"
+ + System.lineSeparator());
+ for (int i = 0; i < matchingCards.size(); i += 1) {
+ result = result.concat("Card " + (i + 1) + ":" + System.lineSeparator());
+ FlashCard card = matchingCards.get(i);
+ result = result.concat(card.returnFlashCard());
+ }
+ result = result.concat(System.lineSeparator());
+ }
+ return result;
+ }
+
+ //@@author xRossKoh
+ /**
+ * Converts a Deck instance into a JSONObject instance.
+ *
+ * @return Deck instance as a JSONObject instance
+ */
+ public JSONObject toJsonObject() {
+ JSONObject jsonDeck = new JSONObject();
+
+ int cardsCount = getDeckSize();
+ JSONArray jsonCards = new JSONArray();
+
+ for (int i = 0; i < cardsCount; i++) {
+ jsonCards.add(cards.get(i).toJsonObject());
+ }
+ jsonDeck.put("deckName", getName());
+ jsonDeck.put("cards", jsonCards);
+ return jsonDeck;
+ }
+
+ public ArrayList getCards() {
+ return cards;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public FlashCard getCard(int index) {
+ assert getDeckSize() > 0;
+ assert (index >= 0 && index < getDeckSize());
+ return cards.get(index);
+ }
+
+ public int getDeckSize() {
+ return cards.size();
+ }
+
+ public int getCardIndex(FlashCard card) {
+ return cards.indexOf(card);
+ }
+
+ public void setDeckName(String input) {
+ this.name = input;
+ }
+
+ @Override
+ public String toString() {
+ String cardsString = "";
+ int cardsCount = getDeckSize();
+
+ for (int i = 0; i < cardsCount; i++) {
+ cardsString += cards.get(i);
+ }
+
+ return getName() + '\n'
+ + cardsCount + '\n'
+ + cardsString;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/flashcard/DeckManager.java b/src/main/java/seedu/cardli/flashcard/DeckManager.java
new file mode 100644
index 0000000000..1288c53f6a
--- /dev/null
+++ b/src/main/java/seedu/cardli/flashcard/DeckManager.java
@@ -0,0 +1,263 @@
+package seedu.cardli.flashcard;
+
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.DeckNotExistException;
+
+import static seedu.cardli.ui.TestUi.DECK_NOT_EXIST_MESSAGE;
+
+
+import java.util.ArrayList;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements the class DeckManager, which contains all Decks and
+ * methods to operate on them, such as add, delete, edit, view, etc.
+ */
+public class DeckManager {
+ public static final String DECK_ALREADY_EXISTS_MESSAGE = "The deck you are trying to create already exists.";
+ private final ArrayList decks;
+ private static final Logger logger = Logger.getLogger(Deck.class.getName());
+
+ public DeckManager() {
+ this.decks = new ArrayList<>();
+ }
+
+ public DeckManager(ArrayList decks) {
+ this.decks = decks;
+ }
+
+ /**
+ * Returns the details of the transfer that took place as a message.
+ *
+ * @param parameters string array containing the destination deck index, current deck index, card index.
+ * @return the details of the transfer that took place as a message.
+ * @throws CardLiException If card does not exist.
+ */
+ public String moveCard(String[] parameters) throws CardLiException {
+ String enteredCurrentDeckIndex = parameters[0];
+ int currentDeckIndex = Integer.parseInt(enteredCurrentDeckIndex);
+ String enteredCardIndex = parameters[1];
+ int cardIndex = Integer.parseInt(enteredCardIndex) - 1;
+ String enteredDeckIndex = parameters[2];
+ int deckIndex = Integer.parseInt(enteredDeckIndex) - 1;
+
+ //get card from current deck
+ FlashCard cardCopy = decks.get(currentDeckIndex).getCard(cardIndex);
+ //add card to destination deck
+ decks.get(deckIndex).addFlashCard(cardCopy);
+ //delete card from current deck
+ decks.get(currentDeckIndex).deleteFlashCard(enteredCardIndex);
+
+ return ("Moved card " + enteredCardIndex + " to " + "deck " + enteredDeckIndex);
+ }
+
+ /**
+ * Returns the details of the edit that took place as a message.
+ *
+ * @param args string array containing the deck index and name.
+ * @return the details of the transfer that took place as a message.
+ */
+ public String editDeck(String[] args) {
+ String enteredDeckIndex = args[0];
+ int deckIndex = Integer.parseInt(enteredDeckIndex) - 1;
+ String deckName = args[1];
+ decks.get(deckIndex).setDeckName(deckName);
+ return ("Changed deck " + enteredDeckIndex + " to " + deckName);
+ }
+
+ public String prepareToAddDeck(String deckName) {
+ if (!hasDeck(deckName)) {
+ addDeck(deckName);
+ return printNewDeck(deckName);
+ } else {
+ return DECK_ALREADY_EXISTS_MESSAGE;
+ }
+ }
+
+ private String printNewDeck(String deckName) {
+ return ("You have just made the deck <<" + deckName + ">>.");
+ }
+
+ private boolean hasDeck(String categoryName) {
+ for (Deck deck : decks) {
+ if (deck.hasSameName(categoryName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean hasDeck(int deckIndex) {
+ return deckIndex >= 0 && deckIndex < getDecksSize();
+ }
+
+ private void addDeck(String deckName) {
+ decks.add(new Deck(deckName));
+ }
+
+ public String deleteDeck(Deck deck) throws DeckNotExistException {
+ String message = returnDeletedDeckMessage(deck);
+ boolean isRemoved = decks.remove(deck);
+ if (!isRemoved) {
+ throw new DeckNotExistException(DECK_NOT_EXIST_MESSAGE);
+ }
+ return message;
+ }
+
+ public String deleteDeck(int deckIndex) {
+ String message = returnDeletedDeckMessage(decks.get(deckIndex));
+ decks.remove(deckIndex);
+ return message;
+ }
+
+ private String returnDeletedDeckMessage(Deck deck) {
+ String result = "\tDeleted deck: ";
+ result = result.concat(deck.getName());
+ return result;
+ }
+
+ public String findCards(String searchInput) {
+ String result = "";
+ if (decks.size() > 0) {
+ for (int i = 0; i < decks.size(); i += 1) {
+ result = result.concat(getDeck(i).returnMatchingFlashCards(searchInput));
+ }
+ } else {
+ result = "There are no decks.";
+ }
+
+ if (result.isEmpty()) {
+ result = "There are no cards matching the search term.";
+ }
+ return result;
+ }
+
+ public String cardHasSameName(String query) {
+ String deckWithSameNameCard = "";
+ for (Deck d : decks) {
+ if (d.hasCardWithSameName(query)) {
+ deckWithSameNameCard = d.getName();
+ }
+ }
+ return deckWithSameNameCard;
+ }
+
+ public String viewDecks() {
+ String result = "";
+ if (getDecksSize() > 0) {
+ int i = 1;
+ result = result.concat("These are your decks: " + System.lineSeparator());
+ for (Deck deck : decks) {
+ result = result.concat("\t" + i + ". " + deck.getName()
+ + System.lineSeparator());
+ i += 1;
+ }
+ } else {
+ result = result.concat("You have no decks.");
+ }
+ return result;
+ }
+
+ /**
+ * Gets all the low scoring cards and put them into a deck.
+ *
+ * @return deck of low scoring cards
+ */
+ private Deck getLowScoringCardsFromAllDecks() {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "Collecting low scoring cards");
+ Deck reviewDeck = new Deck("Review");
+ for (Deck deck : getDecks()) {
+ for (FlashCard card : deck.getCards()) {
+ if (isLowScoring(card)) {
+ reviewDeck.addFlashCard(card);
+ logger.log(Level.INFO, "Added a low scoring card");
+ }
+ }
+ }
+ return reviewDeck;
+ }
+
+ /**
+ * Gets all the low scoring cards from a deck and put them into a deck.
+ *
+ * @return deck of low scoring cards
+ */
+ private Deck getLowScoringCardsFromADeck(Deck deck) {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "Collecting low scoring cards");
+ Deck reviewDeck = new Deck("Review");
+ for (FlashCard card : deck.getCards()) {
+ if (isLowScoring(card)) {
+ reviewDeck.addFlashCard(card);
+ logger.log(Level.INFO, "Added a low scoring card");
+ }
+ }
+ return reviewDeck;
+ }
+
+ /**
+ * Gets all the low scoring cards and put them into a deck.
+ * If index is -1, get low scaring cards from all decks.
+ * Else get low scoring cards from the deck from that index.
+ * The cards that are put into the deck are the same cards objects, in other
+ * words they are not new FlashCard objects.
+ *
+ * @return deck of low scoring cards
+ */
+ public Deck getLowScoringCards(int index) {
+ if (index == -1) {
+ return getLowScoringCardsFromAllDecks();
+ }
+ if (hasDeck(index)) {
+ return getLowScoringCardsFromADeck(getDeck(index));
+ }
+ throw new IndexOutOfBoundsException(DECK_NOT_EXIST_MESSAGE);
+ }
+
+ /**
+ * A card is low scoring if its accumulated user score is less than 50% of
+ * the total score.
+ *
+ * @return true if card is low scoring, false otherwise
+ */
+ private boolean isLowScoring(FlashCard card) {
+ return (double) card.getUserScore() * 100 / card.getTotalScore() < 50;
+ }
+
+ public ArrayList getDecks() {
+ return decks;
+ }
+
+ public int getDeckIndex(Deck deck) {
+ return decks.indexOf(deck);
+ }
+
+ public Deck getDeck(int index) {
+ assert getDecksSize() > 0;
+ assert (index >= 0 && index < getDecksSize());
+ return decks.get(index);
+ }
+
+ public Deck getTestDeck(int index) {
+ if (index == -1) {
+ Deck deckToTest = new Deck("Test");
+ for (Deck deck : getDecks()) {
+ for (FlashCard card : deck.getCards()) {
+ deckToTest.addFlashCard(card);
+ }
+ }
+ return deckToTest;
+ }
+ if (hasDeck(index)) {
+ return decks.get(index);
+ }
+ throw new IndexOutOfBoundsException(DECK_NOT_EXIST_MESSAGE);
+ }
+
+ public int getDecksSize() {
+ return decks.size();
+ }
+}
diff --git a/src/main/java/seedu/cardli/flashcard/FlashCard.java b/src/main/java/seedu/cardli/flashcard/FlashCard.java
new file mode 100644
index 0000000000..5079d81d93
--- /dev/null
+++ b/src/main/java/seedu/cardli/flashcard/FlashCard.java
@@ -0,0 +1,310 @@
+package seedu.cardli.flashcard;
+
+
+import org.json.simple.JSONObject;
+
+
+import java.util.ArrayList;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Represents information within a single flashcard.
+ */
+public class FlashCard {
+
+ private static final String FLASHCARD_TOP_FRONT = "*================FRONT================*";
+ private static final String FLASHCARD_TOP_BACK = "*================BACK=================*";
+ private static final String FLASHCARD_BOTTOM = "*=====================================*";
+ private static final String FLASHCARD_WHITESPACE = " ";
+ private static final String FLASHCARD_TOP_LINE = FLASHCARD_TOP_FRONT
+ + FLASHCARD_WHITESPACE + FLASHCARD_TOP_BACK;
+ private static final String FLASHCARD_BOTTOM_LINE = FLASHCARD_BOTTOM
+ + FLASHCARD_WHITESPACE + FLASHCARD_BOTTOM;
+ private static final int FLASHCARD_MAX_LINE_LENGTH = 37;
+ static final String SEPARATOR = " | ";
+
+ private static final Logger logger = Logger.getLogger("Card");
+ private String front;
+ private String back;
+ private int userScore;
+ private int totalScore;
+
+ public FlashCard(String front, String back) {
+ logger.setLevel(Level.WARNING);
+ assert front != null;
+ assert back != null;
+ this.front = front;
+ this.back = back;
+ this.userScore = 0;
+ this.totalScore = 0;
+ logger.log(Level.INFO, "Card front: " + front);
+ logger.log(Level.INFO, "Card back: " + back);
+ }
+
+ public FlashCard(String front, String back, int userScore, int totalScore) {
+ logger.setLevel(Level.WARNING);
+ assert front != null;
+ assert back != null;
+ this.front = front;
+ this.back = back;
+ this.userScore = userScore;
+ this.totalScore = totalScore;
+ logger.log(Level.INFO, "Card front: " + front);
+ logger.log(Level.INFO, "Card back: " + back);
+ }
+
+ /**
+ * Splits the given word/phrase into a String array so that it
+ * can fit nicely into the flashcard to be printed to the
+ * standard output.
+ *
+ * @param word The word/phrase to be split
+ * @return A String array containing the split input
+ */
+ private String[] splitFlashcardWord(String word) {
+ ArrayList splitWords = new ArrayList<>();
+
+ int index = 0;
+ while (index < word.length()) {
+ String currentLine;
+ if (index + FLASHCARD_MAX_LINE_LENGTH - 1 >= word.length()) {
+ // probably the characters of the word
+ currentLine = word.substring(index);
+ index += FLASHCARD_MAX_LINE_LENGTH;
+ } else {
+ assert index + FLASHCARD_MAX_LINE_LENGTH - 1 < word.length();
+ char terminalChar = word.charAt(index + FLASHCARD_MAX_LINE_LENGTH - 1);
+ if (terminalChar == ' ' || terminalChar == '-') {
+ currentLine = word.substring(index, index + FLASHCARD_MAX_LINE_LENGTH);
+ index += FLASHCARD_MAX_LINE_LENGTH;
+ } else {
+ currentLine = word
+ .substring(index, index + FLASHCARD_MAX_LINE_LENGTH - 1)
+ .concat("-");
+ index += FLASHCARD_MAX_LINE_LENGTH - 1;
+ }
+ assert currentLine.length() == 37;
+ }
+ splitWords.add(currentLine);
+ }
+
+ String[] splitWordsArray = new String[splitWords.size()];
+ splitWordsArray = splitWords.toArray(splitWordsArray);
+ return splitWordsArray;
+ }
+
+ private String returnWhitespaceLine() {
+ String result = "";
+ for (int i = 0; i < FLASHCARD_MAX_LINE_LENGTH; i++) {
+ result = result.concat(FLASHCARD_WHITESPACE);
+ }
+ return result;
+ }
+
+ private String padLineWithWhitespace(String line) {
+ // pads line with whitespaces so that it is centred
+ // and has 35 characters
+ String result = "";
+ int numOfExcessChars = FLASHCARD_MAX_LINE_LENGTH - line.length();
+ for (int i = 0; i < numOfExcessChars / 2; i++) {
+ result = result.concat(FLASHCARD_WHITESPACE);
+ }
+ result = result.concat(line);
+ for (int i = 0; i < (numOfExcessChars + 1) / 2; i++) {
+ result = result.concat(FLASHCARD_WHITESPACE);
+ }
+ return result;
+ }
+
+ private String[] padLinesWithEmptyLines(String[] lines, String emptyLine, int numOfLines) {
+ // creates and returns a String[] so that the number of lines
+ // equals to the given numOfLines
+ String[] newLines = new String[numOfLines];
+ int numOfExcessLines = numOfLines - lines.length;
+ for (int i = 0; i < numOfExcessLines / 2; i++) {
+ newLines[i] = emptyLine;
+ }
+ for (int i = 0; i < lines.length; i++) {
+ newLines[numOfExcessLines / 2 + i] = lines[i];
+ }
+ for (int i = 0; i < (numOfExcessLines + 1) / 2; i++) {
+ newLines[numOfExcessLines / 2 + lines.length + i] = emptyLine;
+ }
+ return newLines;
+ }
+
+ private String joinFrontBackLines(String[] frontLines, String[] backLines) {
+ int numOfLines = 0;
+
+ // pad last line of frontLines and backLines with
+ // whitespaces so that it makes up to 35 characters
+ String frontLinesLast = frontLines[frontLines.length - 1];
+ frontLines[frontLines.length - 1] = padLineWithWhitespace(frontLinesLast);
+ String backLinesLast = backLines[backLines.length - 1];
+ backLines[backLines.length - 1] = padLineWithWhitespace(backLinesLast);
+
+ String[] newFrontLines;
+ String[] newBackLines;
+ // check if front and back have the same number of lines
+ if (frontLines.length == backLines.length) {
+ // join the same line of front and back
+ numOfLines = frontLines.length;
+ newFrontLines = frontLines;
+ newBackLines = backLines;
+ } else {
+ String emptyLine = returnWhitespaceLine();
+ if (frontLines.length < backLines.length) {
+ // front has fewer lines than back
+ numOfLines = backLines.length;
+ // pad frontLines with empty lines to match numOfLines
+ newFrontLines = padLinesWithEmptyLines(frontLines, emptyLine, numOfLines);
+ newBackLines = backLines;
+ } else {
+ assert backLines.length < frontLines.length;
+ // back has fewer lines than front
+ numOfLines = frontLines.length;
+ // pad frontLines with empty lines to match numOfLines
+ newBackLines = padLinesWithEmptyLines(backLines, emptyLine, numOfLines);
+ newFrontLines = frontLines;
+ }
+ }
+ String result = "";
+ for (int i = 0; i < numOfLines; i++) {
+ String line = FLASHCARD_WHITESPACE + newFrontLines[i] + FLASHCARD_WHITESPACE
+ + FLASHCARD_WHITESPACE
+ + FLASHCARD_WHITESPACE + newBackLines[i];
+ result = result.concat(line + System.lineSeparator());
+ }
+ return result;
+ }
+
+ private String joinFrontBack(String front, String back) {
+ String[] frontLines = splitFlashcardWord(front);
+ String[] backLines = splitFlashcardWord(back);
+ String result = joinFrontBackLines(frontLines, backLines);
+ return result;
+ }
+
+ /**
+ * Formats the flashcard to be printed to the standard output
+ * as a String.
+ *
+ * @return A String containing the formatted flashcard
+ */
+ public String returnFlashCard() {
+ String top = FLASHCARD_TOP_LINE + System.lineSeparator();
+ String middle = joinFrontBack(this.front, this.back);
+ String bottom = FLASHCARD_BOTTOM_LINE + System.lineSeparator();
+ String result = top + middle + bottom;
+ return result;
+ }
+
+ public void incrementUserScore() {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "Incrementing flashcard user score");
+ userScore++;
+ }
+
+ public void incrementTotalScore() {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "Incrementing flashcard total score");
+ totalScore++;
+ }
+
+ //@@author xRossKoh
+ /**
+ * Converts a FlashCard instance into a JSONObject instance.
+ *
+ * @return FlashCard instance as a JSONObject instance.
+ */
+ public JSONObject toJsonObject() {
+ JSONObject jsonCard = new JSONObject();
+
+ jsonCard.put("front", getFront());
+ jsonCard.put("back", getBack());
+ jsonCard.put("userScore", getUserScore());
+ jsonCard.put("totalScore", getTotalScore());
+
+ return jsonCard;
+ }
+
+ //@@author astralum
+ /**
+ * Getter for String on front of flashcard.
+ *
+ * @return String on front of flashcard
+ */
+ public String getFront() {
+ logger.setLevel(Level.WARNING);
+ assert this.front != null;
+ logger.log(Level.INFO, "Getting card front: " + front);
+ return this.front;
+ }
+
+ /**
+ * Getter for String on back of flashcard.
+ *
+ * @return String on back of flashcard
+ */
+ public String getBack() {
+ logger.setLevel(Level.WARNING);
+ assert this.back != null;
+ logger.log(Level.INFO, "Getting card back: " + back);
+ return this.back;
+ }
+
+ /**
+ * Getter for userScore.
+ *
+ * @return An integer representing userScore
+ */
+ public int getUserScore() {
+ return this.userScore;
+ }
+
+ /**
+ * Getter for totalScore.
+ *
+ * @return An integer representing totalScore
+ */
+ public int getTotalScore() {
+ return this.totalScore;
+ }
+
+ /**
+ * Setter for front.
+ *
+ * @param input The input to replace front.
+ */
+ public void setFront(String input) {
+ logger.setLevel(Level.WARNING);
+ assert this.front != null;
+ logger.log(Level.INFO, "Getting card front: " + front);
+ this.front = input;
+ }
+
+ /**
+ * Setter for back.
+ *
+ * @param input The input to replace back.
+ */
+ public void setBack(String input) {
+ logger.setLevel(Level.WARNING);
+ assert this.back != null;
+ logger.log(Level.INFO, "Getting card back: " + back);
+ this.back = input;
+ }
+
+ //@@author xRossKoh
+ @Override
+ public String toString() {
+ return getFront() + SEPARATOR
+ + getBack() + SEPARATOR
+ + getUserScore() + SEPARATOR
+ + getTotalScore() + '\n';
+ }
+
+
+}
diff --git a/src/main/java/seedu/cardli/parser/CommandArgumentParser.java b/src/main/java/seedu/cardli/parser/CommandArgumentParser.java
new file mode 100644
index 0000000000..9c21d2d6ca
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/CommandArgumentParser.java
@@ -0,0 +1,14 @@
+package seedu.cardli.parser;
+
+/**
+ * Interface to parse arguments for commands.
+ */
+public interface CommandArgumentParser {
+
+ /**
+ * Parses the given String arguments into a String[].
+ * @param arguments the given String arguments to be parsed
+ * @return the split parameters to be used in the command
+ */
+ String[] parseArguments(String arguments);
+}
diff --git a/src/main/java/seedu/cardli/parser/InnerParser.java b/src/main/java/seedu/cardli/parser/InnerParser.java
new file mode 100644
index 0000000000..5fc0b78a5d
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/InnerParser.java
@@ -0,0 +1,116 @@
+package seedu.cardli.parser;
+
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.InvalidCommand;
+import seedu.cardli.commands.deck.AddCardCommand;
+import seedu.cardli.commands.deck.DeleteCardCommand;
+import seedu.cardli.commands.deck.EditCardCommand;
+import seedu.cardli.commands.deck.ExitDeckCommand;
+import seedu.cardli.commands.deck.HelpInDeckCommand;
+import seedu.cardli.commands.deck.MoveCardCommand;
+import seedu.cardli.commands.deck.ViewCardsCommand;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements the OuterParser class, which identifies the command the
+ * user has input at the deck level and returns a Command class with the
+ * given arguments to be executed.
+ */
+public class InnerParser {
+
+ private static final Logger logger = Logger.getLogger(InnerParser.class.getName());
+
+ private Deck currDeck;
+ private DeckManager deckManager;
+
+ /**
+ * Constructs a {@code InnerParser} with the specified field.
+ * @param currDeck the current {@code Deck} the {@code InnerParser} should operate in
+ */
+ public InnerParser(Deck currDeck) {
+ this.currDeck = currDeck;
+ }
+
+ /**
+ * Constructs a {@code InnerParser} with an empty currDeck.
+ */
+ public InnerParser() {
+ this.currDeck = null;
+ }
+
+ /**
+ * Parses the user's input, identifies the command and creates a {@code Command}
+ * with the arguments that were input.
+ * @param input the user's raw String input
+ * @return a {@code Command} object
+ */
+ public Command parseCommand(String input) {
+ logger.setLevel(Level.WARNING);
+ String commandType = Parser.getCommandType(input);
+ logger.log(Level.INFO, "new user input detected");
+
+ Command command;
+ String arguments;
+
+ switch (commandType) {
+ case "add":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new AddCardCommand(arguments, this.currDeck, this.deckManager);
+ logger.log(Level.INFO, "add (card) command parsed and executed");
+ break;
+ case "edit":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new EditCardCommand(arguments, this.currDeck, this.deckManager);
+ logger.log(Level.INFO, "edit (card) command parsed and executed");
+ break;
+ case "delete":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new DeleteCardCommand(arguments, this.currDeck);
+ logger.log(Level.INFO, "delete (card) command parsed and executed");
+ break;
+ case "view":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ViewCardsCommand(this.currDeck, arguments);
+ logger.log(Level.INFO, "view command parsed and executed");
+ break;
+ case "move":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new MoveCardCommand(arguments, this.currDeck, this.deckManager);
+ logger.log(Level.INFO, "move command parsed and executed");
+ break;
+ case "help":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new HelpInDeckCommand(arguments);
+ logger.log(Level.INFO, "help command parsed and executed");
+ break;
+ case "exit":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ExitDeckCommand(arguments);
+ logger.log(Level.INFO, "exit command parsed and executed");
+ break;
+ default:
+ command = new InvalidCommand();
+ logger.log(Level.INFO, "command was unrecognised and could not be parsed");
+ }
+ return command;
+ }
+
+ /**
+ * Setter for currDeck.
+ */
+ public void setCurrDeck(Deck currDeck) {
+ this.currDeck = currDeck;
+ }
+
+ /**
+ * Setter for deckManager.
+ */
+ public void setDeckManager(DeckManager deckList) {
+ this.deckManager = deckList;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/OuterParser.java b/src/main/java/seedu/cardli/parser/OuterParser.java
new file mode 100644
index 0000000000..f5b4523dad
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/OuterParser.java
@@ -0,0 +1,134 @@
+package seedu.cardli.parser;
+
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.InvalidCommand;
+
+import seedu.cardli.commands.system.AddDeckCommand;
+import seedu.cardli.commands.system.DeleteDeckCommand;
+import seedu.cardli.commands.system.EditDeckCommand;
+import seedu.cardli.commands.system.EnterDeckCommand;
+import seedu.cardli.commands.system.ExitProgrammeCommand;
+import seedu.cardli.commands.system.FindCardsCommand;
+import seedu.cardli.commands.system.HelpCommand;
+import seedu.cardli.commands.system.ReviewCommand;
+import seedu.cardli.commands.system.TestCommand;
+import seedu.cardli.commands.system.ViewDecksCommand;
+import seedu.cardli.commands.system.ViewFlashCardStatsCommand;
+import seedu.cardli.commands.system.ViewTestCommand;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.testing.TestHistory;
+import seedu.cardli.testing.TestManager;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Implements the OuterParser class, which identifies the command the
+ * user has input in the main menu and returns a Command class with the
+ * given arguments to be executed.
+ */
+public class OuterParser {
+
+ private static final Logger logger = Logger.getLogger(OuterParser.class.getName());
+
+ private DeckManager deckManager;
+ private InnerParser innerParser;
+ private TestHistory testHistory;
+ private TestManager testManager;
+
+ /**
+ * Constructs an {@code OuterParser} with the specified fields.
+ */
+ public OuterParser(DeckManager deckManager, InnerParser innerParser,
+ TestHistory testHistory, TestManager testManager) {
+ logger.setLevel(Level.WARNING);
+ this.deckManager = deckManager;
+ this.innerParser = innerParser;
+ this.testHistory = testHistory;
+ this.testManager = testManager;
+ }
+
+ /**
+ * Parses the user's input, identifies the command and creates a {@code Command}
+ * with the arguments that were input.
+ * @param input the user's raw String input
+ * @return a {@code Command} object
+ */
+ public Command parseCommand(String input) {
+ // create a new Command that has `type` and `arguments`
+ // the command should be of type `AddDeckCommand`, `DeleteDeckCommand`, etc.
+ // anyhow, `Command` cannot be instantiated as it is abstract
+ logger.setLevel(Level.WARNING);
+ String commandType = Parser.getCommandType(input);
+ logger.log(Level.INFO, "new user input detected");
+
+ Command command;
+ String arguments;
+
+ switch (commandType) {
+ case "enter":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new EnterDeckCommand(arguments, this.deckManager, this.innerParser);
+ logger.log(Level.INFO, "enter (deck) command parsed and executed");
+ break;
+ case "view":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ViewDecksCommand(this.deckManager, arguments);
+ logger.log(Level.INFO, "view (all decks) command parsed and executed");
+ break;
+ case "viewfc":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ViewFlashCardStatsCommand(arguments, this.testHistory);
+ break;
+ case "viewtest":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ViewTestCommand(arguments, this.testHistory);
+ break;
+ case "add":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new AddDeckCommand(arguments, this.deckManager);
+ logger.log(Level.INFO, "add (deck) command parsed and executed");
+ break;
+ case "edit":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new EditDeckCommand(arguments, this.deckManager);
+ logger.log(Level.INFO, "edit (deck) command parsed and executed");
+ break;
+ case "delete":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new DeleteDeckCommand(arguments, this.deckManager);
+ logger.log(Level.INFO, "delete (deck) command parsed and executed");
+ break;
+ case "find":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new FindCardsCommand(arguments, this.deckManager);
+ logger.log(Level.INFO, "find (card) command parsed and executed");
+ break;
+ case "test":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new TestCommand(arguments, this.testManager);
+ logger.log(Level.INFO, "test command parsed and executed");
+ break;
+ case "review":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ReviewCommand(arguments, this.testManager);
+ logger.log(Level.INFO, "review command parsed and executed");
+ break;
+ case "help":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new HelpCommand(arguments);
+ logger.log(Level.INFO, "help (deck) command parsed and executed");
+ break;
+ case "bye":
+ arguments = Parser.getCommandArguments(commandType, input);
+ command = new ExitProgrammeCommand(arguments);
+ logger.log(Level.INFO, "current list of decks and flashcards saved to text file");
+ logger.log(Level.INFO, "bye command parsed and executed, program will terminate");
+ break;
+ default:
+ command = new InvalidCommand();
+ logger.log(Level.INFO, "command was unrecognised and could not be parsed");
+ }
+ return command;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/Parser.java b/src/main/java/seedu/cardli/parser/Parser.java
new file mode 100644
index 0000000000..461bc5ba64
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/Parser.java
@@ -0,0 +1,38 @@
+package seedu.cardli.parser;
+
+/**
+ * Deals with the parsing of user input at the command line.
+ */
+public class Parser {
+
+ /**
+ * Returns the command type of the user's input.
+ * @param input user's input
+ */
+ public static String getCommandType(String input) {
+ return input.trim().split(" ")[0].toLowerCase();
+ }
+
+ /**
+ * Returns the String containing the arguments to the command.
+ */
+ public static String getCommandArguments(String commandType, String input) {
+ assert input.length() > 0 : "input string should not be empty, at least have command word";
+ return input.substring(commandType.length()).trim();
+ }
+
+ /**
+ * Checks if the given input is an integer or not.
+ *
+ * @param input input given by user
+ * @return true if input is an integer, false otherwise
+ */
+ public static boolean isInteger(String input) {
+ for (int i = 0; i < input.length(); i += 1) {
+ if (!Character.isDigit(input.charAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/parser/TestParser.java b/src/main/java/seedu/cardli/parser/TestParser.java
new file mode 100644
index 0000000000..12391f8bf8
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/TestParser.java
@@ -0,0 +1,38 @@
+package seedu.cardli.parser;
+
+import seedu.cardli.exceptions.FieldEmptyException;
+
+/**
+ * TestParser checks to make sure the user's input is valid.
+ */
+public class TestParser {
+
+ /**
+ * Make sure the user's input is valid.
+ *
+ * @param userResponse raw user's input
+ * @return trimmed user's response
+ * @throws FieldEmptyException if user's response is empty
+ */
+ public static String parseUserResponse(String userResponse) throws FieldEmptyException {
+ if (userResponse.isEmpty()) {
+ throw new FieldEmptyException();
+ }
+ return userResponse.trim();
+ }
+
+ /**
+ * Make sure that the user's input is a number or "all".
+ *
+ * @param input user's input
+ * @return user's input as integer
+ * @throws NumberFormatException if the input is not a number or "all"
+ */
+ public static int toInt(String input) throws NumberFormatException {
+ if (input.equalsIgnoreCase("all")) {
+ return -1;
+ }
+ return Integer.parseInt(input) - 1;
+ }
+
+}
diff --git a/src/main/java/seedu/cardli/parser/deck/AddCardParser.java b/src/main/java/seedu/cardli/parser/deck/AddCardParser.java
new file mode 100644
index 0000000000..b730307ab4
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/deck/AddCardParser.java
@@ -0,0 +1,24 @@
+package seedu.cardli.parser.deck;
+
+import seedu.cardli.parser.CommandArgumentParser;
+import seedu.cardli.parser.Parser;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AddCardParser implements CommandArgumentParser {
+
+ private Logger logger;
+
+ public AddCardParser() {
+ this.logger = Logger.getLogger(Parser.class.getName());
+ logger.setLevel(Level.WARNING);
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) { //add /f /b
+ String[] parameters = arguments.trim().split("/f", 2);
+ parameters = parameters[1].split("/b", 2);
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/deck/DeleteCardParser.java b/src/main/java/seedu/cardli/parser/deck/DeleteCardParser.java
new file mode 100644
index 0000000000..37189ba607
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/deck/DeleteCardParser.java
@@ -0,0 +1,25 @@
+package seedu.cardli.parser.deck;
+
+import seedu.cardli.parser.CommandArgumentParser;
+import seedu.cardli.parser.Parser;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class DeleteCardParser implements CommandArgumentParser {
+
+ private Logger logger;
+
+ public DeleteCardParser() {
+ this.logger = Logger.getLogger(Parser.class.getName());
+ logger.setLevel(Level.WARNING);
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = new String[1];
+ String deckName = arguments;
+ parameters[0] = deckName;
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/deck/EditCardParser.java b/src/main/java/seedu/cardli/parser/deck/EditCardParser.java
new file mode 100644
index 0000000000..b6cef667a0
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/deck/EditCardParser.java
@@ -0,0 +1,15 @@
+package seedu.cardli.parser.deck;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class EditCardParser implements CommandArgumentParser {
+
+ public EditCardParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = arguments.trim().split(" ", 6);
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/deck/MoveCardParser.java b/src/main/java/seedu/cardli/parser/deck/MoveCardParser.java
new file mode 100644
index 0000000000..d4094dda70
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/deck/MoveCardParser.java
@@ -0,0 +1,15 @@
+package seedu.cardli.parser.deck;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class MoveCardParser implements CommandArgumentParser {
+
+ public MoveCardParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = arguments.trim().split(" ", 4);
+ return parameters;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/seedu/cardli/parser/system/AddDeckParser.java b/src/main/java/seedu/cardli/parser/system/AddDeckParser.java
new file mode 100644
index 0000000000..6bc743cfe8
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/system/AddDeckParser.java
@@ -0,0 +1,17 @@
+package seedu.cardli.parser.system;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class AddDeckParser implements CommandArgumentParser {
+
+ public AddDeckParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = new String[1];
+ String deckName = arguments;
+ parameters[0] = deckName;
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/system/DeleteDeckParser.java b/src/main/java/seedu/cardli/parser/system/DeleteDeckParser.java
new file mode 100644
index 0000000000..fec5266f92
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/system/DeleteDeckParser.java
@@ -0,0 +1,16 @@
+package seedu.cardli.parser.system;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class DeleteDeckParser implements CommandArgumentParser {
+ public DeleteDeckParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = new String[1];
+ String deckName = arguments;
+ parameters[0] = deckName;
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/system/EditDeckParser.java b/src/main/java/seedu/cardli/parser/system/EditDeckParser.java
new file mode 100644
index 0000000000..b0ca02a69a
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/system/EditDeckParser.java
@@ -0,0 +1,15 @@
+package seedu.cardli.parser.system;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class EditDeckParser implements CommandArgumentParser {
+
+ public EditDeckParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = arguments.trim().split(" ", 4);
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/system/EnterDeckParser.java b/src/main/java/seedu/cardli/parser/system/EnterDeckParser.java
new file mode 100644
index 0000000000..1e0f8564b5
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/system/EnterDeckParser.java
@@ -0,0 +1,16 @@
+package seedu.cardli.parser.system;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class EnterDeckParser implements CommandArgumentParser {
+ public EnterDeckParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] parameters = new String[1];
+ String deckName = arguments;
+ parameters[0] = deckName;
+ return parameters;
+ }
+}
diff --git a/src/main/java/seedu/cardli/parser/system/FindCardsParser.java b/src/main/java/seedu/cardli/parser/system/FindCardsParser.java
new file mode 100644
index 0000000000..27d7d8882d
--- /dev/null
+++ b/src/main/java/seedu/cardli/parser/system/FindCardsParser.java
@@ -0,0 +1,16 @@
+package seedu.cardli.parser.system;
+
+import seedu.cardli.parser.CommandArgumentParser;
+
+public class FindCardsParser implements CommandArgumentParser {
+
+ public FindCardsParser() {
+ }
+
+ @Override
+ public String[] parseArguments(String arguments) {
+ String[] searchTerms = new String[1];
+ searchTerms[0] = arguments.trim();
+ return searchTerms;
+ }
+}
diff --git a/src/main/java/seedu/cardli/storage/Storage.java b/src/main/java/seedu/cardli/storage/Storage.java
new file mode 100644
index 0000000000..80c694fd48
--- /dev/null
+++ b/src/main/java/seedu/cardli/storage/Storage.java
@@ -0,0 +1,219 @@
+//@@author xRossKoh
+
+package seedu.cardli.storage;
+
+import org.json.simple.JSONObject;
+import org.json.simple.parser.JSONParser;
+import org.json.simple.parser.ParseException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.FlashCard;
+import seedu.cardli.testing.AnswerList;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.Scanner;
+
+import org.json.simple.JSONArray;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Class containing methods to save and parse user data.
+ */
+public class Storage {
+
+ private static final Logger logger = Logger.getLogger(Storage.class.getName());
+
+ /**
+ * Specified file path to save task list.
+ */
+ private static final String CARDS_FILEPATH = "data/Cards_CardLI.json";
+ private static final String TESTS_FILEPATH = "data/Tests_CardLi.json";
+ File cardsFile;
+ File testsFile;
+
+ public Storage() {
+ logger.setLevel(Level.SEVERE);
+ try {
+ this.cardsFile = new File(CARDS_FILEPATH);
+ this.testsFile = new File(TESTS_FILEPATH);
+
+ // create new directory and file if they do not exist
+ if (!cardsFile.exists()) {
+ cardsFile.getParentFile().mkdirs();
+ cardsFile.createNewFile();
+ logger.log(Level.INFO, "data/Cards_CardLI.json created");
+ }
+ if (!testsFile.exists()) {
+ testsFile.getParentFile().mkdirs();
+ testsFile.createNewFile();
+ logger.log(Level.INFO, "data/Tests_CardLI.json created");
+ }
+ } catch (IOException e) {
+ System.out.println((e.getMessage()));
+ logger.log(Level.WARNING, "IOException thrown at Storage()");
+ }
+ }
+
+ /**
+ * Saves user's current decks of flashcards to JSON file.
+ *
+ * @param decks User's current decks of flashcards
+ */
+ @SuppressWarnings("unchecked") // placed method-level to allow for unit testing
+ public void writeCardsToFile(ArrayList decks) {
+ logger.setLevel(Level.SEVERE);
+ try {
+ // instantiate FileWriter object to overwrite specified text file
+ FileWriter fileWriter = new FileWriter(CARDS_FILEPATH, false);
+
+ JSONArray jsonDecks = new JSONArray();
+
+ for (Deck deck : decks) {
+ jsonDecks.add(deck.toJsonObject());
+ }
+
+ fileWriter.write(jsonDecks.toJSONString());
+ fileWriter.close();
+ logger.log(Level.INFO, "All decks of flashcards saved to Cards_CardLI.json");
+ } catch (IOException e) {
+ System.out.println("Something went wrong while saving to data/Cards_CardLI.json...");
+ logger.log(Level.WARNING, "IOException thrown at writeCardsToFile()");
+ }
+ }
+
+ /**
+ * Saves user's current test history to JSON file.
+ *
+ * @param testHistory User's current test history
+ */
+ @SuppressWarnings("unchecked") // placed method-level to allow for unit testing
+ public void writeTestsToFile(ArrayList testHistory) {
+ logger.setLevel(Level.SEVERE);
+ try {
+ // instantiate FileWriter object to overwrite specified text file
+ FileWriter fileWriter = new FileWriter(TESTS_FILEPATH, false);
+
+ JSONArray jsonTestHistory = new JSONArray();
+
+ for (AnswerList answerList : testHistory) {
+ jsonTestHistory.add(answerList.toJsonObject());
+ }
+ fileWriter.write(jsonTestHistory.toJSONString());
+ fileWriter.close();
+ logger.log(Level.INFO, "Test history saved to Cards_CardLI.json");
+ } catch (IOException e) {
+ System.out.println("Something went wrong while saving to data/Tests_CardLI.json...");
+ logger.log(Level.WARNING, "IOException thrown at writeTestsToFile()");
+ }
+ }
+
+ /**
+ * Reads JSON file to return user's saved decks of flashcards.
+ *
+ * @return User's saved decks of flashcards
+ */
+ public ArrayList readCardsFromFile() {
+ logger.setLevel(Level.SEVERE);
+ ArrayList decks = new ArrayList<>();
+
+ try {
+ // instantiate scanner to read file contents
+ Scanner s = new Scanner(this.cardsFile);
+ JSONParser parser = new JSONParser();
+ JSONArray jsonDecks = (JSONArray) parser.parse(s.nextLine());
+
+ for (Object o : jsonDecks) {
+ decks.add(parseDeck((JSONObject) o));
+ }
+ } catch (ParseException e) {
+ System.out.println("Something went wrong parsing data/Cards_CardLI.json...");
+ System.out.println("If you directly edited the JSON file, please revert all changes made to it.");
+ logger.log(Level.WARNING, "ParseException thrown at readCardsFromFile()");
+ } catch (FileNotFoundException e) {
+ logger.log(Level.WARNING, "NoFileFoundException thrown at readCardsFromFile()");
+ } catch (NoSuchElementException e) {
+ logger.log(Level.WARNING, "NoSuchElementException thrown at readCardsFromFile()");
+ }
+ return decks;
+ }
+
+ /**
+ * Reads JSON file to return user's saved test history.
+ *
+ * @return User's saved test history
+ */
+ public ArrayList readTestsFromFile() {
+ logger.setLevel(Level.SEVERE);
+ ArrayList testHistory = new ArrayList<>();
+
+ try {
+ // instantiate scanner to read file contents
+ Scanner s = new Scanner(this.testsFile);
+ JSONParser parser = new JSONParser();
+ JSONArray jsonTestHistory = (JSONArray) parser.parse(s.nextLine());
+
+ for (Object o : jsonTestHistory) {
+ testHistory.add(parseAnswerList((JSONObject) o));
+ }
+ } catch (ParseException e) {
+ System.out.println("Something went wrong parsing data/Tests_CardLI.json...");
+ System.out.println("If you directly edited the JSON file, please revert all changes made to it.");
+ logger.log(Level.WARNING, "ParseException thrown at readTestsFromFile()");
+ } catch (FileNotFoundException e) {
+ logger.log(Level.WARNING, "NoFileFoundException thrown at readTestsFromFile()");
+ } catch (NoSuchElementException e) {
+ logger.log(Level.WARNING, "NoSuchElementException thrown at readTestsFromFile()");
+ }
+ return testHistory;
+ }
+
+ /**
+ * Converts an AnswerList as JSONObject instance to an AnswerList instance.
+ *
+ * @param jsonAnswerList AnswerList as a JSONObject instance
+ * @return AnswerList instance
+ */
+ private AnswerList parseAnswerList(JSONObject jsonAnswerList) {
+ logger.setLevel(Level.WARNING);
+ JSONObject jsonDeck = (JSONObject) jsonAnswerList.get("deck");
+ AnswerList newAnswerList = new AnswerList(parseDeck(jsonDeck));
+ JSONArray jsonAnswers = (JSONArray) jsonAnswerList.get("answerList");
+
+ for (Object o : jsonAnswers) {
+ JSONObject jsonAnswer = (JSONObject) o;
+ newAnswerList.addAnswer((String) jsonAnswer.get("answer"),
+ (int) (long) jsonAnswer.get("questionIndex"));
+ }
+ newAnswerList.setUserScore((int) (long) jsonAnswerList.get("userScore"));
+ logger.log(Level.INFO, "Successfully parsed 1 AnswerList instance");
+ return newAnswerList;
+ }
+
+ /**
+ * Converts a Deck as a JSONObject instance to a Deck instance.
+ *
+ * @param jsonDeck Deck as a JSONObject instance
+ * @return Deck instance
+ */
+ private Deck parseDeck(JSONObject jsonDeck) {
+ logger.setLevel(Level.WARNING);
+ Deck newDeck = new Deck((String) jsonDeck.get("deckName"));
+ JSONArray jsonCards = (JSONArray) jsonDeck.get("cards");
+
+ for (Object o : jsonCards) {
+ JSONObject jsonCard = (JSONObject) o;
+ newDeck.addFlashCard(new FlashCard((String) jsonCard.get("front"),
+ (String) jsonCard.get("back"),
+ (int) (long) jsonCard.get("userScore"),
+ (int) (long) jsonCard.get("totalScore")));
+ }
+ logger.log(Level.INFO, "Successfully parsed 1 Deck instance");
+ return newDeck;
+ }
+}
diff --git a/src/main/java/seedu/cardli/testing/Answer.java b/src/main/java/seedu/cardli/testing/Answer.java
new file mode 100644
index 0000000000..20ed7b3c38
--- /dev/null
+++ b/src/main/java/seedu/cardli/testing/Answer.java
@@ -0,0 +1,76 @@
+package seedu.cardli.testing;
+
+import org.json.simple.JSONObject;
+import seedu.cardli.flashcard.FlashCard;
+
+/**
+ * Represents information about a single user answer when test function is invoked.
+ */
+public class Answer {
+
+ private String answer;
+
+ static final String SEPARATOR = " | ";
+
+ private final int questionIndex;
+ private boolean isAnswered;
+
+ public Answer(String answer, int questionIndex, Boolean isAnswered) {
+ this.answer = answer;
+ this.questionIndex = questionIndex;
+ this.isAnswered = isAnswered;
+ }
+
+ public Answer(String answer, int questionIndex) {
+ this.answer = answer;
+ this.questionIndex = questionIndex;
+ this.isAnswered = false;
+ }
+
+ //getter for front
+ public String getAnswer() {
+ return this.answer;
+ }
+
+ public int getQuestionIndex() {
+ return this.questionIndex;
+ }
+
+ public boolean isCorrect(String userAnswer, FlashCard question) {
+ String lowerCaseUserAnswer = userAnswer.toLowerCase();
+ String lowerCaseAnswer = question.getBack().toLowerCase();
+ return lowerCaseAnswer.equals(lowerCaseUserAnswer);
+ }
+
+ public void setIsAnswered() {
+ this.isAnswered = true;
+ }
+
+ public Boolean isAnswered() {
+ return isAnswered;
+ }
+
+ public void setAnswer(String answer) {
+ this.answer = answer;
+ }
+
+ @Override
+ public String toString() {
+ return getAnswer() + SEPARATOR
+ + getQuestionIndex() + '\n';
+ }
+
+ /**
+ * Converts an Answer instance into a JSONObject instance.
+ *
+ * @return Answer instance as a JSONObject instance
+ */
+ public JSONObject toJsonObject() {
+ JSONObject jsonAnswer = new JSONObject();
+
+ jsonAnswer.put("answer", getAnswer());
+ jsonAnswer.put("questionIndex", getQuestionIndex());
+
+ return jsonAnswer;
+ }
+}
diff --git a/src/main/java/seedu/cardli/testing/AnswerList.java b/src/main/java/seedu/cardli/testing/AnswerList.java
new file mode 100644
index 0000000000..f49838595a
--- /dev/null
+++ b/src/main/java/seedu/cardli/testing/AnswerList.java
@@ -0,0 +1,151 @@
+package seedu.cardli.testing;
+
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+import seedu.cardli.flashcard.Deck;
+
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Contains all the user's answers to the tested deck and his score.
+ */
+public class AnswerList {
+ private final ArrayList answerList;
+ private final Deck deck;
+ private int userScore;
+ private static final Logger logger = Logger.getLogger(Deck.class.getName());
+
+ /**
+ * AnswerList contains the list of answers from a test and the deck
+ * the questions came from.
+ *
+ * @param deck tested deck
+ */
+ public AnswerList(Deck deck) {
+ this.answerList = new ArrayList<>();
+ this.deck = deck;
+ this.userScore = 0;
+ }
+
+ /**
+ * Returns true or false regarding if a question is answered.
+ *
+ * @param index the question in the answerList.
+ * @return true or false regarding if a question is answered.
+ */
+ public Boolean isQuestionAnswered(int index) {
+ Boolean isQuestionAnswered;
+ try {
+ Answer answer = answerList.get(index);
+ isQuestionAnswered = answer.isAnswered();
+ } catch (IndexOutOfBoundsException e) {
+ isQuestionAnswered = false;
+ }
+ return isQuestionAnswered;
+ }
+
+ public void incrementUserScore() {
+ userScore++;
+ }
+
+ public Boolean isEmpty() {
+ return answerList.isEmpty();
+ }
+
+ public Boolean isAllAnswered() {
+ for (Answer a : answerList) {
+ if (!a.isAnswered()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Saves a new user answer to the current list of user answers.
+ *
+ * @param answer String representation of user's answer
+ * @param questionIndex Question number for the question that the answer answers
+ */
+ public void addAnswer(String answer, int questionIndex) {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "Adding card");
+ answerList.add(new Answer(answer, questionIndex));
+ }
+
+ /**
+ * Gets the index of the answer in the list.
+ *
+ * @param answer answer query
+ * @return index of the answer
+ */
+ public int getAnswerIndex(Answer answer) {
+ return answerList.indexOf(answer);
+ }
+
+ public Answer getAnswer(int questionIndex) {
+ return answerList.get(questionIndex);
+ }
+
+ public Deck getDeck() {
+ return deck;
+ }
+
+ public int getSize() {
+ return answerList.size();
+ }
+
+ public ArrayList getAnswerList() {
+ return answerList;
+ }
+
+ public int getUserScore() {
+ return userScore;
+ }
+
+ public void setUserScore(int userScore) {
+ this.userScore = userScore;
+ }
+
+ public void setQuestionAnswer(int questionIndex, String answer) {
+ answerList.get(questionIndex).setAnswer(answer);
+ }
+
+ @Override
+ public String toString() {
+ String answersString = "";
+ int answersCount = getSize();
+
+ for (int i = 0; i < answersCount; i++) {
+ answersString += answerList.get(i);
+ }
+
+ return getDeck().toString()
+ + answersCount + '\n'
+ + answersString
+ + getUserScore();
+
+ }
+
+ /**
+ * Converts an AnswerList instance into a JSONObject instance.
+ *
+ * @return AnswerList instance as a JSONObject instance
+ */
+ public JSONObject toJsonObject() {
+ JSONObject jsonAnswerList = new JSONObject();
+
+ int answerCount = getSize();
+ JSONArray jsonAnswers = new JSONArray();
+
+ for (int i = 0; i < answerCount; i++) {
+ jsonAnswers.add(answerList.get(i).toJsonObject());
+ }
+ jsonAnswerList.put("answerList", jsonAnswers);
+ jsonAnswerList.put("deck", getDeck().toJsonObject());
+ jsonAnswerList.put("userScore", getUserScore());
+ return jsonAnswerList;
+ }
+}
diff --git a/src/main/java/seedu/cardli/testing/Countdown.java b/src/main/java/seedu/cardli/testing/Countdown.java
new file mode 100644
index 0000000000..53d16640a0
--- /dev/null
+++ b/src/main/java/seedu/cardli/testing/Countdown.java
@@ -0,0 +1,125 @@
+package seedu.cardli.testing;
+
+import org.fusesource.jansi.Ansi;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * Implements the Countdown class, which creates a timer which counts down
+ * from the given time upon start and displays the remaining time. Once
+ * the time runs out, displays the given message instead.
+ */
+public class Countdown {
+
+ private Timer timer;
+ private CountdownTimerTask countdownTimerTask;
+ private boolean isRunning;
+
+ /**
+ * Constructs a {@code Countdown} object with the given startValue and
+ * timesUpMessage to be displayed when the time elapsed reaches zero.
+ * @param startValue Value of the timer to start from
+ * @param timesUpMessage Message to be displayed when timer expires
+ */
+ public Countdown(int startValue, String timesUpMessage) {
+ this.timer = new Timer();
+ this.countdownTimerTask = new CountdownTimerTask(startValue, timesUpMessage);
+ this.isRunning = false;
+ }
+
+ /**
+ * Private nested class extending a TimerTask that counts down
+ * and displays the timer on the standard output.
+ */
+ private class CountdownTimerTask extends TimerTask {
+
+ private int timeRemaining;
+ private int ticks;
+ private final String timesUpMessage;
+ private boolean willDisplay;
+
+ CountdownTimerTask(int startValue, String timesUpMessage) {
+ this.timeRemaining = startValue;
+ this.ticks = 0;
+ this.timesUpMessage = timesUpMessage;
+ this.willDisplay = false;
+ }
+
+ private void countDown() {
+ if (this.timeRemaining <= 0) {
+ Countdown.this.stop();
+ return;
+ }
+
+ this.ticks = (this.ticks < 10) ? (this.ticks + 1) : 0;
+ // update timeRemaining when 1s has passed
+ this.timeRemaining = (this.ticks == 10)
+ ? (this.timeRemaining - 1) : this.timeRemaining;
+
+ if (this.ticks > 0) {
+ // allow Countdown to start displaying after 0.1s since start()
+ // this allows some synchronous tasks called after to complete first
+ this.willDisplay = true;
+ }
+ }
+
+ private void display() {
+ if (!this.willDisplay) {
+ // do not display
+ return;
+ }
+
+ String displayed = (this.timeRemaining == 0) ? this.timesUpMessage
+ : "Time remaining: " + this.timeRemaining;
+
+ Ansi ansi = Ansi.ansi()
+ .saveCursorPosition()
+ .cursorUpLine()
+ .cursorToColumn(1)
+ .eraseLine()
+ .append(displayed)
+ .restoreCursorPosition();
+
+ System.out.print(ansi);
+ }
+
+ /**
+ * Starts the TimerTask. Displays a countdown starting
+ * from the initialised time.
+ */
+ @Override
+ public void run() {
+ display();
+ countDown();
+ }
+ }
+
+ /**
+ * Starts the timer with the initialised countdown value.
+ * Displays the countdown in the standard output.
+ */
+ public void start() {
+ this.isRunning = true;
+ int delay = 0;
+ int period = 100; // repeat at intervals of 0.1s
+ timer.scheduleAtFixedRate(this.countdownTimerTask, delay, period);
+ }
+
+ /**
+ * Stops the timer. The timer will no longer run or display.
+ */
+ public void stop() {
+ this.isRunning = false;
+ timer.cancel();
+ }
+
+ /**
+ * Returns the status of the timer, namely whether it is
+ * running or not.
+ * @return true if the timer is running, false if not
+ */
+ public boolean isRunning() {
+ return this.isRunning;
+ }
+}
diff --git a/src/main/java/seedu/cardli/testing/TestHistory.java b/src/main/java/seedu/cardli/testing/TestHistory.java
new file mode 100644
index 0000000000..1e556779fd
--- /dev/null
+++ b/src/main/java/seedu/cardli/testing/TestHistory.java
@@ -0,0 +1,123 @@
+package seedu.cardli.testing;
+
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.flashcard.FlashCard;
+
+import java.util.ArrayList;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * TestHistory keeps track of all answerResponses from previous tests.
+ */
+public class TestHistory {
+ private final ArrayList testHistory;
+ private final Logger logger = Logger.getLogger(TestManager.class.getName());
+
+ private final DeckManager deckManager;
+
+ public TestHistory(DeckManager deckManager) {
+ this.testHistory = new ArrayList<>();
+ this.deckManager = deckManager;
+ }
+
+ public TestHistory(DeckManager deckManager, ArrayList testHistory) {
+ this.testHistory = testHistory;
+ this.deckManager = deckManager;
+ }
+
+ public void addAnswerList(AnswerList answerList) {
+ testHistory.add(answerList);
+ }
+
+ /**
+ * View overall result statistics of all or a single flashcards.
+ *
+ * @return FlashCardStats as a string
+ */
+ public String viewAllFlashcardStats() {
+ logger.setLevel(Level.WARNING);
+ logger.log(Level.INFO, "listing all flashcard stats");
+
+ assert deckManager.getDecks().size() > 0 : "deckList must not be empty";
+
+ String result = "Listing total scores of flashcards for all tests:\n";
+ for (Deck deck : deckManager.getDecks()) {
+ for (FlashCard card : deck.getCards()) {
+ result = result.concat(card.returnFlashCard() + "\nScore: " + card.getUserScore()
+ + " out of " + card.getTotalScore()
+ + "\n");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Call on the method corresponding to the input index.
+ *
+ * @param index user's input index
+ * @return view flashcard message
+ * @throws DeckNotExistException if the test does not exist
+ */
+ public String prepareViewTest(int index) throws DeckNotExistException {
+ if (index == -1) {
+ return viewTests();
+ } else {
+ return viewTestByIndex(index);
+ }
+ }
+
+ /**
+ * Views the results of a test given the index.
+ * Gives the raw score, followed by the percentage.
+ *
+ * @param index index of the test
+ * @return that test as string
+ */
+ public String viewTestByIndex(int index) {
+ AnswerList answerList = testHistory.get(index);
+ int score = answerList.getUserScore();
+ int totalScore = answerList.getSize();
+
+ double percentage = (double) score / totalScore * 100;
+ String percentageAsString = String.format("%.2f", percentage);
+ String result = "For test " + (index + 1) + ": " + answerList.getDeck().getName()
+ + "\nYou scored " + score + " out of " + totalScore
+ + "\nThat is " + percentageAsString + "%!";
+
+ return result;
+ }
+
+ /**
+ * Views the results of the tests in order of all the tests taken.
+ * Gives the raw score, followed by the percentage.
+ *
+ * @return all the tests as a string
+ */
+ public String viewTests() throws DeckNotExistException {
+ String result = "";
+ if (testHistory.size() <= 0) {
+ throw new DeckNotExistException("You have not taken any tests.");
+ }
+ int index = 1;
+ result = result.concat("These are your scores:\n");
+ for (AnswerList answerList : testHistory) {
+ int score = answerList.getUserScore();
+ int totalScore = answerList.getSize();
+ double percentage = (double) score / totalScore * 100;
+ String percentageAsString = String.format("%.2f", percentage);
+ result = result.concat(
+ "Score for test " + index + ": " + answerList.getDeck().getName()
+ + " " + score + "/" + totalScore
+ + " " + percentageAsString + "%\n");
+ index++;
+ }
+ return result;
+ }
+
+ public ArrayList getTestHistory() {
+ return testHistory;
+ }
+}
diff --git a/src/main/java/seedu/cardli/testing/TestManager.java b/src/main/java/seedu/cardli/testing/TestManager.java
new file mode 100644
index 0000000000..54da6c3ef7
--- /dev/null
+++ b/src/main/java/seedu/cardli/testing/TestManager.java
@@ -0,0 +1,304 @@
+package seedu.cardli.testing;
+
+import seedu.cardli.exceptions.EmptyDeckException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.TestParser;
+import seedu.cardli.ui.TestUi;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.FlashCard;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.logging.Logger;
+import java.util.logging.Level;
+
+import static seedu.cardli.ui.TestUi.END_REVIEW_MESSAGE;
+import static seedu.cardli.ui.TestUi.END_TEST_MESSAGE;
+import static seedu.cardli.ui.TestUi.INCORRECT_INPUT_FORMAT_MESSAGE;
+import static seedu.cardli.ui.TestUi.NO_CARDS_TO_REVIEW_MESSAGE;
+import static seedu.cardli.ui.TestUi.NO_CARDS_TO_TEST_MESSAGE;
+import static seedu.cardli.ui.TestUi.TIMES_UP_MESSAGE;
+
+/**
+ * Implements the test function.
+ */
+public class TestManager {
+
+ private static final int TIME_PER_QUESTION = 15;
+
+ private static final String EMPTY_ANSWER = "";
+
+ private final TestUi ui;
+ private final Logger logger = Logger.getLogger(TestManager.class.getName());
+ private final TestHistory testHistory;
+ private final DeckManager deckManager;
+
+ public TestManager(TestHistory testHistory, DeckManager deckManager) {
+ this.logger.setLevel(Level.WARNING);
+ this.testHistory = testHistory;
+ this.deckManager = deckManager;
+ this.ui = new TestUi();
+ }
+
+ public TestManager(TestHistory testHistory, DeckManager deckManager, TestUi ui) {
+ this.logger.setLevel(Level.WARNING);
+ this.testHistory = testHistory;
+ this.deckManager = deckManager;
+ this.ui = ui;
+ }
+
+ /**
+ * Enters test mode and requires user to input the index of the deck that they want to be tested.
+ * If the input is "all", all decks will be tested. If the input is an integer, the deck at
+ * that index will be tested.
+ *
+ * @return end test message
+ */
+ public String startTest() {
+ logger.setLevel(Level.SEVERE);
+ logger.log(Level.INFO, "starting test");
+ ui.printStartTest();
+ String input = ui.getUserMessage();
+ try {
+ logger.log(Level.INFO, "choosing deck to test");
+ int deckIndex = TestParser.toInt(input);
+
+ Deck deckToTest = deckManager.getTestDeck(deckIndex);
+ AnswerList userAnswers = new AnswerList(deckToTest);
+
+ ArrayList shuffledDeck = prepareTestDeck(userAnswers);
+ testInProgress(shuffledDeck, userAnswers);
+
+ markTest(userAnswers);
+ testHistory.addAnswerList(userAnswers);
+ return END_TEST_MESSAGE;
+ } catch (NumberFormatException e) {
+ ui.showMessage(INCORRECT_INPUT_FORMAT_MESSAGE);
+ logger.log(Level.WARNING, "Incorrect format causing NumberFormatException");
+ } catch (IndexOutOfBoundsException e) {
+ ui.showMessage(e.getMessage());
+ logger.log(Level.WARNING, "Deck does not exist causing IndexOutOfBoundsException");
+ } catch (EmptyDeckException e) {
+ ui.showMessage(e.getMessage());
+ logger.log(Level.WARNING, "Empty deck");
+ }
+ return EMPTY_ANSWER;
+ }
+
+ /**
+ * Enters review mode and requires user to input the index of the deck that they want to be reviewed.
+ * If the input is "all", the cards will come from all decks. If the input is an integer, only cards from
+ * the deck at that index will be tested.
+ *
+ * @return end review message
+ */
+ public String startReview() {
+ logger.setLevel(Level.SEVERE);
+ logger.log(Level.INFO, "starting review");
+ ui.printStartReview();
+ String input = ui.getUserMessage();
+ try {
+ logger.log(Level.INFO, "choosing deck to test");
+ int deckIndex = TestParser.toInt(input);
+ Deck deckToReview = deckManager.getLowScoringCards(deckIndex);
+
+ logger.log(Level.INFO, "Reviewing low scoring cards");
+ AnswerList answerList = new AnswerList(deckToReview);
+
+ ArrayList shuffledDeck = prepareTestDeck(answerList);
+ testInProgress(shuffledDeck, answerList);
+
+ markTest(answerList);
+ testHistory.addAnswerList(answerList);
+
+ return END_REVIEW_MESSAGE;
+ } catch (NumberFormatException e) {
+ ui.showMessage(INCORRECT_INPUT_FORMAT_MESSAGE);
+ logger.log(Level.WARNING, "Incorrect format causing NumberFormatException");
+ } catch (IndexOutOfBoundsException e) {
+ ui.showMessage(e.getMessage());
+ logger.log(Level.WARNING, "Incorrect format causing IndexOutOfBoundsException");
+ } catch (EmptyDeckException e) {
+ ui.showMessage(NO_CARDS_TO_REVIEW_MESSAGE);
+ }
+ return EMPTY_ANSWER;
+ }
+
+ //@@author ThaddeusLim99
+
+ /**
+ * Shuffles the test deck and initializes the AnswerList with empty Answers.
+ *
+ * @param userAnswer user's answers
+ * @return shuffled test deck
+ * @throws EmptyDeckException if test deck is empty
+ */
+ public ArrayList prepareTestDeck(AnswerList userAnswer) throws EmptyDeckException {
+ ArrayList deckReplicate = userAnswer.getDeck().getCards();
+ if (deckReplicate.isEmpty()) {
+ throw new EmptyDeckException(NO_CARDS_TO_TEST_MESSAGE);
+ }
+ Collections.shuffle(deckReplicate);
+ logger.log(Level.INFO, "replicated and shuffled flashcard list");
+ logger.log(Level.INFO, "populating userAnswer");
+ //populate userAnswer
+ for (FlashCard question : deckReplicate) {
+ int questionNumber = userAnswer.getDeck().getCardIndex(question);
+ userAnswer.addAnswer(EMPTY_ANSWER, questionNumber);
+ }
+ return deckReplicate;
+ }
+
+ /**
+ * Iterates through the shuffled deck for the user to answer.
+ * The user's answer is saved into an AnswerList.
+ *
+ * @param deckReplicate shuffled test deck
+ * @param userAnswer user's answers
+ */
+ private void testInProgress(ArrayList deckReplicate, AnswerList userAnswer) {
+ boolean allQuestionsAnswered = false;
+ int currentQuestion = 0;
+ int nextQuestionFlag = 0;
+ int numOfQuestions = deckReplicate.size();
+ int timer = numOfQuestions * TIME_PER_QUESTION;
+ Countdown countdown = new Countdown(timer, TIMES_UP_MESSAGE);
+
+ logger.log(Level.INFO, "starting test proper");
+ countdown.start();
+ while (!allQuestionsAnswered && countdown.isRunning()) {
+ logger.log(Level.INFO, "currentQuestion is out of index. Either test finished or user scroll too far");
+ while (currentQuestion >= 0 && currentQuestion < deckReplicate.size() && countdown.isRunning()) {
+ //question is not answered yet
+ if (!userAnswer.isQuestionAnswered(currentQuestion)) {
+ logger.log(Level.INFO, "question not answered yet");
+ nextQuestionFlag = testCard(userAnswer, deckReplicate.get(currentQuestion), countdown);
+ }
+ logger.log(Level.INFO, "setting next question to test");
+ //next question to be tested is currentQuestion - 1
+ if (nextQuestionFlag == 1) {
+ currentQuestion--;
+ } else {
+ //next question to be tested is currentQuestion + 1
+ currentQuestion++;
+ }
+ }
+ logger.log(Level.INFO, "Wraparound for edge case");
+ //wraparound from end of deckReplicate to start of deckReplicate
+ if (currentQuestion == deckReplicate.size()) {
+ currentQuestion = 0;
+ }
+ //wraparound from start of deckReplicate to end of deckReplicate
+ if (currentQuestion == -1) {
+ currentQuestion = deckReplicate.size() - 1;
+ }
+ logger.log(Level.INFO, "checking isAllAnswered");
+ if (userAnswer.isAllAnswered()) {
+ allQuestionsAnswered = true;
+ }
+ }
+ if (countdown.isRunning()) {
+ countdown.stop();
+ }
+ ui.clearScreen();
+ ui.printDividerLine();
+ logger.log(Level.INFO, "Finished test");
+ //let user know testing is over
+ ui.printTestOver();
+ }
+
+ private int testCard(AnswerList userAnswer, FlashCard question, Countdown countdown) {
+ logger.log(Level.INFO, "starting to test a new card");
+
+ //0 means proceed to next question in userAnswer;1 means go back 1 question
+ int nextQuestionFlag = 0;
+
+ int questionNumber = userAnswer.getDeck().getCardIndex(question);
+
+ ui.clearScreen();
+ ui.printDividerLine();
+ ui.printQuestion(question, questionNumber);
+
+ //get user's answer to the card shown(currently assume user inputs only his/her answer)
+ //later version to include question number and parsing to allow for randomised testing
+ logger.log(Level.INFO, "getting user's answer to the question");
+
+ String userResponse = ui.getUserMessage();
+
+ try {
+ userResponse = TestParser.parseUserResponse(userResponse);
+ } catch (FieldEmptyException e) {
+ logger.log(Level.INFO, "No user input");
+ userResponse = EMPTY_ANSWER;
+ ui.printAnswerEmptyError();
+ }
+
+ if (!countdown.isRunning()) {
+ // timer has run out
+ userResponse = EMPTY_ANSWER;
+ }
+
+ //set question as answered with the new user response
+ if (!(userResponse.trim().equalsIgnoreCase("/NEXT") || userResponse.trim().equalsIgnoreCase("/BACK"))) {
+ logger.log(Level.INFO, "Saving answer");
+ userAnswer.setQuestionAnswer(questionNumber, userResponse);
+ userAnswer.getAnswer(questionNumber).setIsAnswered();
+ }
+ //signalling to test previous question next
+ if (userResponse.trim().equalsIgnoreCase("/BACK")) {
+ nextQuestionFlag = 1;
+ }
+
+ assert !userAnswer.isEmpty();
+ assert userAnswer.getSize() > 0;
+ logger.log(Level.INFO, "Finished this card's testing");
+
+ return nextQuestionFlag;
+ }
+ //@@author xRossKoh
+
+ /**
+ * Marks the user's answers then print their results of test to system output.
+ */
+ public void markTest(AnswerList userAnswers) {
+ logger.log(Level.INFO, "starting test check");
+
+ //there must be at least one response to start a test
+ assert userAnswers.getSize() > 0;
+ for (Answer response : userAnswers.getAnswerList()) {
+ markQuestion(userAnswers, response);
+ }
+ ui.printDividerLine();
+ int answersCount = userAnswers.getSize();
+ int score = userAnswers.getUserScore();
+ assert score <= answersCount;
+ System.out.println("You scored " + score + " out of " + answersCount + " for this test.");
+ System.out.println("That is " + Math.round(((double) score / answersCount) * 10000) / 100 + "%!");
+ logger.log(Level.INFO, "all answers checked, score printed to system output");
+ }
+
+ // Marks the user's answer
+ private void markQuestion(AnswerList userAnswers, Answer response) {
+ int responseNumber = userAnswers.getAnswerIndex(response);
+ FlashCard question = userAnswers.getDeck().getCard(responseNumber);
+ String userAnswer = response.getAnswer();
+
+ ui.printDividerLine();
+ //display front of card so that user can understand question
+ ui.printQuestion(question, responseNumber);
+ ui.printCorrectAnswer(question);
+ ui.printUserAnswer(userAnswer);
+
+ if (response.isCorrect(userAnswer, question)) {
+ userAnswers.incrementUserScore();
+ question.incrementUserScore();
+ ui.printCorrectAnsMessage();
+ logger.log(Level.INFO, "user answer is correct");
+ } else {
+ ui.printWrongAnsMessage();
+ logger.log(Level.INFO, "user answer is wrong");
+ }
+ question.incrementTotalScore();
+ }
+}
diff --git a/src/main/java/seedu/cardli/ui/CardLiUi.java b/src/main/java/seedu/cardli/ui/CardLiUi.java
new file mode 100644
index 0000000000..4290f7140c
--- /dev/null
+++ b/src/main/java/seedu/cardli/ui/CardLiUi.java
@@ -0,0 +1,140 @@
+package seedu.cardli.ui;
+
+import seedu.cardli.commands.CommandResult;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Scanner;
+
+public class CardLiUi {
+ private final Scanner in;
+ private final PrintStream out;
+
+ public CardLiUi() {
+ this(System.in, System.out);
+ }
+
+ public CardLiUi(InputStream in, PrintStream out) {
+ this.in = new Scanner(in);
+ this.out = out;
+ }
+
+ //@@author JWweiyin
+ public void printByeMessage() {
+ System.out.println("\tYou did well today! Goodbye!");
+ }
+
+ public static String returnHelpMessage() {
+ String help = "\n"
+ + "................................................................................"
+ + "....................................... \n"
+ + "Here is the list of commands! \n"
+ + "1. add \n"
+ + "Description: Adds a flashcard deck \n"
+ + "Format: add \n\n"
+ + "2. delete \n"
+ + "Description: Deletes a flashcard deck \n"
+ + "Format: delete \n\n"
+ + "3. edit \n"
+ + "Description: Edits the name of a flashcard deck \n"
+ + "Format: edit /d /n \n\n"
+ + "4. view \n"
+ + "Description: Lists flashcard decks \n"
+ + "Format: view \n\n"
+ + "5. enter \n"
+ + "Description: Enters a flashcard deck (further actions can be taken)\n"
+ + "Format: enter \n\n"
+ + "6. test \n"
+ + "Description: Tests flashcards \n"
+ + "Format: test \n\n"
+ + "7. viewfc \n"
+ + "Description: View overall results for flashcards \n"
+ + "Format: viewfc \n\n"
+ + "8. viewtest \n"
+ + "Description: View result of a test \n"
+ + "Format: viewtest \n\n"
+ + "9. review \n"
+ + "Description: Enters review mode, which is same as test mode but tests cards "
+ + "that the user got wrong more often \n"
+ + "Format: review \n\n"
+ + "10. find \n"
+ + "Description: Finds card using word/phrase of the query \n"
+ + "Format: find \n\n"
+ + "11. bye \n"
+ + "Description: Exits the program \n"
+ + "Format: exit \n\n"
+ + "................................................................................"
+ + "....................................... \n";
+ return help;
+ }
+
+ public static String returnHelpInDeckMessage() {
+ String help = "\n"
+ + "................................................................................"
+ + "....................................... \n"
+ + "Here is the list of commands! \n"
+ + "1. add \n"
+ + "Description: Adds a flashcard to a deck \n"
+ + "Format: add /f /b"
+ + " \n\n"
+ + "2. delete \n"
+ + "Description: Deletes a flashcard \n"
+ + "Format: delete \n\n"
+ + "3. edit \n"
+ + "Description: Edits the front or back of a flashcard \n"
+ + "Format: edit /c /s /i \n\n"
+ + "4. move \n"
+ + "Description: Moves a flashcard from current deck to another deck \n"
+ + "Format: move /c "
+ + " /d \n\n"
+ + "5. view \n"
+ + "Description: Lists flashcards in the current deck \n"
+ + "Format: view\n\n"
+ + "6. exit \n"
+ + "Description: Returns the program to main menu. \n"
+ + "Format: exit \n\n"
+ + "................................................................................"
+ + "....................................... \n";
+ return help;
+ }
+
+ //@@author JWweiyin
+ public void printGreetingMessage() {
+ String logo = "\n"
+ + " .----------------. .----------------. .----------------. .----------------. "
+ + " .----------------. .----------------.\n"
+ + "| .--------------. || .--------------. || .--------------. || .--------------. |"
+ + "| .--------------. || .--------------. |\n"
+ + "| | ______ | || | __ | || | _______ | || | ________ | ||"
+ + " | _____ | || | _____ | |\n"
+ + "| | .' ___ | | || | / \\ | || | |_ __ \\ | || | |_ ___ `. | ||"
+ + " | |_ _| | || | |_ _| | |\n"
+ + "| | / .' \\_| | || | / /\\ \\ | || | | |__) | | || | | | `. \\ |"
+ + " || | | | | || | | | | |\n"
+ + "| | | | | || | / ____ \\ | || | | __ / | || | | | | | | ||"
+ + " | | | _ | || | | | | |\n"
+ + "| | \\ `.___.'\\ | || | _/ / \\ \\_ | || | _| | \\ \\_ | || | _| |___.' "
+ + "/ | || | _| |__/ | | || | _| |_ | |\n"
+ + "| | `._____.' | || ||____| |____|| || | |____| |___| | || | |________.' | || "
+ + "| |________| | || | |_____| | |\n"
+ + "| | | || | | || | | || | | || "
+ + "| | || | | |\n"
+ + "| '--------------' || '--------------' || '--------------' || '--------------' |"
+ + "| '--------------' || '--------------' |\n"
+ + " '----------------' '----------------' '----------------' '----------------' "
+ + " '----------------' '----------------'\n";
+ System.out.println("Welcome to\n" + logo);
+ System.out.println("Let's get started!");
+ System.out.println("Type in \"help\" for more details.");
+ }
+
+ public static void printResult(CommandResult result) {
+ System.out.println(result.getResult());
+ }
+
+ //@@author xkisxk
+ public String getUserMessage() {
+ return in.nextLine();
+ }
+}
diff --git a/src/main/java/seedu/cardli/ui/TestUi.java b/src/main/java/seedu/cardli/ui/TestUi.java
new file mode 100644
index 0000000000..2a662c7cd5
--- /dev/null
+++ b/src/main/java/seedu/cardli/ui/TestUi.java
@@ -0,0 +1,104 @@
+package seedu.cardli.ui;
+
+import org.fusesource.jansi.Ansi;
+import seedu.cardli.flashcard.FlashCard;
+import seedu.cardli.testing.AnswerList;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Scanner;
+
+/**
+ * TestUi class handles the input and output during a test or a review.
+ */
+public class TestUi {
+
+ public static final String INCORRECT_INPUT_FORMAT_MESSAGE = "Incorrect input format, "
+ + "make sure the description is either a numeric or all.";
+ public static final String NO_CARDS_TO_REVIEW_MESSAGE = "Congratulations you don't have any low scoring cards!";
+ public static final String DECK_NOT_EXIST_MESSAGE = "Deck index must be a non-negative "
+ + "integer smaller than 2147483647, or \"all\".";
+ public static final String NO_CARDS_TO_TEST_MESSAGE = "There are no cards to test.";
+ public static final String END_TEST_MESSAGE = "End of test. Returning to main menu...";
+ public static final String END_REVIEW_MESSAGE = "End of review. Returning to main menu...";
+ /** The message to be displayed when the timer has expired. */
+ public static final String TIMES_UP_MESSAGE = "TIME'S UP! You can still input an answer, but it won't be graded.";
+
+ private final Scanner in;
+ private final PrintStream out;
+
+ public TestUi() {
+ this(System.in, System.out);
+ }
+
+ public TestUi(InputStream in, PrintStream out) {
+ this.in = new Scanner(in);
+ this.out = out;
+ }
+
+ public void showMessage(String input) {
+ out.println(input);
+ }
+
+ public void clearScreen() {
+ Ansi ansi = Ansi.ansi().eraseScreen();
+ System.out.print(ansi);
+ }
+
+ public void printDividerLine() {
+ System.out.println("--------------------------------------------------");
+ }
+
+ public void printQuestion(FlashCard question, int questionNumber) {
+ out.println("Question " + (questionNumber + 1) + ":");
+ //display front of card so that user can understand question
+ out.println(question.getFront());
+ out.println("Your answer?");
+ out.println(); // extra line for timer
+ }
+
+ public void printCorrectAnswer(FlashCard question) {
+ out.println("Correct answer: " + question.getBack());
+ }
+
+ /**
+ * Prints user's answer for a specified question to the system output.
+ *
+ * @param userAnswer the user's answer for the question
+ */
+ public void printUserAnswer(String userAnswer) {
+ out.println("Your answer: " + userAnswer);
+ }
+
+ public void printTestOver() {
+ out.println("Test Over");
+ }
+
+ public void printStartTest() {
+ out.println("Entering test mode...");
+ out.println("Which deck do you want to test?");
+ out.print("Input deck index (0 or \"all\" to test all decks): ");
+ }
+
+ public void printStartReview() {
+ out.println("Entering review mode...");
+ out.println("Which deck do you want to review?");
+ out.print("Input deck index (0 or \"all\" to review all decks): ");
+ }
+
+ public void printCorrectAnsMessage() {
+ out.println("Well done! You got this question correct");
+ }
+
+ public void printWrongAnsMessage() {
+ out.println("You got this question wrong! Take note of the correct answer!");
+ }
+
+ public void printAnswerEmptyError() {
+ out.println("Remember to provide an answer next time! Don't give up!");
+ }
+
+ public String getUserMessage() {
+ return in.nextLine();
+ }
+}
diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java
deleted file mode 100644
index 5c74e68d59..0000000000
--- a/src/main/java/seedu/duke/Duke.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package seedu.duke;
-
-import java.util.Scanner;
-
-public class Duke {
- /**
- * Main entry-point for the java.duke.Duke application.
- */
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- System.out.println("What is your name?");
-
- Scanner in = new Scanner(System.in);
- System.out.println("Hello " + in.nextLine());
- }
-}
diff --git a/src/test/java/seedu/cardli/CardLiTest.java b/src/test/java/seedu/cardli/CardLiTest.java
new file mode 100644
index 0000000000..e17225abb5
--- /dev/null
+++ b/src/test/java/seedu/cardli/CardLiTest.java
@@ -0,0 +1,62 @@
+package seedu.cardli;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.exceptions.CardLiException;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.exceptions.NoSlashException;
+import seedu.cardli.flashcard.Deck;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class CardLiTest {
+
+
+ @Test
+ public void deleteFlashCard_cardNotExist_expectCardLiException() {
+ Deck fcm = new Deck();
+ String input = "abcdef";
+ assertThrows(CardLiException.class, () -> fcm.deleteFlashCard(input));
+ }
+
+
+
+ @Test
+ public void deleteFlashCard_provideIndex_expectDelete() throws CardLiException {
+ Deck fcm = new Deck();
+ String[] firstCard = {"illness", "byouki"};
+ String[] secondCard = {"to lose", "nakushimasu"};
+ fcm.prepareToAddFlashCard(firstCard);
+ fcm.prepareToAddFlashCard(secondCard);
+ fcm.deleteFlashCard("1");
+ fcm.viewAllFlashCards();
+ assertEquals(1, fcm.getCards().size());
+ fcm.deleteFlashCard("1");
+ }
+
+ @Test
+ public void deleteFlashCard_provideInvalidIndex_expectCardLiException() throws CardLiException {
+ Deck fcm = new Deck();
+ String[] firstCard = {"illness", "byouki"};
+ String[] secondCard = {"to lose", "nakushimasu"};
+ fcm.prepareToAddFlashCard(firstCard);
+ fcm.prepareToAddFlashCard(secondCard);
+ fcm.viewAllFlashCards();
+ assertThrows(CardLiException.class, () -> fcm.deleteFlashCard("6"));
+ fcm.deleteFlashCard("1");
+ fcm.deleteFlashCard("1");
+ }
+
+ @Test
+ public void deleteFlashCard_provideNegativeIndex_expectCardLiException() throws CardLiException {
+ Deck fcm = new Deck();
+ String[] firstCard = {"illness", "byouki"};
+ String[] secondCard = {"to lose", "nakushimasu"};
+ fcm.prepareToAddFlashCard(firstCard);
+ fcm.prepareToAddFlashCard(secondCard);
+ fcm.viewAllFlashCards();
+ assertThrows(CardLiException.class, () -> fcm.deleteFlashCard("0"));
+ fcm.deleteFlashCard("1");
+ fcm.deleteFlashCard("1");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/cardli/DeckTest.java b/src/test/java/seedu/cardli/DeckTest.java
new file mode 100644
index 0000000000..9abdba4016
--- /dev/null
+++ b/src/test/java/seedu/cardli/DeckTest.java
@@ -0,0 +1,41 @@
+//@@author xRossKoh
+
+package seedu.cardli;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.flashcard.FlashCard;
+import seedu.cardli.flashcard.Deck;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeckTest {
+
+ /**
+ * Tests toString() and Deck() constructor.
+ */
+ @Test
+ public void testToString() {
+ Deck deck = new Deck();
+ deck.addFlashCard("testFront1", "testBack1");
+ assertEquals("Untitled\n1\n"
+ + "testFront1 | testBack1 | 0 | 0\n", deck.toString());
+ }
+
+ /**
+ * Tests toJSONObject(), Deck(deckName) constructor
+ * and all 3 implementations of addFlashCard().
+ */
+ @Test
+ public void testToJsonObject() {
+ FlashCard flashCard = new FlashCard("testFront3", "testBack3", 1, 2);
+ Deck deck = new Deck("testDeck");
+ deck.addFlashCard("testFront1", "testBack1");
+ deck.addFlashCard("testFront2", "testBack2", 2, 3);
+ deck.addFlashCard(flashCard);
+ assertEquals("{\"cards\":["
+ + "{\"back\":\"testBack1\",\"front\":\"testFront1\",\"userScore\":0,\"totalScore\":0},"
+ + "{\"back\":\"testBack2\",\"front\":\"testFront2\",\"userScore\":2,\"totalScore\":3},"
+ + "{\"back\":\"testBack3\",\"front\":\"testFront3\",\"userScore\":1,\"totalScore\":2}],"
+ + "\"deckName\":\"testDeck\"}", deck.toJsonObject().toJSONString());
+ }
+}
diff --git a/src/test/java/seedu/duke/DukeTest.java b/src/test/java/seedu/cardli/DukeTest.java
similarity index 89%
rename from src/test/java/seedu/duke/DukeTest.java
rename to src/test/java/seedu/cardli/DukeTest.java
index 2dda5fd651..59f8350af6 100644
--- a/src/test/java/seedu/duke/DukeTest.java
+++ b/src/test/java/seedu/cardli/DukeTest.java
@@ -1,4 +1,4 @@
-package seedu.duke;
+package seedu.cardli;
import static org.junit.jupiter.api.Assertions.assertTrue;
diff --git a/src/test/java/seedu/cardli/FlashCardTest.java b/src/test/java/seedu/cardli/FlashCardTest.java
new file mode 100644
index 0000000000..f99336d029
--- /dev/null
+++ b/src/test/java/seedu/cardli/FlashCardTest.java
@@ -0,0 +1,33 @@
+package seedu.cardli;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.flashcard.FlashCard;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class FlashCardTest {
+
+ private FlashCard flashCard = new FlashCard("testFront", "testBack");
+
+ @Test
+ public void testGetFront() {
+ assertEquals("testFront", flashCard.getFront());
+ }
+
+ @Test
+ public void testGetBack() {
+ assertEquals("testBack", flashCard.getBack());
+ }
+
+ //@@author xRossKoh
+ @Test
+ public void testToString() {
+ assertEquals("testFront | testBack | 0 | 0\n", flashCard.toString());
+ }
+
+ @Test
+ public void testToJsonObject() {
+ assertEquals("{\"back\":\"testBack\",\"front\":\"testFront\",\"userScore\":0,\"totalScore\":0}",
+ flashCard.toJsonObject().toJSONString());
+ }
+}
diff --git a/src/test/java/seedu/cardli/ParserTest.java b/src/test/java/seedu/cardli/ParserTest.java
new file mode 100644
index 0000000000..818fa61ad4
--- /dev/null
+++ b/src/test/java/seedu/cardli/ParserTest.java
@@ -0,0 +1,16 @@
+package seedu.cardli;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ParserTest {
+
+ @Test
+ public void parseCommand_hasFrontWhitespace_success() {
+ String input = " bye\n";
+ assertEquals("bye", Parser.getCommandType(input));
+ }
+
+}
diff --git a/src/test/java/seedu/cardli/TestParserTest.java b/src/test/java/seedu/cardli/TestParserTest.java
new file mode 100644
index 0000000000..47b1f4298f
--- /dev/null
+++ b/src/test/java/seedu/cardli/TestParserTest.java
@@ -0,0 +1,34 @@
+package seedu.cardli;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.exceptions.FieldEmptyException;
+import seedu.cardli.parser.TestParser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestParserTest {
+ @Test
+ public void parseUserResponse_noInput_expectFieldEmptyException() {
+ String input = "";
+ assertThrows(FieldEmptyException.class, () -> TestParser.parseUserResponse(input));
+ }
+
+ @Test
+ public void parseUserResponse_trailingSpaces_expectTrimmedInput() throws FieldEmptyException {
+ String input = "s ";
+ assertEquals("s", TestParser.parseUserResponse(input));
+ }
+
+ @Test
+ public void toInt_invalidInput_expectNumberFormatException() {
+ String input = "call";
+ assertThrows(NumberFormatException.class, () -> TestParser.toInt(input));
+ }
+
+ @Test
+ public void toInt_all_expectMinusOne() {
+ String input = "all";
+ assertEquals(-1, TestParser.toInt("all"));
+ }
+}
diff --git a/src/test/java/seedu/cardli/commands/deck/AddCardCommandTest.java b/src/test/java/seedu/cardli/commands/deck/AddCardCommandTest.java
new file mode 100644
index 0000000000..fcec16a52f
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/deck/AddCardCommandTest.java
@@ -0,0 +1,27 @@
+package seedu.cardli.commands.deck;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AddCardCommandTest {
+
+ @Test
+ public void execute_addingCardWithSameFront_expectError() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("deck1");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "add /f card /b back";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new AddCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("There is already a card with card on the front in deck deck1.", output);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/cardli/commands/deck/EditCardCommandTest.java b/src/test/java/seedu/cardli/commands/deck/EditCardCommandTest.java
new file mode 100644
index 0000000000..f609549bc4
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/deck/EditCardCommandTest.java
@@ -0,0 +1,222 @@
+package seedu.cardli.commands.deck;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.commands.deck.EditCardCommand;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EditCardCommandTest {
+
+ @Test
+ public void execute_emptyString_expectFieldEmptyErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit ";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You cannot leave any field empty! "
+ + "Format should be\nedit /c /s /i ", output);
+ }
+
+ @Test
+ public void execute_duplicateCorrectFlags_expectFlagArgumentErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c /c /s /s /i /i";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You should not use this command's flag as your argument", output);
+ }
+
+ @Test
+ public void execute_incorrectFlags_expectMissingFlagMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /d 1 /j back /e noice";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You are missing the relevant flag/flags", output);
+ }
+
+ @Test
+ public void execute_swappedFlagOrder_expectWrongOrderErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c 1 /i name /s front";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("/c should come before /s, which should come before /i!"
+ + " Format should be\nedit /c /s /i ", output);
+ }
+
+ @Test
+ public void execute_emptyArgument_expectFieldEmptyErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c /s /i";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You cannot leave any field empty! "
+ + "Format should be\nedit /c /s /i ", output);
+ }
+
+ @Test
+ public void execute_incorrectFlagOrder_expectInvalidArgumentsMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit 1 back noice /c /s /i";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Please use the correct flags and in the correct order! "
+ + "\nFormat should be edit /c /s /i ", output);
+ }
+
+ @Test
+ public void execute_incorrectSide_expectInvalidSideErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c 1 /s middle /i name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("What side is this? It's only either front or back", output);
+ }
+
+ @Test
+ public void execute_invalidIntegerIndex_expectInvalidIndexErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c 2 /s front /i name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Incorrect index for Card!", output);
+ }
+
+ @Test
+ public void execute_negativeIntegerIndex_expectArgumentTypeErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c -1 /s front /i name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the card, which is "
+ + "a positive integer!", output);
+ }
+
+ @Test
+ public void execute_stringCard_expectArgumentTypeErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c abc /s front /i name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the card, which is "
+ + "a positive integer!", output);
+ }
+
+ @Test
+ public void execute_largeInteger_expectLargeIntegerErrorMessage() {
+ Deck deck = new Deck();
+ DeckManager deckManager = new DeckManager();
+ deck.addFlashCard("card", "card");
+ String input = "edit /c 2147483648 /s front /i name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deck, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Card index must be smaller than 2147483647.", output);
+ }
+
+ @Test
+ public void execute_cardWithExactSameFrontExists_expectErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("deck1");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ deckManager.getDeck(0).addFlashCard("card2", "card");
+ String input = "edit /c 2 /s front /i card";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("There is already a card with card on the front in deck deck1.", output);
+ }
+
+ @Test
+ public void execute_sameSpellingDifferentCase_expectSuccess() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("deck1");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ deckManager.getDeck(0).addFlashCard("card2", "card");
+ String input = "edit /c 2 /s front /i Card";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Changed front of card 2 to Card", output);
+ }
+
+ @Test
+ public void execute_cardWithSameFrontDoesNotExist_expectSuccess() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("deck1");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ deckManager.getDeck(0).addFlashCard("card2", "card");
+ String input = "edit /c 2 /s front /i no";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Changed front of card 2 to no", output);
+ }
+
+
+
+}
diff --git a/src/test/java/seedu/cardli/commands/deck/MoveCardCommandTest.java b/src/test/java/seedu/cardli/commands/deck/MoveCardCommandTest.java
new file mode 100644
index 0000000000..babe0f3972
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/deck/MoveCardCommandTest.java
@@ -0,0 +1,249 @@
+package seedu.cardli.commands.deck;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.commands.deck.MoveCardCommand;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class MoveCardCommandTest {
+ @Test
+ public void execute_emptyString_expectFieldEmptyErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move ";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You cannot leave any field empty! "
+ + "Format should be\nmove /c /d ", output);
+ }
+
+ @Test
+ public void execute_duplicateCorrectFlags_expectFlagArgumentErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c /c /d /d";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You should not use this command's flag as your argument", output);
+ }
+
+ @Test
+ public void execute_incorrectFlags_expectMissingFlagMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /a 1 /b /1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You are missing the relevant flag/flags", output);
+ }
+
+ @Test
+ public void execute_swappedFlagOrder_expectWrongOrderErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /d 1 /c /1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("/c should come before /d! Format should be\n"
+ + "move /c /d ", output);
+ }
+
+ @Test
+ public void execute_emptyArgument_expectFieldEmptyErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c /d ";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You cannot leave any field empty! "
+ + "Format should be\nmove /c /d ", output);
+ }
+
+ @Test
+ public void execute_incorrectFlagOrder_expectInvalidArgumentsMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move 1 /c 1 /d ";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Please use the correct flags and in the correct order! "
+ + "\nFormat should be move /c /d ", output);
+ }
+
+ @Test
+ public void execute_invalidCardIntegerIndex_expectInvalidIndexErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 2 /d 1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Incorrect index for Card!", output);
+ }
+
+ @Test
+ public void execute_invalidDeckIntegerIndex_expectInvalidIndexErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 1 /d 3";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Incorrect index for deck!", output);
+ }
+
+ @Test
+ public void execute_negativeCardIntegerIndex_expectArgumentTypeErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c -1 /d 2";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the card, "
+ + "which is a positive integer!", output);
+ }
+
+ @Test
+ public void execute_negativeDeckIntegerIndex_expectArgumentTypeErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 1 /d -1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the deck, "
+ + "which is a positive integer!", output);
+ }
+
+ @Test
+ public void execute_stringAsCardIndex_expectArgumentTypeErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c abc /d 2";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the card, "
+ + "which is a positive integer!", output);
+ }
+
+ @Test
+ public void execute_stringAsDeckIndex_expectArgumentTypeErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 1 /d abc";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the deck, "
+ + "which is a positive integer!", output);
+ }
+
+ @Test
+ public void execute_largeIntegerCardIndex_expectLargeIntgerErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 2147483648 /d 2";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Card and Deck index must be both "
+ + "smaller than 2147483647.", output);
+ }
+
+ @Test
+ public void execute_largeIntegerDeckIndex_expectLargeIntgerErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 1 /d 2147483648";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Card and Deck index must be both "
+ + "smaller than 2147483647.", output);
+ }
+
+ @Test
+ public void execute_sameDeckDestination_expectSameDestinationErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ deckManager.prepareToAddDeck("yeeter");
+ deckManager.getDeck(0).addFlashCard("card", "card");
+ String input = "move /c 1 /d 1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new MoveCardCommand(arguments, deckManager.getDeck(0), deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Your card is already in the deck specified!", output);
+ }
+
+
+}
diff --git a/src/test/java/seedu/cardli/commands/system/AddDeckCommandTest.java b/src/test/java/seedu/cardli/commands/system/AddDeckCommandTest.java
new file mode 100644
index 0000000000..667716c821
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/AddDeckCommandTest.java
@@ -0,0 +1,41 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class AddDeckCommandTest {
+
+ public static final String DECK_ALREADY_EXISTS_MESSAGE = "The deck you are trying to create already exists.";
+
+ @Test
+ public void execute_deckWithSameNameDoesNotExist_expectSuccess() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("deck1");
+ String input = "add deck1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new AddDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals(DECK_ALREADY_EXISTS_MESSAGE, output);
+ }
+
+ @Test
+ public void execute_sameSpellingDifferentCase_expectSuccess() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("deck1");
+ String input = "add Deck1";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new AddDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You have just made the deck <>.", output);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/cardli/commands/system/EditDeckCommandTest.java b/src/test/java/seedu/cardli/commands/system/EditDeckCommandTest.java
new file mode 100644
index 0000000000..e0a082bed1
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/EditDeckCommandTest.java
@@ -0,0 +1,149 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.commands.system.EditDeckCommand;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EditDeckCommandTest {
+
+ @Test
+ public void execute_emptyString_expectFieldEmptyErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You cannot leave any field empty! "
+ + "Format should be\nedit /d /n ", output);
+ }
+
+ @Test
+ public void execute_duplicateCorrectFlags_expectFlagArgumentErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /d /d /n /n";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You should not use this command's flag as your argument", output);
+ }
+
+ @Test
+ public void execute_incorrectFlag_expectMissingFlagMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /b 1 /e name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You are missing the relevant flag/flags", output);
+ }
+
+ @Test
+ public void execute_swappedFlagOrder_expectWrongOrderErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /n 1 /d name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Incorrect edit command! /n should come after /d. Format "
+ + "should be\nedit /d /n ", output);
+ }
+
+ @Test
+ public void execute_emptyArguments_expectFieldEmptyErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /d /n";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You cannot leave any field empty! "
+ + "Format should be\nedit /d /n ", output);
+ }
+
+ @Test
+ public void execute_incorrectFlagOrder_expectInvalidArgumentsMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit a b /d /n";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Please use the correct flags and in the correct order! "
+ + "\nFormat + should be edit /d /n ", output);
+ }
+
+ @Test
+ public void execute_invalidIntegerIndex_expectInvalidIndexErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /d 2 /n name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Incorrect index for deck!", output);
+ }
+
+ @Test
+ public void execute_NegativeIntegerIndex_expectArgumentTypeErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /d -1 /n name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the deck, which is "
+ + "a positive integer!", output);
+ }
+
+ @Test
+ public void execute_stringDeck_expectArgumentTypeErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /d abc /n name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You can only input the index of the deck, which is "
+ + "a positive integer!", output);
+ }
+
+ @Test
+ public void execute_largeInteger_expectLargeIntegerErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "edit /d 2147483648 /n name";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EditDeckCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Deck index must be smaller than 2147483647.", output);
+ }
+}
diff --git a/src/test/java/seedu/cardli/commands/system/EnterDeckCommandTest.java b/src/test/java/seedu/cardli/commands/system/EnterDeckCommandTest.java
new file mode 100644
index 0000000000..d1695baad8
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/EnterDeckCommandTest.java
@@ -0,0 +1,41 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.InnerParser;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EnterDeckCommandTest {
+ @Test
+ public void execute_emptyString_expectInvalidInputMessage() {
+ DeckManager deckManager = new DeckManager();
+ InnerParser innerParser = new InnerParser();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "enter";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EnterDeckCommand(arguments, deckManager, innerParser);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("Invalid input. Please input deck index after \"enter\".", output);
+ }
+
+ @Test
+ public void execute_invalidIndex_expectDeckDoesNotExistMessage() {
+ DeckManager deckManager = new DeckManager();
+ InnerParser innerParser = new InnerParser();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "enter 2";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new EnterDeckCommand(arguments, deckManager, innerParser);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("That deck doesn't exist. Please input a valid deck index.", output);
+ }
+
+}
diff --git a/src/test/java/seedu/cardli/commands/system/ExitProgrammeCommandTest.java b/src/test/java/seedu/cardli/commands/system/ExitProgrammeCommandTest.java
new file mode 100644
index 0000000000..8b5e67db88
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/ExitProgrammeCommandTest.java
@@ -0,0 +1,21 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ExitProgrammeCommandTest {
+ @Test
+ public void executeArgument_expectExtraArgumentMessage() {
+ String input = "bye hello world";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new ExitProgrammeCommand(arguments);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("There should not be any arguments.", output);
+ }
+}
diff --git a/src/test/java/seedu/cardli/commands/system/FindCardsCommandTest.java b/src/test/java/seedu/cardli/commands/system/FindCardsCommandTest.java
new file mode 100644
index 0000000000..11dde9b0a9
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/FindCardsCommandTest.java
@@ -0,0 +1,24 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class FindCardsCommandTest {
+ @Test
+ public void execute_emptyString_expectNoSearchTermErrorMessage() {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("yeet");
+ String input = "find ";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new FindCardsCommand(arguments, deckManager);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("You did not input a search term after \"find\".", output);
+ }
+}
diff --git a/src/test/java/seedu/cardli/commands/system/HelpCommandTest.java b/src/test/java/seedu/cardli/commands/system/HelpCommandTest.java
new file mode 100644
index 0000000000..0053c27da5
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/HelpCommandTest.java
@@ -0,0 +1,21 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class HelpCommandTest {
+ @Test
+ public void execute_argument_expectExtraArgumentMessage() {
+ String input = "bye hello world";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new HelpCommand(arguments);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("There should not be any arguments.", output);
+ }
+}
diff --git a/src/test/java/seedu/cardli/commands/system/ViewDecksCommandTest.java b/src/test/java/seedu/cardli/commands/system/ViewDecksCommandTest.java
new file mode 100644
index 0000000000..e5811ebf29
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/ViewDecksCommandTest.java
@@ -0,0 +1,23 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ViewDecksCommandTest {
+ @Test
+ public void executeArgument_expectExtraArgumentMessage() {
+ DeckManager deckManager = new DeckManager();
+ String input = "view abc";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new ViewDecksCommand(deckManager, arguments);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("There should not be any arguments.", output);
+ }
+}
diff --git a/src/test/java/seedu/cardli/commands/system/ViewFlashCardStatsCommandTest.java b/src/test/java/seedu/cardli/commands/system/ViewFlashCardStatsCommandTest.java
new file mode 100644
index 0000000000..3599668d71
--- /dev/null
+++ b/src/test/java/seedu/cardli/commands/system/ViewFlashCardStatsCommandTest.java
@@ -0,0 +1,25 @@
+package seedu.cardli.commands.system;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.parser.Parser;
+import seedu.cardli.testing.TestHistory;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ViewFlashCardStatsCommandTest {
+ @Test
+ public void executeArgument_expectExtraArgumentMessage() {
+ DeckManager deckManager = new DeckManager();
+ TestHistory testHistory = new TestHistory(deckManager);
+ String input = "viewfc 100";
+ String commandType = Parser.getCommandType(input);
+ String arguments = Parser.getCommandArguments(commandType, input);
+ Command test = new ViewFlashCardStatsCommand(arguments, testHistory);
+ CommandResult result = test.execute();
+ String output = result.getResult();
+ assertEquals("There should not be any arguments.", output);
+ }
+}
diff --git a/src/test/java/seedu/cardli/flashcard/DeckManagerTest.java b/src/test/java/seedu/cardli/flashcard/DeckManagerTest.java
new file mode 100644
index 0000000000..43d35914d1
--- /dev/null
+++ b/src/test/java/seedu/cardli/flashcard/DeckManagerTest.java
@@ -0,0 +1,61 @@
+package seedu.cardli.flashcard;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.exceptions.DeckNotExistException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+
+class DeckManagerTest {
+
+ @Test
+ void prepareToAddDeck_deckAdded_expectOne() throws DeckNotExistException {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("Test");
+ assertEquals(1, deckManager.getDecksSize());
+ deckManager.deleteDeck(deckManager.getDeck(0));
+ }
+
+ @Test
+ void getTestDeck_indexOutOfBounds_expectIndexOutOfBoundsException() {
+ DeckManager deckManager = new DeckManager();
+ assertThrows(IndexOutOfBoundsException.class, () -> deckManager.getTestDeck(1));
+ }
+
+ @Test
+ void hasDeck_noDecks_expectFalse() {
+ DeckManager deckManager = new DeckManager();
+ assertFalse(deckManager.hasDeck(1));
+ }
+
+ @Test
+ void hasDeck_hasDeck_expectTrue() throws DeckNotExistException {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("Test");
+ assertTrue(deckManager.hasDeck(0));
+ deckManager.deleteDeck(deckManager.getDeck(0));
+ }
+
+ @Test
+ void getTestDeck_twoCards_expectTwoCards() throws DeckNotExistException {
+ DeckManager deckManager = new DeckManager();
+ deckManager.prepareToAddDeck("Test Deck 1");
+ deckManager.prepareToAddDeck("Test Deck 2");
+ deckManager.getDeck(0).addFlashCard("test card 1", "test card 1");
+ deckManager.getDeck(1).addFlashCard("test card 2", "test card 2");
+ assertEquals(2, deckManager.getTestDeck(-1).getDeckSize());
+ deckManager.deleteDeck(deckManager.getDeck(0));
+ deckManager.deleteDeck(deckManager.getDeck(0));
+ }
+
+
+ @Test
+ void deleteDeck_noDecks_expectIndexOutOfBoundsException() {
+ DeckManager deckManager = new DeckManager();
+ Deck deck = new Deck("euyhfdsifnkjadsanauheaiu");
+ assertThrows(IndexOutOfBoundsException.class, () -> deckManager.deleteDeck(1));
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/cardli/flashcard/DeckTest.java b/src/test/java/seedu/cardli/flashcard/DeckTest.java
new file mode 100644
index 0000000000..12214fee9d
--- /dev/null
+++ b/src/test/java/seedu/cardli/flashcard/DeckTest.java
@@ -0,0 +1,45 @@
+package seedu.cardli.flashcard;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.commands.Command;
+import seedu.cardli.commands.CommandResult;
+import seedu.cardli.commands.deck.EditCardCommand;
+import seedu.cardli.parser.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class DeckTest {
+
+ @Test
+ public void hasCardWithSameName_matchesCardExactly_returnTrue() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ assertEquals(true, deck.hasCardWithSameName("card"));
+
+ }
+
+ @Test
+ public void hasCardWithSameName_hasTrailingSpaces_returnTrue() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ assertEquals(true, deck.hasCardWithSameName("card "));
+
+ }
+
+ @Test
+ public void hasCardWithSameName_differenceInCase_returnFalse() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ assertEquals(false, deck.hasCardWithSameName("cArd"));
+
+ }
+
+ @Test
+ public void hasCardWithSameName_completelyDifferentQuery_returnFalse() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ assertEquals(false, deck.hasCardWithSameName("ohno"));
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/seedu/cardli/testing/AnswerListTest.java b/src/test/java/seedu/cardli/testing/AnswerListTest.java
new file mode 100644
index 0000000000..a5e6adebb1
--- /dev/null
+++ b/src/test/java/seedu/cardli/testing/AnswerListTest.java
@@ -0,0 +1,81 @@
+package seedu.cardli.testing;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.flashcard.FlashCard;
+import seedu.cardli.testing.Answer;
+import seedu.cardli.testing.AnswerList;
+import seedu.cardli.testing.TestHistory;
+import seedu.cardli.testing.TestManager;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AnswerListTest {
+ @Test
+ public void getScore_noAnswers_expectZero() {
+ Deck deck = new Deck();
+ AnswerList answerList = new AnswerList(deck);
+ assertEquals(0, answerList.getUserScore());
+ }
+
+ @Test
+ public void getScore_oneCorrectAnswer_expectOne() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ deck.addFlashCard("card2", "card2");
+ AnswerList answerList = new AnswerList(deck);
+ answerList.addAnswer("card", 1);
+ answerList.addAnswer("card", 2);
+ DeckManager deckManager = new DeckManager();
+ TestHistory testHistory = new TestHistory(deckManager);
+ TestManager testManager = new TestManager(testHistory, deckManager);
+ testManager.markTest(answerList);
+ assertEquals(1, answerList.getUserScore());
+ }
+
+ //@@author xRossKoh
+ @Test
+ public void testToString() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ deck.addFlashCard("card2", "card2");
+ AnswerList answerList = new AnswerList(deck);
+ answerList.addAnswer("card", 1);
+ answerList.addAnswer("card", 2);
+ DeckManager deckManager = new DeckManager();
+ TestHistory testHistory = new TestHistory(deckManager);
+ TestManager testManager = new TestManager(testHistory, deckManager);
+ testManager.markTest(answerList);
+ assertEquals("Untitled\n"
+ + "2\n"
+ + "card | card | 1 | 1\n"
+ + "card2 | card2 | 0 | 1\n"
+ + "2\n"
+ + "card | 1\n"
+ + "card | 2\n"
+ + "1", answerList.toString());
+ }
+
+ @Test
+ public void testToJsonObject() {
+ Deck deck = new Deck();
+ deck.addFlashCard("card", "card");
+ deck.addFlashCard("card2", "card2");
+ AnswerList answerList = new AnswerList(deck);
+ answerList.addAnswer("card", 1);
+ answerList.addAnswer("card", 2);
+ DeckManager deckManager = new DeckManager();
+ TestHistory testHistory = new TestHistory(deckManager);
+ TestManager testManager = new TestManager(testHistory, deckManager);
+ testManager.markTest(answerList);
+ assertEquals("{\"answerList\":"
+ + "[{\"questionIndex\":1,\"answer\":\"card\"},"
+ + "{\"questionIndex\":2,\"answer\":\"card\"}],"
+ + "\"deck\":{\"cards\":"
+ + "[{\"back\":\"card\",\"front\":\"card\",\"userScore\":1,\"totalScore\":1},"
+ + "{\"back\":\"card2\",\"front\":\"card2\",\"userScore\":0,\"totalScore\":1}],"
+ + "\"deckName\":\"Untitled\"},\"userScore\":1}",
+ answerList.toJsonObject().toJSONString());
+ }
+}
diff --git a/src/test/java/seedu/cardli/testing/AnswerTest.java b/src/test/java/seedu/cardli/testing/AnswerTest.java
new file mode 100644
index 0000000000..a66987f133
--- /dev/null
+++ b/src/test/java/seedu/cardli/testing/AnswerTest.java
@@ -0,0 +1,50 @@
+package seedu.cardli.testing;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.testing.Answer;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class AnswerTest {
+
+ @Test
+ public void testGetAnswer_sameAnswer_expectCorrectAnswer() {
+ assertEquals("testAnswer",
+ new Answer("testAnswer", 1).getAnswer());
+ }
+
+ @Test
+ public void testGetQuestionIndex_questionOne_expectOne() {
+ Answer answer = new Answer("testAnswer", 1,true);
+ assertEquals(1, answer.getQuestionIndex());
+ }
+
+ @Test
+ public void testIsAnswered_true_expectTrue() {
+ Answer answer = new Answer("testAnswer", 1,false);
+ answer.setIsAnswered();
+ assertEquals(true, answer.isAnswered());
+ }
+
+ @Test
+ public void testSetAnswer_sameAnswer_expectCorrectAnswer() {
+ Answer answer = new Answer("", 1,true);
+ answer.setAnswer("testAnswer");
+ assertEquals("testAnswer", answer.getAnswer());
+ }
+
+ //@@author xRossKoh
+ @Test
+ public void testToString() {
+ Answer answer = new Answer("testAnswer", 1,true);
+ assertEquals("testAnswer | 1\n", answer.toString());
+ }
+
+ @Test
+ public void testToJsonObject() {
+ Answer answer = new Answer("testAnswer", 1,true);
+ assertEquals("{\"questionIndex\":1,\"answer\":\"testAnswer\"}",
+ answer.toJsonObject().toJSONString());
+ }
+
+}
diff --git a/src/test/java/seedu/cardli/testing/TestHistoryTest.java b/src/test/java/seedu/cardli/testing/TestHistoryTest.java
new file mode 100644
index 0000000000..4723484aba
--- /dev/null
+++ b/src/test/java/seedu/cardli/testing/TestHistoryTest.java
@@ -0,0 +1,100 @@
+package seedu.cardli.testing;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class TestHistoryTest {
+
+ DeckManager deckManager = new DeckManager();
+
+ @Test
+ void testPrepareViewTest_validIndex_expectResultMessage() throws DeckNotExistException {
+ deckManager.prepareToAddDeck("Test");
+ Deck deck = deckManager.getDeck(0);
+ deck.addFlashCard("Card1", "Card1");
+ deck.addFlashCard("Card2", "Card2");
+ AnswerList answerList = new AnswerList(deck);
+ answerList.addAnswer("W2", 0);
+ answerList.addAnswer("W", 1);
+ TestHistory testHistory = new TestHistory(deckManager);
+ testHistory.addAnswerList(answerList);
+ assertEquals(testHistory.prepareViewTest(0), "For test 1: Test\n"
+ + "You scored 0 out of 2\n"
+ + "That is 0.00%!");
+ deckManager.deleteDeck(deck);
+ }
+
+ @Test
+ void testPrepareViewTest_all_expectResultMessage() throws DeckNotExistException {
+ deckManager.prepareToAddDeck("Test");
+ deckManager.prepareToAddDeck("Test2");
+
+ Deck deck = deckManager.getDeck(0);
+ deck.addFlashCard("Card1", "Card1");
+ Deck deckTwo = deckManager.getDeck(1);
+ deckTwo.addFlashCard("Card2", "Card2");
+
+ AnswerList answerList = new AnswerList(deck);
+ answerList.addAnswer("W2", 0);
+ AnswerList answerListTwo = new AnswerList(deckTwo);
+ answerListTwo.addAnswer("W", 0);
+
+ TestHistory testHistory = new TestHistory(deckManager);
+ testHistory.addAnswerList(answerList);
+ testHistory.addAnswerList(answerListTwo);
+
+ assertEquals("These are your scores:\n"
+ + "Score for test 1: Test 0/1 0.00%\n"
+ + "Score for test 2: Test2 0/1 0.00%",
+ testHistory.prepareViewTest(-1).trim());
+
+ deckManager.deleteDeck(deck);
+ deckManager.deleteDeck(deckTwo);
+ }
+
+ @Test
+ void testViewAllFlashcardStats_testedOnce_expectResultMessage() throws DeckNotExistException {
+ deckManager.prepareToAddDeck("Test");
+ deckManager.prepareToAddDeck("Test2");
+
+ Deck deck = deckManager.getDeck(0);
+ deck.addFlashCard("Card1", "Card1", 0, 1);
+ Deck deckTwo = deckManager.getDeck(1);
+ deckTwo.addFlashCard("Card2", "Card2", 1, 1);
+
+ AnswerList answerList = new AnswerList(deck);
+ answerList.addAnswer("W2", 0);
+ AnswerList answerListTwo = new AnswerList(deckTwo);
+ answerListTwo.addAnswer("Card2", 0);
+
+ TestHistory testHistory = new TestHistory(deckManager);
+ testHistory.addAnswerList(answerList);
+ testHistory.addAnswerList(answerListTwo);
+
+ assertEquals("Listing total scores of flashcards for all tests:\n"
+ + deck.getCard(0).returnFlashCard()
+ + "\nScore: 0 out of 1\n"
+ + deckTwo.getCard(0).returnFlashCard()
+ + "\nScore: 1 out of 1\n", testHistory.viewAllFlashcardStats());
+
+ deckManager.deleteDeck(deck);
+ deckManager.deleteDeck(deckTwo);
+ }
+
+ @Test
+ void testPrepareViewTest_all_expectDeckNotExistException() {
+ TestHistory testHistory = new TestHistory(deckManager);
+ assertThrows(DeckNotExistException.class, () -> testHistory.prepareViewTest(-1));
+ }
+
+ @Test
+ void testPrepareViewTest_noAnswerList_expectOutOfBoundsException() {
+ TestHistory testHistory = new TestHistory(deckManager);
+ assertThrows(IndexOutOfBoundsException.class, () -> testHistory.prepareViewTest(1));
+ }
+}
diff --git a/src/test/java/seedu/cardli/testing/TestManagerTest.java b/src/test/java/seedu/cardli/testing/TestManagerTest.java
new file mode 100644
index 0000000000..917b4ec533
--- /dev/null
+++ b/src/test/java/seedu/cardli/testing/TestManagerTest.java
@@ -0,0 +1,178 @@
+package seedu.cardli.testing;
+
+import org.junit.jupiter.api.Test;
+import seedu.cardli.exceptions.DeckNotExistException;
+import seedu.cardli.exceptions.EmptyDeckException;
+import seedu.cardli.flashcard.Deck;
+import seedu.cardli.flashcard.DeckManager;
+import seedu.cardli.ui.TestUi;
+
+import java.io.ByteArrayInputStream;
+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.cardli.ui.TestUi.DECK_NOT_EXIST_MESSAGE;
+import static seedu.cardli.ui.TestUi.END_REVIEW_MESSAGE;
+import static seedu.cardli.ui.TestUi.END_TEST_MESSAGE;
+import static seedu.cardli.ui.TestUi.INCORRECT_INPUT_FORMAT_MESSAGE;
+import static seedu.cardli.ui.TestUi.NO_CARDS_TO_REVIEW_MESSAGE;
+import static seedu.cardli.ui.TestUi.NO_CARDS_TO_TEST_MESSAGE;
+
+class TestManagerTest {
+
+ DeckManager deckManager = new DeckManager();
+ TestHistory testHistory = new TestHistory(deckManager);
+
+ @Test
+ void testAllCardsShuffled_emptyDeck_expectEmptyDeckException() {
+ Deck deck = new Deck("Test");
+ AnswerList answerList = new AnswerList(deck);
+ TestManager testManager = new TestManager(testHistory, deckManager);
+ assertThrows(EmptyDeckException.class, () -> testManager.prepareTestDeck(answerList));
+ }
+
+ @Test
+ void testStartReview_emptyDeck_expectCaughtEmptyDeckMessage() {
+ deckManager.prepareToAddDeck("Test");
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream("0".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ testManager.startReview();
+
+ String outputText = byteArrayOutputStream.toString().trim();
+ String actual = outputText.substring(outputText.length() - NO_CARDS_TO_REVIEW_MESSAGE.length()).trim();
+ assertEquals(actual, NO_CARDS_TO_REVIEW_MESSAGE);
+ deckManager.deleteDeck(0);
+ }
+
+ @Test
+ void testStartReview_noDeck_expectCaughtIndexOutOfBoundsMessage() {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream("110852".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ testManager.startReview();
+
+ String outputText = byteArrayOutputStream.toString().trim();
+ String actual = outputText.substring(outputText.length() - DECK_NOT_EXIST_MESSAGE.length()).trim();
+ assertEquals(actual, DECK_NOT_EXIST_MESSAGE);
+ }
+
+ @Test
+ void testStartReview_invalidInputFormat_expectCaughtInvalidFormatMessage() {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream("asjdaldkallaskdj".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ testManager.startReview();
+
+ String outputText = byteArrayOutputStream.toString().trim();
+ String actual = outputText.substring(outputText.length() - INCORRECT_INPUT_FORMAT_MESSAGE.length()).trim();
+ assertEquals(actual, INCORRECT_INPUT_FORMAT_MESSAGE);
+ }
+
+ @Test
+ void testStartReview_CorrectIncorrectAndNoAnswers_expectEndTestMessage() throws DeckNotExistException {
+ deckManager.prepareToAddDeck("Test");
+ Deck deck = deckManager.getDeck(0);
+ deck.addFlashCard("Card1", "Card1", 3, 5);
+ deck.addFlashCard("Card2", "Card2", 2, 5);
+ deck.addFlashCard("Card3", "Card3", 0, 5);
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(
+ "1\n/next\n/back\nCard2\n/back\nWrong".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ String outputText = testManager.startReview().trim();
+ String actual = outputText.substring(outputText.length() - END_REVIEW_MESSAGE.length()).trim();
+ assertEquals(actual, END_REVIEW_MESSAGE);
+ deckManager.deleteDeck(deck);
+ }
+
+ @Test
+ void testStartTest_emptyDeck_expectCaughtEmptyDeckMessage() {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream("0".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ testManager.startTest();
+
+ String outputText = byteArrayOutputStream.toString().trim();
+ String actual = outputText.substring(outputText.length() - NO_CARDS_TO_TEST_MESSAGE.length()).trim();
+ assertEquals(actual, NO_CARDS_TO_TEST_MESSAGE);
+ }
+
+ @Test
+ void testStartTest_noDeck_expectCaughtIndexOutOfBoundsMessage() {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream("110852".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ testManager.startTest();
+
+ String outputText = byteArrayOutputStream.toString().trim();
+ String actual = outputText.substring(outputText.length() - DECK_NOT_EXIST_MESSAGE.length()).trim();
+ assertEquals(actual, DECK_NOT_EXIST_MESSAGE);
+ }
+
+ @Test
+ void testStartTest_invalidInputFormat_expectCaughtInvalidFormatMessage() {
+ ByteArrayInputStream inputStream = new ByteArrayInputStream("call".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ testManager.startTest();
+
+ String outputText = byteArrayOutputStream.toString().trim();
+ String actual = outputText.substring(outputText.length() - INCORRECT_INPUT_FORMAT_MESSAGE.length()).trim();
+ assertEquals(actual, INCORRECT_INPUT_FORMAT_MESSAGE);
+ }
+
+ @Test
+ void testStartTest_CorrectIncorrectAndNoAnswers_expectEndTestMessage() throws DeckNotExistException {
+ deckManager.prepareToAddDeck("Test");
+ Deck deck = deckManager.getDeck(0);
+ deck.addFlashCard("Card1", "Card1");
+ deck.addFlashCard("Card2", "Card2");
+ deck.addFlashCard("Card3", "Card3");
+
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(
+ "1\n/next\n/back\nCard1\n/back\nWrong\n\n".getBytes());
+ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(byteArrayOutputStream);
+ TestUi testUi = new TestUi(inputStream, ps);
+
+ TestManager testManager = new TestManager(testHistory, deckManager, testUi);
+
+ String outputText = testManager.startTest();
+ String actual = outputText.substring(outputText.length() - END_TEST_MESSAGE.length()).trim();
+ assertEquals(actual, END_TEST_MESSAGE);
+ deckManager.deleteDeck(deck);
+ }
+}
\ No newline at end of file
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 892cb6cae7..d4b3067226 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,9 +1,18 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
+Welcome to
-What is your name?
-Hello James Gosling
+ .----------------. .----------------. .----------------. .----------------. .----------------. .----------------.
+| .--------------. || .--------------. || .--------------. || .--------------. || .--------------. || .--------------. |
+| | ______ | || | __ | || | _______ | || | ________ | || | _____ | || | _____ | |
+| | .' ___ | | || | / \ | || | |_ __ \ | || | |_ ___ `. | || | |_ _| | || | |_ _| | |
+| | / .' \_| | || | / /\ \ | || | | |__) | | || | | | `. \ | || | | | | || | | | | |
+| | | | | || | / ____ \ | || | | __ / | || | | | | | | || | | | _ | || | | | | |
+| | \ `.___.'\ | || | _/ / \ \_ | || | _| | \ \_ | || | _| |___.' / | || | _| |__/ | | || | _| |_ | |
+| | `._____.' | || ||____| |____|| || | |____| |___| | || | |________.' | || | |________| | || | |_____| | |
+| | | || | | || | | || | | || | | || | | |
+| '--------------' || '--------------' || '--------------' || '--------------' || '--------------' || '--------------' |
+ '----------------' '----------------' '----------------' '----------------' '----------------' '----------------'
+
+Let's get started!
+Type in "help" for more details.
+Exiting
+ You did well today! Goodbye!
diff --git a/text-ui-test/data/Cards_CardLI.json b/text-ui-test/data/Cards_CardLI.json
new file mode 100644
index 0000000000..0637a088a0
--- /dev/null
+++ b/text-ui-test/data/Cards_CardLI.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/text-ui-test/data/Tests_CardLi.json b/text-ui-test/data/Tests_CardLi.json
new file mode 100644
index 0000000000..0637a088a0
--- /dev/null
+++ b/text-ui-test/data/Tests_CardLi.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/text-ui-test/desktop.ini b/text-ui-test/desktop.ini
new file mode 100644
index 0000000000..d957fd188d
--- /dev/null
+++ b/text-ui-test/desktop.ini
@@ -0,0 +1,4 @@
+[ViewState]
+Mode=
+Vid=
+FolderType=Generic
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index f6ec2e9f95..0abaeaa993 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -1 +1 @@
-James Gosling
\ No newline at end of file
+bye
\ No newline at end of file