/**
 * This JS file allows downloading png files from svg cavas. This codes belong to: https://github.com/exupero/saveSvgAsPng
 *
 */

(function () {
    var out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this;

    var doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp "&#160;">]>';

    function isElement(obj) {
        return obj instanceof HTMLElement || obj instanceof SVGElement;
    }

    function requireDomNode(el) {
        if (!isElement(el)) {
            throw new Error('an HTMLElement or SVGElement is required; got ' + el);
        }
    }

    function isExternal(url) {
        return url && url.lastIndexOf('http', 0) == 0 && url.lastIndexOf(window.location.host) == -1;
    }

    function inlineImages(el, callback) {
        requireDomNode(el);

        var images = el.querySelectorAll('image'),
            left = images.length,
            checkDone = function () {
                if (left === 0) {
                    callback();
                }
            };

        checkDone();
        for (var i = 0; i < images.length; i++) {
            (function (image) {
                var href = image.getAttributeNS("http://www.w3.org/1999/xlink", "href");
                if (href) {
                    if (isExternal(href.value)) {
                        console.warn("Cannot render embedded images linking to external hosts: " + href.value);
                        return;
                    }
                }
                var canvas = document.createElement('canvas');
                var ctx = canvas.getContext('2d');
                var img = new Image();
                img.crossOrigin = "anonymous";
                href = href || image.getAttribute('href');
                if (href) {
                    img.src = href;
                    img.onload = function () {
                        canvas.width = img.width;
                        canvas.height = img.height;
                        ctx.drawImage(img, 0, 0);
                        image.setAttributeNS("http://www.w3.org/1999/xlink", "href", canvas.toDataURL('image/png'));
                        left--;
                        checkDone();
                    };
                    img.onerror = function () {
                        console.log("Could not load " + href);
                        left--;
                        checkDone();
                    }
                } else {
                    left--;
                    checkDone();
                }
            })(images[i]);
        }
    }

    function styles(el, options, cssLoadedCallback) {
        var selectorRemap = options.selectorRemap;
        var modifyStyle = options.modifyStyle;
        var css = "";
        // each font that has extranl link is saved into queue, and processed
        // asynchronously
        var fontsQueue = [];
        var sheets = document.styleSheets;
        for (var i = 0; i < sheets.length; i++) {
            try {
                var rules = sheets[i].cssRules;
            } catch (e) {
                console.warn("Stylesheet could not be loaded: " + sheets[i].href);
                continue;
            }

            if (rules != null) {
                for (var j = 0, match; j < rules.length; j++, match = null) {
                    var rule = rules[j];
                    if (typeof (rule.style) != "undefined") {
                        var selectorText;

                        try {
                            selectorText = rule.selectorText;
                        } catch (err) {
                            console.warn('The following CSS rule has an invalid selector: "' + rule + '"', err);
                        }

                        try {
                            if (selectorText) {
                                match = el.querySelector(selectorText) || el.parentNode.querySelector(selectorText);
                            }
                        } catch (err) {
                            console.warn('Invalid CSS selector "' + selectorText + '"', err);
                        }

                        if (match) {
                            var selector = selectorRemap ? selectorRemap(rule.selectorText) : rule.selectorText;
                            var cssText = modifyStyle ? modifyStyle(rule.style.cssText) : rule.style.cssText;
                            css += selector + " { " + cssText + " }\n";
                        } else if (rule.cssText.match(/^@font-face/)) {
                            // below we are trying to find matches to external link. E.g.
                            // @font-face {
                            //   // ...
                            //   src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
                            // }
                            //
                            // This regex will save extrnal link into first capture group
                            var fontUrlRegexp = /url\(["']?(.+?)["']?\)/;
                            // TODO: This needs to be changed to support multiple url declarations per font.
                            var fontUrlMatch = rule.cssText.match(fontUrlRegexp);

                            var externalFontUrl = (fontUrlMatch && fontUrlMatch[1]) || '';
                            var fontUrlIsDataURI = externalFontUrl.match(/^data:/);
                            if (fontUrlIsDataURI) {
                                // We should ignore data uri - they are already embedded
                                externalFontUrl = '';
                            }

                            if (externalFontUrl === 'about:blank') {
                                // no point trying to load this
                                externalFontUrl = '';
                            }

                            if (externalFontUrl) {
                                // okay, we are lucky. We can fetch this font later

                                //handle url if relative
                                if (externalFontUrl.startsWith('../')) {
                                    externalFontUrl = sheets[i].href + '/../' + externalFontUrl
                                } else if (externalFontUrl.startsWith('./')) {
                                    externalFontUrl = sheets[i].href + '/.' + externalFontUrl
                                }

                                fontsQueue.push({
                                    text: rule.cssText,
                                    // Pass url regex, so that once font is downladed, we can run `replace()` on it
                                    fontUrlRegexp: fontUrlRegexp,
                                    format: getFontMimeTypeFromUrl(externalFontUrl),
                                    url: externalFontUrl
                                });
                            } else {
                                // otherwise, use previous logic
                                css += rule.cssText + '\n';
                            }
                        }
                    }
                }
            }
        }

        // Now all css is processed, it's time to handle scheduled fonts
        processFontQueue(fontsQueue);

        function getFontMimeTypeFromUrl(fontUrl) {
            var supportedFormats = {
                'woff2': 'font/woff2',
                'woff': 'font/woff',
                'otf': 'application/x-font-opentype',
                'ttf': 'application/x-font-ttf',
                'eot': 'application/vnd.ms-fontobject',
                'sfnt': 'application/font-sfnt',
                'svg': 'image/svg+xml'
            };
            var extensions = Object.keys(supportedFormats);
            for (var i = 0; i < extensions.length; ++i) {
                var extension = extensions[i];
                // TODO: This is not bullet proof, it needs to handle edge cases...
                if (fontUrl.indexOf('.' + extension) > 0) {
                    return supportedFormats[extension];
                }
            }

            // If you see this error message, you probably need to update code above.
            console.error('Unknown font format for ' + fontUrl + '; Fonts may not be working correctly');
            return 'application/octet-stream';
        }

        function processFontQueue(queue) {
            if (queue.length > 0) {
                // load fonts one by one until we have anything in the queue:
                var font = queue.pop();
                processNext(font);
            } else {
                // no more fonts to load.
                cssLoadedCallback(css);
            }

            function processNext(font) {
                // TODO: This could benefit from caching.
                var oReq = new XMLHttpRequest();
                oReq.addEventListener('load', fontLoaded);
                oReq.addEventListener('error', transferFailed);
                oReq.addEventListener('abort', transferFailed);
                oReq.open('GET', font.url);
                oReq.responseType = 'arraybuffer';
                oReq.send();

                function fontLoaded() {
                    // TODO: it may be also worth to wait until fonts are fully loaded before
                    // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet )
                    var fontBits = oReq.response;
                    var fontInBase64 = arrayBufferToBase64(fontBits);
                    updateFontStyle(font, fontInBase64);
                }

                function transferFailed(e) {
                    console.warn('Failed to load font from: ' + font.url);
                    console.warn(e);
                    css += font.text + '\n';
                    processFontQueue(queue);
                }

                function updateFontStyle(font, fontInBase64) {
                    var dataUrl = 'url("data:' + font.format + ';base64,' + fontInBase64 + '")';
                    css += font.text.replace(font.fontUrlRegexp, dataUrl) + '\n';

                    // schedule next font download on next tick.
                    setTimeout(function () {
                        processFontQueue(queue)
                    }, 0);
                }

            }
        }

        function arrayBufferToBase64(buffer) {
            var binary = '';
            var bytes = new Uint8Array(buffer);
            var len = bytes.byteLength;

            for (var i = 0; i < len; i++) {
                binary += String.fromCharCode(bytes[i]);
            }

            return window.btoa(binary);
        }
    }

    function getDimension(el, clone, dim) {
        var v = (el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim]) ||
            (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
            el.getBoundingClientRect()[dim] ||
            parseInt(clone.style[dim]) ||
            parseInt(window.getComputedStyle(el).getPropertyValue(dim));
        return (typeof v === 'undefined' || v === null || isNaN(parseFloat(v))) ? 0 : v;
    }

    function reEncode(data) {
        data = encodeURIComponent(data);
        data = data.replace(/%([0-9A-F]{2})/g, function (match, p1) {
            var c = String.fromCharCode('0x' + p1);
            return c === '%' ? '%25' : c;
        });
        return decodeURIComponent(data);
    }

    out$.prepareSvg = function (el, options, cb) {
        requireDomNode(el);

        options = options || {};
        options.scale = options.scale || 1;
        options.responsive = options.responsive || false;
        var xmlns = "http://www.w3.org/2000/xmlns/";

        inlineImages(el, function () {
            var outer = document.createElement("div");
            var clone = el.cloneNode(true);
            var width, height;
            if (el.tagName == 'svg') {
                width = options.width || getDimension(el, clone, 'width');
                height = options.height || getDimension(el, clone, 'height');
            } else if (el.getBBox) {
                var box = el.getBBox();
                width = box.x + box.width;
                height = box.y + box.height;
                clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));

                var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                svg.appendChild(clone);
                clone = svg;
            } else {
                console.error('Attempted to render non-SVG element', el);
                return;
            }

            clone.setAttribute("version", "1.1");
            if (!clone.getAttribute('xmlns')) {
                clone.setAttributeNS(xmlns, "xmlns", "http://www.w3.org/2000/svg");
            }
            if (!clone.getAttribute('xmlns:xlink')) {
                clone.setAttributeNS(xmlns, "xmlns:xlink", "http://www.w3.org/1999/xlink");
            }

            if (options.responsive) {
                clone.removeAttribute('width');
                clone.removeAttribute('height');
                clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
            } else {
                clone.setAttribute("width", width * options.scale);
                clone.setAttribute("height", height * options.scale);
            }

            clone.setAttribute("viewBox", [
                options.left || 0,
                options.top || 0,
                width,
                height
            ].join(" "));

            var fos = clone.querySelectorAll('foreignObject > *');
            for (var i = 0; i < fos.length; i++) {
                if (!fos[i].getAttribute('xmlns')) {
                    fos[i].setAttributeNS(xmlns, "xmlns", "http://www.w3.org/1999/xhtml");
                }
            }

            outer.appendChild(clone);

            // In case of custom fonts we need to fetch font first, and then inline
            // its url into data-uri format (encode as base64). That's why style
            // processing is done asynchonously. Once all inlining is finshed
            // cssLoadedCallback() is called.
            styles(el, options, cssLoadedCallback);

            function cssLoadedCallback(css) {
                // here all fonts are inlined, so that we can render them properly.
                var s = document.createElement('style');
                s.setAttribute('type', 'text/css');
                s.innerHTML = "<![CDATA[\n" + css + "\n]]>";
                var defs = document.createElement('defs');
                defs.appendChild(s);
                clone.insertBefore(defs, clone.firstChild);

                if (cb) {
                    var outHtml = outer.innerHTML;
                    outHtml = outHtml.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');
                    cb(outHtml, width, height);
                }
            }
        });
    };

    out$.svgAsDataUri = function (el, options, cb) {
        out$.prepareSvg(el, options, function (svg) {
            var uri = 'data:image/svg+xml;base64,' + window.btoa(reEncode(doctype + svg));
            if (cb) {
                cb(uri);
            }
        });
    };

    out$.svgAsPngUri = function (el, options, cb) {
        requireDomNode(el);

        options = options || {};
        options.encoderType = options.encoderType || 'image/png';
        options.encoderOptions = options.encoderOptions || 0.8;

        var convertToPng = function (src, w, h) {
            var canvas = document.createElement('canvas');
            var context = canvas.getContext('2d');
            canvas.width = w;
            canvas.height = h;

            if (options.canvg) {
                options.canvg(canvas, src);
            } else {
                context.drawImage(src, 0, 0);
            }

            if (options.backgroundColor) {
                context.globalCompositeOperation = 'destination-over';
                context.fillStyle = options.backgroundColor;
                context.fillRect(0, 0, canvas.width, canvas.height);
            }

            var png;
            try {
                png = canvas.toDataURL(options.encoderType, options.encoderOptions);
            } catch (e) {
                if ((typeof SecurityError !== 'undefined' && e instanceof SecurityError) || e.name == "SecurityError") {
                    console.error("Rendered SVG images cannot be downloaded in this browser.");
                    return;
                } else {
                    throw e;
                }
            }
            cb(png);
        };

        if (options.canvg) {
            out$.prepareSvg(el, options, convertToPng);
        } else {
            out$.svgAsDataUri(el, options, function (uri) {
                var image = new Image();

                image.onload = function () {
                    convertToPng(image, image.width, image.height);
                };

                image.onerror = function () {
                    console.error(
                        'There was an error loading the data URI as an image on the following SVG\n',
                        window.atob(uri.slice(26)), '\n',
                        "Open the following link to see browser's diagnosis\n",
                        uri);
                };

                image.src = uri;
            });
        }
    };

    out$.download = function (name, uri) {
        if (navigator.msSaveOrOpenBlob) {
            navigator.msSaveOrOpenBlob(uriToBlob(uri), name);
        } else {
            var saveLink = document.createElement('a');
            var downloadSupported = 'download' in saveLink;
            if (downloadSupported) {
                saveLink.download = name;
                saveLink.style.display = 'none';
                document.body.appendChild(saveLink);
                try {
                    var blob = uriToBlob(uri);
                    var url = URL.createObjectURL(blob);
                    saveLink.href = url;
                    saveLink.onclick = function () {
                        requestAnimationFrame(function () {
                            URL.revokeObjectURL(url);
                        })
                    };
                } catch (e) {
                    console.warn('This browser does not support object URLs. Falling back to string URL.');
                    saveLink.href = uri;
                }
                saveLink.click();
                document.body.removeChild(saveLink);
            } else {
                window.open(uri, '_temp', 'menubar=no,toolbar=no,status=no');
            }
        }
    };

    function uriToBlob(uri) {
        var byteString = window.atob(uri.split(',')[1]);
        var mimeString = uri.split(',')[0].split(':')[1].split(';')[0];
        var buffer = new ArrayBuffer(byteString.length);
        var intArray = new Uint8Array(buffer);
        for (var i = 0; i < byteString.length; i++) {
            intArray[i] = byteString.charCodeAt(i);
        }
        return new Blob([buffer], {type: mimeString});
    }

    out$.saveSvg = function (el, name, options) {
        requireDomNode(el);

        options = options || {};
        out$.svgAsDataUri(el, options, function (uri) {
            out$.download(name, uri);
        });
    };

    out$.saveSvgAsPng = function (el, name, options) {
        requireDomNode(el);

        options = options || {};
        out$.svgAsPngUri(el, options, function (uri) {
            out$.download(name, uri);
        });
    };

    // if define is defined create as an AMD module
    if (typeof define !== 'undefined') {
        define(function () {
            return out$;
        });
    }

})();
