(function ($) {
    "use strict";
    const optionsDefaults = {
        /* action='download' options */
        filename: "table.csv",

        /* action='output' options */
        appendTo: "body",

        /* general options */
        separator: ",",
        newline: "\n",
        quoteFields: true,
        trimContent: true,
        excludeColumns: "",
        excludeRows: ""
    };

    let options = {};

    function quote(text) {
        return "\"" + text.replace(/"/g, "\"\"") + "\"";
    }

    // taken from http://stackoverflow.com/questions/3665115/create-a-file-in-memory-for-user-to-download-not-through-server
    function download(filename, text) {
        const element = document.createElement("a");
        element.setAttribute("href", "data:text/csv;charset=utf-8,\ufeff" + encodeURIComponent(text));
        element.setAttribute("download", filename);

        element.style.display = "none";
        document.body.appendChild(element);

        element.click();

        document.body.removeChild(element);
    }

    function convert(table) {
        let output = "";

        const rows = table.find("tr.export").not(options.excludeRows);

        const numCols = rows.first().find("td,th").filter(":visible").not(options.excludeColumns).length;

        rows.each(function (ignore, elem) {
            $(elem).find("td,th").filter(":visible").not(options.excludeColumns)
                .each(function (i, col) {
                    const column = $(col);

                    // Strip whitespaces
                    const content = options.trimContent
                        ? $.trim(column.text())
                        : column.text();

                    output += options.quoteFields
                        ? quote(content)
                        : content;
                    if (i !== numCols - 1) {
                        output += options.separator;
                    } else {
                        output += options.newline;
                    }
                });
        });

        return output;
    }

    $.fn.table2csv = function (action, opt) {
        if (typeof action === "object") {
            opt = action;
            action = "download";
        } else if (action === undefined) {
            action = "download";
        }

        // type checking
        if (typeof action !== "string") {
            throw new Error("\"action\" argument must be a string");
        }
        if (opt !== undefined && typeof opt !== "object") {
            throw new Error("\"options\" argument must be an object");
        }

        options = $.extend({}, optionsDefaults, opt);

        const table = this.filter("table"); // TODO use $.each

        if (table.length <= 0) {
            throw new Error("table2csv must be called on a <table> element");
        }

        if (table.length > 1) {
            throw new Error("converting multiple table elements at once is not supported yet");
        }

        let csv = convert(table);

        switch (action) {
        case "download":
            download(options.filename, csv);
            break;
        case "output":
            $(options.appendTo).append($("<pre>").text(csv));
            break;
        case "return":
            return csv;
        default:
            throw new Error("\"action\" argument must be one of the supported action strings");
        }

        return this;
    };

}(jQuery));
