/* eslint-disable no-var,no-console */
/**
 * Проверка на масштабирование изображений в браузере.
 * Срабатывает, если натуральный размер изображения намного больше отображаемого на странице,
 * то есть браузер грузит большую картинку и масштабирует её до маленькой.
 */
(function() {
    if (!window.Promise || !String.prototype.startsWith || window.MSInputMethodContext) {
        // Не запускаем проверку в IE11 и браузерах, не поддерживающих нужные API
        return;
    }

    /**
     * Минимальный дополнительный трафик с картинки, при превышении которого срабатывает проверка
     * @type {number}
     */
    var EXTRA_TRAFFIC_KB_LIMIT = 4;

    class Img {
        constructor(imgElement, src) {
            // console.log('Проверяем', imgElement, src);
            this.img = imgElement;
            this.src = src || this.img.src;
            this.className = this.img.className;
        }

        /**
         * Массив URL, которые не будут проверяться
         */
        ignoredImageSources = [
            // 'https://example.com/img/placeholder.png'
        ];

        /**
         * Изображения, которые надо игнорировать:
         * - или проверено и ничего плохого не найдено
         * - или уже заведена задача на починку
         *
         * Игнорируются, если все перечисленные свойства совпадут.
         * Если какое-то свойство здесь не указано - оно не будет проверяться.
         *
         * Свойства:
         *   className - точный className DOM-элемента
         *   w, h - ширина и высота DOM-элемента
         *   nw, nh - натуральные размеры изображения
         *   xScale, yScale - коэффициент масштабирования по осям
         */
        ignoredImages = [
        ];

        calculateDimensions() {
            this.w = this.img.offsetWidth;
            this.h = this.img.offsetHeight;
            this.nw = this.img.naturalWidth;
            this.nh = this.img.naturalHeight;
            this.calculateScale();
            return Promise.resolve();
        }

        calculateScale() {
            this.xScale = this.nw / this.w;
            this.yScale = this.nh / this.h;
        }

        checkDimensions() {
            if (this.shouldIgnoreImageBefore()) {
                return;
            }

            this.calculateDimensions().then(function() {
                if (this.shouldIgnoreImageAfter()) {
                    return;
                }

                var w = this.w,
                    h = this.h,
                    nw = this.nw,
                    nh = this.nh;

                if (w === 0 || h === 0) {
                    // Скрытое изображение - не репортим
                    // this.report('Скрытое изображение, можно грузить лениво');
                    return;
                }

                if (nw <= w && nh <= h) {
                    // Увеличенное изображение - не репортим
                    return;
                }

                if (this.xScale === 2 && this.yScale === 2 && this.src.endsWith('_2x')) {
                    // Изображение retina 2x
                    return;
                }

                if (this.xScale < 3 && this.xScale > 1 / 3 && this.yScale < 3 && this.yScale > 1 / 3) {
                    // Увеличение или уменьшение менее, чем в 3 раза - OK
                    return;
                }

                // 10000 - эмпирическая константа, дающая примерно похожие числа в проверенных случаях
                // Для более точных результатов надо усложнять алгоритм, что сейчас нецелесообразно,
                // т.к. самые значительные различия находятся и таким алгоритмом
                var extraTrafficKb = Math.round((nw * nh - w * h) / 10000);
                if (extraTrafficKb < EXTRA_TRAFFIC_KB_LIMIT) {
                    return;
                }

                this.report(
                    'Масштабированное изображение: потеря трафика около ' + extraTrafficKb + 'кБ' +
                    ' видимый размер: ' + w + 'x' + h
                );
            }.bind(this));
        }

        shouldIgnoreImageBefore() {
            return this.ignoredImageSources.indexOf(this.src) !== -1;
        }

        matches(props) {
            for (var prop in props) {
                if (props.hasOwnProperty(prop) && props[prop] !== this[prop]) {
                    return false;
                }
            }

            return true;
        }

        shouldIgnoreImageAfter() {
            return this.ignoredImages.some(function(props) {
                return this.matches(props);
            }, this);
        }

        report(message) {
            message += ' натуральный размер: ' + this.nw + 'x' + this.nh;
            message += ' class: "' + this.className + '"';
            if (!this.src.startsWith('data:image')) {
                message += ' src: ' + this.src;
            }
            console.log(message, this.img);
            this.img.style.outline = '3px dotted red';
        }
    }

    class BgImg extends Img {
        calculateDimensions() {
            return Promise.all([
                this.calculateImgDimensions(),
                this.calculateBgDimensions()
            ]).then(function() {
                this.calculateScale();
            }.bind(this));
        }

        calculateImgDimensions() {
            return new Promise(function(resolve) {
                var img = new Image();

                img.onload = function() {
                    img.onload = img.onerror = null;
                    this.nw = img.naturalWidth;
                    this.nh = img.naturalHeight;
                    resolve();
                }.bind(this);

                img.onerror = function() {
                    // Игнорируем ошибку загрузки изображения
                    img.onload = img.onerror = null;
                    this.nw = this.nh = 0;
                    resolve();
                }.bind(this);

                img.src = this.src;
            }.bind(this));
        }

        calculateBgDimensions() {
            var backgroundSize = this.img.style.backgroundSize;
            if (backgroundSize) {
                var match = backgroundSize.match(/(\d+)px (\d+)px/);
                if (match) {
                    this.w = parseInt(match[1]);
                    this.h = parseInt(match[2]);
                    return;
                }
            }

            this.w = this.img.offsetWidth || 0;
            this.h = this.img.offsetHeight || 0;
        }

        shouldIgnoreImageBefore() {
            var src = this.src;

            if (
                src === 'none' ||
                src.startsWith('https://favicon.yandex.net/favicon/v2/') ||
                this.ignoredImageSources.indexOf(src) !== -1
            ) {
                return true;
            }

            if (src.startsWith('data:image/')) {
                // Короткие data-url не проверяем
                return src.length < 1000;
            }

            return !/^(https?:\/\/|\/\/)/.test(src);
        }
    }

    var i;
    var images = document.querySelectorAll('img[src]');
    console.log('Проверяю', images.length, 'изображений');
    for (i = 0; i < images.length; i++) {
        new Img(images[i]).checkDimensions();
    }

    /*
        background-image только в inline-стилях можно найти так:
        document.querySelectorAll('[style*="background"][style*="url("]')
        Но нас интересуют computed-стили, поэтому проверяем все элементы DOM
     */
    var allElements = document.querySelectorAll('*');
    var bgImagesCount = 0;
    for (i = 0; i < allElements.length; i++) {
        var container = allElements[i];
        var backgroundImage = getComputedStyle(container).backgroundImage;

        if (!backgroundImage.startsWith('url(')) {
            continue;
        }

        backgroundImage = backgroundImage.replace(/^url\("?|"?\)$/g, '');
        if (backgroundImage.indexOf('url(') === -1) {
            new BgImg(container, backgroundImage).checkDimensions();
            bgImagesCount++;
            continue;
        }

        var bgImages = backgroundImage.split(/"?\),\s*url\("?/);
        bgImagesCount += bgImages.length;
        for (var j = 0; j < bgImages.length; j++) {
            new BgImg(container, bgImages[j]).checkDimensions();
        }
    }
    console.log('Проверяю', bgImagesCount, 'фоновых изображений');
})();