Nyaa.se Batch downloader

Batch download torrents from nyaa.se

当前为 2015-09-28 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Nyaa.se Batch downloader
// @namespace   Autodownload
// @author      Victorique
// @description Batch download torrents from nyaa.se
// @include     http://www.nyaa.se/?page=search&cats=*&filter=*&term=*&user=*
// @include     http://www.nyaa.se/?page=search&filter=*&term=*&user=*
// @include     http://www.nyaa.se/?page=search&term=*&user=*
// @include     *://www.nyaa.eu/?page=search&cats=*&filter=*&term=*&user=*
// @include     *://www.nyaa.eu/?page=search&filter=*&term=*&user=*
// @include     *://www.nyaa.eu/?page=search&term=*&user=*
// @version     5.3.11
// @icon        https://i.imgur.com/nx5ejHb.png
// @license     MIT
// @run-at      document-start
// @grant       none
// @require     https://code.jquery.com/jquery-2.1.4.min.js
// ==/UserScript==

/*
var e = document.createElement("script");

e.src = 'http://localhost/userScripts/AutoDownloader.user.js';
e.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
*/

"use strict";

/* OBJECT CREATION START */
var Episode = (function () {
    /**
     * An Episode represents an table row in the current page
     * @param   {Number} res          The resolution used for this episode
     * @param   {String} downloadLink The download link for this episode
     * @param   {Number} seeds        The seed count for this episode
     * @param   {Number} leechers     The leech count for this episode
     * @param   {String} uid          The ID of this episode
     * @param   {Number} resides      The page that this Episode resides in
     * @param   {String} title        The title of the episode
     * @param   {Number} size          The size in MB of the episode
     * @returns {Object} the proto of itself
     */
    function Episode(res, downloadLink, seeds, leechers, uid, resides, title, size) {

        if (typeof res !== "number") {
            throw "res must be a number";
        }

        if (typeof downloadLink !== "string") {
            throw "downloadLink must be a string";
        }

        if (typeof seeds !== "number") {
            throw "seeds must be a number";
        }

        if (typeof leechers !== "number") {
            throw "leechers must be a number";
        }

        if (typeof uid !== "string") {
            throw "uid must be a string";
        }

        if (typeof resides !== "number") {
            throw "resides must be a number";
        }

        if (typeof title !== "string") {
            throw "Title must be a string";
        }

        if (typeof size !== "number") {
            throw "size must be a number";
        }
        var _res = res;

        var _downloadLink = downloadLink;

        var _seeds = seeds;

        var _leechers = leechers;

        var _uid = uid;

        var _resides = resides;

        var _title = title;

        var _size = size;

        this.getRes = function () {
            return _res;
        };

        this.getDownloadLink = function () {
            return _downloadLink;
        };

        this.getSeeds = function () {
            return _seeds;
        };

        this.getLeechers = function () {
            return _leechers;
        };

        this.getUid = function () {
            return _uid;
        };

        this.getResides = function () {
            return _resides;
        };

        this.getTitle = function () {
            return _title;
        };

        this.getSize = function () {
            return _size;
        };

        return this;
    }
    Episode.prototype = {
        constructor: Episode
    };
    return Episode;
}());

var Anime = (function () {

    var currentAnime = null;
    var currentSubber = null;

    var _AbstractEps = (function () {
        /**
         * Array of Episode Objects
         */
        var eps = [];

        var abstractGetEps = function (skipSeedLimit) {
            if (typeof skipSeedLimit !== "boolean") {
                throw "skipOptions must be true or false";
            }
            var minSeeds = Options.Seeds.minSeeds;
            if (minSeeds > -1 && skipSeedLimit == false) {
                var arrayOfEps = [];
                for (var i = 0; i < eps.length; i++) {
                    var currentEp = eps[i];
                    if (currentEp.getSeeds() < minSeeds) {
                        continue;
                    }
                    arrayOfEps.push(currentEp);
                }
                return arrayOfEps;
            } else {
                return eps;
            }
        };

        var addEp = function (ep) {
            if (!(ep instanceof Episode)) {
                throw "addEp must take an Episode object";
            }
            if (_validRes(ep.getRes()) === false) {
                throw new TypeError("The Episode supplied does not have a valid resolution");
            }
            if (eps.length > 0) {
                for (var i = 0; i < eps.length; i++) {
                    var epi = eps[i];
                    if (Utils.deepEquals(epi, ep)) {
                        console.warn("The episode supplied already exsists, this episode has been ignored");
                        return;
                    }
                }
            }
            eps.push(ep);
        };

        var removeEpisodeFromAnime = function (obj) {
            var arr = eps;
            var i = arr.length;
            while (i--) {
                if (arr[i] === obj) {
                    arr.splice(i, 1);
                }
            }
        };

        return {
            abstractGetEps: abstractGetEps,
            addEp: addEp,
            removeEpisodeFromAnime: removeEpisodeFromAnime
        };
    }());

    /**
     * Array of available resolutions on the page
     */
    var availableRes = [];

    /**
     * Array of supported resolutions for this program
     */
    var supportedRes = [{
        "id": 1,
        "res": 1080,
        "fullRes": "1920x1080"
    }, {
        "id": 2,
        "res": 720,
        "fullRes": "1280x720"
    }, {
        "id": 3,
        "res": 480,
        "fullRes": "640x480"
    }, {
        "id": 4,
        "res": 360,
        "fullRes": "640x360"
    }];

    /**
     * Set the current Anime name
     * @param {String} anime The of the Anime
     */
    var setCurrentAnime = function (anime) {
        if (anime === "*") {
            anime = "Everything";
        }
        currentAnime = anime;
    };

    /**
     * Set the name of the current subber
     * @param {String} sub Name of the current subber
     */
    var setCurrentSubber = function (sub) {
        currentSubber = sub;
    };

    /**
     * Get the current Subber
     * @returns {String} The name of the Subber for this anime
     */
    var getCurrentSubber = function () {
        return currentSubber;
    };

    /**
     * Get the current anime name
     * @returns {String} Name of the anime
     */
    var getCurrentAnime = function () {
        return currentAnime;
    };

    var getSupportedRes = function () {
        return supportedRes;
    };

    var addSupportedRes = function (ResObj) {
        if (typeof ResObj !== "object") {
            throw "ResObj must be a object";
        }
        supportedRes.push(ResObj);
    };

    var getAvailableResolutions = function () {
        return availableRes;
    };

    var addAvailableResolutions = function (res, fullRes) {
        if (typeof res !== "number") {
            throw "res must be of type number";
        }

        if (typeof fullRes !== "string" && fullRes !== null) {
            throw "Full res must be a string or null";
        }

        if (_resExists(res)) {
            return;
        }
        availableRes.push({
            "res": res,
            "fullRes": fullRes
        });
    };

    var removeAvailableResolutions = function (resToRemove) {
        if (typeof resToRemove !== "number" && typeof resToRemove !== "string") {
            throw "the res to remove can only be a number or string";
        }

        for (var i = 0; i < availableRes.length; i++) {
            var currentRes = availableRes[i];
            for (var res in currentRes) {
                if (currentRes.hasOwnProperty(res)) {
                    var localRes = currentRes[res];
                    if (localRes === resToRemove) {
                        availableRes._remove_(currentRes);
                    }
                }
            }
        }
    };

    var _resExists = function (_res) {
        for (var i = 0; i < availableRes.length; i++) {
            var currentRes = availableRes[i];
            for (var res in currentRes) {
                if (currentRes.hasOwnProperty(res)) {
                    var localRes = currentRes[res];
                    if (localRes === _res) {
                        return true;
                    }
                }
            }
        }
        return false;
    };


    /**
     * Get the avrage seeds for a specified res
     * @param   {Number} res The res to get the avg seeds for
     * @returns {Number} The avg seeds
     */
    var avgSeedsForRes = function (res, skipSeedLimit) {
        if (typeof res !== "number") {
            throw "res Must be an number";
        }
        var seedCount = 0;
        if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) {
            return 0;
        }
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                seedCount += currentEp.getSeeds();
            }
        }
        return Math.round(seedCount = seedCount / getamountOfEpsFromRes(res, skipSeedLimit));
    };

    /**
     * Get the avrage leechers for a specified res
     * @param   {Number} res The res to get the avg seeds for
     * @returns {Number} The avg leechers
     */
    var avgPeersForRes = function (res, skipSeedLimit) {
        if (typeof res !== "number") {
            throw "res Must be an number";
        }
        var leechCount = 0;
        if (getamountOfEpsFromRes(res, skipSeedLimit) === 0) {
            return 0;
        }
        var eps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (currentEp.getRes() === res) {
                leechCount += currentEp.getLeechers();
            }
        }
        return Math.round(leechCount = leechCount / getamountOfEpsFromRes(res, skipSeedLimit));
    };

    var getTotalSizeForRes = function (res, skipSeedLimit, decimals) {
        if (typeof res !== "number") {
            throw "res Must be an number";
        }
        var eps = getEpsForRes(res, skipSeedLimit);

        var currentSize = 0;
        for (var i = 0; i < eps.length; i++) {
            var ep = eps[i];
            currentSize += ep.getSize();
        }

        return convertMb(currentSize, decimals);

        function convertMb(mb, decimals) {
            function formatBytes(bytes, decimals) {
                if (bytes == 0) {
                    return '0 Byte';
                }
                var k = 1024;
                var dm = decimals + 1 || 3;
                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
                var i = Math.floor(Math.log(bytes) / Math.log(k));
                return (bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i];
            }
            return formatBytes(((Math.pow(2, 20)) / 1) * mb, decimals);
        }

    };



    /**
     * Get the total amount of eps for a res
     * @param   {Number} res res
     * @returns {Number} The amount of eps for the res
     */
    var getamountOfEpsFromRes = function (res, skipSeedLimit) {
        if (typeof res !== "number") {
            throw "res must be of type 'number'";
        }
        return getEpsForRes(res, skipSeedLimit).length;
    };

    /**
     * Add Episodes to the array
     * @param {Episode} ep The Anime object to add
     */
    var addEps = function (ep) {
        _AbstractEps.addEp(ep);
    };


    /**
     * Add an array of Episode object to the Anime object
     * @param {Array} episode Array of Episode objects to add
     */
    var addAllEps = function (episode) {
        for (var i = 0; i < episode.length; i++) {
            _AbstractEps.addEp(episode[i]);
        }
    };

    var _validRes = function (res) {
        return _resExists(res); // if the res exists, then it's valid
    };

    /**
     * Get the Anime objects for a specified res
     * @param   {Number}  res res to use
     * @returns {Episode} Array of Episodes that match the specified res
     */
    var getEpsForRes = function (res, skipSeedLimit) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var arrayOfEps = [];
        var iterEps = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (var i = 0; i < iterEps.length; i++) {
            var currentEp = iterEps[i];
            if (currentEp.getRes() === res) {
                arrayOfEps.push(currentEp);
            }
        }
        return arrayOfEps;
    };

    /**
     * Given a JQuery object that represents a "tr" of the table, this will return that Episode's UID
     * @param   {Object} obj The Jquery representation of a tr (table row)
     * @returns {String} The UID of that Episode
     */
    var getUidFromJqueryObject = function (obj) {
        if (!Utils.isjQueryObject(obj)) {
            throw "Object must be of type 'Jquery'";
        }
        if (obj.is("tr")) {
            var anchor = (function () {
                var tableRows = obj.find("td.tlistdownload > a");
                if (tableRows.length > 1) {
                    throw "Object must be unique";
                }
                return _getUidFromAnchor(tableRows.get(0));
            }());
            return anchor;
        }
        return null;
    };

    /**
     * Get the Episode from a given anchor tag or url
     * @param   {Object}  anchor Jquery or pure JS anchor dom element or URL String
     * @returns {Episode} The eipside that matches the Anchor
     */
    var getEpisodeFromAnchor = function (anchor) {
        var link = (function () {
            if (Utils.isjQueryObject(anchor)) {
                return anchor.get(0);
            }
            return anchor;
        }());
        var uid = _getUidFromAnchor(link);
        return getEpisodeFromUid(uid, true);
    };

    /**
     * Get the Episode object given a UID
     * @param   {String}  uid The Episode UID
     * @returns {Episode} The Episode that matches the UID
     */
    var getEpisodeFromUid = function (uid, skipSeedLimit) {
        if (typeof uid !== "string") {
            throw "uid must be of type String";
        }
        var iterEp = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (var i = 0; i < iterEp.length; i++) {
            var currentEp = iterEp[i];
            if (currentEp.getUid() === uid) {
                return currentEp;
            }
        }
        return null;
    };

    /**
     * Get an array of Episodes that from the page that it came from
     * @param   {Number}  resides The page where the Episode originated from
     * @param   {Boolean} exclude Match this page only, or exculde this page and return all other objects. if true, will return all Episodes that are not of the passed in page
     * @returns {Episode} Array of Episodes
     */
    var getEpisodesFromResidence = function (resides, exclude, skipSeedLimit) {
        if (typeof resides !== "number") {
            throw "resides must be a number";
        }
        var arrayOfEps = [];
        var iterEp = _AbstractEps.abstractGetEps(skipSeedLimit);
        for (var i = 0; i < iterEp.length; i++) {
            var currentEp = iterEp[i];
            if (exclude === true) {
                if (currentEp.getResides() !== resides) {
                    arrayOfEps.push(currentEp);
                }
            } else {
                if (currentEp.getResides() === resides) {
                    arrayOfEps.push(currentEp);
                }
            }
        }
        return arrayOfEps;
    };

    /**
     * Get the UID from an anchor tag
     * @param   {Object} anchor Dom element of an Anchor
     * @returns {String} The UID
     */
    var _getUidFromAnchor = function (anchor) {
        if (typeof anchor === "string") {
            return anchor.split("=").pop();
        }
        return anchor.href.split("=").pop();
    };

    /**
     * Get an array of all the pages avalible in the Anime (tables)
     * @returns {Array} Array of URLS
     */
    var getPageUrls = function (asHref, page) {
        if (typeof page === "undefined") {
            page = $('div.pages');
        }
        if (typeof asHref !== "boolean") {
            throw "asHref must be a boolean";
        }
        if (asHref === false) {
            return $(page).filter(function (i) {
                return i === 0;
            });
        }
        var urls = [];
        $.each(page.filter(function (i) {
            return i === 0;
        }).find('a'), function (k, v) {
            urls.push(this.href);
        });
        return urls;
    };

    /**
     * Remove an episode from the Episode array based on UI
     * @param {String} uid the UID
     */
    var removeEpisodesFromUid = function (uid) {
        var episode = getEpisodeFromUid(uid, true);
        _AbstractEps.removeEpisodeFromAnime(episode);
    };

    /**
     * Remove all episodes that match a page number
     * @param {Number}  resides The page number
     * @param {Boolean} exclude if true, this will remove all Episode objects that do not match the passed in page number. if false, removes all episodes that match the passed in page number
     */
    var removeEpisodesFromResidence = function (resides, exclude) {
        if (typeof exclude !== "boolean") {
            throw "excluse must be true or false";
        }
        var episodes = getEpisodesFromResidence(resides, exclude, true);
        for (var i = 0; i < episodes.length; i++) {
            var currentEp = episodes[i];
            _AbstractEps.removeEpisodeFromAnime(currentEp);
        }
    };

    var getAmountOfEps = function () {
        return _AbstractEps.abstractGetEps(true).length;
    };


    return {
        setCurrentAnime: setCurrentAnime,
        getCurrentAnime: getCurrentAnime,
        addEps: addEps,
        getEpsForRes: getEpsForRes,
        getamountOfEpsFromRes: getamountOfEpsFromRes,
        setCurrentSubber: setCurrentSubber,
        getCurrentSubber: getCurrentSubber,
        avgSeedsForRes: avgSeedsForRes,
        getUidFromJqueryObject: getUidFromJqueryObject,
        getEpisodeFromUid: getEpisodeFromUid,
        getEpisodeFromAnchor: getEpisodeFromAnchor,
        getPageUrls: getPageUrls,
        addAllEps: addAllEps,
        getEpisodesFromResidence: getEpisodesFromResidence,
        removeEpisodesFromUid: removeEpisodesFromUid,
        removeEpisodesFromResidence: removeEpisodesFromResidence,
        avgPeersForRes: avgPeersForRes,
        getAmountOfEps: getAmountOfEps,
        addAvailableResolutions: addAvailableResolutions,
        getAvailableResolutions: getAvailableResolutions,
        removeAvailableResolutions: removeAvailableResolutions,
        getSupportedRes: getSupportedRes,
        addSupportedRes: addSupportedRes,
        getTotalSizeForRes: getTotalSizeForRes
    };
}());


/** Utility functions ***/

var Utils = (function () {

    (function () {

        $.fn.extend({
            sortTable: function (col) {
                typeof col === ("undefined") ? col = -1 : col = col;
                if (this.length === 0) {
                    return;
                }
                if (typeof col !== "number") {
                    throw "col must be of type number. Got type" + typeof col + " instead";
                }
                if (!this.is("table")) {
                    throw "Error: sortList can only be used on a table!";
                }

                var rows = $(this).find("tbody").children('tr').get();;
                if (col === -1) {
                    rows.sort(function (a, b) {
                        return $(a).text().toUpperCase().localeCompare($(b).text().toUpperCase());
                    });
                } else {
                    rows.sort(function (a, b) {
                        var textA = $(a).children('td').eq(col).text().toUpperCase();
                        var textB = $(b).children('td').eq(col).text().toUpperCase();
                        return textA.localeCompare(textB);
                    });
                }
                for (var i = 0; i < rows.length; i++) {
                    $(this).find("tbody").append(rows[i]);
                }
                return this;
            }
        });

        // sortBy for arrays
        (function () {
            function _sortByAttr(attr) {
                var sortOrder = 1;
                if (attr[0] == "-") {
                    sortOrder = -1;
                    attr = attr.substr(1);
                }
                return function (a, b) {
                    if (typeof a[attr] === "undefined" || typeof b[attr] === "undefined") {
                        throw "There is no property with the value of " + attr.toString() + " inside this object";
                    }
                    var result = a[attr].toUpperCase().localeCompare(b[attr].toUpperCase());
                    return result * sortOrder;
                }
            }

            function _getSortFunc() {
                if (arguments.length == 0) {
                    throw "Zero length arguments not allowed for Array.sortBy()";
                }
                var args = arguments;
                return function (a, b) {
                    for (var result = 0, i = 0; result == 0 && i < args.length; i++) {
                        result = _sortByAttr(args[i])(a, b);
                    }
                    return result;
                }
            }
            Object.defineProperty(Array.prototype, "sortBy", {
                enumerable: false,
                writable: true,
                value: function () {
                    return this.sort(_getSortFunc.apply(null, arguments));
                }
            });
        }());

        // remove from Array
        (function () {
            Object.defineProperty(Array.prototype, "_remove_", {
                enumerable: false,
                value: function (itemToRemove) {
                    var i = this.length;
                    while (i--) {
                        if (isjQueryObject(itemToRemove)) {
                            if ($(this[i]).is(itemToRemove)) {
                                this.splice(i, 1);
                            }
                        } else {
                            if (this[i] === itemToRemove) {
                                this.splice(i, 1);
                            }
                        }
                    }
                }
            });
        }());
    }());

    var sortSelect = function (selElem) {
        var tmpAry = [];
        for (var i = 0; i < selElem.options.length; i++) {
            tmpAry[i] = [];
            tmpAry[i][0] = selElem.options[i].text;
            tmpAry[i][1] = selElem.options[i].dataset.url;
        }
        tmpAry.sort(function (a, b) {
            return a[0].toUpperCase().localeCompare(b[0].toUpperCase());
        });
        while (selElem.options.length > 0) {
            selElem.options[0] = null;
        }
        for (var i = 0; i < tmpAry.length; i++) {
            var op = new Option(tmpAry[i][0]);
            op.dataset.url = tmpAry[i][1]
            selElem.options[i] = op;
        }
    };

    var isjQueryObject = function (obj) {
        if (obj instanceof jQuery || 'jquery' in Object(obj)) {
            return true;
        } else {
            return false;
        }
    };


    /**
     * Disable the given button
     * @param {Object} button Jquery object of the button
     */
    var disableButton = function (button) {
        button.prop('disabled', true);
    };

    /**
     * Enable the given button
     * @param {Object} button Jquery object of the button
     */
    var enableButton = function (button) {
        button.prop('disabled', false);
    };

    /**
     * Do the downloads
     * @param {Object} event The event to decide if it is a download all or a downlaod selection (to make this method more abstract)
     */
    var doDownloads = function (event) {
        $("#crossPage").prop("disabled", true);
        var type = $(event.target).data("type");
        var amountOfAnime;
        var collectionOfAnime;
        var download = false;
        var html = UI.builDownloadAlert(type);
        var urlsToDownload = [];
        $("#alertUser").html(html).slideDown("slow");

        if (type === "downloadSelected") {
            $.each($('.checkboxes:checked').prev('a'), function (k, v) {
                var ep = Anime.getEpisodeFromAnchor(this);
                urlsToDownload.push(ep.getDownloadLink());
            });
        } else if (type === "downloadSelects") {
            $.each($('#animeSelection option:selected'), function (k, v) {
                var url = this.dataset.url;
                urlsToDownload.push(url);
            });
        } else {
            var eps = Anime.getEpsForRes(parseInt($("#downloadRes").val()), false);
            for (var i = 0; i < eps.length; i++) {
                urlsToDownload.push(eps[i].getDownloadLink());
            }
        }

        bindAlertControls();

        function bindAlertControls() {
            $("#alertButtonCancel").on("click", function () {
                $("#alertUser").slideUp("slow");
                $("#crossPage").prop("disabled", false);
            });

            $("#alertButton").on("click", function () {
                doIt(urlsToDownload);
            });
        }

        function doIt(urls) {
            for (var i = 0; i < urls.length; i++) {
                var currentUrl = urls[i];
                var link = document.createElement("a");
                link.href = currentUrl;
                link.download = "";
                link.click();
                link.remove();
            }
            $("#alertUser").slideUp("slow");
            $("#crossPage").prop("disabled", false);
        }
    };

    /**
     * Returns if the checkbox is checked
     * @param   {Object}  checkbox The checkbox
     * @returns {Boolean} If ehcked or not
     */
    var checkBoxValid = function (checkbox) {
        return checkbox.is(":checked");
    };

    var _minSeedsSet = function () {
        return Options.Seeds.minSeeds !== -1;
    };

    /**
     * Return the current page offset (what table page you are on)
     * @returns {Number} The offset
     */
    var getCurrentPageOffset = function () {
        return parseInt((typeof QueryString.offset === "undefined") ? 1 : QueryString.offset);
    };

    /**
     * Returns true of false if you can support HTML5 storeag
     * @returns {Boolean}
     */
    var html5StoreSupport = function () {
        try {
            return 'localStorage' in window && window['localStorage'] !== null;
        } catch (e) {
            return false;
        }
    };

    var cleanAvailableResolutions = function () {
        var avRes = Anime.getAvailableResolutions();
        for (var i = 0; i < Anime.getAvailableResolutions().length; i++) {
            var currentRes = avRes[i].res;
            if (Anime.getamountOfEpsFromRes(currentRes, true) === 0) {
                Anime.removeAvailableResolutions(currentRes);
            }
        }
    };

    var injectCss = function (css) {
        if (_isUrl(css)) {
            $("<link>").prop({
                "type": "text/css",
                "rel": "stylesheet"
            }).attr("href", css).appendTo("head");
        } else {
            $("<style>").prop("type", "text/css").html(css).appendTo("head");
        }
    };

    var _isUrl = function (url) {
        var matcher = new RegExp(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/);
        return matcher.test(url);
    };

    var sortAllControls = function () {
        sortSelect(document.getElementById("animeSelection"));
        sortSelect(document.getElementById("downloadRes"));
        $("#info").sortTable(0);
    }

    var reBind = function () {
        $("input[name='filterSelect']").off("change").on("change", handleSelect);

        $("#clearResOptions").off("click").on("click", handleSelect);

        function handleSelect(event) {
            var resTOFilter = $(event.target).data("set")
            $("#selectAnime").html(UI.buildSelect(resTOFilter));
            Utils.sortAllControls();
            reBind();
        }
    };

    var equals = function (episode, toEqual) {
        if (!(episode instanceof Episode && toEqual instanceof Episode)) {
            throw "both objects must be episodes";
        }
        return episode.getUid() === toEqual.getUid();
    };

    var deepEquals = function (episode, toEqual) {
        if (!(episode instanceof Episode && toEqual instanceof Episode)) {
            throw "both objects must be episodes";
        }
        for (var methods in episode) {
            if (episode.hasOwnProperty(methods)) {
                if (typeof episode[methods] === "function") {
                    var method = episode[methods];
                    var method2 = toEqual[methods];
                    if (method.call(this) !== method2.call(this)) {
                        return false;
                    }
                }
            }
        }
        return true;
    };

    return {
        disableButton: disableButton,
        enableButton: enableButton,
        doDownloads: doDownloads,
        checkBoxValid: checkBoxValid,
        getCurrentPageOffset: getCurrentPageOffset,
        html5StoreSupport: html5StoreSupport,
        injectCss: injectCss,
        cleanAvailableResolutions: cleanAvailableResolutions,
        sortAllControls: sortAllControls,
        reBind: reBind,
        sortSelect: sortSelect,
        isjQueryObject: isjQueryObject,
        equals: equals,
        deepEquals: deepEquals
    };
}());

var UI = (function () {
    /**
     * Build the download infomation table
     * @returns {String} The html of the built table
     */
    var buildTable = function () {
        var html = "";
        html += "<table style='width: 100%' id='info'>";

        html += "<caption>Download infomation</caption>";

        html += "<thead>";
        html += "<tr>";
        html += "<th>resolution</th>";
        html += "<th>Episode count</th>";
        html += "<th>Average seeds</th>";
        html += "<th>Average leechers</th>";
        html += "<th>Total size</th>";
        html += "</tr>";
        html += "</thead>";

        html += "<tbody>";
        var allRes = Anime.getAvailableResolutions();
        for (var i = 0; i < allRes.length; i++) {
            var currRes = allRes[i];
            var localRes = currRes.res;
            html += "<tr>";
            html += "<td>" + (localRes === -1 ? "Others" : localRes + "p") + "</td>";
            html += "<td>" + Anime.getamountOfEpsFromRes(localRes, true) + "</td>";
            html += "<td>" + Anime.avgSeedsForRes(localRes, true) + "</td>";
            html += "<td>" + Anime.avgPeersForRes(localRes, true) + "</td>";
            html += "<td>" + Anime.getTotalSizeForRes(localRes, true) + " (aprox)</td>";
            html += "</tr>";
        }

        html += "</tbody>";
        html += "</table>";


        return html;
    };

    var buildDropdownSelections = function () {
        var html = "";
        html += "<select style=\"margin-right:5px;\" id=\"downloadRes\">";
        var allRes = Anime.getAvailableResolutions();
        for (var i = 0; i < allRes.length; i++) {
            var currRes = allRes[i];
            var localRes = currRes.res;
            html += "<option value=" + localRes + ">" + (localRes === -1 ? "Others" : localRes + "p") + "</option>";
        }

        html += "</select>";

        return html;
    };

    var builDownloadAlert = function (type) {
        if (typeof type !== "string") {
            throw "type must a string";
        }
        var amountOfAnime = 0;
        var selectedRes = parseInt($("#downloadRes").val());
        var res = null;
        if (type === "downloadSelected") {
            amountOfAnime = $(".checkboxes:checked").length;
            res = "custom";
        } else if (type === "downloadSelects") {
            amountOfAnime = $("#animeSelection option:selected").length;
            res = "custom";
        } else {
            amountOfAnime = Anime.getamountOfEpsFromRes(selectedRes, false);
            res = selectedRes === -1 ? "Others" : selectedRes + "p";
        }
        var seedLimit = Options.Seeds.minSeeds === -1 ? "None" : Options.Seeds.minSeeds;

        var totalSize = null;
        if (res !== "custom") {
            totalSize = Anime.getTotalSizeForRes(parseInt(res), false);
        }


        var html = "";
        html += "<div class='alert alert-success'>";
        html += "<div><strong>Download: " + res + "</strong></div> <br />";
        html += "<div><strong>Seed Limit: " + seedLimit + "</strong></div>";
        if (totalSize !== null) {
            html += "<br /><div><strong>Total size: " + totalSize + " (aprox)</strong></div>";
        }
        html += "<p>You are about to download " + amountOfAnime + " ep(s)</p>";
        html += "<p>This will cause " + amountOfAnime + " download pop-up(s) Are you sure you want to continue?</p>";
        html += "<p>If there are a lot of eps, your browser might stop responding for a while. This is normal. If you are on Google Chrome, it will ask you to allow multiple-downloads</p>";
        html += "<button id='alertButton'>Okay</button>";
        html += "<button id='alertButtonCancel'>Cancel</button>";
        html += "</div>";
        return html;
    };

    var showAjaxErrorAlert = function (AjaxInfo) {
        if (!$("#parseErrors").is(":hidden")) {
            return;
        }
        $("#parseErrors").html("");
        var html = "";
        html += "<div class='alert alert-danger'>";
        html += "<p>There was an error in getting the infomation from page: '" + AjaxInfo.error.pageAtError + "'</p>";
        html += "<p>The last successful page parsed was page number " + (AjaxInfo.currentPage === null ? 1 : AjaxInfo.currentPage) + " </p>";
        html += "<button id='errorClose'> close </button>";
        html += "</div>";
        $("#parseErrors").html(html);
        $("#parseErrors").slideDown("slow");
        $("#errorClose").off("click").on("click", function () {
            $("#parseErrors").slideUp("slow", function () {
                $(this).html("");
            });
        });
    };


    var buildSelect = function (resTOFilter) {
        resTOFilter = typeof resTOFilter === "undefined" ? "none" : resTOFilter;
        var html = "";
        html += "<div id='selectWrapper'>";
        html += "<div id='selectContainer'>";
        html += "<p>Or you can select episodes here:</p>";
        html += "<p>Seed limit: " + (Options.Seeds.minSeeds === -1 ? "None" : Options.Seeds.minSeeds) + "</p>"
        html += "<select id='animeSelection' multiple size='20'>";
        var allRes = Anime.getAvailableResolutions();
        for (var i = 0; i < allRes.length; i++) {
            var currRes = allRes[i];
            var localRes = currRes.res;
            var eps = Anime.getEpsForRes(localRes, false);
            for (var j = 0; j < eps.length; j++) {
                var currentEp = eps[j];
                if (resTOFilter == "none") {
                    html += "<option data-url=" + currentEp.getDownloadLink() + ">";
                    html += currentEp.getTitle();
                    html += "</option>";
                } else {
                    if (currentEp.getRes() == resTOFilter) {
                        html += "<option data-url=" + currentEp.getDownloadLink() + ">";
                        html += currentEp.getTitle();
                        html += "</option>";
                    }
                }
            }
        }
        html += "</select>";
        html += "<span>Filter select control: </span>";
        var checked = false;
        for (var i = 0; i < allRes.length; i++) {
            if (resTOFilter == allRes[i].res) {
                checked = true;
            }
            html += "<input type='radio' " + (checked ? "checked" : "") + " data-set= '" + allRes[i].res + "' name='filterSelect'/>" + "<label>" + (allRes[i].res === -1 ? "Others" : allRes[i].res + "p") + "</label>";
            checked = false;
        }
        html += "<a id='clearResOptions' data-set='none' >clear</a>";
        html += "</div>";
        html += "</div>";
        return html;
    };


    var buildCustomResOptons = function () {
        $("input[name='filterSelect']").on("change", function (e) {
            var resTOFilter = this.id;
            $("#selectAnime").html(UI.buildSelect(resTOFilter));
            Utils.reBind();
        });
    };

    return {
        buildDropdownSelections: buildDropdownSelections,
        buildTable: buildTable,
        builDownloadAlert: builDownloadAlert,
        showAjaxErrorAlert: showAjaxErrorAlert,
        buildSelect: buildSelect
    };
}());

var DataParser = (function () {
    var table = null;

    var isParsing = false;

    var setTable = function (_table) {
        table = _table;
    };
    /**
     * Parses a table and returns an array of Episodes from it
     * @param   {Object}  table Jquery representation of the anime table
     * @returns {Episode} Array of Episodes
     */
    var parseTable = function (currentPage) {
        if (table === null) {
            throw "no table to parse on, table is null";
        }

        var trRow = table.find("img[src*='www-37.png']").closest("tr");

        var eps = [];

        $.each($(trRow), function (k, v) {
            var Dres = parseRes(this);
            if (Dres === -1) {
                Anime.addAvailableResolutions(-1, null);
            } else {
                Anime.addAvailableResolutions(Dres.res, Dres.fullRes);
            }
            var info = getEpisodeInfo(this);
            eps.push(new Episode(typeof Dres.res === ("undefined") ? -1 : Dres.res, info.currentDownloadLink, info.seeds, info.leech, info.uid, currentPage, info.title, info.size));

        });
        return eps;

        function parseRes(eventContent) {
            var suppRes = Anime.getSupportedRes();
            for (var i = 0; i < Anime.getSupportedRes().length; i++) {
                var currRes = suppRes[i].res;
                var currFullRes = suppRes[i].fullRes;
                if ($(eventContent).children("td:nth-child(2)").text().indexOf(currRes + "p") > -1 || $(eventContent).children("td:nth-child(2)").text().indexOf(currFullRes) > -1) {
                    return suppRes[i];
                }
            }
            return -1;
        }

        function getEpisodeInfo(eventContent) {
            return {
                "currentDownloadLink": $(eventContent).find('td:nth-child(3) >a').attr('href'),
                "seeds": (isNaN(parseInt($(eventContent).find("td.tlistsn").text()))) ? 0 : parseInt($(eventContent).find("td.tlistsn").text()),
                "leech": (isNaN(parseInt($(eventContent).find("td.tlistln").text()))) ? 0 : parseInt($(eventContent).find("td.tlistln").text()),
                "title": $(eventContent).children(".tlistname").text(),
                "uid": Anime.getUidFromJqueryObject($(eventContent)),
                "size": parseFloat($(eventContent).find("td.tlistsize").text())
            };
        }
    };

    return {
        parseTable: parseTable,
        setTable: setTable,
        isParsing: isParsing
    };
}());

var QueryString = function () {
    var query_string = {};
    var query = window.location.search.substring(1);
    var vars = query.split('&');
    for (var i = 0; i < vars.length; i++) {
        var pair = vars[i].split('=');
        if (typeof query_string[pair[0]] === 'undefined') {
            query_string[pair[0]] = pair[1];
        } else if (typeof query_string[pair[0]] === 'string') {
            var arr = [
                query_string[pair[0]],
                pair[1]
            ];
            query_string[pair[0]] = arr;
        } else {
            query_string[pair[0]].push(pair[1]);
        }
    }
    return query_string;
}();

var Options = (function () {
    var Seeds = {};
    var Res = {};
    Object.defineProperty(Seeds, "minSeeds", {
        enumerable: true,
        set: function (seeds) {
            if (typeof seeds !== "number") {
                throw "seeds must be a number";
            }
            this._minSeeds = seeds;
            if (Utils.html5StoreSupport() === true) { // also set it on the local DB
                if (this._minSeeds === -1) {
                    Localstore.removeMinSeedsFromStore();
                } else {
                    Localstore.setMinSeedsFromStore(this._minSeeds);
                }
            }
        },
        get: function () {
            return typeof this._minSeeds === "undefined" ? -1 : this._minSeeds;
        }

    });

    Object.defineProperties(Res, {
        "getAll": {
            get: function () {
                return Anime.getSupportedRes();
            }
        },
        "add": {
            value: function (customRes) {
                if (typeof customRes !== "object") {
                    throw "res must be an object";
                }
                if (typeof customRes.fullRes !== "string") {
                    throw new TypeError("Full res must be a string");
                }

                if (typeof customRes.res !== "number") {
                    throw new TypeError("res must be a number");
                }

                if (typeof customRes.id !== "number") {
                    throw new TypeError("id must be a number");
                }
                try {
                    Localstore.addCustomResToStore(customRes);
                } catch (e) {
                    throw (e);
                }
            }
        },

        "remove": {
            value: function (resToRemove) {
                if (resToRemove !== null && typeof resToRemove !== "number") {
                    throw "resToRemove must be null or an number";
                }
                Localstore.removeCustomResFromStore(resToRemove)
            }
        }
    });

    return {
        Seeds: Seeds,
        Res: Res
    };
}());

//Local storeage object
var Localstore = {
    getMinSeedsFromStore: function () {
        return localStorage.getItem("minSeeds");
    },
    setMinSeedsFromStore: function (seeds) {
        localStorage.setItem('minSeeds', seeds);
    },
    removeMinSeedsFromStore: function () {
        localStorage.removeItem("minSeeds");
    },

    addCustomResToStore: function (resObject) {
        // get all current custom res
        var currentReses = JSON.parse(localStorage.getItem("customRes"));
        if (currentReses === null) {
            localStorage.setItem("customRes", JSON.stringify([]));
            return;
        }
        for (var i = 0; i < currentReses.length; i++) {
            var currResObj = currentReses[i];
            if (resObject.res === currResObj.res || resObject.fullRes === currResObj.fullRes) {
                throw TypeError("ERROR: The supplied resolution already exists");
            }
        }
        // add new res
        currentReses.push(resObject);

        // convert back into string
        var parsedRes = JSON.stringify(currentReses);

        localStorage.setItem("customRes", parsedRes);

    },

    getCustomResFromStore: function () {
        return JSON.parse(localStorage.getItem("customRes"));
    },

    removeCustomResFromStore: function (id) {
        if (id === null) {
            localStorage.removeItem("customRes");
            return;
        }
        var allObj = this.getCustomResFromStore();
        for (var i = 0; i < allObj.length; i++) {
            var currObjId = allObj[i].id;
            if (currObjId === id) {
                allObj._remove_(allObj[i]);
            }
        }
        var parsedRes = JSON.stringify(allObj);

        localStorage.setItem("customRes", parsedRes);
    }
};

// Download fix for firefox
HTMLElement.prototype.click = function () {
    var evt = this.ownerDocument.createEvent('MouseEvents');
    evt.initMouseEvent('click', true, true, this.ownerDocument.defaultView, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
    this.dispatchEvent(evt);
};

/* OBJECT CREATION END */

$(document).ready(function () {
    init(); // init the pannel and set up objects and listeners
    AfterInit(); // set page laod items and settings after the object and ui is built
});

function init() {
    setAnimeObj();
    buildUi();
    bindListeners();


    function setAnimeObj() {
        // Set currentAnime
        if (QueryString.term !== "") {
            Anime.setCurrentAnime(decodeURIComponent(QueryString.term).split("+").join(" "));
        } else {
            Anime.setCurrentAnime("Unknown");
        }

        // set subber

        Anime.setCurrentSubber($(".notice > a > *").html());

        // Set eps

        DataParser.setTable($("table.tlist"));

        // set the seed limit
        var eps = DataParser.parseTable(Utils.getCurrentPageOffset());
        if (Localstore.getMinSeedsFromStore() !== null) {
            var minSeeds = parseInt(Localstore.getMinSeedsFromStore());
            Options.Seeds.minSeeds = minSeeds;
        }

        Anime.addAllEps(eps);

    }

    function buildUi() {
        makeStyles();
        buildPanel();
        afterBuild();

        function makeStyles() {
            var styles = "";

            // Panel
            styles += ".panel{background-color: #fff; border: 1px solid transparent; border-radius: 4px; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); margin-bottom: 20px; margin-left: 8px; margin-right: 8px; width: auto; margin-top: 19px;}";
            styles += ".panel-success {border-color: #d6e9c6;}";
            styles += ".collapse{cursor: pointer; position: absolute; right: 4px; top: 2px;}";
            styles += ".panel-heading{   border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; padding: 4px 15px; text-align:center}";
            styles += ".panel-success > .panel-heading {background-color: #dff0d8; border-color: #d6e9c6; color: #3c763d; position: relative;}";
            styles += ".panel-title {color: inherit; margin-bottom: 0; margin-top: 0; padding: 6px; display: inline-block;}";
            styles += ".panel-body {padding: 15px;}";
            styles += ".avgSeeds{floar:left; padding-right:10px; color:#3c763d;}";
            styles += ".checkboxes{left:1px; margin:0; padding:0; position: relative; top: 1px; z-index: 1;}";
            styles += "#topbar{z-index: 2 !important;}";

            // Infomation
            styles += "#info, #info th, #info td {border: 1px solid black;border-collapse: collapse;}";
            styles += "#info th, #info td {padding: 5px;text-align: left;}";
            styles += "label[for='MinSeeds']{ display: block; margin-top: 10px;}";
            styles += "#SaveMinSeeds{margin-left:5px;}";

            // Alerts
            styles += ".alert {border: 1px solid transparent;border-radius: 4px;margin-bottom: 20px;padding: 15px; position:relative;}";
            styles += ".alert-success {background-color: #dff0d8;border-color: #d6e9c6;color: #3c763d;}";
            styles += ".alert-danger {color: #A94442;background-color: #F2DEDE;border-color: #EBCCD1;}";
            styles += "#alertUser, #parseErrors{margin-top: 15px;}";
            styles += "#alertButton{position:absolute; bottom:5px; right:5px;}";
            styles += "#alertButtonCancel{position:absolute; bottom:5px; right: 66px;}";
            styles += "#errorClose{position:absolute; bottom:5px; right: 11px;}";

            // Anime Selects
            styles += "#animeSelection{width: 100%;}";
            styles += "#clearResOptions{margin-left: 10px; cursor: pointer;}";

            // Custom Resolutions

            styles += "#customResButtonDiv {margin-top:10px;}";

            styles += "#downloadCustomButton{float:right;}";
            Utils.injectCss(styles);
            Utils.injectCss("https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css");

        }

        function buildPanel() {
            var html = "";
            html += '<div class="panel panel-success">';

            html += '<div class="panel-heading">';
            html += '<h3 id="panel-title" class="panel-title"></h3>';
            html += '<i class="fa fa-minus collapse" id="collapseToggle" title="Hide"></i>';
            html += "</div>";

            html += '<div class="panel-body" id="pannelContent"></div>';

            html += '</div>';
            $(".content > .notice").after(html);
            buildPanelContent();

            function buildPanelContent() {
                var html = "";
                html += "<div>";
                $("#panel-title").html("<span> Download \"" + Anime.getCurrentAnime() + " (" + Anime.getCurrentSubber() + ")\"</span>");
                if (Anime.getAmountOfEps() === 0) {
                    html += "<span> No translated anime found or error occured</span>";
                    html += "</div>";
                    $("#pannelContent").html(html);
                    return;
                }
                html += "<span>Pick a resolution: </span>";

                html += "<span id='selectDownload'>";

                html += UI.buildDropdownSelections();

                html += "</span>";

                html += "<button type=\"button\" data-type='downloadAll' id=\"downloadAll\">Download all</button>";

                html += "<button type='button' id='downloadCustomButton' data-type='downloadSelected' >download your selected items</button>";

                html += "</div>";

                html += "<div id='options'>";


                html += "<label for='crossPage'> include Cross pages</label>";
                html += "<input type='checkbox' id='crossPage' /> ";

                html += "<label for='MinSeeds'>Minimum seeders:</label>";
                html += "<input type='number' min='0' id='MinSeeds' title='Any episode that is below this limit will be excluded from the download.'/>";
                html += "<button type='button' id='SaveMinSeeds'>Save</button>";

                /*  html += "<div id='customResButtonDiv'>";
                html += "<button id='customRes'> Manage resolutions </button>";
                html += "</div>";
                */
                html += "</div>";

                html += "<div id='tableInfo'>";
                html += UI.buildTable();
                html += "</div>";

                html += "<div id='alertUser' class='hide'></div>";

                html += "<div class='selectAnime' id='selectAnime'>";
                html += UI.buildSelect();
                html += "</div>";
                html += "<button id='acceptSelect' data-type='downloadSelects'>Select for download</button>";

                html += "<div id='parseErrors' class ='hide'></div>";
                $("#pannelContent").html(html);
            }
        }

        function afterBuild() {
            makeCheckBoxes();
            sortLists();

            function makeCheckBoxes() {
                $(".tlistdownload > a").after("<input class='checkboxes' type='checkbox'/>");
            }

            function sortLists() {
                Utils.sortAllControls();
            }
        }

    }

    function bindListeners() {
        $("#downloadAll").on("click", function (e) {
            Utils.doDownloads(e);
        });

        $("#downloadCustomButton").on("click", function (e) {
            Utils.doDownloads(e);
        });

        $(".checkboxes").on("click", function (e) {
            if (Utils.checkBoxValid($(".checkboxes"))) {
                Utils.enableButton($("#downloadCustomButton"));
            } else {
                Utils.disableButton($("#downloadCustomButton"));
            }
        });

        $("#crossPage").on("click", function (e) {
            preformParsing(Anime.getPageUrls(true));

            function preformParsing(urls) {
                if (Utils.checkBoxValid($("#crossPage"))) {
                    $("#tableInfo").html("<p>Please wait while we parse each page...</p>");
                    $("#selectAnime").html("");
                    $("#acceptSelect").hide();
                    $("#crossPage, #downloadAll").prop("disabled", true);
                    $("#parseErrors").slideUp("fast", function () {
                        $(this).html("");
                    });
                    var AjaxInfo = {
                        error: {
                            pageAtError: null
                        },
                        currentPage: null
                    };
                    urls.reduce(function (prev, cur, index) {
                        return prev.then(function (data) {
                            return $.ajax(cur).then(function (data) {
                                DataParser.isParsing = true;
                                var currentPage = cur.split("=").pop();
                                if (cur.indexOf("offset") === -1) {
                                    currentPage = 1;
                                }
                                AjaxInfo.currentPage = currentPage;
                                var table = $(data).find("table.tlist");
                                DataParser.setTable(table);
                                Anime.addAllEps(DataParser.parseTable(parseInt(currentPage)));
                                $("#tableInfo").append("<div>Page " + currentPage + " Done </div>");
                            }, function () {
                                AjaxInfo.error.pageAtError = cur.split("=").pop();
                            });
                        });
                    }, $().promise()).always(function () {
                        if (AjaxInfo.error.pageAtError !== null) {
                            UI.showAjaxErrorAlert(AjaxInfo);
                        }
                        $("#tableInfo").html(UI.buildTable());
                        $("#downloadRes").html(UI.buildDropdownSelections());
                        $("#selectAnime").html(UI.buildSelect());
                        Utils.sortAllControls();
                        $("#acceptSelect").show();
                        Utils.reBind();
                        $("#crossPage, #downloadAll").prop("disabled", false);
                        DataParser.isParsing = false;
                    });
                } else { // when un-chekced, clear the Episodes of all eps that are not of the current page
                    $("#tableInfo").html("<p>Please wait while we re-calculate the Episodes</p>");
                    var currentPage = Utils.getCurrentPageOffset();
                    Anime.removeEpisodesFromResidence(currentPage, true);
                    Utils.cleanAvailableResolutions();
                    $("#downloadRes").html(UI.buildDropdownSelections());
                    $("#tableInfo").html(UI.buildTable());
                    $("#selectAnime").html(UI.buildSelect());
                    Utils.reBind();
                    Utils.sortAllControls();
                }

            }
        });

        $("#SaveMinSeeds").on("click", function (e) {
            if (parseInt($("#MinSeeds").val()) < 0) {
                alert("number cannot be negative");
                return;
            }
            var value = parseInt($("#MinSeeds").val() === "" ? -1 : $("#MinSeeds").val());
            Options.Seeds.minSeeds = value;
            if (value === -1) {
                alert("Minimum seeds have been cleared");
            } else {
                alert("Minimum seeds now set to: " + value);
            }
            $("#selectAnime").html(UI.buildSelect());
            Utils.sortAllControls();
            Utils.reBind();
        });

        $("#collapseToggle").on("click", function () {
            $("#pannelContent").stop(true, true).slideToggle("slow", function () {
                if ($(this).is(":hidden")) {
                    $("#collapseToggle").removeClass("fa-minus").addClass("fa-plus");
                    $("#collapseToggle").attr("title", "Show");
                } else {
                    $("#collapseToggle").addClass("fa-minus").removeClass("fa-plus");
                    $("#collapseToggle").attr("title", "Hide");
                }
            });
        });

        Utils.reBind();

        $("#acceptSelect").on("click", function (e) {
            Utils.doDownloads(e);
        });
    }
}

function AfterInit() {
    initButtonStates();
    if (Utils.html5StoreSupport()) {
        setOptionsFromLocalStore();
    }

    function initButtonStates() {
        if (Utils.checkBoxValid($(".checkboxes"))) {
            Utils.enableButton($("#downloadCustomButton"));
        } else {
            Utils.disableButton($("#downloadCustomButton"));
        }
    }

    function setOptionsFromLocalStore() {
        // Min seeds
        if (Localstore.getMinSeedsFromStore() !== null) {
            $("#MinSeeds").val(Options.Seeds.minSeeds);
        }

        //customRes 
        /* In devlopment
        if (Options.Res.getAll === null) {
            Options.Res.add({});
        } else {
            Anime.addSupportedRes(Options.Res.getAll);
        }
        */
    }
}