import {TextField} from "@engine/renderable/impl/ui/components/textField";
import {Device} from "../../misc/device";
import {Game, SCALE_STRATEGY} from "../../core/game";
import {Rect} from "../../geometry/rect";
import {Color} from "../common/color";
import {Size} from "../../geometry/size";
import {Rectangle} from "@engine/renderable/impl/geometry/rectangle";
import {Ellipse} from "@engine/renderable/impl/geometry/ellipse";
import {Image} from "@engine/renderable/impl/general/image";
import {ResourceLink} from "@engine/resources/resourceLink";
import {Mesh} from "@engine/renderable/abstract/mesh";
import {Font} from "@engine/renderable/impl/general/font";
import {Line} from "@engine/renderable/impl/geometry/line";
import {RenderableModel} from "@engine/renderable/abstract/renderableModel";
import {ICubeMapTexture, ITexture} from "@engine/renderer/common/texture";
import {IDestroyable, Optional} from "@engine/core/declarations";
import {AlphaBlendStack} from "@engine/renderer/common/alphaBlendStack";
import {RendererHelper} from "@engine/renderer/abstract/rendererHelper";
import {IMatrixTransformable} from "@engine/renderer/webGl/base/matrixStack";
import {IStateStackPointer} from "@engine/renderer/webGl/base/frameBufferStack";
import {IFilter} from "@engine/renderer/common/ifilter";
import {mat4} from "@engine/geometry/mat4";
import MAT16 = mat4.MAT16;

interface IHTMLElement extends HTMLElement{
    requestFullScreen:()=>Promise<void>;
    mozRequestFullScreen:()=>Promise<void>;
    webkitRequestFullScreen:()=>Promise<void>;
}

interface IDocument extends Document {
    cancelFullScreen:()=>void;
    mozCancelFullScreen:()=>void;
    webkitCancelFullScreen:()=>void;
}

export interface IRenderTarget {
    getResourceLink():ResourceLink<ITexture>;
    destroy():void;
}

export abstract class AbstractRenderer implements IDestroyable,IMatrixTransformable {

    public abstract type:string;

    public container:HTMLElement;
    public debugTextField:TextField;
    public clearBeforeRender:boolean = true;
    public readonly clearColor:Color = Color.RGB(0,0,0);
    public readonly viewPortSize = new Size();

    protected abstract rendererHelper: RendererHelper;

    private alphaBlendStack:AlphaBlendStack = new AlphaBlendStack();
    private fullScreenRequested:boolean = false;

    protected constructor(protected game:Game){
        this.game = game;
    }

    public requestFullScreen():void {
        const canRequest:boolean = this._requestFullScreen();
        if (canRequest) {
            const fn = ()=>{
                if (this.fullScreenRequested) this._requestFullScreen();
                document.body.removeEventListener('click',fn);
            };
            document.body.addEventListener('click',fn);
        }
    }

    public cancelFullScreen():void {
        this.fullScreenRequested =false;
        const doc:IDocument = globalThis.document as IDocument;
        if(doc.cancelFullScreen) {
            (doc).cancelFullScreen();
        } else if(doc.mozCancelFullScreen) {
            doc.mozCancelFullScreen();
        } else if(doc.webkitCancelFullScreen) {
            doc.webkitCancelFullScreen();
        }
    }


    public beforeItemStackDraw(filters:IFilter[],forceDrawChildrenOnNewSurface:boolean):IStateStackPointer {
        return undefined!;
    }

    public afterItemStackDraw(stackPointer:IStateStackPointer):void {}

    public beforeFrameDraw(filters:IFilter[]):IStateStackPointer {
        return undefined!;
    }

    public afterFrameDraw(stackPointer:IStateStackPointer):void {}

    public destroy():void {
        globalThis.removeEventListener('resize',this.onResize);
    }

    public abstract getError():Optional<{code:number,desc:string}>;

    public abstract drawImage(img:Image):void;

    public abstract drawRectangle(rectangle:Rectangle):void;

    public abstract setLockRect(rect:Rect):void;

    public abstract unsetLockRect():void;

    public abstract drawLine(line:Line):void;

    public abstract drawMesh(m:Mesh):void;

    public abstract drawEllipse(ellispe:Ellipse):void;

    public abstract transformReset():void;

    public abstract transformSave():void;

    public abstract transformRestore():void;

    public abstract transformTranslate(x:number, y:number, z?:number):void;

    public abstract transformRotationReset():void;

    public abstract transformScale(x:number, y:number, z?:number):void;

    public abstract transformRotateX(a:number):void;

    public abstract transformSkewX(a:number):void;

    public abstract transformSkewY(a:number):void;

    public abstract transformRotateY(a:number):void;

    public abstract transformRotateZ(a:number):void;

    public abstract transformSet(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number, v14: number, v15: number): void;

    public abstract transformGet():Readonly<MAT16>;

    public killObject(r:RenderableModel):void {}

    public saveAlphaBlend():void{
        this.alphaBlendStack.save();
    }

    public restoreAlphaBlend():void{
        this.alphaBlendStack.restore();
    }

    public setAlphaBlend(alpha:number):void{
        this.alphaBlendStack.mult(alpha);
    }

    public getAlphaBlend():number{
        return this.alphaBlendStack.getCurrentValue();
    }

    public log(...args:any[]):void {
        if (!DEBUG) return;
        let textField:TextField = this.debugTextField;
        if (!textField) {
            textField = new TextField(this.game);
            textField.setFont(Font.getSystemFont());
             // todo temporary solution, refactor to debug layer creation
            textField.revalidate();
            this.debugTextField = textField;
        }
        textField.setScene(this.game.getCurrScene());
        let res:string = '';
        Array.prototype.slice.call(arguments).forEach((txt:any)=>{
            if (txt===undefined) txt = 'undefined';
            else if (txt===null) txt = 'null';
            else if (txt instanceof HTMLElement) {
                txt = `[object ${txt.tagName}]`;
            }
            else if (txt.toJSON) {
                txt = JSON.stringify(txt.toJSON(),undefined,4);
            }
            else if (typeof txt==='function') {
                txt = txt.toString();
            }
            else {
                if (!(txt as string).substr) {
                    try{
                        txt = JSON.stringify(txt);
                    } catch (e){
                        if (txt.constructor && txt.constructor.name) txt = `[object ${txt.constructor.name}]`;
                        else txt = txt.toString();
                    }
                }
            }
            res+=`${txt}\n`;
        });
        textField.setText(textField.getText()+res);
        textField.onGeometryChanged();
        while (textField.size.height>this.game.size.height) {
            const strings:string[] = textField.getText().split('\n');
            strings.shift();
            textField.setText(strings.join('\n'));
            textField.onGeometryChanged();
        }
    }

    public clearLog():void {
        if (!DEBUG) return;
        if (!this.debugTextField) return;
        this.debugTextField.setText('');
    }

    public getHelper():RendererHelper{
        return this.rendererHelper;
    }

    public abstract createTexture(bitmap:ImageBitmap|HTMLImageElement):ITexture;

    public abstract createCubeTexture(
        imgLeft:ImageBitmap|HTMLImageElement,
        imgRight:ImageBitmap|HTMLImageElement,
        imgTop:ImageBitmap|HTMLImageElement,
        imgBottom:ImageBitmap|HTMLImageElement,
        imgFront:ImageBitmap|HTMLImageElement,
        imgBack:ImageBitmap|HTMLImageElement
    ):ICubeMapTexture;


    protected registerResize():void {
        this.onResize();
        globalThis.addEventListener('resize', () => {
            this.onResize();
            setTimeout(()=>this.onResize(),100);
        });
        // to prevent zoom on ios
        document.addEventListener('gesturestart', (e:Event)=>e.preventDefault());
        this.container.addEventListener('gesturestart', (e:Event)=>e.preventDefault());
    }

    protected onResize():void {
        const container:HTMLElement = this.container;

        const [innerWidth,innerHeight] = this.getScreenResolution();

        if (this.game.scaleStrategy===SCALE_STRATEGY.NO_SCALE) return;
        else if (this.game.scaleStrategy===SCALE_STRATEGY.STRETCH) {
            container.style.width = `${innerWidth}px`;
            container.style.height = `${innerHeight}px`;
            this.viewPortSize.setWH(innerWidth,innerHeight);
            this.game.scale.setXY(innerWidth/this.game.size.width,innerHeight/this.game.size.height);
            this.game.pos.setXY(0);
        } else {
            // else FIT
            const canvasRatio:number = this.game.size.height / this.game.size.width;
            const windowRatio:number = innerHeight / innerWidth;
            let width:number;
            let height:number;

            if (windowRatio < canvasRatio) {
                height = innerHeight;
                width = height / canvasRatio;
            } else {
                width = innerWidth;
                height = width * canvasRatio;
            }

            this.game.scale.setXY(width / this.game.size.width, height / this.game.size.height);
            this.game.pos.setXY(
                (innerWidth - width) / 2,
                (innerHeight - height) / 2
            );

            this.container.style.width = width + 'px';
            this.container.style.height = height + 'px';
            this.container.style.marginTop = `${this.game.pos.y}px`;

            this.viewPortSize.setWH(width,height);
        }

    }

    private getScreenResolution():[number,number]{
        return [globalThis.innerWidth,globalThis.innerHeight];
    }

    private _requestFullScreen():boolean {
        const element:IHTMLElement = this.container as IHTMLElement;
        this.fullScreenRequested = true;
        let canRequest:boolean = false;
        if(element.requestFullScreen) {
            element.requestFullScreen();
            canRequest = true;
        } else if(element.mozRequestFullScreen) {
            element.mozRequestFullScreen();
            canRequest = true;
        } else if(element.webkitRequestFullScreen) {
            element.webkitRequestFullScreen();
            canRequest = true;
        }
        return canRequest;
    }
}
