// (C) Copyright 2015 Martin Dougiamas
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '../../../providers/logger';
import { CoreSitesProvider } from '../../../providers/sites';
import { CoreSite } from '../../../classes/site';

/**
 * Service that provides some features regarding lists of courses and categories.
 */
@Injectable()
export class CoreCoursesProvider {
    public static SEARCH_PER_PAGE = 20;
    public static ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey';
    protected logger;

    constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) {
        this.logger = logger.getInstance('CoreCoursesProvider');
    }

    /**
     * Get categories. They can be filtered by id.
     *
     * @param {number} categoryId Category ID to get.
     * @param {boolean} [addSubcategories] If it should add subcategories to the list.
     * @param {string} [siteId] Site to get the courses from. If not defined, use current site.
     * @return {Promise<any[]>} Promise resolved with the categories.
     */
    getCategories(categoryId: number, addSubcategories?: boolean, siteId?: string) : Promise<any[]> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            // Get parent when id is the root category.
            let criteriaKey = categoryId == 0 ? 'parent' : 'id',
                data = {
                    criteria: [
                        { key: criteriaKey, value: categoryId }
                    ],
                    addsubcategories: addSubcategories ? 1 : 0
                },
                preSets = {
                    cacheKey: this.getCategoriesCacheKey(categoryId, addSubcategories)
                }

            return site.read('core_course_get_categories', data, preSets);
        });
    }

    /**
     * Get cache key for get categories methods WS call.
     *
     * @param {number} categoryId Category ID to get.
     * @param {boolean} [addSubcategories] If add subcategories to the list.
     * @return {string} Cache key.
     */
    protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean) : string {
        return this.getRootCacheKey() + 'categories:' + categoryId + ':' + !!addSubcategories;
    }

    /**
     * Given a list of course IDs to get course options, return the list of courseIds to use.
     *
     * @param {number[]} courseIds Course IDs.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise}            Promise resolved with the list of course IDs.
     */
    protected getCourseIdsForOptions(courseIds: number[], siteId?: string) : Promise<number[]> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            const siteHomeId = site.getSiteHomeId();

            if (courseIds.length == 1) {
                // Only 1 course, check if it belongs to the user courses. If so, use all user courses.
                return this.getUserCourses(true, siteId).then((courses) => {
                    let courseId = courseIds[0],
                        useAllCourses = false;

                    if (courseId == siteHomeId) {
                        // It's site home, use all courses.
                        useAllCourses = true;
                    } else {
                        for (let i = 0; i < courses.length; i++) {
                            if (courses[i].id == courseId) {
                                useAllCourses = true;
                                break;
                            }
                        }
                    }

                    if (useAllCourses) {
                        // User is enrolled, retrieve all the courses.
                        courseIds = courses.map((course) => {
                            return course.id;
                        });

                        // Always add the site home ID.
                        courseIds.push(siteHomeId);
                    }

                    return courseIds;
                }).catch(() => {
                    // Ignore errors.
                    return courseIds;
                });
            } else {
                return courseIds;
            }
        });
    }

    /**
     * Get the root cache key for the WS calls related to courses.
     *
     * @return {string} Root cache key.
     */
    protected getRootCacheKey() : string {
        return 'mmCourses:';
    }

    /**
     * Check if My Courses is disabled in a certain site.
     *
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<boolean>} Promise resolved with true if disabled, rejected or resolved with false otherwise.
     */
    isMyCoursesDisabled(siteId?: string) : Promise<boolean> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return this.isMyCoursesDisabledInSite(site);
        });
    }

    /**
     * Check if My Courses is disabled in a certain site.
     *
     * @param {CoreSite} [site] Site. If not defined, use current site.
     * @return {boolean} Whether it's disabled.
     */
    isMyCoursesDisabledInSite(site: CoreSite) : boolean {
        site = site || this.sitesProvider.getCurrentSite();
        return site.isFeatureDisabled('$mmSideMenuDelegate_mmCourses');
    }

    /**
     * Check if Search Courses is disabled in a certain site.
     *
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<boolean>} Promise resolved with true if disabled, rejected or resolved with false otherwise.
     */
    isSearchCoursesDisabled(siteId?: string) : Promise<boolean> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return this.isSearchCoursesDisabledInSite(site);
        });
    }

    /**
     * Check if Search Courses is disabled in a certain site.
     *
     * @param {CoreSite} [site] Site. If not defined, use current site.
     * @return {boolean} Whether it's disabled.
     */
    isSearchCoursesDisabledInSite(site: CoreSite) : boolean {
        site = site || this.sitesProvider.getCurrentSite();
        return site.isFeatureDisabled('$mmCoursesDelegate_search');
    }

    /**
     * Get course.
     *
     * @param {number} id ID of the course to get.
     * @param {string} [siteId] Site to get the courses from. If not defined, use current site.
     * @return {Promise<any>} Promise resolved with the course.
     */
    getCourse(id: number, siteId?: string) : Promise<any> {
        return this.getCourses([id], siteId).then((courses) => {
            if (courses && courses.length > 0) {
                return courses[0];
            }
            return Promise.reject(null);
        });
    }

    /**
     * Get the enrolment methods from a course.
     *
     * @param {number} id ID of the course.
     * @param {string} [siteId] Site ID. If not defined, use current site.
     * @return {Promise<any[]} Promise resolved with the methods.
     */
    getCourseEnrolmentMethods(id: number, siteId?: string) : Promise<any[]> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            let params = {
                    courseid: id
                },
                preSets = {
                    cacheKey: this.getCourseEnrolmentMethodsCacheKey(id)
                }

            return site.read('core_enrol_get_course_enrolment_methods', params, preSets);
        });
    }

    /**
     * Get cache key for get course enrolment methods WS call.
     *
     * @param {number} id Course ID.
     * @return {string} Cache key.
     */
    protected getCourseEnrolmentMethodsCacheKey(id: number) : string {
        return this.getRootCacheKey() + 'enrolmentmethods:' + id;
    }

    /**
     * Get info from a course guest enrolment method.
     *
     * @param {number} instanceId Guest instance ID.
     * @param {string} [siteId] Site ID. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the info is retrieved.
     */
    getCourseGuestEnrolmentInfo(instanceId: number, siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            let params = {
                    instanceid: instanceId
                },
                preSets = {
                    cacheKey: this.getCourseGuestEnrolmentInfoCacheKey(instanceId)
                }

            return site.read('enrol_guest_get_instance_info', params, preSets).then((response) => {
                return response.instanceinfo;
            });
        });
    }

    /**
     * Get cache key for get course guest enrolment methods WS call.
     *
     * @param {number} instanceId Guest instance ID.
     * @return {string} Cache key.
     */
    protected getCourseGuestEnrolmentInfoCacheKey(instanceId: number) : string {
        return this.getRootCacheKey() + 'guestinfo:' + instanceId;
    }

    /**
     * Get courses.
     * Warning: if the user doesn't have permissions to view some of the courses passed the WS call will fail.
     * The user must be able to view ALL the courses passed.
     *
     * @param {number[]} ids List of IDs of the courses to get.
     * @param {string} [siteId] Site to get the courses from. If not defined, use current site.
     * @return {Promise<any[]>}  Promise resolved with the courses.
     */
    getCourses(ids: number[], siteId?: string) : Promise<any[]> {
        if (!Array.isArray(ids)) {
            return Promise.reject(null);
        } else if (ids.length === 0) {
            return Promise.resolve([]);
        }

        return this.sitesProvider.getSite(siteId).then((site) => {
            let data = {
                    options: {
                        ids: ids
                    }
                },
                preSets = {
                    cacheKey: this.getCoursesCacheKey(ids)
                }

            return site.read('core_course_get_courses', data, preSets);
        });
    }

    /**
     * Get cache key for get courses WS call.
     *
     * @param {number[]} ids Courses IDs.
     * @return {string} Cache key.
     */
    protected getCoursesCacheKey(ids: number[]) : string {
        return this.getRootCacheKey() + 'course:' + JSON.stringify(ids);
    }

    /**
     * Get courses. They can be filtered by field.
     *
     * @param {string} [field] The field to search. Can be left empty for all courses or:
     *                             id: course id.
     *                             ids: comma separated course ids.
     *                             shortname: course short name.
     *                             idnumber: course id number.
     *                             category: category id the course belongs to.
     * @param {any} [value] The value to match.
     * @param {string} [siteId] Site ID. If not defined, use current site.
     * @return {Promise<any[]>} Promise resolved with the courses.
     */
    getCoursesByField(field?: string, value?: any, siteId?: string) : Promise<any[]> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            let data = {
                    field: field || '',
                    value: field ? value : ''
                },
                preSets = {
                    cacheKey: this.getCoursesByFieldCacheKey(field, value)
                }

            return site.read('core_course_get_courses_by_field', data, preSets).then((courses) => {
                if (courses.courses) {
                    // Courses will be sorted using sortorder if avalaible.
                    return courses.courses.sort((a, b) => {
                        if (typeof a.sortorder == 'undefined' && typeof b.sortorder == 'undefined') {
                            return b.id - a.id;
                        }

                        if (typeof a.sortorder == 'undefined') {
                            return 1;
                        }

                        if (typeof b.sortorder == 'undefined') {
                            return -1;
                        }

                        return a.sortorder - b.sortorder;
                    });
                }

                return Promise.reject(null);
            });
        });
    }

    /**
     * Get cache key for get courses WS call.
     *
     * @param {string} [field] The field to search.
     * @param {any} [value] The value to match.
     * @return {string} Cache key.
     */
    protected getCoursesByFieldCacheKey(field?: string, value?: any) : string {
        field = field || '';
        value = field ? value : '';
        return this.getRootCacheKey() + 'coursesbyfield:' + field + ':' + value;
    }

    /**
     * Check if get courses by field WS is available.
     *
     * @return {boolean} Whether get courses by field is available.
     */
    isGetCoursesByFieldAvailable() : boolean {
        let currentSite = this.sitesProvider.getCurrentSite();
        return currentSite.wsAvailable('core_course_get_courses_by_field');
    }

    /**
     * Get the navigation and administration options for the given courses.
     *
     * @param {number[]} courseIds IDs of courses to get.
     * @param {string} [siteId] Site ID. If not defined, current site.
     * @return {Promise<{navOptions: any, admOptions: any}>} Promise resolved with the options for each course.
     */
    getCoursesOptions(courseIds: number[], siteId?: string) : Promise<{navOptions: any, admOptions: any}> {
        siteId = siteId || this.sitesProvider.getCurrentSiteId();

        // Get the list of courseIds to use based on the param.
        return this.getCourseIdsForOptions(courseIds, siteId).then((courseIds) => {
            let promises = [],
                navOptions,
                admOptions;

            // Get user navigation and administration options.
            promises.push(this.getUserNavigationOptions(courseIds, siteId).catch(() => {
                // Couldn't get it, return empty options.
                return {};
            }).then((options) => {
                navOptions = options;
            }));

            promises.push(this.getUserAdministrationOptions(courseIds, siteId).catch(() => {
                // Couldn't get it, return empty options.
                return {};
            }).then((options) => {
                admOptions = options;
            }));

            return Promise.all(promises).then(() => {
                return {navOptions: navOptions, admOptions: admOptions};
            });
        });
    }

    /**
     * Get the common part of the cache keys for user administration options WS calls.
     *
     * @return {string} Cache key.
     */
    protected getUserAdministrationOptionsCommonCacheKey() : string {
        return this.getRootCacheKey() + 'administrationOptions:';
    }

    /**
     * Get cache key for get user administration options WS call.
     *
     * @param {number[]} courseIds IDs of courses to get.
     * @return {string} Cache key.
     */
    protected getUserAdministrationOptionsCacheKey(courseIds: number[]) : string {
        return this.getUserAdministrationOptionsCommonCacheKey() + courseIds.join(',');
    }

    /**
     * Get user administration options for a set of courses.
     *
     * @param {number[]} courseIds IDs of courses to get.
     * @param {string} [siteId] Site ID. If not defined, current site.
     * @return {Promise<any>} Promise resolved with administration options for each course.
     */
    getUserAdministrationOptions(courseIds: number[], siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            let params = {
                    courseids: courseIds
                },
                preSets = {
                    cacheKey: this.getUserAdministrationOptionsCacheKey(courseIds)
                }

            return site.read('core_course_get_user_administration_options', params, preSets).then((response) => {
                // Format returned data.
                return this.formatUserOptions(response.courses);
            });
        });
    }

    /**
     * Get the common part of the cache keys for user navigation options WS calls.
     *
     * @param {number[]} courseIds IDs of courses to get.
     * @return {string} Cache key.
     */
    protected getUserNavigationOptionsCommonCacheKey() : string {
        return this.getRootCacheKey() + 'navigationOptions:';
    }

    /**
     * Get cache key for get user navigation options WS call.
     *
     * @return {string} Cache key.
     */
    protected getUserNavigationOptionsCacheKey(courseIds: number[]) : string {
        return this.getUserNavigationOptionsCommonCacheKey() + courseIds.join(',');
    }

    /**
     * Get user navigation options for a set of courses.
     *
     * @param {number[]} courseIds IDs of courses to get.
     * @param {string} [siteId] Site ID. If not defined, current site.
     * @return {Promise<any>} Promise resolved with navigation options for each course.
     */
    getUserNavigationOptions(courseIds: number[], siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            let params = {
                    courseids: courseIds
                },
                preSets = {
                    cacheKey: this.getUserNavigationOptionsCacheKey(courseIds)
                }

            return site.read('core_course_get_user_navigation_options', params, preSets).then((response) => {
                // Format returned data.
                return this.formatUserOptions(response.courses);
            });
        });
    }

    /**
     * Format user navigation or administration options.
     *
     * @param {any[]} courses Navigation or administration options for each course.
     * @return {any} Formatted options.
     */
    protected formatUserOptions(courses: any[]) : any {
        let result = {};

        courses.forEach((course) => {
            let options = {};

            if (course.options) {
                course.options.forEach((option) => {
                    options[option.name] = option.available;
                });
            }

            result[course.id] = options;
        });

        return result;
    }

    /**
     * Get a course the user is enrolled in. This function relies on getUserCourses.
     * preferCache=true will try to speed up the response, but the data returned might not be updated.
     *
     * @param {number} id ID of the course to get.
     * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise.
     * @param {string} [siteId] Site to get the courses from. If not defined, use current site.
     * @return {Promise<any>} Promise resolved with the course.
     */
    getUserCourse(id: number, preferCache?: boolean, siteId?: string) : Promise<any> {
        if (!id) {
            return Promise.reject(null);
        }

        return this.getUserCourses(preferCache, siteId).then((courses) => {
            let course;
            for (let i in courses) {
                if (courses[i].id == id) {
                    course = courses[i];
                    break;
                }
            }

            return course ? course : Promise.reject(null);
        });
    }

    /**
     * Get user courses.
     *
     * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise.
     * @param {string} [siteId] Site to get the courses from. If not defined, use current site.
     * @return {Promise<any[]>} Promise resolved with the courses.
     */
    getUserCourses(preferCache?: boolean, siteId?: string) : Promise<any[]> {
        return this.sitesProvider.getSite(siteId).then((site) => {

            let userId = site.getUserId(),
                data = {
                    userid: userId
                },
                preSets = {
                    cacheKey: this.getUserCoursesCacheKey(),
                    omitExpires: !!preferCache
                };

            return site.read('core_enrol_get_users_courses', data, preSets);
        });
    }

    /**
     * Get cache key for get user courses WS call.
     *
     * @return {string} Cache key.
     */
    protected getUserCoursesCacheKey() : string {
        return this.getRootCacheKey() + 'usercourses';
    }

    /**
     * Invalidates get categories WS call.
     *
     * @param {number} categoryId Category ID to get.
     * @param {boolean} [addSubcategories] If it should add subcategories to the list.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCategories(categoryId: number, addSubcategories?: boolean, siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getCategoriesCacheKey(categoryId, addSubcategories));
        });
    }

    /**
     * Invalidates get course WS call.
     *
     * @param {number} id Course ID.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCourse(id: number, siteId?: string) : Promise<any> {
        return this.invalidateCourses([id], siteId);
    }

    /**
     * Invalidates get course enrolment methods WS call.
     *
     * @param {number} id Course ID.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCourseEnrolmentMethods(id: number, siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getCourseEnrolmentMethodsCacheKey(id));
        });
    }

    /**
     * Invalidates get course guest enrolment info WS call.
     *
     * @param {number} instanceId Guest instance ID.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCourseGuestEnrolmentInfo(instanceId: number, siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getCourseGuestEnrolmentInfoCacheKey(instanceId));
        });
    }

    /**
     * Invalidates the navigation and administration options for the given courses.
     *
     * @param {number[]} courseIds IDs of courses to get.
     * @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCoursesOptions(courseIds: number[], siteId?: string) : Promise<any> {
        siteId = siteId || this.sitesProvider.getCurrentSiteId();

        return this.getCourseIdsForOptions(courseIds, siteId).then((ids) => {
            let promises = [];

            promises.push(this.invalidateUserAdministrationOptionsForCourses(ids, siteId));
            promises.push(this.invalidateUserNavigationOptionsForCourses(ids, siteId));

            return Promise.all(promises);
        });
    }

    /**
     * Invalidates get courses WS call.
     *
     * @param {number[]} ids Courses IDs.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCourses(ids: number[], siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getCoursesCacheKey(ids));
        });
    }

    /**
     * Invalidates get courses by field WS call.
     *
     * @param {string} [field] See getCoursesByField for info.
     * @param {any} [value] The value to match.
     * @param {string} [siteId] Site Id. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateCoursesByField(field?: string, value?: any, siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getCoursesByFieldCacheKey(field, value));
        });
    }

    /**
     * Invalidates all user administration options.
     *
     * @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateUserAdministrationOptions(siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKeyStartingWith(this.getUserAdministrationOptionsCommonCacheKey());
        });
    }

    /**
     * Invalidates user administration options for certain courses.
     *
     * @param {number[]} courseIds IDs of courses.
     * @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateUserAdministrationOptionsForCourses(courseIds: number[], siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getUserAdministrationOptionsCacheKey(courseIds));
        });
    }

    /**
     * Invalidates get user courses WS call.
     *
     * @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateUserCourses(siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getUserCoursesCacheKey());
        });
    }

    /**
     * Invalidates all user navigation options.
     *
     * @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateUserNavigationOptions(siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKeyStartingWith(this.getUserNavigationOptionsCommonCacheKey());
        });
    }

    /**
     * Invalidates user navigation options for certain courses.
     *
     * @param {number[]} courseIds IDs of courses.
     * @param {string} [siteId] Site ID to invalidate. If not defined, use current site.
     * @return {Promise<any>} Promise resolved when the data is invalidated.
     */
    invalidateUserNavigationOptionsForCourses(courseIds: number[], siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {
            return site.invalidateWsCacheForKey(this.getUserNavigationOptionsCacheKey(courseIds));
        });
    }

    /**
     * Check if WS to retrieve guest enrolment data is available.
     *
     * @return {boolean} Whether guest WS is available.
     */
    isGuestWSAvailable() : boolean {
        let currentSite = this.sitesProvider.getCurrentSite();
        return currentSite && currentSite.wsAvailable('enrol_guest_get_instance_info');
    }

    /**
     * Search courses.
     *
     * @param {string} text Text to search.
     * @param {number} [page=0] Page to get.
     * @param {number} [perPage] Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE.
     * @param {string} [siteId] Site ID. If not defined, use current site.
     * @return {Promise<{total: number, courses: any[]}>} Promise resolved with the courses and the total of matches.
     */
    search(text: string, page = 0, perPage?: number, siteId?: string) : Promise<{total: number, courses: any[]}> {
        perPage = perPage || CoreCoursesProvider.SEARCH_PER_PAGE;

        return this.sitesProvider.getSite(siteId).then((site) => {
            let params = {
                    criterianame: 'search',
                    criteriavalue: text,
                    page: page,
                    perpage: perPage
                }, preSets = {
                    getFromCache: false
                }

            return site.read('core_course_search_courses', params, preSets).then((response) => {
                return {total: response.total, courses: response.courses};
            });
        });
    }

    /**
     * Self enrol current user in a certain course.
     *
     * @param {number} courseId Course ID.
     * @param {string} [password] Password to use.
     * @param {number} [instanceId] Enrol instance ID.
     * @param {string} [siteId] Site ID. If not defined, use current site.
     * @return {Promise<any>} Promise resolved if the user is enrolled. If the password is invalid, the promise is rejected
     *                        with an object with code = CoreCoursesProvider.ENROL_INVALID_KEY.
     */
    selfEnrol(courseId: number, password = '', instanceId?: number, siteId?: string) : Promise<any> {
        return this.sitesProvider.getSite(siteId).then((site) => {

            let params: any = {
                courseid: courseId,
                password: password
            }
            if (instanceId) {
                params.instanceid = instanceId;
            }

            return site.write('enrol_self_enrol_user', params).then((response) : any => {
                if (response) {
                    if (response.status) {
                        return true;
                    } else if (response.warnings && response.warnings.length) {
                        let message;
                        response.warnings.forEach((warning) => {
                            // Invalid password warnings.
                            if (warning.warningcode == '2' || warning.warningcode == '3' || warning.warningcode == '4') {
                                message = warning.message;
                            }
                        });

                        if (message) {
                            return Promise.reject({code: CoreCoursesProvider.ENROL_INVALID_KEY, message: message});
                        } else {
                            return Promise.reject(response.warnings[0]);
                        }
                    }
                }
                return Promise.reject(null);
            });
        });
    }
}