# Client.js

    const request = require("./request.js");

    const API_BASE_URL = "api.showmyhomework.co.uk";
    const SATCHELONE = "https://www.satchelone.com"

    const CLIENTID_WEB = "55283c8c45d97ffd88eb9f87e13f390675c75d22b4f2085f43b0d7355c1f";
    const CLIENTSECRET_WEB = "c8f7d8fcd0746adc50278bc89ed6f004402acbbf4335d3cb12d6ac6497d3";

    const Attachment = require("./structures/Attachment.js");
    const ClassGroup = require("./structures/ClassGroup.js");
    const ClientAuthentication = require("./structures/ClientAuthentication.js");
    const ClientEvent = require("./structures/ClientEvent.js");
    const Employee = require("./structures/Employee.js");
    const FlexibleTask = require("./structures/FlexibleTask.js");
    const FlexibleTaskSubmission = require("./structures/FlexibleTaskSubmission.js")
    const Homework = require("./structures/Homework.js");
    const HomeworkSubmission = require("./structures/HomeworkSubmission.js");
    const Quiz = require("./structures/Quiz.js");
    const QuizQuestion = require("./structures/QuizQuestion.js");
    const QuizSubmission = require("./structures/QuizSubmission.js");
    const QuizSubmissionComment = require("./structures/QuizSubmissionComment.js");
    const QuizSubmissionQuestion = require("./structures/QuizSubmissionQuestion.js");
    const School = require("./structures/School.js");
    const SchoolPrivateInformation = require("./structures/SchoolPrivateInformation.js");
    const StandardSubject = require("./structures/StandardSubject.js");
    const Student = require("./structures/Student.js");
    const SubmissionMethod = require("./structures/SubmissionMethod.js");
    const SubmissionComment = require("./structures/SubmissionComment.js");
    const SubmissionEvent = require("./structures/SubmissionEvent.js");
    const Task = require("./structures/Task.js");
    const User = require("./structures/User.js");
    const UserPrivateInformation = require("./structures/UserPrivateInformation.js");

    /**
     * Represents access to SatchelOne's SMHW API.
     */
    class SMHWClient {
        /**
         * Instantiate a SMHW Client.
         * @param {Object} options Options to instantiate the client with.
         */
        constructor (options = {}) {
            /**
             * Options for the client.
             * @type {Object}
             * @private
             */
            this._options = options;

            /**
             * Authentication information for the client.
             * @type {ClientAuthentication}
             * @private
             */
            this._auth = null;

            /**
             * The active client heartbeat.
             * @type {Number}
             * @private
             */
            this._heartbeat = null;

            /**
             * The timestamp of the last heartbeat.
             * @type {Number}
             * @private
             */
            this._last_heartbeat = null;

            /**
             * The student user of the client.
             * @type {Student}
             */
            this.student = null;

            /**
             * The school that the student user of the client belongs to.
             * @type {School}
             */
            this.school = null;

            /**
             * Private information of the client user.
             * @type {UserPrivateInformation}
             */
            this.user = null;
        }

        /**
         * Keep a client connection alive.
         * @returns {Promise}
         * @ignore
         */
        beat() {
            return new Promise((resolve, reject) => {
                this.make("GET", "api/heartbeat", {
                    referer: "/calendar/personal"
                }).then(response => {
                    this._last_heartbeat = response.time;

                    resolve(response);
                }).catch(reject);
            });
        }

        /**
         * @typedef RequestOptions
         * @property {String} [host] The base hostname for the request.
         * @property {Number} [port] The port of the request.
         * @property {Object} [query] The querystring options for the request.
         * @property {Object} [body] The post data body of the request.
         * @property {Object} [headers] The headers for the request.
         * @property {String} [referer] The referer path for the request.
         * @property {Object} [payload] The JSON payload for the request.
         */

        /**
         * Make a normal request or an authorised one if authentication is available.
         * @param {String} method The method of request to use.
         * @param {String} path The path of the resource to make a request to.
         * @param {RequestOptions} options Options for the request.
         * @returns {Promise<Object>}
         * @private
         * @ignore
         */
        make(method, path, options = {}) {
            if (!options.headers) {
                options.headers = {};
            }

            if (this._auth) {
                options.headers.authorization = "Bearer " + this._auth.access_token;
            }

            return request.make(method, path, options);
        }

        /**
         * Log in to a SMHW account for an access token to make authorised requests to SMHW.
         * @param {Number} school_id The ID of the school of the account to log in to.
         * @param {String} username The username of the account to log in to.
         * @param {String} password The password of the account to log in to. (Not stored)
         * @returns {Promise}
         */
        login(school_id, username, password) {
            return new Promise((resolve, reject) => {
                this.make("POST", "/oauth/token", {
                    body: {
                        grant_type: "password",
                        username: username,
                        password: password,
                        school_id: school_id
                    },
                    query: {
                        client_id: CLIENTID_WEB,
                        client_secret: CLIENTSECRET_WEB
                    },
                    headers: {
                        "accept": "*/*"
                    },
                    referer: "/login"
                }).then(response => {
                    if (response.errors) {
                        reject(response.errors);
                        return;
                    }

                    this._auth = new ClientAuthentication(response);

                    this.make("GET", "/api/students/" + this._auth.user_id, {
                        query: {
                            include: "user_private_info,school"
                        }
                    }).then(response => {
                        this.student = new Student(this, response.student);
                        this.school = new School(this, response.schools[0]);
                        this.user = new UserPrivateInformation(this, response.user_private_infos[0]);

                        resolve(true);

                        if (this.options.keep_heartbeat) {
                            this._heartbeat = setInterval(_ => {
                                this.beat();
                            });
                        }
                    }).catch(reject);
                }).catch(reject);
            });
        }

        /**
         * Stop a continuous client connection.
         */
        stopHeartbeat() {
            if (this._heartbeat) {
                clearInterval(this._heartbeat);
            }
        }

        /**
         * Get submission methods on SMHW.
         * @param {Array<Number>} ids An array of submission method IDs to retrieve.
         * @returns {Promise<Array<SubmissionMethod>>}
         */
        getSubmissionMethods(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getSubmissionMethods([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/submission_methods").then(response => {
                    if (response.sumbission_methods) {
                        if (ids) {
                            var sumbission_methods = response.submission_methods.filter(submission_method => ids.indexOf(submission_method.id) !== -1);

                            resolve(submission_methods.map(submission_method => new SubmissionMethod(this, submission_method)));
                        } else {
                            resolve(response.submission_methods.map(submission_method => new SubmissionMethod(this, submission_method)));
                        }
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get standard subjects on SMHW.
         * @param {Array<Number>} ids An array of standard subject IDs to retrieve.
         * @returns {Promise<Array<StandardSubject>>}
         */
        getStandardSubjects(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getStandardSubjects([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/standard_subjects").then(response => {
                    if (response.standard_subjects) {
                        if (ids) {
                            var standard_subjects = response.standard_subjects.filter(standard_subject => ids.indexOf(standard_subject.id) !== -1);

                            resolve(standard_subjects.map(standard_subject => new StandardSubject(this, standard_subject)));
                        } else {
                            resolve(response.standard_subjects.map(standard_subject => new StandardSubject(this, standard_subject)));
                        }
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a school by its ID.
         * @param {Number} id The ID of the school to retrieve.
         * @returns {Promise<School>}
         */
        getSchool(id) {
            return new Promise((resolve, reject) => {
                this.make("GET", "/api/schools/" + id).then(response => {
                    if (response.school) {
                        resolve(new School(this.client, response.school));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        getSchoolPrivateInformation(id) {
            return new Promise((resolve, reject) => {
                this.make("GET", "/api/school_private_infos/" + id).then(response => {
                    if (response.school_private_info) {
                        resolve(new SchoolPrivateInformation(this.client, response.school_private_info));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get client events fired for the client user.
         * @param {Object} options Event request options.
         * @param {Array<Number>} ids An array of event IDs to retrieve.
         * @returns {Promise<Array<ClientEvent>>}
         */
        getOwnEvents(options, ids) {
            if (!options) {
                return this.getOwnEvents({}, ids);
            }

            if (ids && !Array.isArray(ids)) {
                return this.getOwnEvents(options, [ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/events", {
                    query: {
                        limit: options.limit || 21,
                        offset: options.offset || 0,
                        recipient_id: options.recipient_id || this.user.id,
                        visible: typeof options.visible === "undefined" ? true : options.visible
                    }
                }).then(response => {
                    if (response.events) {
                        if (ids) {
                            var events = response.events.filter(event => ids.indexOf(event.id) !== -1);

                            resolve(events.map(event => new ClientEvent(this, event)));
                        } else {
                            resolve(response.events.map(event => new ClientEvent(this, event)));
                        }
                    } else {
                        reject(response);
                    }
                })
            });
        }

        /**
         * Get client events.
         * @param {Array<Number>} ids An array of Event IDs to retrieve.
         * @returns {Promise<Array<ClientEvent>>}
         */
        getEvents(ids) {
            if (!ids) {
                return this.getOwnEvents();
            } else if (!Array.isArray(ids)) {
                return this.getEvents([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/events", {
                    query: {
                        ids: ids
                    }
                }).then(response => {
                    if (response.events) {
                        if (ids) {
                            var events = response.events.filter(event => ids.indexOf(event.id) !== -1);

                            resolve(events.map(event => new ClientEvent(this, event)));
                        } else {
                            resolve(response.events.map(event => new ClientEvent(this, event)));
                        }
                    } else {
                        reject(response);
                    }
                })
            });
        }

        /**
         * Get a client event by its ID.
         * @param {Number} id The ID of the event to retrieve.
         * @returns {Promise<ClientEvent>}
         */
        getEvent(id) {
            return new Promise((resolve, reject) => {
                this.getEvents([id]).then(events => {
                    resolve(events[0]);
                }).catch(reject);
            });
        }

        /**
         * Get submission events by their IDs.
         * @param {Array<Number>} ids An array of submission event IDs to retrieve.
         * @returns {Promise<Array<SubmissionEvents>>}
         */
        getSubmissionEvents(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getSubmissionEvents([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/submission_events", {
                    query: {
                        ids: ids
                    }
                }).then(response => {
                    if (response.submission_events) {
                        resolve(response.submission_events.map(submission_event => new SubmissionEvent(this, submission_event)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a submission event by its ID.
         * @param {Number} id The ID of the submission event to retrieve.
         * @returns {Promise<SubmissionEvent>}
         */
        getSubmissionEvent(id) {
            return new Promise((resolve, reject) => {
                this.getSubmissionEvents([id]).then(submission_events => {
                    resolve(submission_events[0]);
                }).catch(reject);
            });
        }

        /**
         * Get tasks set.
         * @param {Array<Number>} ids An array of task IDs to retrieve.
         * @returns {Promise<Array<Task>>}
         */
        getTasks(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getTasks([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/todos").then(response => {
                    if (response.todos) {
                        if (ids) {
                            var todos = response.todos.filter(todo => ids.indexOf(todo.id) !== -1);

                            resolve(todos.map(todo => new Task(this, todo)));
                        } else {
                            resolve(response.todos.map(todo => new Task(this, todo)));
                        }
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a task by its ID.
         * @param {Number} id The ID of the task to retrieve.
         * @returns {Promise<Task>}
         */
        getTask(id) {
            return new Promise((resolve, reject) => {
                this.getTasks([id]).then(tasks => {
                    resolve(tasks[0]);
                }).catch(reject);
            });
        }

        /**
         * Get students by their IDs.
         * @param {Array<Number>} ids An array of Student IDs to retrieve.
         * @returns {Promise<Array<Student>>}
         */
        getStudents(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getStudents([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/students", {
                    query: {
                        ids: ids
                    }
                }).then(response => {
                    if (response.students) {
                        resolve(response.students.map(student => new Student(this, student)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a student by its ID.
         * @param {Number} id The ID of the student to retrieve.
         * @returns {Promise<Student>}
         */
        getStudent(id) {
            return new Promise((resolve, reject) => {
                this.getStudents([id]).then(students => {
                    resolve(students[0]);
                }).catch(reject);
            });
        }

        /**
         * Get employees by their IDs.
         * @param {Array<Number>} ids An array of employee IDs to retrieve.
         * @returns {Promise<Array<Employee>>}
         */
        getEmployees(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getEmployees([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/employees", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.employees) {
                        resolve(response.employees.map(employee => new Employee(this, employee)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get an employee by its ID.
         * @param {Number} id The ID of the employee to retrieve.
         * @returns {Promise<Employee>}
         */
        getEmployee(id) {
            return new Promise((resolve, reject) => {
                this.getEmployees([id]).then(employees => {
                    resolve(employees[0]);
                }).catch(reject);
            });
        }

        /**
         * Get users by their IDs.
         * @param {Array<Number>} ids An array of user IDs to retrieve.
         * @returns {Promise<Array<User>>}
         */
        getUsers(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getUsers([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/users", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.users) {
                        resolve(response.users.map(user => new User(this, user)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a user by its ID.
         * @param {Number} id The ID of the user to retrieve.
         * @returns {Promise<User>}
         */
        getUser(id) {
            return new Promise((resolve, reject) => {
                this.getUsers([id]).then(users => {
                    resolve(users[0]);
                }).catch(reject);
            });
        }

        /**
         * Get class groups by their IDs.
         * @param {Array<Number>} ids An array of class group IDs to retrieve.
         * @returns {Promise<Array<ClassGroup>>}
         */
        getClassGroups(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getClassGroups([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/class_groups", {
                    query: {
                        ids: ids
                    }
                }).then(response => {
                    if (response.class_groups) {
                        resolve(response.class_groups.map(class_group => new ClassGroup(this, class_group)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a class group by its ID.
         * @param {Number} id The ID of the class group to retrieve.
         * @returns {Promise<ClassGroup>}
         */
        getClassGroup(id) {
            return new Promise((resolve, reject) => {
                this.getClassgroups([id]).then(class_groups => {
                    resolve(class_groups[0]);
                }).catch(reject);
            });
        }

        /**
         * Get submission commenst by their IDs.
         * @param {Array<Number>} ids An array of submission comment IDs to retrieve.
         * @returns {Promise<Array<SubmissionComment>>}
         */
        getSubmissionComments(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getSubmissionComments([ids]);
            }

            return new Promise((resolve, reject) => {
                this._client.make("GET", "/api/submission_comments", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.submission_comments) {
                        resolve(response.submission_comments.map(submission_comment => new SubmissionComment(this, submission_comment)));
                    } else {
                        reject(response);
                    }
                });
            });
        }

        /**
         * Get a submission comment by its ID.
         * @param {Number} id The ID of the submission comment to retrieve.
         * @returns {Promise<SubmissionComment>}
         */
        getSubmissionComment(id) {
            return new Promise((resolve, reject) => {
                this.getSubmissionComments([id]).then(submission_comments => {
                    resolve(submission_comments[0]);
                }).catch(reject);
            });
        }

        /**
         * Get attachments by their IDs.
         * @param {Array<Number>} ids An array of attachment IDs to retrieve.
         * @returns {Promise<Array<Attachment>>}
         */
        getAttachments(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getAttachments([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/attachments", {
                    query: {
                        ids: ids
                    }
                }).then(response => {
                    if (response.attachments) {
                        resolve(response.attachments.map(attachment => new Attachment(this, attachment)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get an attachment by its ID.
         * @param {Number} id The ID of the attachment to retrieve.
         * @returns {Promise<Attachment>}
         */
        getAttachment(id) {
            return new Promise((resolve, reject) => {
                this.getAttachments([id]).then(attachments => {
                    resolve(attachments[0]);
                }).catch(reject);
            });
        }

        /**
         * Get homework assignments by their IDs.
         * @param {Array<Number>} ids An array of homework IDs to retrieve.
         * @returns {Promise<Array<Homework>>}
         */
        getHomeworks(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getHomeworks([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/homeworks", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.homeworks) {
                        resolve(response.homeworks.map(homework => new Homework(this, homework)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a homework assignment by its ID.
         * @param {Number} id The ID of the homework assignment to retrieve.
         * @returns {Promise<Homework>}
         */
        getHomework(id) {
            return new Promise((resolve, reject) => {
                this.getHomeworks([id]).then(homeworks => {
                    resolve(homeworks[0]);
                }).catch(reject);
            });
        }

        /**
         * Get homework submissions by their IDs.
         * @param {Array<Number>}
         * @returns {Promise<Array<HomeworkSubmission>>}
         */
        getHomeworkSubmissions(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getHomeworkSubmissions([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/homework_submissions", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.homework_submissions) {
                        resolve(response.homework_submissions.map(homework_submission => new HomeworkSubmission(this, homework_submission)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a homework submission by its ID.
         * @param {Number} id The ID of the homework submission to retrieve.
         * @returns {Promise<HomeworkSubmission>}
         */
        getHomeworkSubmission(id) {
            return new Promise((resolve, reject) => {
                this.getHomeworkSubmissions([id]).then(homework_submissions => {
                    resolve(homework_submissions[0]);
                }).catch(reject);
            });
        }

        /**
         * Get flexible task assignments by their IDs.
         * @param {Array<Number>} ids An array of flexible task IDs to retrieve.
         * @returns {Promise<Array<FlexibleTask>>}
         */
        getFlexibleTasks(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getFlexibleTasks([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/flexible_tasks", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.flexible_tasks) {
                        resolve(response.flexible_tasks.map(flexible_task => new FlexibleTask(this, flexible_task)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a flexible task assignment by its ID.
         * @param {Number} id The ID of the flexible task assignment to retrieve.
         * @returns {Promise<FlexibleTask>}
         */
        getFlexibleTask(id) {
            return new Promise((resolve, reject) => {
                this.getFlexibleTasks([id]).then(flexible_tasks => {
                    resolve(flexible_tasks[0]);
                }).catch(reject);
            });
        }

        /**
         * Get homework submissions by their IDs.
         * @param {Array<Number>}
         * @returns {Promise<Array<FlexibleTaskSubmission>>}
         */
        getFlexibleTaskSubmissions(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getFlexibleTaskSubmissions([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/flexible_task_submissions", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.flexible_task_submissions) {
                        resolve(response.flexible_task_submissions.map(flexible_task_submission => new FlexibleTaskSubmission(this, flexible_task_submission)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a flexible task submission by its ID.
         * @param {Number} id The ID of the flexible task submission to retrieve.
         * @returns {Promise<FlexibleTaskSubmission>}
         */
        getFlexibleTaskSubmission(id) {
            return new Promise((resolve, reject) => {
                this.getFlexibleTaskSubmissions([id]).then(flexible_task_submissions => {
                    resolve(flexible_task_submissions[0]);
                }).catch(reject);
            });
        }

        /**
         * Get quiz assignments by their IDs.
         * @param {Array<Number>} ids An array of quiz IDs to retrieve.
         * @returns {Promise<Array<Quiz>>}
         */
        getQuizzes(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getQuizzes([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/quizzes", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.quizzes) {
                        resolve(response.quizzes.map(quiz => new Quiz(this, quiz)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a quiz assignment by its ID.
         * @param {Number} id The ID of the quiz assignment to retrieve.
         * @returns {Promise<Quiz>}
         */
        getQuiz(id) {
            return new Promise((resolve, reject) => {
                this.getQuizzes([id]).then(quizzes => {
                    resolve(quizzes[0]);
                }).catch(reject);
            });
        }

        /**
         * Get quiz questions by their IDs.
         * @param {Array<Number>} ids An array of quiz question IDs to retrieve.
         * @returns {Promise<Array<QuizQuestion>>}
         */
        getQuizQuestions(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getQuizQuestions([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/quiz_questions", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.quiz_questions) {
                        resolve(response.quiz_questions.map(quiz_question => new QuizQuestion(this, quiz_question)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a quiz question by its ID.
         * @param {Number} id The ID of the quiz question to retrieve.
         * @returns {Promise<QuizQuestion>}
         */
        getQuizQuestion(id) {
            return new Promise((resolve, reject) => {
                this.getQuizQuestions([id]).then(quiz_questions => {
                    resolve(quiz_questions[0]);
                }).catch(reject);
            });
        }

        /**
         * Get an array of quiz submissions by their IDs.
         * @param {Array<Number>} ids An array of quiz submission IDs to retrieve.
         * @returns {Promise<Array<QuizSubmission>>}
         */
        getQuizSubmissions(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getQuizSubmissions([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/quiz_submissions", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.quiz_submissions) {
                        resolve(response.quiz_submissions.map(quiz_submission => new QuizSubmission(this, quiz_submission)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a quiz submission by its ID.
         * @param {Number} id The ID of the quiz submission to retrieve.
         * @returns {Promise<QuizSubmission>}
         */
        getQuizSubmission(id) {
            return new Promise((resolve, reject) => {
                this.getQuizSubmissions([id]).then(quiz_submissions => {
                    resolve(quiz_submissions[0]);
                }).catch(reject);
            });
        }

        /**
         * Get an array of quiz submission questions by their IDs.
         * @param {Array<Number>} ids An array of quiz submission question IDs to retrieve.
         * @returns {Promise<Array<QuizSubmissionQuestion>>}
         */
        getQuizSubmissionQuestions(ids) {
            if (ids && !Array.isArray(ids)) {
                return this.getQuizSubmissionQuestions([ids]);
            }

            return new Promise((resolve, reject) => {
                this.make("GET", "/api/quiz_submission_questions", {
                    query: {
                        ids
                    }
                }).then(response => {
                    if (response.quiz_submission_questions) {
                        resolve(response.quiz_submission_questions.map(quiz_submission_question => new QuizSubmissionQuestion(this, quiz_submission_question)));
                    } else {
                        reject(response);
                    }
                }).catch(reject);
            });
        }

        /**
         * Get a quiz submission question by its ID.
         * @param {Number} id The ID of the quiz submission question to retrieve.
         * @returns {Promise<QuizSubmissionQuestion>}
         */
        getQuizSubmissionQuestion(id) {
            return new Promise((resolve, reject) => {
                this.getQuizSubmissionQuestion([id]).then(quiz_submission_questions => {
                    resolve(quiz_submission_questions[0]);
                }).catch(reject);
            });
        }
    }

    module.exports = SMHWClient;