import { ILeaferBase, ILeaf, ILeafInputData, ILeafData, ILeaferCanvas, IRenderOptions, IBoundsType, ILocationType, IMatrixWithBoundsData, ILayoutBoundsData, IValue, ILeafLayout, InnerId, IHitCanvas, IRadiusPointData, IEventListenerMap, IEventListener, IEventListenerId, IEvent, IObject, IFunction, IPointData, IBoundsData, IBranch, IFindMethod, IMatrixData, IAttrDecorator, IMatrixWithBoundsScaleData, IMatrixWithScaleData, IAlign, IJSONOptions, IEventMap, IEventOption, IAxis, IMotionPathData, IUnitData, IRotationPointData, ITransition, IValueFunction } from '@leafer/interface'
import { BoundsHelper, IncrementId, MatrixHelper, PointHelper } from '@leafer/math'
import { LeafData } from '@leafer/data'
import { LeafLayout } from '@leafer/layout'
import { LeafDataProxy, LeafMatrix, LeafBounds, LeafEventer, LeafRender } from '@leafer/display-module'
import { boundsType, useModule, defineDataProcessor } from '@leafer/decorator'
import { LeafHelper } from '@leafer/helper'
import { ChildEvent } from '@leafer/event'
import { Plugin } from '@leafer/debug'


const { LEAF, create } = IncrementId
const { toInnerPoint, toOuterPoint, multiplyParent } = MatrixHelper
const { toOuterOf } = BoundsHelper
const { copy, move } = PointHelper
const { moveLocal, zoomOfLocal, rotateOfLocal, skewOfLocal, moveWorld, zoomOfWorld, rotateOfWorld, skewOfWorld, transform, transformWorld, setTransform, getFlipTransform, getLocalOrigin, getRelativeWorld, drop } = LeafHelper

@useModule(LeafDataProxy)
@useModule(LeafMatrix)
@useModule(LeafBounds)
@useModule(LeafEventer)
@useModule(LeafRender)
export class Leaf implements ILeaf {

    public get tag(): string { return this.__tag }
    public set tag(_value: string) { }

    public get __tag(): string { return 'Leaf' }

    public readonly innerId: InnerId  // 内部唯一标识
    public get innerName(): string { return this.__.name || this.tag + this.innerId }

    public get __DataProcessor() { return LeafData }
    public get __LayoutProcessor() { return LeafLayout }

    public leafer?: ILeaferBase
    public parent?: ILeaf

    public get leaferIsCreated(): boolean { return this.leafer && this.leafer.created }
    public get leaferIsReady(): boolean { return this.leafer && this.leafer.ready }

    public get isLeafer(): boolean { return false }
    public get isBranch(): boolean { return false }
    public get isBranchLeaf(): boolean { return false }

    public syncEventer?: ILeaf // 同步触发一样事件的元素
    public lockNormalStyle?: boolean

    public __: ILeafData
    public __layout: ILeafLayout

    public __world: IMatrixWithBoundsScaleData
    public __local?: IMatrixWithBoundsData // and localStrokeBounds? localRenderBounds?

    public __nowWorld?: IMatrixWithBoundsScaleData // use __world or __cameraWorld render
    public __cameraWorld?: IMatrixWithBoundsScaleData // use camera matrix render  

    public get __localMatrix(): IMatrixData { return this.__local || this.__layout }
    public get __localBoxBounds(): IBoundsData { return this.__local || this.__layout }

    public __worldOpacity: number

    // now transform
    public get worldTransform(): IMatrixWithScaleData { return this.__layout.getTransform('world') as IMatrixWithScaleData }
    public get localTransform(): IMatrixData { return this.__layout.getTransform('local') }

    // now bounds
    public get boxBounds(): IBoundsData { return this.getBounds('box', 'inner') }
    public get renderBounds(): IBoundsData { return this.getBounds('render', 'inner') }
    public get worldBoxBounds(): IBoundsData { return this.getBounds('box') }
    public get worldStrokeBounds(): IBoundsData { return this.getBounds('stroke') }
    public get worldRenderBounds(): IBoundsData { return this.getBounds('render') }

    // now opacity
    public get worldOpacity(): number { this.__layout.update(); return this.__worldOpacity }

    public __level: number // layer level  0 -> branch -> branch -> deep
    public __tempNumber: number // temp sort

    public get __worldFlipped(): boolean { return this.__world.scaleX < 0 || this.__world.scaleY < 0 }

    public __hasAutoLayout?: boolean
    public __hasMask?: boolean
    public __hasEraser?: boolean
    public __hitCanvas?: IHitCanvas

    public get __onlyHitMask(): boolean { return this.__hasMask && !this.__.hitChildren }
    public get __ignoreHitWorld(): boolean { return (this.__hasMask || this.__hasEraser) && this.__.hitChildren }
    public get __inLazyBounds(): boolean { return this.leaferIsCreated && this.leafer.lazyBounds.hit(this.__world) }

    public get pathInputed(): boolean { return this.__.__pathInputed as unknown as boolean }

    // event
    public set event(map: IEventMap) { this.on(map) }

    public __captureMap?: IEventListenerMap
    public __bubbleMap?: IEventListenerMap

    // branch 
    public children?: ILeaf[]

    // other
    public noBounds?: boolean

    public destroyed: boolean


    constructor(data?: ILeafInputData) {
        this.innerId = create(LEAF)
        this.reset(data)
        if (this.__bubbleMap) this.__emitLifeEvent(ChildEvent.CREATED)
    }


    public reset(data?: ILeafInputData): void {
        if (this.leafer) this.leafer.forceRender(this.__world) // fix: add old bounds rendering

        this.__world = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, x: 0, y: 0, width: 0, height: 0, scaleX: 1, scaleY: 1 }
        if (data !== null) this.__local = { a: 1, b: 0, c: 0, d: 1, e: 0, f: 0, x: 0, y: 0, width: 0, height: 0 }

        this.__worldOpacity = 1

        this.__ = new this.__DataProcessor(this)
        this.__layout = new this.__LayoutProcessor(this)

        if (this.__level) this.resetCustom()
        if (data) {
            if ((data as ILeaf).__) data = (data as ILeaf).toJSON()
            data.children ? this.set(data) : Object.assign(this, data)
        }
    }

    public resetCustom(): void {
        this.__hasMask = this.__hasEraser = null
        this.forceUpdate()
    }


    public waitParent(item: IFunction, bind?: IObject): void {
        if (bind) item = item.bind(bind)
        this.parent ? item() : this.on(ChildEvent.ADD, item, 'once')
    }

    public waitLeafer(item: IFunction, bind?: IObject): void {
        if (bind) item = item.bind(bind)
        this.leafer ? item() : this.on(ChildEvent.MOUNTED, item, 'once')
    }

    public nextRender(item: IFunction, bind?: IObject, off?: 'off'): void {
        this.leafer ? this.leafer.nextRender(item, bind, off) : this.waitLeafer(() => this.leafer.nextRender(item, bind, off))
    }

    public removeNextRender(item: IFunction): void {
        this.nextRender(item, null, 'off')
    }

    public __bindLeafer(leafer: ILeaferBase | null): void {
        if (this.isLeafer && leafer !== null) leafer = this as unknown as ILeaferBase

        if (this.leafer && !leafer) this.leafer.leafs--

        this.leafer = leafer

        if (leafer) {
            leafer.leafs++
            this.__level = this.parent ? this.parent.__level + 1 : 1
            if ((this as ILeaf).animation) this.__runAnimation('in')
            if (this.__bubbleMap) this.__emitLifeEvent(ChildEvent.MOUNTED)
        } else {
            this.__emitLifeEvent(ChildEvent.UNMOUNTED)
        }

        if (this.isBranch) {
            const { children } = this
            for (let i = 0, len = children.length; i < len; i++) {
                children[i].__bindLeafer(leafer)
            }
        }
    }

    // data

    public set(_data: IObject, _isTemp?: boolean): void { }
    public get(_name?: string): ILeafInputData | IValue { return undefined }

    public setAttr(name: string, value: any): void { (this as IObject)[name] = value }
    public getAttr(name: string): any { return (this as IObject)[name] }

    public getComputedAttr(name: string): any { return (this.__ as IObject)[name] }

    public toJSON(options?: IJSONOptions): IObject {
        if (options) this.__layout.update()
        return this.__.__getInputData(null, options)
    }

    public toString(options?: IJSONOptions): string {
        return JSON.stringify(this.toJSON(options))
    }

    public toSVG(): string { return undefined }

    public __SVG(_data: IObject): void { }

    public toHTML(): string { return undefined }

    // LeafDataProxy rewrite

    public __setAttr(_attrName: string, _newValue: IValue): boolean { return true }

    public __getAttr(_attrName: string): IValue { return undefined }

    public setProxyAttr(_attrName: string, _newValue: IValue): void { }

    public getProxyAttr(_attrName: string): IValue { return undefined }

    // ---


    // find

    public find(_condition: number | string | IFindMethod, _options?: any): ILeaf[] { return undefined }

    public findTag(_tag: string | string[]): ILeaf[] { return undefined }

    public findOne(_condition: number | string | IFindMethod, _options?: any): ILeaf | undefined { return undefined }

    public findId(_id: number | string): ILeaf | undefined { return undefined }

    // ---


    // @leafer-in/state rewrite

    public focus(_value?: boolean): void { }

    public updateState(): void { }

    // ---


    public updateLayout(): void {
        this.__layout.update()
    }

    public forceUpdate(attrName?: string): void {
        if (attrName === undefined) attrName = 'width'
        else if (attrName === 'surface') attrName = 'blendMode'
        const value = this.__.__getInput(attrName);
        (this.__ as any)[attrName] = value === undefined ? null : undefined;
        (this as any)[attrName] = value
    }

    public forceRender(_bounds?: IBoundsData, _sync?: boolean): void {
        this.forceUpdate('surface')
    }

    public __extraUpdate(): void {
        if (this.leaferIsReady) this.leafer.layouter.addExtra(this) // add part 额外更新元素
    }

    // LeafMatrix rewrite

    public __updateWorldMatrix(): void { }

    public __updateLocalMatrix(): void { }

    // ---

    // LeafBounds rewrite

    public __updateWorldBounds(): void { }

    public __updateLocalBounds(): void { }


    public __updateLocalBoxBounds(): void { }

    public __updateLocalStrokeBounds(): void { }

    public __updateLocalRenderBounds(): void { }

    // box

    public __updateBoxBounds(): void { }

    public __updateContentBounds(): void { }

    public __updateStrokeBounds(): void { }

    public __updateRenderBounds(): void { }


    public __updateAutoLayout(): void { }

    public __updateFlowLayout(): void { }

    public __updateNaturalSize(): void { }


    public __updateStrokeSpread(): number { return 0 }

    public __updateRenderSpread(): number { return 0 }

    public __onUpdateSize(): void { }

    // ---


    public __updateEraser(value?: boolean): void {
        this.__hasEraser = value ? true : this.children.some(item => item.__.eraser)
    }

    public __renderEraser(canvas: ILeaferCanvas, options: IRenderOptions): void {  // path eraser
        canvas.save()
        this.__clip(canvas, options)
        const { renderBounds: r } = this.__layout
        canvas.clearRect(r.x, r.y, r.width, r.height)
        canvas.restore()
    }

    public __updateMask(_value?: boolean): void {
        this.__hasMask = this.children.some(item => item.__.mask && item.__.visible && item.__.opacity)
    }

    // LeafMask rewrite

    public __renderMask(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }


    // ---


    // convert

    public __getNowWorld(options: IRenderOptions): IMatrixWithBoundsScaleData {
        if (options.matrix) {
            if (!this.__cameraWorld) this.__cameraWorld = {} as IMatrixWithBoundsScaleData
            const cameraWorld = this.__cameraWorld
            multiplyParent(this.__world, options.matrix, cameraWorld, undefined, this.__world)
            toOuterOf(this.__layout.renderBounds, cameraWorld, cameraWorld)
            return cameraWorld
        } else {
            return this.__world
        }
    }

    public getTransform(relative?: ILocationType | ILeaf): IMatrixData {
        return this.__layout.getTransform(relative || 'local')
    }


    public getBounds(type?: IBoundsType, relative?: ILocationType | ILeaf): IBoundsData {
        return this.__layout.getBounds(type, relative)
    }

    public getLayoutBounds(type?: IBoundsType, relative?: ILocationType | ILeaf, unscale?: boolean): ILayoutBoundsData {
        return this.__layout.getLayoutBounds(type, relative, unscale)
    }

    public getLayoutPoints(type?: IBoundsType, relative?: ILocationType | ILeaf): IPointData[] {
        return this.__layout.getLayoutPoints(type, relative)
    }


    public getWorldBounds(inner: IBoundsData, relative?: ILeaf, change?: boolean): IBoundsData {
        const matrix = relative ? getRelativeWorld(this, relative) : this.worldTransform
        const to = change ? inner : {} as IBoundsData
        toOuterOf(inner, matrix, to)
        return to
    }


    public worldToLocal(world: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
        if (this.parent) {
            this.parent.worldToInner(world, to, distance, relative)
        } else {
            if (to) copy(to, world)
        }
    }

    public localToWorld(local: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
        if (this.parent) {
            this.parent.innerToWorld(local, to, distance, relative)
        } else {
            if (to) copy(to, local)
        }
    }

    public worldToInner(world: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
        if (relative) {
            relative.innerToWorld(world, to, distance)
            world = to ? to : world
        }
        toInnerPoint(this.worldTransform, world, to, distance)
    }

    public innerToWorld(inner: IPointData, to?: IPointData, distance?: boolean, relative?: ILeaf): void {
        toOuterPoint(this.worldTransform, inner, to, distance)
        if (relative) relative.worldToInner(to ? to : inner, null, distance)
    }

    // simple

    public getBoxPoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        return this.getBoxPointByInner(this.getInnerPoint(world, relative, distance, change), null, null, true)
    }

    public getBoxPointByInner(inner: IPointData, _relative?: ILeaf, _distance?: boolean, change?: boolean): IPointData {
        const point = change ? inner : { ...inner } as IPointData, { x, y } = this.boxBounds
        move(point, -x, -y)
        return point
    }

    public getInnerPoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        const point = change ? world : {} as IPointData
        this.worldToInner(world, point, distance, relative)
        return point
    }

    public getInnerPointByBox(box: IPointData, _relative?: ILeaf, _distance?: boolean, change?: boolean): IPointData {
        const point = change ? box : { ...box } as IPointData, { x, y } = this.boxBounds
        move(point, x, y)
        return point
    }

    public getInnerPointByLocal(local: IPointData, _relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        return this.getInnerPoint(local, this.parent, distance, change)
    }

    public getLocalPoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        const point = change ? world : {} as IPointData
        this.worldToLocal(world, point, distance, relative)
        return point
    }

    public getLocalPointByInner(inner: IPointData, _relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        return this.getWorldPoint(inner, this.parent, distance, change)
    }

    public getPagePoint(world: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        const layer = this.leafer ? this.leafer.zoomLayer : this
        return layer.getInnerPoint(world, relative, distance, change)
    }

    public getWorldPoint(inner: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        const point = change ? inner : {} as IPointData
        this.innerToWorld(inner, point, distance, relative)
        return point
    }

    public getWorldPointByBox(box: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        return this.getWorldPoint(this.getInnerPointByBox(box, null, null, change), relative, distance, true)
    }

    public getWorldPointByLocal(local: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        const point = change ? local : {} as IPointData
        this.localToWorld(local, point, distance, relative)
        return point
    }

    public getWorldPointByPage(page: IPointData, relative?: ILeaf, distance?: boolean, change?: boolean): IPointData {
        const layer = this.leafer ? this.leafer.zoomLayer : this
        return layer.getWorldPoint(page, relative, distance, change)
    }


    // transform 

    public setTransform(matrix: IMatrixData, resize?: boolean, transition?: ITransition): void {
        setTransform(this, matrix, resize, transition)
    }

    public transform(matrix: IMatrixData, resize?: boolean, transition?: ITransition): void {
        transform(this, matrix, resize, transition)
    }

    public move(x: number | IPointData, y?: number, transition?: ITransition): void {
        moveLocal(this, x, y, transition)
    }


    public moveInner(x: number | IPointData, y?: number, transition?: ITransition): void {
        moveWorld(this, x, y, true, transition)
    }

    public scaleOf(origin: IPointData | IAlign, scaleX: number, scaleY?: number | ITransition, resize?: boolean, transition?: ITransition): void {
        zoomOfLocal(this, getLocalOrigin(this, origin), scaleX, scaleY, resize, transition)
    }

    public rotateOf(origin: IPointData | IAlign, rotation: number, transition?: ITransition): void {
        rotateOfLocal(this, getLocalOrigin(this, origin), rotation, transition)
    }

    public skewOf(origin: IPointData | IAlign, skewX: number, skewY?: number, resize?: boolean, transition?: ITransition): void {
        skewOfLocal(this, getLocalOrigin(this, origin), skewX, skewY, resize, transition)
    }


    public transformWorld(worldTransform?: IMatrixData, resize?: boolean, transition?: ITransition): void {
        transformWorld(this, worldTransform, resize, transition)
    }

    public moveWorld(x: number | IPointData, y?: number, transition?: ITransition): void {
        moveWorld(this, x, y, false, transition)
    }

    public scaleOfWorld(worldOrigin: IPointData, scaleX: number, scaleY?: number | ITransition, resize?: boolean, transition?: ITransition): void {
        zoomOfWorld(this, worldOrigin, scaleX, scaleY, resize, transition)
    }

    public rotateOfWorld(worldOrigin: IPointData, rotation: number): void {
        rotateOfWorld(this, worldOrigin, rotation)
    }

    public skewOfWorld(worldOrigin: IPointData, skewX: number, skewY?: number, resize?: boolean, transition?: ITransition): void {
        skewOfWorld(this, worldOrigin, skewX, skewY, resize, transition)
    }

    public flip(axis: IAxis, transition?: ITransition): void {
        transform(this, getFlipTransform(this, axis), false, transition)
    }


    // @leafer-in/resize rewrite

    public scaleResize(scaleX: number, scaleY = scaleX, _noResize?: boolean): void {
        (this as ILeaf).scaleX *= scaleX;
        (this as ILeaf).scaleY *= scaleY
    }

    public __scaleResize(_scaleX: number, _scaleY: number): void { }


    public resizeWidth(_width: number): void { }

    public resizeHeight(_height: number): void { }


    // @leafer-ui/hit LeafHit rewrite

    public __hitWorld(_point: IRadiusPointData): boolean { return true }

    public __hit(_local: IRadiusPointData): boolean { return true }

    public __hitFill(_inner: IRadiusPointData): boolean { return true }

    public __hitStroke(_inner: IRadiusPointData, _strokeWidth: number): boolean { return true }

    public __hitPixel(_inner: IRadiusPointData): boolean { return true }

    public __drawHitPath(_canvas: ILeaferCanvas): void { }

    public __updateHitCanvas(): void { }

    // ---


    // LeafRender rewrite

    public __render(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }

    public __drawFast(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }

    public __draw(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }


    public __clip(_canvas: ILeaferCanvas, _options: IRenderOptions): void { }

    public __renderShape(_canvas: ILeaferCanvas, _options: IRenderOptions, _ignoreFill?: boolean, _ignoreStroke?: boolean): void { }


    public __updateWorldOpacity(): void { }

    public __updateChange(): void { }

    // ---


    // path

    public __drawPath(_canvas: ILeaferCanvas): void { }

    public __drawRenderPath(_canvas: ILeaferCanvas): void { }

    public __updatePath(): void { }

    public __updateRenderPath(): void { }

    // ---


    // @leafer-in/motion-path rewrite

    public getMotionPathData(): IMotionPathData {
        return Plugin.need('path')
    }

    public getMotionPoint(_motionDistance: number | IUnitData): IRotationPointData {
        return Plugin.need('path')
    }

    public getMotionTotal(): number {
        return 0
    }

    public __updateMotionPath(): void { }

    // ---


    // @leafer-in/animate rewrite
    public __runAnimation(_type: 'in' | 'out', _complete?: IFunction): void { }


    // Branch rewrite

    public __updateSortChildren(): void { }

    public add(_child: ILeaf | ILeaf[] | ILeafInputData | ILeafInputData[], _index?: number): void { }

    public remove(_child?: ILeaf | number | string | IFindMethod, destroy?: boolean): void {
        if (this.parent) this.parent.remove(this, destroy)
    }

    public dropTo(parent: ILeaf, index?: number, resize?: boolean): void {
        drop(this, parent, index, resize)
    }

    // ---


    // LeafEventer rewrite

    public on(_type: string | string[] | IEventMap, _listener?: IEventListener, _options?: IEventOption): void { }

    public off(_type?: string | string[], _listener?: IEventListener, _options?: IEventOption): void { }

    public on_(_type: string | string[], _listener: IEventListener, _bind?: IObject, _options?: IEventOption): IEventListenerId { return undefined }

    public off_(_id: IEventListenerId | IEventListenerId[]): void { }

    public once(_type: string | string[], _listener: IEventListener, _capture?: boolean): void { }

    public emit(_type: string, _event?: IEvent | IObject, _capture?: boolean): void { }

    public emitEvent(_event?: IEvent, _capture?: boolean): void { }

    public hasEvent(_type: string, _capture?: boolean): boolean { return false }

    // ---

    static changeAttr(attrName: string, defaultValue: IValue | IValueFunction, fn?: IAttrDecorator): void {
        fn ? this.addAttr(attrName, defaultValue, fn) : defineDataProcessor(this.prototype, attrName, defaultValue)
    }

    static addAttr(attrName: string, defaultValue: IValue | IValueFunction, fn?: IAttrDecorator, helpValue?: IValue): void {
        if (!fn) fn = boundsType
        fn(defaultValue, helpValue)(this.prototype, attrName)
    }


    public __emitLifeEvent(type: string): void {
        if (this.hasEvent(type)) this.emitEvent(new ChildEvent(type, this, this.parent))
    }


    public destroy(): void {
        if (!this.destroyed) {
            if (this.parent) this.remove()
            if (this.children) (this as unknown as IBranch).clear()

            this.__emitLifeEvent(ChildEvent.DESTROY)

            this.__.destroy()
            this.__layout.destroy();

            (this as ILeaf).destroyEventer()
            this.destroyed = true
        }
    }

}
