import { AnimationClip, IAnimationTrack } from "../AnimationClip";
import { Container, Sprite } from "../display";
import { Shape } from "../graphics";
import { HashObject } from "../HashObject";
import { Matrix } from "../math";
import { BaseTexture, Texture } from "../texture";
import { clamp, createCanvas, createImage, rgb2hex, TextureCache } from "../utils";
import { AnimationNode } from "./AnimationNode";

/**
 * svga动画的承载显示类
 */
export class SvgaAni extends AnimationNode {
    /**
     * 原始数据，接口在解析类上，不搞先，尽量只获取，不修改
     */
    protected rawData: VideoEntity;
    /**
     * 总时间，秒计
     */
    get totalTime(): number {
        return this.rawData && this.rawData.frames * (1 / this.fps) || 0;
    };
    /**
     * 总帧数
     */
    get totalFrames(): number {
        return this.rawData && this.rawData.frames || 0;
    };
    /**
     * 动画显示宽度
     */
    get videoWidth(): number {
        return this.rawData && this.rawData.videoSize.width || null;
    };
    /**
     * 动画显示高度
     */
    get videoHeight(): number {
        return this.rawData && this.rawData.videoSize.height || null;
    };

    /**
     * 每秒刷新帧数，没设置过直接用数据里的
     */
    get fps() {
        //没设置过就用数据里的
        return this._fps || this.rawData && this.rawData.FPS || null;
    }
    set fps(v: number) {
        this._fps = v;
    }
    /**
     * 用于控制动画，这里面的按帧数计，animationClip.totalTime是总帧数，因为文件标记的是帧，而不是时间
     */
    animationClip: AnimationClip;
    constructor(data: VideoEntity) {
        super(data);//里面执行了init
        this._instanceType = "SvgaAni";
    }
    /**
     * 初始化方法
     * @param data 
     * @returns 
     */
    init(data: VideoEntity) {
        if (!data || data == this.rawData) return;
        //记录原数据
        this.rawData = data;
        //移除所有子级，后续考虑销毁
        this.removeAllChildren();
        //缓存图片
        if (data.images && !data.textures) {//带图片数据的待测试
            data.textures = {};
            for (var key in data.images) {
                var src = data.images[key];
                if (src.indexOf("iVBO") === 0 || src.indexOf("/9j/2w") === 0) {
                    src = 'data:image/png;base64,' + src;
                }//图片链接时的宽高适配再说，暂时没有遇到过
                let imgTag = createImage();
                imgTag.src = src;
                data.textures[key] = new Texture(new BaseTexture(imgTag));//这种方法不会缓存进全局
            }
        }
        var tracks: SvgaTrack[] = [];
        //处理子级
        data.sprites.forEach((ele) => {
            if (!ele.imageKey) return;
            let sprite = this.addChild(new Sprite(
                this.rawData.textures ?
                    this.rawData.textures[ele.imageKey] ://自身没存的，取全局的，有图片单独处理出去
                    TextureCache[ele.imageKey] ||
                    TextureCache[ele.imageKey + ".png"] || null
            ));
            //记录一下用到的图片标识，否则将找不到该显示对象;
            sprite["imageKey"] = ele.imageKey;
            tracks.push(new SvgaTrack(
                sprite,
                ele.frames
            ))
        })
        //初始值设置下
        tracks.forEach((t) => { t.resetValue() });
        //合成所有时间轴，总时间按总帧数传，其实可能应该用this.totalFrames-1的，无所谓了，只是最后一帧停留了一帧
        if (!this.animationClip) {
            this.animationClip = new AnimationClip(tracks, this.totalFrames);
        } else {
            this.animationClip.init(tracks, this.totalFrames)
        }
        //数据更新
        this._onRawDataUpdate();
    }
    /**
     * 添加一个动画部件，主要用于换装添加部件
     * @param imageKey 用于查找图层对应动画的key值，只找首个用key的图层，所以建议视觉保证使用唯一
     * @param child 需要设置动画的显示对象
     * @param index 需要显示的层级
     * @param x 调整位置x
     * @param y 调整位置y
     * @param scaleX 
     * @param scaleY 
     * @param rotation 
     * @param anchorX 
     * @param anchorY 
     * @returns 返回添加的部件
     */
    addAniPart<T extends Container>(
        imageKey: string,
        child: T,
        index: number,
        x?: number,
        y?: number,
        scaleX?: number,
        scaleY?: number,
        rotation?: number,
        anchorX?: number,
        anchorY?: number
    ): T {
        if (!child || !imageKey || !this.rawData || !this.animationClip) return;
        var oriFrames: FrameEntity[], sprites = this.rawData.sprites;
        for (var i = 0; i < sprites.length; i++) {
            if (sprites[i].imageKey == imageKey) {
                oriFrames = sprites[i].frames;
                break;
            }
        }
        if (!oriFrames) {
            console.warn("未找到" + imageKey + "对应的数据")
            return;
        }
        var frames = SvgaAni.deepCopyFrames(oriFrames, x, y, scaleX, scaleY, rotation, anchorX, anchorY);
        //总帧数肯定一样的，直接往tracks里push吧
        this.animationClip["tracks"].push(new SvgaTrack(
            this.addChildAt(child, index),
            frames
        ))
        //返回添加的部分
        return child;
    }
    /**
     * 移动部件
     * @param child 
     * @returns 
     */
    removeAniPart<T extends Container>(child: T): T {
        //先从父级移除
        this.removeChild(child);
        //再从tracks移除，为了不想加polyfill，不用find或findIndex
        let tracks = this.animationClip["tracks"] as SvgaTrack[];
        //找出track；
        for (var i = 0; i < tracks.length; i++) {
            if (tracks[i]["obj"] == child) {
                tracks.splice(i, 1);
                break;
            }
        }
        return child
    }
    /**
     * 给图层修改图片，一般用于出奖动效的奖品图片的替换，尺寸不一致时会做居中适配
     * @param imagekey 会查找所有用了imagekey的图层
     * @param imageUrl 图片路径
     */
    setImage(imagekey: string, imageUrl: string) {
        //先获取原先贴图，为了宽高适配，，这种如何保证base64已经加载完毕，考虑lottie的图片宽高信息存过来？
        let cs = this.getSpritesByImageKey(imagekey);
        if (!cs.length) return;
        var texture: Texture = cs[0].texture;
        var width = texture.width;
        var height = texture.height;
        let image = createImage();
        image.onload = function () {
            let newTexture: Texture
            if (image.width == width && image.height == height) {
                newTexture = Texture.from(image);
            } else {
                var canvas = createCanvas()//document.createElement("canvas");
                canvas.width = width;
                canvas.height = height;
                var ctx = canvas.getContext("2d")
                //适配绘制,为了全部显示在canvas中
                var scaleCan = width / height;
                var scale = image.width / image.height;
                if (scaleCan > scale) {
                    //定高
                    ctx.drawImage(image, 0, 0, image.width, image.height
                        , (width - scale * height) / 2, 0, scale * height, height)
                } else {
                    //定宽
                    ctx.drawImage(image, 0, 0, image.width, image.height
                        , 0, (height - width / scale) / 2, width, width / scale)
                }
                newTexture = Texture.fromCanvas(canvas)
            }
            //修改相应sprite
            cs.forEach((c) => { c.texture = newTexture; })
        }
        image.src = imageUrl;
    }
    /**
     * 给对应图层修改纹理，直接替换，所以建议原纹理和替换纹理尺寸一致
     * @param imagekey 会查找所有用了imagekey的图层
     * @param texture 纹理
     */
    setTexture(imagekey: string, texture: Texture) {
        let cs = this.getSpritesByImageKey(imagekey);
        cs.forEach((c) => { c.texture = texture; })
    }
    /**
     * 根据imagekey获取所有用到过的图层
     * @param imagekey 
     * @returns 
     */
    getSpritesByImageKey(imagekey: string): Sprite[] {
        let cs: Sprite[] = [];//找都找了，就全记录吧
        for (var i = 0; i < this.children.length; i++) {
            if (this.children[i]["imageKey"] == imagekey) {
                cs.push(this.children[i] as Sprite)
            }
        }
        return cs;
    }
    /**
     * 用源数据拷贝一份，用相应参数，并未拷贝遮罩或矢量数据
     * @param frames 源数据
     * @param x 偏移x，默认0
     * @param y 偏移y，默认0
     * @param scaleX 相对缩放x，默认1
     * @param scaleY 相对缩放y，默认1
     * @param rotation 相对旋转,角度制，默认0
     * @param anchorX 相对锚点x，默认0
     * @param anchorY 相对锚点y，默认0
     */
    static deepCopyFrames(
        frames: FrameEntity[],
        x: number = 0,
        y: number = 0,
        scaleX: number = 1,
        scaleY: number = 1,
        rotation: number = 0,
        anchorX: number = 0,
        anchorY: number = 0
    ) {
        var cf = [];
        rotation *= Math.PI / 180;
        //@ts-ignore
        var lt: Matrix = {};
        lt.a = Math.cos(rotation) * scaleX;
        lt.b = Math.sin(rotation) * scaleX;
        lt.c = -Math.sin(rotation) * scaleY;
        lt.d = Math.cos(rotation) * scaleY;
        lt.tx = x + anchorX - ((anchorX * lt.a) + (anchorY * lt.c));
        lt.ty = y + anchorY - ((anchorX * lt.b) + (anchorY * lt.d));
        for (var j = 0; j < frames.length; j++) {
            var frame = frames[j];
            const pt = frame.transform;
            var f = { alpha: 0, transform: null };
            //透明度
            f.alpha = frame.alpha;
            f.transform = {
                a: (lt.a * pt.a) + (lt.b * pt.c),
                b: (lt.a * pt.b) + (lt.b * pt.d),
                c: (lt.c * pt.a) + (lt.d * pt.c),
                d: (lt.c * pt.b) + (lt.d * pt.d),
                tx: (lt.tx * pt.a) + (lt.ty * pt.c) + pt.tx,
                ty: (lt.tx * pt.b) + (lt.ty * pt.d) + pt.ty,
            };
            cf.push(f)
        }
        return cf;
    }
}
/**
 * 导出只是当作类型接口用
 */
interface VideoEntity {
    /**
     * SVGA 文件版本
     */
    version: string;
    /**
     * 影片尺寸
     */
    videoSize: {
        width: number;
        height: number;
    };
    /**
     * 帧率，60，30等每秒
     */
    FPS: number;
    /**
     * 总帧数
     */
    frames: number;
    /**
     * base64图片数据记录
     */
    images: {
        [key: string]: string
    };
    /**
     * 缓存的纹理
     */
    textures: {
        [key: string]: Texture
    }
    /**
     * 图片是否已被缓存，缓存全局，注意名字覆盖
     */
    hasBeenCached: boolean;
    /**
     * sprite对象数据
     */
    sprites: SpriteEntity[];

}
interface SpriteEntity {
    /**
     * 暂时没用
     */
    matteKey: string;
    /**
     * 图片key值
     */
    imageKey: string;
    /**
     * 帧数据数组
     */
    frames: FrameEntity[];
}
/**
 * 还有很多其他数据，暂不需要，比如矢量路径和遮罩路径暂时都无
 */
interface FrameEntity {
    /**
     * 透明度
     */
    alpha: number;
    /**
     * 2维矩阵数据
     */
    transform: {
        a: number,
        b: number,
        c: number,
        d: number,
        tx: number,
        ty: number,
    };
    /**
     * 遮罩数据
     */
    maskPath?: { _d: string, _styles: any, _transform: any }
}

class SvgaTrack extends HashObject implements IAnimationTrack {
    constructor(
        private obj: Container,
        private frames: FrameEntity[],
    ) {
        super();
        this._instanceType = "SvgaTrack";
    }
    /**
     * 这里用的帧数
     * @param time 帧小数
     */
    setValue(time: number) {
        //处理time
        time = Math.round(clamp(time, 0, this.frames.length - 1));
        //找对应数据
        var frame = this.frames[time], sprite = this.obj;
        //layout不晓得干嘛用，暂不管
        if (frame.alpha < 0.05) {
            sprite.alpha = 0;
        } else {
            sprite.alpha = frame.alpha;
            //先判断transform是否相等
            if (!Matrix.isEqual(sprite.transform.localMatrix, frame.transform as Matrix)) {
                sprite.transform.localMatrix.copy(frame.transform)
                sprite.transform._parentID = -1;
            }
            //遮罩
            if (frame.maskPath) {
                //记录一个
                if (!sprite["cusMask"]) sprite["cusMask"] = new Shape();
                //没遮罩加上
                if (!sprite.mask) sprite.mask = sprite.addChild(sprite["cusMask"]);
                let mask = sprite.mask;
                drawBezierShape(mask, frame.maskPath);
            } else if (sprite.mask) {
                sprite.removeChild(sprite.mask)
                sprite.mask = null;
            }
        }
    }
    resetValue() {
        this.setValue(0);
    }
    destroy() {
        this.obj = null
    }
}

function drawBezierShape(mask: Shape = new Shape, data: { _d: string, _styles: any, _transform: any }, useStyle: boolean = false) {
    mask.clear();
    const styles = data._styles;
    if (useStyle && styles) {//待测试，两个都有的时候是否能正常绘制，毕竟都执行了beginPath
        if (styles.stroke) {
            mask.beginStroke(
                rgb2hex(styles.stroke),
                styles.strokeWidth || undefined,
                styles.lineCap || undefined,
                styles.lineJoin || undefined,
                styles.miterLimit || undefined,
                styles.stroke[3]
            )
        }
        if (styles.fill) {
            mask.beginFill(rgb2hex(styles.fill), styles.fill[3])
        }
        // if (styles && styles.lineDash) {
        //     ctx.setLineDash(styles.lineDash);
        // }
    } else {
        //简单绘制
        mask.beginFill();
    }
    //会用到的，再说TODO
    if (data._transform) {
        //用于修改所有路径的矩阵数据
        mask.transform.localMatrix.copy(data._transform)
        mask.transform._parentID = -1;
    }
    let currentPoint = { x: 0, y: 0, x1: 0, y1: 0, x2: 0, y2: 0 };
    const validMethods = 'MLHVCSQRZmlhvcsqrz'
    const d = data._d.replace(/([a-zA-Z])/g, '|||$1 ').replace(/,/g, ' ');
    d.split('|||').forEach(segment => {
        if (segment.length == 0) return;
        const firstLetter = segment.substr(0, 1);
        if (validMethods.indexOf(firstLetter) >= 0) {
            const args = segment.substr(1).trim().split(" ");
            switch (firstLetter) {
                case 'M':
                    currentPoint.x = Number(args[0]);
                    currentPoint.y = Number(args[1]);
                    mask.moveTo(currentPoint.x, currentPoint.y);
                    break;
                case 'm':
                    currentPoint.x += Number(args[0]);
                    currentPoint.y += Number(args[1]);
                    mask.moveTo(currentPoint.x, currentPoint.y);
                    break;
                case 'L':
                    currentPoint.x = Number(args[0]);
                    currentPoint.y = Number(args[1]);
                    mask.lineTo(currentPoint.x, currentPoint.y);
                    break;
                case 'l':
                    currentPoint.x += Number(args[0]);
                    currentPoint.y += Number(args[1]);
                    mask.lineTo(currentPoint.x, currentPoint.y);
                    break;
                case 'H':
                    currentPoint.x = Number(args[0]);
                    mask.lineTo(currentPoint.x, currentPoint.y);
                    break;
                case 'h':
                    currentPoint.x += Number(args[0]);
                    mask.lineTo(currentPoint.x, currentPoint.y);
                    break;
                case 'V':
                    currentPoint.y = Number(args[0]);
                    mask.lineTo(currentPoint.x, currentPoint.y);
                    break;
                case 'v':
                    currentPoint.y += Number(args[0]);
                    mask.lineTo(currentPoint.x, currentPoint.y);
                    break;
                case 'C':
                    currentPoint.x1 = Number(args[0]);
                    currentPoint.y1 = Number(args[1]);
                    currentPoint.x2 = Number(args[2]);
                    currentPoint.y2 = Number(args[3]);
                    currentPoint.x = Number(args[4]);
                    currentPoint.y = Number(args[5]);
                    mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
                    break;
                case 'c':
                    currentPoint.x1 = currentPoint.x + Number(args[0]);
                    currentPoint.y1 = currentPoint.y + Number(args[1]);
                    currentPoint.x2 = currentPoint.x + Number(args[2]);
                    currentPoint.y2 = currentPoint.y + Number(args[3]);
                    currentPoint.x += Number(args[4]);
                    currentPoint.y += Number(args[5]);
                    mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
                    break;
                case 'S':
                    if (currentPoint.x1 && currentPoint.y1 && currentPoint.x2 && currentPoint.y2) {
                        currentPoint.x1 = currentPoint.x - currentPoint.x2 + currentPoint.x;
                        currentPoint.y1 = currentPoint.y - currentPoint.y2 + currentPoint.y;
                        currentPoint.x2 = Number(args[0]);
                        currentPoint.y2 = Number(args[1]);
                        currentPoint.x = Number(args[2]);
                        currentPoint.y = Number(args[3]);
                        mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
                    } else {
                        currentPoint.x1 = Number(args[0]);
                        currentPoint.y1 = Number(args[1]);
                        currentPoint.x = Number(args[2]);
                        currentPoint.y = Number(args[3]);
                        mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
                    }
                    break;
                case 's':
                    if (currentPoint.x1 && currentPoint.y1 && currentPoint.x2 && currentPoint.y2) {
                        currentPoint.x1 = currentPoint.x - currentPoint.x2 + currentPoint.x;
                        currentPoint.y1 = currentPoint.y - currentPoint.y2 + currentPoint.y;
                        currentPoint.x2 = currentPoint.x + Number(args[0]);
                        currentPoint.y2 = currentPoint.y + Number(args[1]);
                        currentPoint.x += Number(args[2]);
                        currentPoint.y += Number(args[3]);
                        mask.bezierCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x2, currentPoint.y2, currentPoint.x, currentPoint.y);
                    } else {
                        currentPoint.x1 = currentPoint.x + Number(args[0]);
                        currentPoint.y1 = currentPoint.y + Number(args[1]);
                        currentPoint.x += Number(args[2]);
                        currentPoint.y += Number(args[3]);
                        mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
                    }
                    break;
                case 'Q':
                    currentPoint.x1 = Number(args[0]);
                    currentPoint.y1 = Number(args[1]);
                    currentPoint.x = Number(args[2]);
                    currentPoint.y = Number(args[3]);
                    mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
                    break;
                case 'q':
                    currentPoint.x1 = currentPoint.x + Number(args[0]);
                    currentPoint.y1 = currentPoint.y + Number(args[1]);
                    currentPoint.x += Number(args[2]);
                    currentPoint.y += Number(args[3]);
                    mask.quadraticCurveTo(currentPoint.x1, currentPoint.y1, currentPoint.x, currentPoint.y);
                    break;
                case 'A':
                    break;
                case 'a':
                    break;
                case 'Z':
                case 'z':
                    mask.closePath();
                    break;
                default:
                    break;
            }
        }
    })

    if (useStyle && styles) {//待测试，两者是否都能执行
        if (styles.fill) mask.endFill();
        if (styles.stroke) mask.endStroke();
    } else {
        //简单，直接绘制
        mask.endFill();
    }
    //返回
    return mask;
}