import { IModelChangesListener } from "../Model/ModelManipulator";
import { ItemChange } from "../Model/ModelChange";
import { Size } from "@devexpress/utils/lib/geometry/size";
import { UnitConverter } from "@devexpress/utils/lib/class/unit-converter";
import { Browser } from "@devexpress/utils/lib/browser";
import { RenderUtils } from "./Utils";
import { CanvasManagerBase } from "./CanvasManagerBase";
import { ITextMeasurer } from "./Measurer/ITextMeasurer";
import { svgNS, RenderHelper } from "./RenderHelper";
import { ExportDOMManipulator } from "./DOMManipulator";
import { ColorUtils } from "@devexpress/utils/lib/utils/color";

const EXPORT_IMAGE_QUALITY = 1;
declare let canvg: any;

export class CanvasExportManager extends CanvasManagerBase implements IModelChangesListener {
    static base64Start = "data:image/svg+xml;base64,";
    static exportStyleRules = [
        ".dxdi-canvas .shape ", ".dxdi-canvas .connector ", ".dxdi-canvas text", ".dxdi-canvas.export"
    ];
    static exportStyleAttributes = [
        "fill", "stroke", "stroke-width", "stroke-linejoin",
        "font-family", "font-size", "font-weight", "font-style", "text-decoration", "text-anchor"
    ];


    constructor(private itemsContainer: SVGElement, measurer: ITextMeasurer, instanceId: string) {
        super(1, new ExportDOMManipulator(measurer), instanceId);
    }
    getSvgImage(modelSize: Size, pageColor: number, exportAsInline: boolean, exportForBinaryImage: boolean): SVGSVGElement {
        const svgEl = RenderHelper.createSvgElement(undefined, true);
        const modelAbsSize = modelSize.clone().applyConverter(UnitConverter.twipsToPixelsF).clone().applyConverter(Math.ceil);
        RenderUtils.updateSvgElementSize(svgEl, modelAbsSize.width, modelAbsSize.height, true);
        svgEl.style.backgroundColor = ColorUtils.colorToHash(pageColor);
        this.createTextFloodFilter(this.instanceId, undefined, svgEl, pageColor);
        const exportCssRules = !exportAsInline && !Browser.IE && this.getExportCssRules();
        if(exportCssRules) {
            const style = document.createElementNS(svgNS, "style");
            style.innerHTML = exportCssRules;
            svgEl.appendChild(style);
        }
        if(exportForBinaryImage) { 
            const bk = document.createElementNS(svgNS, "rect");
            bk.setAttributeNS(null, "x", "0");
            bk.setAttributeNS(null, "y", "0");
            bk.setAttributeNS(null, "height", modelAbsSize.height.toString());
            bk.setAttributeNS(null, "width", modelAbsSize.width.toString());
            bk.setAttributeNS(null, "fill", svgEl.style.backgroundColor);
            svgEl.appendChild(bk);
        }
        for(let i = 0; i < this.itemsContainer.childNodes.length; i++) {
            const node = this.itemsContainer.childNodes[i].cloneNode(true);
            if(!exportCssRules)
                this.inlineStyle(node, this.itemsContainer.childNodes[i]);

            svgEl.appendChild(node);
        }
        return svgEl;
    }
    getSvgImageUrl(modelSize: Size, pageColor: number, exportAsInline: boolean): string {
        const svgEl = this.getSvgImage(modelSize, pageColor, exportAsInline, false);
        return this.getSvgBase64String(svgEl);
    }
    private getSvgString(svgElement: Node) {
        return new XMLSerializer().serializeToString(svgElement);
    }
    private getSvgBase64String(svgElement: Node) {
        const xml = this.getSvgString(svgElement);
        return CanvasExportManager.base64Start + this.getBase64EncodeUnicode(xml);
    }
    private getBase64EncodeUnicode(s) {
        return btoa(encodeURIComponent(s).replace(/%([0-9A-F]{2})/g,
            (match, p1) => String.fromCharCode(parseInt("0x" + p1, 16))
        ));
    }
    private getExportCssRules(): string {
        for(let i = 0; i < document.styleSheets.length; i++) {
            const rules = this.getRules(document.styleSheets[i]);
            if(rules) {
                let cssText = "";
                for(let j = 0; j < rules.length; j++) {
                    const rule = rules[j];
                    const selectorText = this.isCSSStyleRule(rule) ? rule.selectorText : null;
                    if(selectorText && this.checkSelector(selectorText))
                        cssText += rule.cssText + "\n";
                }
                if(cssText.length > 0)
                    return "\n" + cssText;
            }
        }
    }
    private checkSelector(selectorText: string): boolean {
        for(let i = 0; i < CanvasExportManager.exportStyleRules.length; i++)
            if(selectorText.indexOf(CanvasExportManager.exportStyleRules[i]) === 0)
                return true;

        return false;
    }
    private getRules(styleSheet: StyleSheet) {
        try {
            return this.isCSSStyleSheet(styleSheet) ? styleSheet.rules || styleSheet.cssRules : null;
        }
        catch{ } 
    }
    private isCSSStyleSheet(stylesheet: StyleSheet): stylesheet is CSSStyleSheet {
        return (<CSSStyleSheet>stylesheet).rules !== undefined;
    }
    private isCSSStyleRule(rule: CSSRule): rule is CSSStyleRule {
        return (<CSSStyleRule>rule).selectorText !== undefined;
    }
    private inlineStyle(destNode, srcNode) {
        for(let i = 0; i < destNode.childNodes.length; i++) {
            const child = destNode.childNodes[i];
            if(!child.tagName) continue;

            if(child.tagName === "g")
                this.inlineStyle(child, srcNode.childNodes[i]);
            else if(child.style) {
                const style = window.getComputedStyle(srcNode.childNodes[i]);
                if(style !== undefined)
                    for(let index = 0; index < CanvasExportManager.exportStyleAttributes.length; index++) {
                        const styleProperty = CanvasExportManager.exportStyleAttributes[index];
                        child.style.setProperty(styleProperty, style.getPropertyValue(styleProperty));
                    }
                this.inlineStyle(child, srcNode.childNodes[i]);
            }
        }
    }
    exportSvgImage(modelSize: Size, pageColor: number, callback: (url: string) => void) {
        callback(this.getSvgImageUrl(modelSize, pageColor, true));
    }
    private exportBinaryImage(modelSize: Size, pageColor: number, mimeType: string, callback: (url: string) => void, useCanvgForExportToImage?: boolean) {
        const modelAbsSize = this.getAbsoluteSize(modelSize).clone().applyConverter(Math.ceil);
        const canvasEl = document.createElement("canvas");
        canvasEl.width = modelAbsSize.width;
        canvasEl.height = modelAbsSize.height;
        const ctx = canvasEl.getContext("2d");
        ctx.fillStyle = ColorUtils.colorToHash(pageColor);
        ctx.fillRect(0, 0, modelAbsSize.width, modelAbsSize.height);

        if((useCanvgForExportToImage || Browser.IE) && typeof canvg === "object")
            this.exportBinaryImageCanvgAsync(modelSize, pageColor, canvasEl, ctx, mimeType).then((dataUrl) => callback(dataUrl));
        else if(Browser.IE && typeof canvg === "function")
            this.exportBinaryImageCanvgOld(modelSize, pageColor, canvasEl, ctx, mimeType, callback);
        else {
            const imgEl = new Image();
            imgEl.width = modelAbsSize.width;
            imgEl.height = modelAbsSize.height;
            imgEl.setAttribute("crossOrigin", "anonymous");
            imgEl.onload = function() {
                ctx.drawImage(imgEl, 0, 0);
                callback(canvasEl.toDataURL(mimeType, EXPORT_IMAGE_QUALITY));
            };
            imgEl.src = this.getSvgImageUrl(modelSize, pageColor, true);
        }
    }
    private exportBinaryImageCanvgOld(modelSize: Size, pageColor: number, canvasEl: HTMLCanvasElement, ctx: CanvasRenderingContext2D, mimeType: string, callback: (url: string) => void) {
        const svgEl = this.getSvgImage(modelSize, pageColor, true, false);
        const svgStr = this.getSvgString(svgEl);
        ctx["drawSvg"](
            svgStr, 0, 0, null, null, {
                renderCallback: function() {
                    callback(canvasEl.toDataURL(mimeType, EXPORT_IMAGE_QUALITY));
                }
            }
        );
    }
    private async exportBinaryImageCanvgAsync(modelSize: Size, pageColor: number, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, mimeType: string): Promise<string> {
        const svgEl = this.getSvgImage(modelSize, pageColor, true, true);
        const svgStr = this.getSvgString(svgEl);
        const v = canvg.Canvg.fromString(ctx, svgStr);
        await v.render();
        return canvas.toDataURL(mimeType, EXPORT_IMAGE_QUALITY);
    }
    exportPngImage(modelSize: Size, pageColor: number, callback: (url: string) => void, useCanvgForExportToImage?: boolean) {
        this.exportBinaryImage(modelSize, pageColor, "image/png", callback, useCanvgForExportToImage);
    }
    exportJpgImage(modelSize: Size, pageColor: number, callback: (url: string) => void, useCanvgForExportToImage?: boolean) {
        this.exportBinaryImage(modelSize, pageColor, "image/jpeg", callback, useCanvgForExportToImage);
    }

    notifyModelChanged(changes: ItemChange[]) { }
    notifyPageColorChanged(color: number) { }
    notifyPageSizeChanged(pageSize: Size, pageLandscape: boolean) { }

}
