Nyaa.se Batch downloader

Batch download torrents from nyaa.se

Від 25.07.2015. Дивіться остання версія.

// ==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=*
// @version     5.1.1
// @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
     * @returns {Object} the proto of itself
     */
    function Episode(res, downloadLink, seeds, leechers, uid, resides, title) {

        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";
        }
        var _res = res;

        var _downloadLink = downloadLink;

        var _seeds = seeds;

        var _leechers = leechers;

        var _uid = uid;

        var _resides = resides;

        var _title = title;

        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;
        };

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

var Anime = (function () {

    var currentAnime = null;
    var currentSubber = null;
    /**
     * Array of Episode Objects
     */
    var eps = [];

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

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

    /**
     * Set the current Anime name
     * @param {String} anime The of the Anime
     */
    var setCurrentAnime = function (anime) {
        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 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) {
                        _removeObjectFromArray(availableRes, 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) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var seedCount = 0;
        if (getamountOfEpsFromRes(res) === 0) {
            return 0;
        }
        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));
    };

    /**
     * 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) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var leechCount = 0;
        if (getamountOfEpsFromRes(res) === 0) {
            return 0;
        }
        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));
    };

    /**
     * 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) {
        if (typeof res !== "number") {
            throw "res must be of type 'number'";
        }
        return getEpsForRes(res).length;
    };


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

    /**
     * Add Episodes to the array
     * @param {Episode} ep The Anime object to add
     */
    var addEps = function (ep) {
        if (typeof ep !== "object" && !ep instanceof Episode) {
            throw "addEps must take an Episode object";
        }
        if (_validRes(ep.getRes()) === false) {
            throw new TypeError("The Episode supplied does not have a valid resolution");
        }
        eps.push(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++) {
            var currEp = episode[i];
            if (typeof currEp !== "object" && !currEp instanceof Episode) {
                throw "addEps must take an Episode object";
            }
            if (_validRes(currEp.getRes()) === false) {
                throw new TypeError("The Episode supplied does not have a valid resolution");
            }
            eps.push(currEp);
        }
    };

    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) {
        if (typeof res !== "number") {
            throw "res Must be an int";
        }
        var minSeeds = Options.Seeds.minSeeds;
        var arrayOfEps = [];
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[i];
            if (minSeeds > -1) {
                if (currentEp.getSeeds() < minSeeds) {
                    continue;
                }
            }
            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 (!_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
     * @param   {Object}  anchor Jquery or pure JS anchor dom element
     * @returns {Episode} The eipside that matches the Anchor
     */
    var getEpisodeFromAnchor = function (anchor) {
        var link = (function () {
            if (_isjQueryObject(anchor)) {
                return anchor.get(0);
            }
            return anchor;
        }());
        var uid = _getUidFromAnchor(link);
        return getEpisodeFromUid(uid);
    };

    /**
     * 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) {
        if (typeof uid !== "string") {
            throw "uid must be of type String";
        }
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[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) {
        if (typeof resides !== "number") {
            throw "resides must be a number";
        }
        var arrayOfEps = [];
        for (var i = 0; i < eps.length; i++) {
            var currentEp = eps[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) {
        return anchor.href.split("=").pop();
    };

    /**
     * Remove an object from an array
     * @param {Array}  arr Array to search in
     * @param {Object} obj Object to remove
     */
    var _removeObjectFromArray = function (arr, obj) {
        var i = arr.length;
        while (i--) {
            if (arr[i] === obj) {
                arr.splice(i, 1);
            }
        }
    };

    /**
     * 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 episodes = getEpisodeFromUid(uid);
        for (var i = 0; i < episodes.length; i++) {
            var currentEp = episodes[i];
            _removeObjectFromArray(eps, currentEp);
        }
    };

    /**
     * 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);
        for (var i = 0; i < episodes.length; i++) {
            var currentEp = episodes[i];
            _removeObjectFromArray(eps, currentEp);
        }
    };

    var getAmountOfEps = function () {
        return eps.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
    };
}());


/** Utility functions ***/
var Utils = (function () {
    /**
     * 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.buildAlert(type);
        var epsToDownload = [];
        $("#alertUser").html(html).slideDown("slow");

        if (type === "downloadSelected") {

            $.each($('.checkboxes:checked').prev('a'), function (k, v) {
                epsToDownload.push(Anime.getEpisodeFromAnchor(this));
            });

        } else {
            epsToDownload = Anime.getEpsForRes(parseInt($("#downloadRes").val()));
        }

        bindAlertControls();

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

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

        function doIt(eps) {
            for (var i = 0; i < eps.length; i++) {
                var currentEp = eps[i];
                var currentDownloadLink = currentEp.getDownloadLink();
                var link = document.createElement("a");
                link.download = currentEp.getTitle();
                link.href = currentDownloadLink;
                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 () {
        for (var i = 0; i < Anime.getAvailableResolutions().length; i++) {
            var currentRes = Anime.getAvailableResolutions()[i].res;
            if (Anime.getamountOfEpsFromRes(currentRes) === 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);
    };

    return {
        disableButton: disableButton,
        enableButton: enableButton,
        doDownloads: doDownloads,
        checkBoxValid: checkBoxValid,
        getCurrentPageOffset: getCurrentPageOffset,
        html5StoreSupport: html5StoreSupport,
        injectCss: injectCss,
        cleanAvailableResolutions: cleanAvailableResolutions
    };
}());

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 += "</tr>";
        html += "</thead>";

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

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


        return html;
    };

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

        html += "</select>";

        return html;
    };

    var buildAlert = 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 {
            amountOfAnime = Anime.getamountOfEpsFromRes(selectedRes);
            res = selectedRes === -1 ? "Others" : selectedRes + "p";
        }
        var seedLimit = Options.Seeds.minSeeds === -1 ? "None" : Options.Seeds.minSeeds;


        var html = "";
        html += "<div class='alert alert-success'>";
        html += "<div><strong>Download: " + res + "</strong></div> <br />";
        html += "<div><strong>Seed Limit: " + seedLimit + "</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;
    };

    return {
        buildDropdownSelections: buildDropdownSelections,
        buildTable: buildTable,
        buildAlert: buildAlert

    };
}());

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='http://files.nyaa.se/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);
            var title = $(this).children(".tlistname").text();
            eps.push(new Episode(typeof Dres.res === ("undefined") ? -1 : Dres.res, info.currentDownloadLink, info.seeds, info.leech, info.uid, currentPage, title));

        });
        return eps;

        function parseRes(eventContent) {
            for (var i = 0; i < Anime.getSupportedRes().length; i++) {
                var currRes = Anime.getSupportedRes()[i].res;
                var currFullRes = Anime.getSupportedRes()[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 Anime.getSupportedRes()[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()),
                "uid": Anime.getUidFromJqueryObject($(eventContent))
            };
        }
    };

    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 = {};
    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;
        }

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

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

// 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 > span").html());

        // Set eps

        DataParser.setTable($("table.tlist"));
        var eps = DataParser.parseTable(Utils.getCurrentPageOffset());

        Anime.addAllEps(eps);

    }

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

        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;}";
            styles += ".panel-success {border-color: #d6e9c6;}";
            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;}";
            styles += ".panel-success > .panel-footer + .panel-collapse > .panel-body {border-bottom-color: #d6e9c6;}";
            styles += ".panel-footer {background-color: #f5f5f5; border-bottom-left-radius: 3px; border-bottom-right-radius: 3px; border-top: 1px solid #ddd; padding: 10px 15px;}";
            styles += ".panel-title {color: inherit; margin-bottom: 0; margin-top: 0; padding: 6px;}";
            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;}";

            // 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 += "#alertUser{margin-top: 15px;}";
            styles += "#alertButton{position:absolute; bottom:5px; right:5px;}"
            styles += "#alertButtonCancel{position:absolute; bottom:5px; right: 66px;}"

            styles += "#downloadCustomButton{float:right;}";
            Utils.injectCss(styles);

        }

        function buildPanel() {
            var html = "";
            html += '<div class="panel panel-success" style="margin-left: 9px; margin-top: 19px; width: 851px;">';

            html += '<div class="panel-heading">';
            html += '<h3 id="panel-title" class="panel-title"></h3>';
            html += "</div>";

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

            html += '</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 but not the infomation table (coming soon).'/>";
                html += "<button type='button' id='SaveMinSeeds'>Save</button>";

                html += "</div>";

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

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

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

    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>");
                    $("#crossPage, #downloadAll").prop("disabled", true);
                    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;
                                }
                                var table = $(data).find("table.tlist");
                                DataParser.setTable(table);
                                Anime.addAllEps(DataParser.parseTable(parseInt(currentPage)));
                                $("#tableInfo").append("<div>Page " + currentPage + " Done </div>");
                            });
                        });
                    }, $().promise()).done(function () {
                        $("#tableInfo").html(UI.buildTable());
                        $("#downloadRes").html(UI.buildDropdownSelections());
                        $("#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());
                }
            }
        });

        $("#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);
            }
        });
    }

}

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

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

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