import * as PIXI from 'pixi.js'
import { extend, getValue } 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
    }
    private _fixTextSize(size: Layout.size) {
        const { transform, container } = this
        const { width: cw, height: ch } = container
        const { 
            content = '', 
            fontSize = 20, 
            lineHeight = fontSize * 1.5, 
            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 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 _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

        // 计算布局
        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)

        if (scale !== 1) 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 setScale(scale: number) {
        const { target, initScale } = this
        target.scale.set(initScale.x * scale, initScale.y * scale)
    }
    private _inheritStyle(): any {
        const { container, style, target } = 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]
                }
            })
            // 处理 textAlign
            if (parentStyle['textAlign'] && target['type'] === 'text') {
                switch (parentStyle['textAlign']) {
                    case 'left':
                        if (!style['left']) style['left'] = 0
                        if (!style['centerY']) style['centerY'] = 0
                        break
                    case 'right':
                        if (!style['right']) style['right'] = 0
                        if (!style['centerY']) style['centerY'] = 0
                        break
                    case 'center':
                        if (!style['centerX']) style['centerX'] = 0
                        if (!style['centerY']) style['centerY'] = 0
                        break
                }
            }
        }
    }
    // 渲染
    public render(container: any) {
        if (container) {
            this.container = this.transform.container(container)
            // 继承属性
            this._inheritStyle()
            // 初始化布局
            this._setLayout()
            // 修正布局
            this._fixRender();
            // 触发 hook
            ['update', 'mounted'].map((hookName: string) => {
                this.config.Component.emit(hookName, {
                    instance: this.target,
                    style: this.style,
                })
            })
        } else {
            console.warn('layout.render must have a container.')
        }
    }

    private _fixRender() {
        // 处理 图片加载 / 插入顺序 造成的布局错乱
        if (
            ['sprite', 'container'].includes(this.target['type']) && 
            !this.target['texture'].valid
        ) {
            let completed = false
            this.target['texture'].on('update', () => {
                if (!completed) {
                    this.forceUpdate()
                    completed = true
                }
            })
        } else if (this.target.children.length) {
            this.forceUpdate()
        }
    }

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

    public append(child: any) {
        child.layout.appendTo(this.target)
    }

    // 添加并渲染
    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
        }
    }

    // 更新布局
    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
    }
    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()
            const children = getValue(target, 'children')
            if (children.length) {
                children.map((child: any) => {
                    child.layout && child.layout.update()
                })
            }
            // 触发更新 hook
            config.Component.emit('update', {
                instance: this.target,
                style: this.style,
            })
        }
    }

    public refreshContianer() {
        this.container = this.transform.container(this.container.element)
    }

    public forceUpdate() {
        const children = getValue(this.target, 'children')
        this.update()
        if (children.length) {
            children.map((child: any) => {
                if (child.layout) {
                    child.layout.refreshContianer()
                    child.layout.update()
                }
            })
        }
    }

    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,
        });

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

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