invalidVideoNames = new ArrayList<>();
+ for (VideoName each: this.targetVideoNames) {
+ if (!(model.hasVideo(this.moduleCode, this.lectureName, each))) {
+ invalidVideoNames.add(each);
+ }
+ }
+
+ if (invalidVideoNames.size() == 0) {
+ VideoEditInfo[] editedVideos = new VideoEditInfo[this.targetVideoNames.size()];
+
+ for (int i = 0; i < this.targetVideoNames.size(); i++) {
+ VideoName videoName = this.targetVideoNames.get(i);
+ DeleteVideoCommand dvc = new DeleteVideoCommand(this.moduleCode, this.lectureName, videoName);
+ CommandResult r = dvc.execute(model);
+
+ editedVideos[i] = r.getVideoEditInfoList().get(0);
+ }
+
+ return new CommandResult(String.format(MESSAGE_SUCCESS,
+ targetVideoNames.size(),
+ this.moduleCode,
+ this.lectureName,
+ MultipleEventsParser.convertArrayListToString(targetVideoNames)),
+ editedVideos);
+ } else {
+ throw new CommandException(String.format((
+ invalidVideoNames.size() == 1
+ ? Messages.MESSAGE_VIDEO_DOES_NOT_EXIST
+ : Messages.MESSAGE_VIDEOS_DO_NOT_EXIST
+ ),
+ MultipleEventsParser.convertArrayListToString(invalidVideoNames),
+ this.moduleCode,
+ this.lectureName));
+ }
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof DeleteMultipleVideosCommand) {
+ DeleteMultipleVideosCommand dmvc = (DeleteMultipleVideosCommand) other;
+ return this.moduleCode.equals(dmvc.moduleCode)
+ && this.lectureName.equals(dmvc.lectureName)
+ && this.targetVideoNames.equals(dmvc.targetVideoNames);
+ } else {
+ return false;
+ }
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/delete/DeleteVideoCommand.java b/src/main/java/seedu/address/logic/commands/delete/DeleteVideoCommand.java
new file mode 100644
index 00000000000..8971638957e
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/delete/DeleteVideoCommand.java
@@ -0,0 +1,92 @@
+package seedu.address.logic.commands.delete;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LECTURE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+
+import seedu.address.commons.core.Messages;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.CommandResult.VideoEditInfo;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.lecture.LectureName;
+import seedu.address.model.lecture.ReadOnlyLecture;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.video.Video;
+import seedu.address.model.video.VideoName;
+
+/**
+ * Deletes a video identified using its name, within a lecture, within a module
+ */
+public class DeleteVideoCommand extends DeleteCommand {
+
+ public static final String MESSAGE_USAGE =
+ "(3) Deletes one or more videos from a lecture.\n"
+ + "Parameters: {video_name_1}[, {video_name_2}[, ...]] " + PREFIX_MODULE + " {module_code} "
+ + PREFIX_LECTURE + " {lecture_name}\n"
+ + "Example: " + COMMAND_WORD + " Video 01 " + PREFIX_MODULE + " CS2102 " + PREFIX_LECTURE + " Lecture 01";
+
+ public static final String MESSAGE_DELETE_VIDEO_SUCCESS = "Deleted Video: %1$s from Lecture %2$s in Module %3$s";
+
+ private final ModuleCode moduleCode;
+ private final LectureName lectureName;
+ private final VideoName targetVideoName;
+
+ /**
+ * Creates a Delete Video Command executable that deletes a video with {@code targetVideoName}
+ * from lecture with {@code lectureName} in module of {@code moduleCode}
+ *
+ * @param targetVideoName Name of Video to delete
+ * @param moduleCode Module Code of module that contains lecture that video is within
+ * @param lectureName Name of Lecture that video is within
+ */
+ public DeleteVideoCommand(ModuleCode moduleCode, LectureName lectureName, VideoName targetVideoName) {
+ this.moduleCode = moduleCode;
+ this.lectureName = lectureName;
+ this.targetVideoName = targetVideoName;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ // TODO: could try to encapsulate this to reuse
+ requireNonNull(model);
+
+ if (!model.hasModule(moduleCode)) {
+ throw new CommandException(String.format(Messages.MESSAGE_MODULE_DOES_NOT_EXIST, moduleCode));
+ }
+
+ if (!model.hasLecture(moduleCode, lectureName)) {
+ throw new CommandException(String.format(Messages.MESSAGE_LECTURE_DOES_NOT_EXIST, lectureName, moduleCode));
+ }
+
+ ReadOnlyLecture lecture = model.getLecture(moduleCode, lectureName);
+
+ if (!model.hasVideo(moduleCode, lectureName, targetVideoName)) {
+ throw new CommandException(String.format(Messages.MESSAGE_VIDEO_DOES_NOT_EXIST,
+ targetVideoName,
+ lectureName,
+ moduleCode));
+ }
+
+ Video targetVideo = model.getVideo(moduleCode, lectureName, targetVideoName);
+
+ // TODO: repetition ends here
+
+ model.deleteVideo(lecture, targetVideo);
+
+ return new CommandResult(String.format(MESSAGE_DELETE_VIDEO_SUCCESS,
+ targetVideoName,
+ lectureName,
+ moduleCode),
+ new VideoEditInfo(moduleCode, lectureName, targetVideo, null));
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other == this
+ || (other instanceof DeleteVideoCommand
+ && moduleCode.equals(((DeleteVideoCommand) other).moduleCode)
+ && lectureName.equals(((DeleteVideoCommand) other).lectureName)
+ && targetVideoName.equals(((DeleteVideoCommand) other).targetVideoName));
+ }
+}
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditCommand.java
new file mode 100644
index 00000000000..93130c8d440
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditCommand.java
@@ -0,0 +1,144 @@
+package seedu.address.logic.commands.edit;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_CONTEXT_USAGE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_CODE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_LECTURE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_MODULE;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_NAME;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TAG;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_TIMESTAMP;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_UNWATCH;
+import static seedu.address.logic.parser.CliSyntax.PREFIX_WATCH;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.Command;
+import seedu.address.model.tag.Tag;
+
+/**
+ * Edits the details of a module, lecture, or video in the tracker.
+ */
+public abstract class EditCommand extends Command {
+
+ /** The command word for the edit command. */
+ public static final String COMMAND_WORD = "edit";
+
+ /** A message describing how the edit command can be used. */
+ public static final String MESSAGE_USAGE = "\n" + COMMAND_WORD + ":\n"
+ + "(1) Edits the details of a module in the tracker.\n"
+ + "Parameters: "
+ + "{module_code} "
+ + "[" + PREFIX_CODE + " {updated_code}] "
+ + "[" + PREFIX_NAME + " {updated_name}] "
+ + "[" + PREFIX_TAG + " {tag_1}[, {tag_2}[, ...]]]\n"
+ + "Example: " + COMMAND_WORD + " CS2040S "
+ + PREFIX_CODE + " CS2040 "
+ + PREFIX_NAME + " Data Structures and Algorithms "
+ + PREFIX_TAG + " Heavy, Math, Analysis\n\n"
+ + "(2) Edits the details of a lecture in a module.\n"
+ + "Parameters: "
+ + "{lecture_name} "
+ + PREFIX_MODULE + " {module_code} "
+ + "[" + PREFIX_NAME + " {updated_name}] "
+ + "[" + PREFIX_TAG + " {tag_1}[, {tag_2}[, ...]]]\n"
+ + "Example: " + COMMAND_WORD + " Week 1 "
+ + PREFIX_MODULE + " CS2040S "
+ + PREFIX_NAME + " Week 01 Introduction "
+ + PREFIX_TAG + " Intro, Important\n\n"
+ + "(3) Edits the details of a video in a lecture.\n"
+ + "Parameters: "
+ + "{video_name} "
+ + PREFIX_MODULE + " {module_code} "
+ + PREFIX_LECTURE + " {lecture_name} "
+ + "[" + PREFIX_NAME + " {updated_name}] "
+ + "[" + PREFIX_TIMESTAMP + " {updated_timestamp}] "
+ + "[" + PREFIX_WATCH + "] "
+ + "[" + PREFIX_UNWATCH + "] "
+ + "[" + PREFIX_TAG + " {tag_1}[, {tag_2}[, ...]]]\n"
+ + "Example: " + COMMAND_WORD + " Video 1 "
+ + PREFIX_MODULE + " CS2040S "
+ + PREFIX_LECTURE + " Week 1 "
+ + PREFIX_NAME + " Video 01 Grade Breakdown "
+ + PREFIX_WATCH + " "
+ + PREFIX_TAG + " Intro, Short\n\n"
+ + MESSAGE_CONTEXT_USAGE;
+
+ /** The error message for when no fields to edit is provided. */
+ public static final String MESSAGE_NOT_EDITED = "At least one field to edit must be provided.";
+
+ /**
+ * Stores the details to edit the entity (module, lecture, or video) with.
+ * Each non-empty field value will replace the corresponding field value of the entity.
+ */
+ public abstract static class EditEntityDescriptor {
+ private Set tags;
+
+ /**
+ * Constructs an {@code EditEntityDescriptor}.
+ */
+ public EditEntityDescriptor() {}
+
+ /**
+ * Copy constructor.
+ *
+ * @param toCopy The {@code EditEntityDescriptor} to copy.
+ */
+ public EditEntityDescriptor(EditEntityDescriptor toCopy) {
+ requireNonNull(toCopy);
+
+ setTags(toCopy.tags);
+ }
+
+ /**
+ * If {@code tags} is non-null, returns an {@code Optional} containing an immutable tag set, which throws
+ * {@code UnsupportedOperationException} if modification is attempted.
+ *
+ * Else, returns an {@code Optional#empty()}.
+ *
+ * @return An {@code Optional} containing an immutable tag set if {@code tags} is non-null. Otherwise,
+ * {@code Optional#empty()}.
+ */
+ public Optional> getTags() {
+ return tags == null ? Optional.empty() : Optional.of(Collections.unmodifiableSet(tags));
+ }
+
+ /**
+ * Replace the elements in this object's {@code tags} with the elements in {@code newTags}.
+ *
+ * @param newTags The new tags.
+ */
+ public void setTags(Set newTags) {
+ this.tags = newTags == null ? null : new HashSet<>(newTags);
+ }
+
+ /**
+ * Returns true if at least one field is edited.
+ *
+ * @return True if at least one field is edited. Otherwise, false.
+ */
+ public boolean isAnyFieldEdited() {
+ return CollectionUtil.isAnyNonNull(tags);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EditEntityDescriptor)) {
+ return false;
+ }
+
+ EditEntityDescriptor descriptor = (EditEntityDescriptor) other;
+
+ return getTags().equals(descriptor.getTags());
+ }
+ }
+
+}
diff --git a/src/main/java/seedu/address/logic/commands/edit/EditLectureCommand.java b/src/main/java/seedu/address/logic/commands/edit/EditLectureCommand.java
new file mode 100644
index 00000000000..6c3c3372acd
--- /dev/null
+++ b/src/main/java/seedu/address/logic/commands/edit/EditLectureCommand.java
@@ -0,0 +1,199 @@
+package seedu.address.logic.commands.edit;
+
+import static java.util.Objects.requireNonNull;
+import static seedu.address.commons.core.Messages.MESSAGE_LECTURE_DOES_NOT_EXIST;
+import static seedu.address.commons.core.Messages.MESSAGE_MODULE_DOES_NOT_EXIST;
+import static seedu.address.commons.util.CollectionUtil.requireAllNonNull;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+import seedu.address.commons.util.CollectionUtil;
+import seedu.address.logic.commands.CommandResult;
+import seedu.address.logic.commands.CommandResult.LectureEditInfo;
+import seedu.address.logic.commands.exceptions.CommandException;
+import seedu.address.model.Model;
+import seedu.address.model.lecture.Lecture;
+import seedu.address.model.lecture.LectureName;
+import seedu.address.model.lecture.ReadOnlyLecture;
+import seedu.address.model.module.ModuleCode;
+import seedu.address.model.module.ReadOnlyModule;
+import seedu.address.model.tag.Tag;
+import seedu.address.model.video.Video;
+
+/**
+ * Edits the details of a lecture in a module.
+ */
+public class EditLectureCommand extends EditCommand {
+
+ /** The message for when a {@code Lecture} is successfully edited. */
+ public static final String MESSAGE_SUCCESS = "Edited lecture of module %s: %s";
+
+ /** The error message for when a duplicate {@code Lecture} is detected. */
+ public static final String MESSAGE_DUPLICATE_LECTURE = "This lecture already exists in %s.";
+
+ private final ModuleCode moduleCode;
+ private final LectureName lectureName;
+ private final EditLectureDescriptor editLectureDescriptor;
+
+ /**
+ * Constructs an {@code EditLectureCommand} to edit the details of a lecture whose name is {@code lectureName} in
+ * the module whose code is {@code moduleCode}.
+ *
+ * @param moduleCode The code of the module containing the lecture.
+ * @param lectureName The name of the lecture to be edited.
+ * @param editLectureDescriptor The details to edit the lecture with.
+ */
+ public EditLectureCommand(ModuleCode moduleCode, LectureName lectureName,
+ EditLectureDescriptor editLectureDescriptor) {
+
+ requireAllNonNull(moduleCode, lectureName, editLectureDescriptor);
+
+ this.moduleCode = moduleCode;
+ this.lectureName = lectureName;
+ this.editLectureDescriptor = editLectureDescriptor;
+ }
+
+ @Override
+ public CommandResult execute(Model model) throws CommandException {
+ requireNonNull(model);
+
+ ReadOnlyModule module = getModuleContainingLecture(model);
+ ReadOnlyLecture lectureToEdit = getLectureToEdit(module);
+ Lecture editedLecture = createEditedLecture(lectureToEdit);
+
+ validateLectureIsNotDuplicate(module, lectureToEdit, editedLecture);
+
+ model.setLecture(module, lectureToEdit, editedLecture);
+
+ return createSuccessResult(lectureToEdit, editedLecture);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+
+ if (!(other instanceof EditLectureCommand)) {
+ return false;
+ }
+
+ EditLectureCommand otherCommand = (EditLectureCommand) other;
+
+ return moduleCode.equals(otherCommand.moduleCode)
+ && lectureName.equals(otherCommand.lectureName)
+ && editLectureDescriptor.equals(otherCommand.editLectureDescriptor);
+ }
+
+ private ReadOnlyModule getModuleContainingLecture(Model model) throws CommandException {
+ requireNonNull(model);
+
+ if (!model.hasModule(moduleCode)) {
+ throw new CommandException(String.format(MESSAGE_MODULE_DOES_NOT_EXIST, moduleCode));
+ }
+
+ return model.getModule(moduleCode);
+ }
+
+ private ReadOnlyLecture getLectureToEdit(ReadOnlyModule module) throws CommandException {
+ requireNonNull(module);
+
+ if (!module.hasLecture(lectureName)) {
+ throw new CommandException(String.format(MESSAGE_LECTURE_DOES_NOT_EXIST, lectureName, moduleCode));
+ }
+
+ return module.getLecture(lectureName);
+ }
+
+ private Lecture createEditedLecture(ReadOnlyLecture lectureToEdit) {
+ requireNonNull(lectureToEdit);
+
+ LectureName updatedName = editLectureDescriptor.getName().orElse(lectureToEdit.getName());
+ Set updatedTags = editLectureDescriptor.getTags().orElse(lectureToEdit.getTags());
+
+ List