/*
 * nus-ivle-api
 * https://github.com/Zhuochun/nus-ivle-api
 *
 * Copyright (c) 2012 Zhuochun
 * Licensed under the MIT license.
 */

(function($, window, undefined) {

    "use strict";
    /*jshint jquery:true, laxcomma:true, maxerr:50 */
    /*global exports*/

    // The base and api URL of IAPI
    var baseUrl = "https://ivle.nus.edu.sg/api/",
        apiUrl  = baseUrl + "lapi.svc/",

    // Wrap ajax to allow jsonp
        jsonp = function(url) {
            return $.ajax({
                  type : "GET"
                , dataType : "jsonp"
                , contentType :"application/x-javascript"
                , url : url
                , xhrFields : { widthCredentials : false }
            });
        },

    // Generate uri for IVLE api
    // Note: make sure params exists if use AuthToken
        uri = function(key, token, api, params) {
            return apiUrl + api + "?APIKey=" + key +
                (params === undefined ?  "&Token=" + token :
                    "&AuthToken=" + token + "&" + decodeURIComponent($.param(params))) +
                        "&output=json";
        },

    // Get the actual result from data return, null if data is invalid
        getResult = function(data) {
            if (data && (data.Comments === "Valid login!" ||
                         data.Comments === "" /* in WebCasts/Workbin return empty string! */)) {
                return data.Results;
            } else {
                return null;
            }
        },

    // process the query if callback is provided
        processQuery = function(query, callback) {
            return callback ? query.success(function(d) { callback(getResult(d)); }) : query;
        },

/* ========================================
 * Define ivle
 * ======================================== */

        ivle;

    // The top-level namespace. All public classes and modules will
    // be attached to this. Exported for both CommonJS and the browser.
    if (typeof exports !== 'undefined') {
        ivle = exports;
    } else {
        ivle = window.ivle = {};
    }

    // Current version of the library. Keep in sync with `package.json`.
    ivle.VERSION = '0.3.0';

    // Get the token string from window.location
    ivle.getToken = function(href) {
        var result = (/\?token=(\w+)/ig).exec(href || window.location.href);
        return result ? result[1] : null;
    };

    // get the login url
    ivle.login = function(key, redirectUrl) {
        return baseUrl + "login/?apikey=" + key + "&url=" + encodeURIComponent(redirectUrl);
    };

    // filter the result
    ivle.filterResult = getResult;

    // generate user
    ivle.User = function(key, token) {
        return new User(key, token);
    };

/* ========================================
 * Define User
 * ======================================== */

    function User(key, token) {
        this.KEY = key;
        this.TOKEN = token;
    }

    User.prototype = {
        constructor: User,

        init: function() {
            var self = this;

            return $.when(self.validate(), self.get("Profile_View", {}))
                    .done(function(arg1, arg2) {
                        self.data = getResult(arg2[0])[0];
                    });
        },

        get: function(api, params) {
            return jsonp(uri(this.KEY, this.TOKEN, api, params));
        },

        validate: function(callback) {
            var self = this;

            return this.get("Validate").success(function(data) {
                // update the token
                if (data.Success && data.Token !== self.TOKEN) {
                    self.TOKEN = data.Token;
                }

                if (callback) { callback(data.Success); }
            });
        },

        profile: function(key) {
            return key ? this.data[key] : this.data;
        },

        Module: function(data) {
            return new Module(this, data);
        },

        modules: function(options, callback) {
            if ($.isFunction(options)) {
                callback = options;
                options  = {};
            }

            var self = this,
                opt = $.extend({Duration: 0, IncludeAllInfo: true}, options),
                query = this.get("Modules", opt),
                createModules = function(data) {
                    var m, modules = [], result = getResult(data);

                    if (result) {
                        for (m in result) {
                            modules.push(new Module(self, result[m]));
                        }

                        callback(modules);
                    } else {
                        callback(result);
                    }
                };

            return callback ? query.success(createModules) : query;
        },

        modulesTaken: function(callback) {
            var self = this,
                query = this.get("Modules_Taken", {StudentId: self.profile("UserID")});

            return processQuery(query, callback);
        },

        unreadAnnouncements: function(callback) {
            var query = this.get("Announcements_Unread", {TitleOnly: false});

            return processQuery(query, callback);
        },

        search: function(type, options, callback) {
            var opt = $.extend({IncludeAllInfo: true}, options),
                query = this.get("Modules_Search", opt);

            if (type === "Modules") {
                return processQuery(query, callback);
            } else {
                throw new Error("Search invalid type");
            }
        },

        download: function(file) {
            var url = baseUrl + "downloadfile.ashx?APIKey=" + this.KEY + 
                    "&AuthToken=" + this.TOKEN + "&target=workbin&ID=";

            if (typeof file === "string") {
                return url + file;
            } else if (file.ID) {
                return url + file.ID;
            } else {
                return null;
            }
        }
    };

/* ========================================
 * Define Module
 * ======================================== */

    function Module(user, data) {
        this._user = user;
        this._data = data;
        this._lastUpdate = new Date();
    }

    Module.prototype = {
        constructor: Module,

        get: function(key) {
            return this._data[key];
        },

        update: function() {
            var self = this;

            this._user.get("Module", {
                Duration: 0,
                IncludeAllInfo: true,
                CourseID: self.get("ID"),
                TitleOnly: false
            }).success(function(data) {
                var result = getResult(data);

                if (result && result.length > 0) {
                    self._lastUpdate = new Date();
                    self._data = data.Results[0];
                }
            });
        },

        gradebooksAsync: function(callback) {
            var self = this,
                query = this._user.get("Gradebook_ViewItems", {
                            CourseID: self.get("ID")
                        });

            return processQuery(query, callback);
        }
    };

    var modInfos = "announcements forums workbins webcasts gradebooks polls webLinks lecturers descriptions".split(" "),
        capitalize = function(str) { return str.slice(0,1).toUpperCase() + str.slice(1); },
        i, extendMod = function(key) { return function() { return this.get(key); }; };

    for (i in modInfos) {
        Module.prototype[modInfos[i].toLowerCase()] =
            extendMod.apply(Module.prototype, [capitalize(modInfos[i])]);
    }

    var modApi = [
            {api: "announcements", "options": {Duration: 0, TitleOnly: false}},
            {api: "workbins", "options": {Duration: 0, TitleOnly: false, WorkbinID: ""}},
            {api: "forums", "options": {Duration: 0, IncludeThreads: true, TitleOnly: false}},
            {api: "webcasts", "options": {Duration: 0}}
        ],
        asyncFun = function(i) {
            return function(options, callback) {
                if ($.isFunction(options)) {
                    callback = options;
                    options  = {};
                }

                var self = this,
                    opt = $.extend({CourseID: self.get("ID")}, i.options, options),
                    query = this._user.get(capitalize(i.api), opt);

                return processQuery(query, callback);
            };
        };

    for (i in modApi) {
        Module.prototype[modApi[i].api + "Async"] = 
            asyncFun.apply(Module.prototype, [modApi[i]]);
    }

})(jQuery, window);