
import { AnimationClip, IAnimationTrack } from "../AnimationClip";
import { HashObject } from "../HashObject";
import { Container, Sprite } from "../display";
import { BaseTexture, Texture } from "../texture";
import { getBezierEasing } from "./forLottie/BezierEaser"
import { buildBezierProp } from "./forLottie/buildBezierProp";
import { createImage, TextureCache } from "../utils";
import { Shape } from "../graphics";
import { AnimationNode } from "./AnimationNode";

export class Lottie extends AnimationNode {
    /**
     * 原始数据，尽量只获取，不修改
     */
    protected rawData: LottieData;
    /**
     * 总时间，秒计
     */
    get totalTime(): number {
        if (!this.rawData) return 0;
        const { op, ip } = this.rawData;
        return (op - ip) * (1 / this.fps);
    };
    /**
     * 总帧数
     */
    get totalFrames(): number {
        if (!this.rawData) return 0;
        const { op, ip } = this.rawData;
        return op - ip;
    };
    /**
     * 动画显示宽度
     */
    get videoWidth(): number {
        return this.rawData && this.rawData.w || 0;
    };
    /**
     * 动画显示高度
     */
    get videoHeight(): number {
        return this.rawData && this.rawData.h || 0;
    };
    /**
     * 重写
     * 每秒刷新帧数，没设置过直接用数据里的
     */
    get fps() {
        //没设置过就用数据里的
        return this._fps || this.rawData && this.rawData.fr || null;
    }
    set fps(v: number) {
        this._fps = v;
    }
    //lottie图层主容器
    private lottieContainer: LottieContainer;
    constructor(data: LottieData) {
        super(data);//里面执行了init
        this._instanceType = "Lottie";
    }
    /**
     * 初始化方法
     * @param data 
     * @returns 
     */
    public init(data: LottieData) {
        if (!data || data == this.rawData) return;
        this.rawData = data;
        //名字
        this.name = data.nm;
        //初始化图片 有assets但无textures
        if (data.assets && !data.textures) {
            data.textures = {};
            data.assets.forEach((a) => {
                if (a.layers) {//合成嵌套的情况
                    data.compositions = data.compositions || {};
                    data.compositions[a.id] = a.layers;
                    return;
                }
                let imgTag = createImage();
                imgTag.src = a.p;
                data.textures[a.id] = new Texture(new BaseTexture(imgTag));
            })
        }
        //原先有的，先销毁
        this.lottieContainer && this.lottieContainer.destroy();
        //初始化一个容器；
        this.lottieContainer = this.addChild(new LottieContainer());
        //生成时间轴
        var tracks = createLottieTracks(
            this.lottieContainer,
            data.layers,
            data.ip,
            data.textures,
            data.compositions
        );
        //合成所有时间轴
        if (!this.animationClip) {
            this.animationClip = new AnimationClip(tracks, this.totalFrames);
        } else {
            this.animationClip.init(tracks, this.totalFrames)
        }
    }
}

interface LottieData {
    "fr": number,//帧率  30  60等
    "ip": number,//开始帧
    "op": number,//结束帧
    "w": number,//宽度
    "h": number,//高度
    "nm": string,//名字
    "layers": LayerData[],
    "assets"?: {
        "id": string,//图片id，与layers里的refId对应
        "w": number,
        "h": number,
        "p": string,//base64数据
        "layers": LayerData[],//合成嵌套的
    }[],
    "textures"?: { [key: string]: Texture }//缓存的贴图，为了上面的assets里的图片数据，不进全局缓存，
    // "components"?: { [key: string]: LottieData }
    compositions?: { [key: string]: LayerData[] }
}
interface LayerData {
    "ind": number,//id唯一
    "ty": number,//类型，2是图片，0是合成
    "nm": string//"owl_sleep.png",//暂时就是图片
    "refId": string,
    "parent"?: number,//父级id
    "ks": KsData;
    "ip": number,//开始帧
    "op": number,//结束帧
    "st": number,
    "w"?: number,//合成的会有
    "h"?: number,//合成的会有
    hasMask?: boolean,
    masksProperties?: ILottieMaskData[],//遮罩数据
}
interface KsData {
    o: KeyData //透明度
    r: KeyData //旋转
    p: KeyData //位置
    a: KeyData //锚点
    s: KeyData //缩放
}
interface KeyData {
    a: number,//貌似没用
    k: KeyAniData[] | number[] | number,
    x: string,//可能有表达式
}

interface KeyAniData {
    t: number,
    s: number[],

    i: { x: number | number[], y: number | number[] },
    o: { x: number | number[], y: number | number[] },

    to: number[],
    ti: number[],

    h: number,//暂时用是否存在判断。有标识瞬切，不缓动，待测试

    //后面记录的东西
    bezierData?,//to存在时的贝塞尔数据
    fnc?,//贝塞尔函数，可能是数组，

}
interface ILottieMaskData {
    inv: boolean,//true需要处理矩形wh的矩形遮罩
    mode: "n" | "a",//遮罩模式，一般是a
    pt: {
        "a": number,
        "k": {
            "i": [number, number][],
            "o": [number, number][],
            "v": [number, number][],
            "c": boolean
        },
        "ix": number
    },
    "o": {
        "a": number,
        "k": number,
        "ix": number
    },
    "x": {
        "a": number,
        "k": number,
        "ix": number
    },
    "nm": string
}

enum LoopType {
    pingpong = "pingpong",
    cycle = "cycle",
}

interface LoopData {
    loopInOrOut: 0 | 1 | 2,//0表示没有,1表示in前面循环，2表示out后续循环
    type: LoopType,
    duration: number,//为0表示全循环
}
/**
 * 内部使用,不对外导出,这里track的时间统统是帧时间,所以对于AnimationClip的update传的时间做换算
 * 注意lottie的透明度是不影响子级的
 */
class LottieBaseTrack extends HashObject implements IAnimationTrack {
    constructor(
        protected obj: Sprite,
        private type?: "r" | "o" | "s" | "p",
        private times?: KeyAniData[],
        private loop?: LoopData,
        private ip: number = 0,//偏移
    ) {
        super();
        this._instanceType = "LottieBaseTrack";
    }
    /**
     * 子类重写
     * @param time 
     */
    setValue(time: number) {
        time -= this.ip;
        // if (!this.obj.visible) return
        var value = this.cacValue(time);
        switch (this.type) {
            case "r":
                this.obj.rotation = value[0];
                break;
            case "o":
                this.obj.alpha = value[0] / 100;
                break;
            case "s":
                this.obj.scale.set(value[0] / 100, value[1] / 100);
                break;
            case "p":
                this.obj.position.set(value[0] - this.obj.anchorX, value[1] - this.obj.anchorY);
                break;
        }
    }
    /**
     * 
     * @param time 
     * @returns 
     */
    protected cacValue(time: number) {
        //计算真实时间，因为部分又表达式的
        if (this.loop && this.loop.loopInOrOut) {
            time = (this.loop.loopInOrOut == 1 ? loopIn : loopOut)(this.loop.type, this.loop.duration, this.times, time)
        }
        if (time <= this.times[0].t) return this.times[0].s;
        if (time >= this.times[this.times.length - 1].t) return this.times[this.times.length - 1].s;
        //其他的计算补间了要，找前后两个索引
        var after = this.findIndex(time);
        var before = after - 1;
        var da = this.times[after];
        var db = this.times[before];
        var beforeValue = db.s, afterValue = da.s;
        var value: number[]
        //有路径的
        if (db.to) {
            db.fnc = db.fnc || getBezierEasing(db.o.x, db.o.y, db.i.x, db.i.y).get;
            value = buildBezierProp(
                beforeValue,
                afterValue,
                db.to,
                db.ti,
                db.t,
                da.t,
                time,
                db.fnc
            )
        }
        //有h的
        else if (db.h === 1) {
            value = beforeValue
        } else {
            db.fnc = db.fnc || generateFuncs(db.o, db.i, beforeValue.length);
            var newValue = [], perc: number, delta = da.t - db.t;
            for (var i = 0; i < beforeValue.length; i += 1) {
                if (time >= da.t) {
                    perc = 1;
                } else if (time < db.t) {
                    perc = 0;
                } else {
                    if (!db.fnc) {//线性
                        perc = (time - db.t) / delta;
                    }
                    else if (db.fnc.constructor === Array) {
                        perc = db.fnc[i]((time - db.t) / delta);
                    } else {
                        perc = db.fnc((time - db.t) / delta);
                    }
                }
                newValue[i] = beforeValue[i] + (afterValue[i] - beforeValue[i]) * perc;;
            }
            value = newValue
        }
        return value;
    }

    /**
     * 首尾已判断，得到后一位索引
     * @param times 
     * @param time 
     * @returns 
     */
    protected findIndex(time: number): number {
        var low = 0;
        var high = this.times.length - 2;
        if (high == 0) return 1;
        var current = high >>> 1;
        while (true) {
            if (this.times[current + 1].t <= time)
                low = current + 1;
            else
                high = current;
            if (low == high)
                return low + 1;
            current = (low + high) >>> 1;
        }
    }
    resetValue() {
        this.setValue(0)
    }
    destroy() {
        this.obj = null;
    }
}
/**
 * 部件的出场时间,就是个范围,
 * 每个对象都有,优先判断
 */
class LottieVisibleTrack extends HashObject implements IAnimationTrack {
    constructor(
        protected obj: Container,
        private inTime: number,
        private outTime: number,
        private startTime: number,
        private ip: number = 0,//偏移
    ) {
        super();
        this._instanceType = "LottieVisibleTrack";
    }
    /**
     * @param time 
     */
    setValue(time: number) {
        time -= this.ip;
        this.obj.visible = this.inTime <= time && this.outTime >= time
    }
    resetValue() {

    }
    destroy() {
        this.obj = null
    }
}


function loopOut(type: LoopType, duration: number, keyframes: KeyAniData[], currentFrame: number) {
    var lastKeyFrame = keyframes[keyframes.length - 1].t;
    if (currentFrame <= lastKeyFrame) return currentFrame;

    if (!duration || duration > keyframes.length - 1) {
        duration = keyframes.length - 1;
    }
    var firstKeyFrame = keyframes[keyframes.length - 1 - duration].t;
    var cycleDuration = lastKeyFrame - firstKeyFrame;

    // var i, len, ret;
    if (type === 'pingpong') {
        var iterations = Math.floor((currentFrame - firstKeyFrame) / cycleDuration);
        if (iterations % 2 !== 0) {
            return cycleDuration - (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame;
        }
    }
    //其他的再说
    // else if(){

    // }
    return (currentFrame - firstKeyFrame) % cycleDuration + firstKeyFrame;
}


//给出时间就行,自行判断是否计算，根据
function loopIn(type: LoopType, duration: number, keyframes: KeyAniData[], currentFrame: number) {
    var firstKeyFrame = keyframes[0].t;
    if (currentFrame >= firstKeyFrame) return currentFrame;
    //0时取全部
    if (!duration || duration > keyframes.length - 1) {
        duration = keyframes.length - 1;
    }
    var lastKeyFrame = keyframes[duration].t;
    var cycleDuration = lastKeyFrame - firstKeyFrame;

    // var i, len, ret;
    if (type === LoopType.pingpong) {
        var iterations = Math.floor((firstKeyFrame - currentFrame) / cycleDuration);
        if (iterations % 2 === 0) {
            return (firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame;
        }
    }
    //其他的再说
    // else if(){

    // }
    return cycleDuration - (firstKeyFrame - currentFrame) % cycleDuration + firstKeyFrame;
}

function generateFuncs(outV, inV, len) {
    //无参数时直接返回null
    if (!outV || !inV) return null;
    var outX, outY, inX, inY
    if (outV.x.constructor === Array) {
        var fncts = [];
        for (var i = 0; i < len; i++) {
            outX = (typeof outV.x[i] === 'undefined') ? outV.x[0] : outV.x[i];
            outY = (typeof outV.y[i] === 'undefined') ? outV.y[0] : outV.y[i];
            inX = (typeof inV.x[i] === 'undefined') ? inV.x[0] : inV.x[i];
            inY = (typeof inV.y[i] === 'undefined') ? inV.y[0] : inV.y[i];
            fncts[i] = getBezierEasing(outX, outY, inX, inY).get;
        }
        return fncts;
    } else {
        outX = outV.x;
        outY = outV.y;
        inX = inV.x;
        inY = inV.y;
        return getBezierEasing(outX, outY, inX, inY).get;
    }
}

function getLoopData(x: string): LoopData {
    if (!x) return null;
    //取数字
    var rr = +x.replace(/[^0-9]/ig, "");
    var loopInOrOut: 0 | 1 | 2 = 0;
    if (x.indexOf("loopOut") >= 0) {
        loopInOrOut = 2
    } else if (x.indexOf("loopIn") >= 0) {
        loopInOrOut = 1
    }
    var type: LoopType;
    if (x.indexOf("pingpong") >= 0) {
        type = LoopType.pingpong
    } else if (x.indexOf("cycle") >= 0) {
        type = LoopType.cycle
    }
    return { loopInOrOut, type, duration: rr }
}

/**
 * 
 */
interface ILottieLater {
    _mark: boolean;
    _isLottieLayer: boolean;
    _ind: number
    _parentId: number
}
/**
 * 一个子级完全平铺，但是子级却存在父级id记录的容器类，lottie会继承自他，还有合成的也继承自他
 */
class LottieContainer extends Container {
    /**
     * 是图片，但有额外属性
     */
    children: ((Sprite | LottieContainer) & ILottieLater)[];
    /**
     * 对所有的进行刷新，，根据cParent进行迭代刷新
     * 层级有问题，只能平铺，手动计算矩阵
     * 因为要平铺，所以记录cParent和ind  从1开始，也许只需要+1就行，还是用ind记录查找吧
     * 遍历找
     */
    updateTransform() {
        //super不行，到时查
        this.displayObjectUpdateTransform();
        this.children.forEach((c) => {
            this._recursivePostUpdateTransformAA(c);
        })
        this.children.forEach((c) => {
            c._mark = false;
        })
    }

    private findChildByInd(ind: number) {
        for (var i = 0; i < this.children.length; i++) {
            if (this.children[i]._ind === ind) return this.children[i]
        }
        return null
    }

    private _recursivePostUpdateTransformAA(c: (Sprite | LottieContainer) & ILottieLater) {
        if (c._isLottieLayer && c._parentId) {
            //ind从1开始，所以不用考虑0，且不应该存在 p没有的情况
            var p = this.findChildByInd(c._parentId)
            this._recursivePostUpdateTransformAA(p);
            if (!c._mark) {
                c._mark = true;
                // c.transform.updateWorldMatrix(p.transform);
                //用临时父级的方式，否则合成嵌套的子级不会更新
                const cacheParent = c.parent;
                c.parent = p;
                c.updateTransform();
                c.parent = cacheParent;
                //透明度单独计算，不跟p保持，这个到时要测试下，可以需要向上递归取透明度才是对的
                c._worldAlpha = c.alpha * c.parent._worldAlpha;
            }
        }
        //直接进行tans
        else if (!c._mark) {
            c.updateTransform();//alpha跟父级有关，这里透明度也一样
            c._mark = true
        }
    }
}


/**
 * 递归创建lottie时间轴，
 * @param root 
 * @param layers 
 * @param offset 
 * @param textures 
 * @param compositions 
 * @param tracks 
 * @returns 
 */
function createLottieTracks(
    root: LottieContainer,
    layers: LayerData[],
    offset: number,
    textures: { [key: string]: Texture },
    compositions?: { [key: string]: LayerData[] },
    tracks: (LottieVisibleTrack | LottieBaseTrack)[] = [],
) {
    for (var i = layers.length - 1; i >= 0; i--) {
        let layer = layers[i];
        const { ty, refId, ks, parent, ind, ip, op, st, hasMask, masksProperties } = layer;
        let c: (Sprite | LottieContainer) & ILottieLater;
        //如果存在组件嵌套
        if (ty == 0 && compositions) {
            c = root.addChild(new LottieContainer()) as LottieContainer & ILottieLater;
            //递归创建，宽高和名字暂时不需要，
            createLottieTracks(
                c,
                compositions[refId],
                offset + ip,
                textures,
                compositions,
                tracks
            );
        }
        //图片图层，不能判断2.因为有部分是Container
        else {
            c = root.addChild(new Sprite(
                refId ?
                    textures ?
                        textures[refId] :
                        TextureCache[refId] ||
                        TextureCache[refId + ".png"] : null
            ) as Sprite & ILottieLater);
        }
        //遮罩数据，遮罩矩阵和c一致，所以加在c里面
        if (hasMask && masksProperties && masksProperties.length) {
            c.mask = c.addChild(createLottieMask(masksProperties, layer.w, layer.h));
        }
        //标记下
        c._isLottieLayer = true;
        //记录一下数据，查找父级的时候有用
        c._ind = ind, c._parentId = parent;
        //处理下锚点，基本不会有锚点动画
        var ad = typeof ks.a.k[0] == "number" ? ks.a.k : ks.a.k[0].s;
        c.anchor.set(ad[0], ad[1]);
        //加显示隐藏的
        tracks.push(new LottieVisibleTrack(c, ip, op, st, offset))
        //@ts-ignore透明度
        if (ks.o.k.length) {
            tracks.push(
                //@ts-ignore
                new LottieBaseTrack(c, "o", ks.o.k, getLoopData(ks.o.x), offset)
            )
        } else {
            //@ts-ignore
            c.alpha = ks.o.k / 100
        }

        //@ts-ignore 旋转
        if (ks.r.k.length) {
            tracks.push(
                //@ts-ignore
                new LottieBaseTrack(c, "r", ks.r.k, getLoopData(ks.r.x), offset)
            )
        } else {
            //@ts-ignore
            c.rotation = ks.r.k
        }

        //位置
        if (typeof ks.p.k[0] != "number") {
            tracks.push(
                //@ts-ignore
                new LottieBaseTrack(c, "p", ks.p.k, getLoopData(ks.p.x), offset)
            )
        } else {
            //@ts-ignore
            var p = ks.p.k;
            c.position.set(p[0] - ad[0], p[1] - ad[1])
        }

        //缩放
        if (typeof ks.s.k[0] != "number") {
            tracks.push(
                //@ts-ignore
                new LottieBaseTrack(c, "s", ks.s.k, getLoopData(ks.s.x), offset)
            )
        } else {
            //@ts-ignore
            var s = ks.s.k;
            c.scale.set(s[0] / 100, s[1] / 100)
        }
    }
    return tracks;
}


/**
 * 生成lottie的遮罩矢量
 * @param masksProperties 
 * @param w 
 * @param h 
 * @returns 
 */
function createLottieMask(masksProperties: ILottieMaskData[], w: number, h: number): Shape {
    var mask = new Shape();
    mask.beginFill();
    masksProperties.forEach((m) => {
        if (m.mode !== 'n') {
            if (m.inv) {
                mask.moveTo(0, 0);
                mask.lineTo(w, 0);
                mask.lineTo(w, h);
                mask.lineTo(0, h);
                mask.lineTo(0, 0);
            }
            var data = m.pt.k;
            mask.moveTo(data.v[0][0], data.v[0][1]);
            var j, jLen = data.v.length;
            for (j = 1; j < jLen; j++) {
                mask.bezierCurveTo(
                    data.o[j - 1][0] + data.v[j - 1][0],
                    data.o[j - 1][1] + data.v[j - 1][1],
                    data.i[j][0] + data.v[j][0],
                    data.i[j][1] + data.v[j][1],
                    data.v[j][0],
                    data.v[j][1]
                );
            }
            mask.bezierCurveTo(
                data.o[j - 1][0] + data.v[j - 1][0],
                data.o[j - 1][1] + data.v[j - 1][1],
                data.i[0][0] + data.v[0][0],
                data.i[0][1] + data.v[0][1],
                data.v[0][0],
                data.v[0][1]
            );
            if (data.c) {
                //TODO
            }
        }
    })
    mask.endFill();
    return mask
}



