import * as PIXI from 'pixi.js'
import { extend, getValue, error, warn } from '../../utils'
import { transform } from './transform'
import { patch } from './patch'

class Layout {
    public config: Layout.config
    public container: Layout.containerStatus
    public target: Layout.element
    public style: Layout.style
    public newStyle: Layout.style
    public transform: Layout.transform
    public rlayout: Layout.layout
    public pixilayout: Layout.layout
    private initScale: Layout.scale
    public realScale: Layout.scale
    public textStyle: PIXI.TextStyle
    constructor(target: Layout.element, style: Layout.style = {}, config: Layout.config) {
        this.target = target
        this.style = style
        this.config = config
        this.transform = transform(config.uiDesignRatio)
        this.initScale = target.scale
    }
    /*
    *   ----------
    *   布局渲染函数
    *   ----------
    */
    // 初始渲染函数
    public render(container: any) {
        if (container) {
            this.container = this.transform.container(container)
            // 初始化布局
            this._setLayout()
            // 修正布局
            this._fixRender();
            // 触发 hook
            ['update', 'mounted'].map((hookName: string) => {
                this.config.Component.emit(hookName, {
                    instance: this.target,
                    style: this.style,
                })
            })
        } else {
            warn('layout.render must have a container.')
        }
    }
    // 获取计算布局
    private _getRlayout() {
        const { target, style, container } = this
        let size
        // 当初始化 或者 宽高发生改变时，才计算
        // 否则沿用旧值
        if (!this.rlayout || this.styleHasChanged(['width', 'height'])) {
            size = this.transform.elementSize(target, style, container)
        } else if (this.rlayout) {
            size = {
                width: this.rlayout.width,
                height: this.rlayout.height,
            }
        }

        if (target['type'] === 'text') {
            // text 标签特殊处理
            // 宽高随文字自适应
            size = this._fixTextSize(size)
        }
        // 位置
        const pos = this.transform.elementPos(size, style, container)
        return extend(size, pos)
    }
    // 设置布局
    private _setLayout() {
        const { target, style, container } = this
        const { scale = 1, anchor = { x: .5, y: .5 } } = style

        // 继承属性
        if (target['type'] === 'text') {
            this._inheritStyle()
        }

        // 计算布局
        this.rlayout = this._getRlayout()
        this.pixilayout = this.transform.rlayout2pixilayout(container, anchor, this.rlayout)

        // 设置锚点
        if (this.styleHasChanged(['anchor'])) {
            const action = target['anchor'] ? 'anchor' : 'pivot'
            target[action].set(anchor.x, anchor.y)
        }

        // 绘制
        patch(this)

        this.setScale(scale)

        // 获取当前元素的真实缩放值
        if (this.styleHasChanged(['width', 'height'])) {
            if (target['type'] === 'container') {
                this.realScale = { 
                    x: this.rlayout.width / 500, 
                    y: this.rlayout.height / 500,
                }   
            } else {
                this.realScale = this.transform.actualScale(target)
            }
        }
    }

    // 更新布局
    public update(newStyle?: any) {
        const { target, config } = this
        config.Component.emit('beforeUpdate', newStyle)
        
        if (this.hasLayoutUpdatedByOutSet()) {
            this.updateStyle()
        }
        
        this.newStyle = newStyle
        this.style = extend(this.style, newStyle || {})
        if (this.container) {
            this._setLayout()
            this.updateChild()
            // 触发更新 hook
            config.Component.emit('update', {
                instance: this.target,
                style: this.style,
            })
        }
    }

    // 重绘子级
    public updateChild() {
        const children = getValue(this.target, 'children')
        if (children.length) {
            children.map((child: any) => {
                if (child.layout) {
                    child.layout.refreshContianer()
                    child.layout.update()
                    child.layout.updateChild()
                }
            })
        }
    }

    /*
    *   ----------
    *   修正类函数
    *   ----------
    */
    // 修正文字的宽高
    private _fixTextSize(size: Layout.size) {
        const { transform, container } = this
        const { width: cw, height: ch } = container
        const { 
            content = '', 
            fontSize = 20, 
            lineHeight = fontSize * 1.2, 
            color = 0x000000, 
            ...rest 
        } = this.style || {}
        const textStyle = new PIXI.TextStyle(extend({
            fontSize: transform.length(cw, fontSize),
            breakWords: true,
            wordWrap: true,
            wordWrapWidth: size.width,
            lineHeight: transform.length(ch, lineHeight),
            fill: color,
        }, rest))
        this.textStyle = textStyle

        const copyText = new PIXI.Text(content, textStyle)
        return {
            width: copyText.width,
            height: copyText.height,
        }
    }
    // 重绘
    // 修复因 图片加载 / 插入顺序 导致的布局计算错误
    private _fixRender() {
        if (
            ['sprite', 'container', 'animatedsprite'].includes(this.target['type']) && 
            !this.target['texture'].valid
            ) {
            // 在图片加载完成时执行深度重绘
            let completed = false
            this.target['texture'].on('update', () => {
                if (!completed) {
                    this.update()
                    completed = true
                }
            })
        } else if (this.target.children.length) {
            // 自身渲染后，更新子节点
            this.updateChild()
        }
    }

    /*
    *   ----------
    *   业务工具函数
    *   ----------
    */
    //  判断 style 中的属性是否被修改
    // 用于性能优化，当值不变时，直接复用原值
    private styleHasChanged(changeKeyArr: string[]) {
        let flag = false
        if (this.newStyle) {
            for (let i = 0; i < changeKeyArr.length; i++) {
                const key = changeKeyArr[i]
                if (this.newStyle[key]) {
                    flag = true
                    break
                }
            }
        } else {
            flag = true
        }
        return flag
    }
    // 从父级继承属性
    private _inheritStyle(): any {
        const { container, style } = this
        const parentStyle = getValue(container, 'element.layout.style')

        if (parentStyle) {
            // 文字属性继承
            ['fontSize', 'color', 'lineHeight', 'fontWeight', 'fontStyle', 'fontFamily'].map((key: string) => {
                if (!style[key] && parentStyle[key]) {
                    style[key] = parentStyle[key]
                }
            })

            const _handle = (css: string, attrs: string[]) => {
                const value = parentStyle[css]
                if (value) {
                    const type = typeof +value
                    if (type === 'number' && +value) {
                        if (!style[attrs[0]])style[attrs[0]] = +value
                    } else if (attrs.concat('center').includes(value)) {
                        const attr = value === 'center' ? attrs[1] : value
                        if (!style[attr])style[attr] = 0
                    }
                }
            }
            // 处理继承的 text 定位属性
            _handle('textAlign', ['left', 'centerX', 'right'])
            _handle('textJustify', ['top', 'centerY', 'bottom'])
        }
    }
    
    // 更新父级状态
    public refreshContianer() {
        const parent = this.container ? this.container.element : this.target.parent 
        this.container = this.transform.container(parent as Layout.element)
    }

    // 判断 style 与 当前值 是否同步
    private hasLayoutUpdatedByOutSet() {
        let flag = false
        if (this.newStyle) {
            const { target, pixilayout } = this
            const attrs = ['width', 'height', 'x', 'y', 'rotation']
            for (let i = 0; i < attrs.length; i++) {
                const key = attrs[i]
                if (target[key] !== pixilayout[key]) {
                    flag = true
                }   
            }
        }
        return flag
    }

    /*
    *   ----------
    *   公共API
    *   ----------
    */
    // 设置缩放
    public setScale(scale: number) {
        const { target, initScale } = this
        target.scale.set(initScale.x * scale, initScale.y * scale)
    }

    public appendTo(container: any, renderable: boolean = true) {
        if (container && container.addChild) {
            container.addChild(this.target)
            if (renderable) {
                this.render(container)
            }
        } else {
            error(`the container is not exsit!`)
        }
    }

    public append(child: any, renderable: boolean = true) {
        child.layout.appendTo(this.target, renderable)
    }

    // 添加并渲染
    private _addChildAt(parent: any, child: any, index: number) {
        if (parent.addChildAt) {
            parent.addChildAt(child, index)
            child.layout.render(parent)
        }
    }

    // 添加兄弟节点
    private _addSibling(element: any, index: number) {
        if (this.container && getValue(element, 'layout')) {
            const parent = this.container.element
            const child = element
            const childIndex = parent.getChildIndex(this.target) + index
            this._addChildAt(parent, child, childIndex)
        }
    }
    // 插入兄弟节点
    private _insertSibling(element: any, index: number) {
        const parent = getValue(element, 'layout.container.element')
        if (parent) {
            const target = element
            const child = this.target
            const childIndex = parent.getChildIndex(target) + index
            this._addChildAt(parent, child, childIndex)
        }
    }

    // 判断目标位置是否已经是自身
    private _isMe(target: any, child: any, index: number) {
        const parent = getValue(target, 'layout.container.element')
        if (parent) {
            const targetPosIndex = parent.getChildIndex(target) + index
            const targetPosEl = parent.children[targetPosIndex]
            if (targetPosEl === child) {
                return true
            }
        }
        return false
    }

    public before(element: any) {
        if (!this._isMe(this.target, element, -1)) {
            this._addSibling(element, 0)
        }
    }

    public after(element: any) {
        if (!this._isMe(this.target, element, 1)) {
            this._addSibling(element, 1)
        }
    }

    public insertBefore(element: any) {
        if (!this._isMe(element, this.target, -1)) {
            this._insertSibling(element, 0)
        }
    }

    public insertAfter(element: any) {
        if (!this._isMe(element, this.target, 1)) {
            this._insertSibling(element, 1)
        }
    }

    public remove() {
        const parent = getValue(this, 'container.element')
        if (parent) {
            return parent.removeChild(this.target)
        } else {
            return this.target.destroy ? this.target.destroy() : this.target
        }
    }

    // 反向同步 style
    public updateStyle() {
        const { target, style, container } = this
        const { anchor = { x: .5, y: .5 } } = style
        const { width, height, x, y, rotation } = target
        this.pixilayout = { width, height, x, y, rotation }

        const rlayout = this.transform.pixilayout2rlayout(
            container,
            anchor,
            this.pixilayout,
        )
        
        this.rlayout = rlayout
        this.style.left = rlayout.x
        this.style.top = rlayout.y
        this.style.width = rlayout.width
        this.style.height = rlayout.height
        this.style.rotation = rlayout.rotation
    }
}

export function layout(co: any) {
    co.on('created', ({ instance, style, uiDesignRatio }) => {
        // 初始化布局实例
        instance.layout = new Layout(instance, style, {
            uiDesignRatio,
            Component: co,
        });

        // 挂载 api
        ['append', 'appendTo', 'before', 'after', 'insertBefore', 'insertAfter', 'remove'].map((fn: string) => {
            instance[fn] = (target: any, ...extra) => {
                instance.layout[fn](target, ...extra)
                return instance
            }
        })

        instance.updateLayout = (style?: Layout.style) => {
            instance.layout.update(style)
            return instance
        }
    })
}
