import * as PIXI from 'pixi.js'
import { extend, getValue, error, warn, mountApi, forin } 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
    public realScale: Layout.scale
    public textStyle: PIXI.TextStyle
    private initScale: Layout.scale
    private styleDiffPool: string[]
    private styleFromParent: {
        fontSize?: number | string,
        color?: number | string,
        lineHeight?: number | string,
        fontWeight?: number | string,
        fontStyle?: string,
        fontFamily?: string,
        horPos?: {
            left?: number | string,
            right?: number | string,
            centerX?: number | string,
        },
        verPos?: {
            top?: number | string,
            bottom?: number | string,
            centerY?: number | string,
        },
    }
    constructor(target: Layout.element, style: Layout.style = {}, config: Layout.config) {
        this.target = target
        this.config = config
        this.transform = transform(config.uiDesignRatio)
        this.initScale = target.scale

        // 初始化设置 style
        this.extendStyle(style, true)
    }
    /*
    *   ----------
    *   布局渲染函数
    *   ----------
    */
    // 初始渲染函数
    public render(container: any) {
        if (container) {
            this.container = this.transform.container(container)
            const { target } = this
            // 继承属性
            if (target['type'] === 'text') {
                this.style = this.textInheritStyle(this.style)
            }
            // 初始化布局
            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.')
        }
    }

    // 更新布局
    public update(newStyle: any = {}, options?: Layout.updateOption) {
        const { config, target, container } = this
        if (!container) return

        const { refreshStyle = false, updateChild = true, replaceStyle = false } = options || {}
        config.Component.emit('beforeUpdate', newStyle)

        // 继承属性
        if (target['type'] === 'text') {
            newStyle = this.textInheritStyle(newStyle)
        }
        
        // 当手动修改元素布局属性后
        // 可以通过 refreshStyle 反向同步 style
        // TOFIX: 如果元素被子元素撑开，反向同步 可能会导致子级布局错乱
        refreshStyle && this.updateStyle()
        
        // 合并 style
        this.extendStyle(newStyle, replaceStyle)
        this._setLayout()

        // 当修改可能影响子级的布局时，更新子级
        updateChild && this.updateChild(options)

        // 触发更新 hook
        config.Component.emit('update', {
            instance: this.target,
            style: this.style,
        })
        return target
    }
    // 获取计算布局
    private _getRlayout() {
        const { target, container, rlayout } = this
        let size
        // 当初始化 或者 宽高发生改变时，才计算
        // 否则沿用旧值
        if (this.styleHasChanged(['width', 'height', 'content', 'backgroundImage'])) {
            size = this.transform.elementSize(target, this.style, container)
        } else if (rlayout) {
            size = {
                width: rlayout.width,
                height: rlayout.height,
            }
        }

        if (target['type'] === 'text') {
            size = this.fixTextSize(size)
        }
        // 位置
        const pos = this.transform.elementPos(size, this.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)

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

        // 绘制
        patch(this)

        this.setScale(scale)

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

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

    /*
    *   ----------
    *   修正类函数
    *   ----------
    */
    // 修正文字的宽高
    private fixTextSize(size: Layout.size) {
        let { style } = this
        const { transform, container } = this
        const { width: cw, height: ch } = container
        const { content = '' } = style
        style = extend({}, this.styleFromParent, style)
        
        style.breakWords = true
        style.wordWrap = true
        style.wordWrapWidth = size.width
        if (style.color) style.fill = style.color
        if (style.fontSize) style.fontSize = transform.length(cw, style.fontSize)
        if (style.lineHeight) style.lineHeight = transform.length(ch, style.lineHeight)

        this.textStyle = new PIXI.TextStyle(style)
        const copyText = new PIXI.Text(`${content}`, this.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()
        }
    }

    /*
    *   ----------
    *   业务工具函数
    *   ----------
    */
    // 是否有 水平方向 位置属性
    private hasHorPosAttr(style) {
        return (style.hasOwnProperty('left') || style.hasOwnProperty('right') || style.hasOwnProperty('centerX'))
    }
    // 是否有 竖直方向 位置属性
    private hasVerPosAttr(style) {
        return (style.hasOwnProperty('top') || style.hasOwnProperty('bottom') || style.hasOwnProperty('centerY'))
    }
    private styleDiff(oldStyle: Layout.style, newStyle: Layout.style, replaceStyle: boolean) {
        const diff: string[] = []
        if (newStyle) {
            // 新增或者修改
            forin(newStyle, (key: string, value: any) => {
                // 全替换
                if (replaceStyle) {
                    diff.push(key)
                } else {
                // 合并
                    const oldValue = oldStyle[key]
                    if (oldValue !== value) {
                        diff.push(key)
                    }
                }
            })

            // 删除
            forin(oldStyle, (key: string) => {
                if (!newStyle[key]) {
                    diff.push(key)
                }
            })
        }
        return diff
    }
    // 合并 style
    private extendStyle(newStyle, replaceStyle: boolean = false) {
        this.styleDiffPool = this.styleDiff(this.style, newStyle, replaceStyle)
        if (replaceStyle) {
            if (this.style.content && !newStyle.content) { 
                newStyle.content = this.style.content 
            }
            this.style = newStyle
        } else {
            const horAttrs = ['left', 'right', 'centerX']
            const verAttrs = ['top', 'bottom', 'centerY']
            const removeAttr = (attrs, newKey) => {
                if (attrs.includes(newKey)) {
                    attrs.map((attr) => delete this.style[attr])
                }
            }
            // 避免同时出现同类布局属性
            // 如 left: 0 / centerX: 0
            Object.keys(newStyle).map((newKey: string) => {
                removeAttr(horAttrs, newKey)
                removeAttr(verAttrs, newKey)
            })

            extend(true, this.style, newStyle)
        }
    }
    //  判断 style 中的属性是否被修改
    // 用于性能优化，当值不变时，直接复用原值
    private styleHasChanged(changeKeyArr: string[]) {
        let flag = false
        // 只要有一个值改变，则马上 break 返回
        for (let i = 0; i < changeKeyArr.length; i++) {
            const key = changeKeyArr[i]
            if (this.styleDiffPool.includes(key)) {
                flag = true
                break
            }
        }
        return flag
    }
    // 从父级继承属性
    private _inheritStyle(): any {
        const styleFromParent = {}
        const { container } = this
        const parentStyle = getValue(container, 'element.layout.style')

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

            // 处理继承的 text 定位属性
            const _handle = (css: string, attrs: string[]) => {
                const value = parentStyle[css]
                if (value) {
                    const store = styleFromParent[css === 'textAlign' ? 'horPos' : 'verPos'] = {}
                    if (typeof value === 'number') {
                        store[attrs[0]] = +value
                    } else if (attrs.concat('center').includes(value)) {
                        const attr = value === 'center' ? attrs[1] : value
                        store[attr] = 0
                    }
                }
            }
            _handle('textAlign', ['left', 'centerX', 'right'])
            _handle('textJustify', ['top', 'centerY', 'bottom'])
        }

        return styleFromParent
    }

    // 继承属性
    private textInheritStyle(newStyle) {
        const styleFromParent = this._inheritStyle()
        const { horPos, verPos, ...rest } = styleFromParent
        if (!this.hasHorPosAttr(this.style)) {
            newStyle = extend(newStyle, horPos)
        }
        if (!this.hasVerPosAttr(this.style)) {
            newStyle = extend(newStyle, verPos)
        }
        extend(newStyle, rest)
        return newStyle
    }
    
    // 更新父级状态
    public refreshContianer() {
        const parent = this.container ? this.container.element : this.target.parent 
        this.container = this.transform.container(parent as Layout.element)
    }
    /*
    *   ----------
    *   公共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)
        return 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)
        }
        return this.target
    }

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

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

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

    public remove() {
        const parent = getValue(this, 'container.element')
        if (parent) {
            parent.removeChild(this.target)
        } else {
            this.target.destroy && this.target.destroy()
        }
        return 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
    }

    public transformStyle(style: Layout.style) {
        const { target, container } = this
        const _style = extend({}, this.style, style)
        const { anchor = { x: .5, y: .5 } } = _style
        let size = this.transform.elementSize(target, _style, container)

        if (target['type'] === 'text') {
            size = this.fixTextSize(size)
        }
        // 位置
        const pos = this.transform.elementPos(size, style, container)
        const rlayout = extend(size, pos)
        return this.transform.rlayout2pixilayout(container, anchor, rlayout)
    }

    public getStyle(key?: string) {
        return key ? this.style[key] : this.style
    }
}
export function layout(co: any) {
    co.on('created', ({ instance, style, uiDesignRatio }) => {
        // 初始化布局实例
        instance.layout = new Layout(instance, style, {
            uiDesignRatio,
            Component: co,
        })

        // 挂载 api
        const exportApi = [
            'transformStyle', 
            'append', 
            'appendTo',
            'before',
            'after',
            'insertBefore',
            'insertAfter',
            'remove',
            {
                setStyle: 'update',
            }, 
            'getStyle',
        ]

        mountApi(instance, 'layout', exportApi)
    })
}
