import { canUseNewCanvasBlendModes } from "./canUseNewCanvasBlendModes";
import Texture from "../texture/Texture";


/**
 * Number of steps which will be used as a cap when rounding colors.
 *
 * @type {number}
 */
let cacheStepsPerColorChannel: number = 8;

/**
 * Tint cache boolean flag.
 *
 * @type {boolean}
 */
let convertTintToImage: boolean = false;

/**
 * 做缓存使用，若不转换为Image，一般不用
 */
let defaultCanvas: HTMLCanvasElement;
/**
 * 调色方法，根据混色是否可用
 */
let tintMethod: Function = canUseNewCanvasBlendModes() ? tintWithMultiply : tintWithPerPixel;

/**
 * Basically this method just needs a sprite and a color and tints the sprite with the given color.
 *
 * @param {Texture} texture - the sprite to tint
 * @param {number} color - the color to use to tint the sprite with
 * @return {HTMLCanvasElement} The tinted canvas
 */
export function getTintedTexture(texture: Texture, color: number): HTMLCanvasElement {
    color = roundColor(color);

    const stringColor = `#${(`00000${(color | 0).toString(16)}`).substr(-6)}`;

    texture["tintCache"] = texture["tintCache"] || {};

    const cachedTexture = texture["tintCache"][stringColor];

    let canvas;

    if (cachedTexture) {
        if (cachedTexture.tintId === texture._updateID) {
            return texture["tintCache"][stringColor];
        }

        canvas = texture["tintCache"][stringColor];
    }
    else {
        canvas = defaultCanvas || document.createElement('canvas');
    }

    tintMethod(texture, color, canvas);

    canvas.tintId = texture._updateID;

    if (convertTintToImage) {
        // is this better?
        const tintImage = new Image();

        tintImage.src = canvas.toDataURL();

        texture["tintCache"][stringColor] = tintImage;
    }
    else {
        texture["tintCache"][stringColor] = canvas;
        // if we are not converting the texture to an image then we need to lose the reference to the canvas
        defaultCanvas = null;
    }

    return canvas;
};

/**
 * Tint a texture using the 'multiply' operation.
 * @param {Texture} texture - the texture to tint
 * @param {number} color - the color to use to tint the sprite with
 * @param {HTMLCanvasElement} canvas - the current canvas
 */
function tintWithMultiply(texture: Texture, color: number, canvas: HTMLCanvasElement): void {
    const context = canvas.getContext('2d');
    const crop = texture._frame.clone();

    canvas.width = Math.ceil(crop.width);
    canvas.height = Math.ceil(crop.height);

    context.save();
    context.fillStyle = `#${(`00000${(color | 0).toString(16)}`).substr(-6)}`;

    context.fillRect(0, 0, crop.width, crop.height);

    context.globalCompositeOperation = 'multiply';

    context.drawImage(
        texture.baseTexture.source,
        crop.x,
        crop.y,
        crop.width,
        crop.height,
        0,
        0,
        crop.width,
        crop.height
    );

    context.globalCompositeOperation = 'destination-atop';

    context.drawImage(
        texture.baseTexture.source,
        crop.x,
        crop.y,
        crop.width,
        crop.height,
        0,
        0,
        crop.width,
        crop.height
    );
    context.restore();
};

/**
 * Tint a texture using the 'overlay' operation.
 *
 * @param {Texture} texture - the texture to tint
 * @param {number} color - the color to use to tint the sprite with
 * @param {HTMLCanvasElement} canvas - the current canvas
 */
function tintWithOverlay(texture: Texture, color: number, canvas: HTMLCanvasElement): void {
    const context = canvas.getContext('2d');
    const crop = texture._frame.clone();

    canvas.width = Math.ceil(crop.width);
    canvas.height = Math.ceil(crop.height);

    context.save();
    context.globalCompositeOperation = 'copy';
    context.fillStyle = `#${(`00000${(color | 0).toString(16)}`).substr(-6)}`;
    context.fillRect(0, 0, crop.width, crop.height);

    context.globalCompositeOperation = 'destination-atop';
    context.drawImage(
        texture.baseTexture.source,
        crop.x,
        crop.y,
        crop.width,
        crop.height,
        0,
        0,
        crop.width,
        crop.height
    );

    // context.globalCompositeOperation = 'copy';
    context.restore();
};

/**
 * Tint a texture pixel per pixel.
 *
 * @param {Texture} texture - the texture to tint
 * @param {number} color - the color to use to tint the sprite with
 * @param {HTMLCanvasElement} canvas - the current canvas
 */
function tintWithPerPixel(texture: Texture, color: number, canvas: HTMLCanvasElement): void {
    const context = canvas.getContext('2d');
    const crop = texture._frame.clone();

    canvas.width = Math.ceil(crop.width);
    canvas.height = Math.ceil(crop.height);

    context.save();
    context.globalCompositeOperation = 'copy';
    context.drawImage(
        texture.baseTexture.source,
        crop.x,
        crop.y,
        crop.width,
        crop.height,
        0,
        0,
        crop.width,
        crop.height
    );
    context.restore();

    const rgbValues = hex2rgb(color);
    const r = rgbValues[0];
    const g = rgbValues[1];
    const b = rgbValues[2];

    const pixelData = context.getImageData(0, 0, crop.width, crop.height);

    const pixels = pixelData.data;

    for (let i = 0; i < pixels.length; i += 4) {
        pixels[i + 0] *= r;
        pixels[i + 1] *= g;
        pixels[i + 2] *= b;
    }

    context.putImageData(pixelData, 0, 0);
};


function rgb2hex(rgb) {
    return (((rgb[0] * 255) << 16) + ((rgb[1] * 255) << 8) + (rgb[2] * 255 | 0));
}
function hex2rgb(hex, out?) {
    out = out || [];
    out[0] = ((hex >> 16) & 0xFF) / 255;
    out[1] = ((hex >> 8) & 0xFF) / 255;
    out[2] = (hex & 0xFF) / 255;
    return out;
}
function roundColor(color) {
    const step = cacheStepsPerColorChannel;
    const rgbValues = hex2rgb(color);
    rgbValues[0] = Math.min(255, (rgbValues[0] / step) * step);
    rgbValues[1] = Math.min(255, (rgbValues[1] / step) * step);
    rgbValues[2] = Math.min(255, (rgbValues[2] / step) * step);
    return rgb2hex(rgbValues);
}