import * as PIXI from 'pixi.js'
import { extend, getValue, error, warn, mountApi, forin, setValue } 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 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,
        },
    }
    public rendered: boolean = false
    constructor(target: Layout.element, style: Layout.style = {}, config: Layout.config) {
        this.target = target
        this.config = config
        this.transform = transform(config.uiDesignRatio)

        // 初始化设置 style
        this.mergeStyle(style, true)
    }
    /*
    *   ----------
    *   布局渲染函数
    *   ----------
    */
    // 初始渲染函数
    public render(container: any) {
        if (container) {
            const { target } = this
            this.container = this.transform.container(container)
            // 继承属性
            if (target.type === 'text') {
                this.styleFromParent = this.inheritStyle()
            }

            // 初始化布局
            this.setLayout()

            // 修正布局
            this.fixRender()

            this.initScale = {
                x: target.scale.x,
                y: target.scale.y,
            }
            
            this.setScale(this.style.scale)

            const { opacity, zIndex } = this.style
            if (opacity) target.alpha = +opacity
            if (zIndex) target.zIndex = +zIndex

            this.setVisible();

            // 触发 hook
            ['update', 'mounted'].map((hookName: string) => {
                this.config.Component.emit(hookName, {
                    instance: this.target,
                    style: this.style,
                })
            })

            this.rendered = true
        } else {
            warn('layout.render must have a container.')
        }
    }

    // 更新布局
    public update(newStyle: any = {}, options: Layout.updateOption = {}) {
        const { config, target, container } = this
        const { refreshStyle = false, updateChild = true, replaceStyle = false } = options

        if (!container) return
        // 当元素未渲染时，先执行 render
        if (!this.rendered) {
            this.render(container.element)
            return target
        }
        
        config.Component.emit('beforeUpdate', newStyle)
        
        // 更新继承属性
        if (target.type === 'text') {
            this.styleFromParent = this.inheritStyle()
        }

        if (newStyle.backgroundFrame) {
            this.updateBackgroundFrame(newStyle)
        }
        
        // 当手动修改元素布局属性后
        // 可以通过 refreshStyle 反向同步 style
        // TOFIX: 如果元素被子元素撑开，反向同步 可能会导致子级布局错乱
        refreshStyle && this.updateStyle()
        
        // 合并 新style 到 this.style 中
        this.mergeStyle(newStyle, replaceStyle)

        this.setLayout()

        // 尺寸发生变化时
        // 初始化缩放值
        this.initScale = {
            x: target.scale.x,
            y: target.scale.y,
        }       
        // 设置 scale
        // 需先恢复，再更新子级
        this.setScale({ x: 1, y: 1 }) 
        
        // 当修改可能影响子级的布局时，更新子级
        updateChild && this.updateChild()
        
        // 设置缩放
        this.setScale(this.style.scale)

        const { opacity } = this.style
        if (opacity) target.alpha = +opacity
        if (this.styleHasChanged('zIndex')) {
            target.zIndex = +(this.style.zIndex as number) 
            container.element.sortChildren()
        }

        this.setVisible()

        // 触发更新 hook
        config.Component.emit('update', {
            instance: this.target,
            style: this.style,
        })
        return target
    }
    private updateBackgroundFrame(newStyle: Layout.style) {
        // TODOS: 限制消除
        if (this.style.backgroundFrame) {
            const [x, y, w, h] = newStyle.backgroundFrame as number[]
            const [, , ow, oh] = this.style.backgroundFrame as number[]

            if (w === ow || h === oh) {
                this.target['texture'].frame = new PIXI.Rectangle(x, y, w, h)
            } else {
                warn('the width and heigth of backgroundFrame are not changed')
            }
        } else {
            warn('if you want to change the backgroundFrame, the init style also must set backgroundFrame')
        }
    }
    // 获取计算布局
    private getRlayout() {
        const { target, container } = this
        let size
        // 当初始化 或者 宽高发生改变时，才计算
        // 否则沿用旧值
        size = this.transform.elementSize(target, this.style, container)

        let posStyle = this.style

        if (target['type'] === 'text') {
            size = this.fixTextSize(size)
            
            // 文字需要继承 textAlign / textJustify 位置属性
            const { horPos = {}, verPos = {} } = this.styleFromParent
            posStyle = this.extendStyle(horPos, verPos, this.style)
        }
        // 位置
        const pos = this.transform.elementPos(size, posStyle, container)
        return extend(size, pos)
    }
    // 设置布局
    private setLayout() {
        const { target, container } = this
        const anchor = (this.style.anchor || { x: .5, y: .5 }) as Layout.point

        if (this.sizeHasChanged()) {
            target.scale.set(1, 1)
        }
        
        // 计算布局
        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)
    }

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

    /*
    *   ----------
    *   修正类函数
    *   ----------
    */
    // 修正文字的宽高
    private fixTextSize(size: Layout.size) {
        let { style } = this
        const { transform, container: { width: cw, height: ch } } = this
        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 { width, height } = new PIXI.Text(`${content}`, this.textStyle)
        return { width, height }
    }
    // 重绘
    // 修复因 图片加载 / 插入顺序 导致的布局计算错误
    private fixRender() {
        if (
            ['sprite', '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()
        }
    }

    /*
    *   ----------
    *   业务工具函数
    *   ----------
    */
    public extendStyle(...styleArr: Layout.style[]) {
        const horAttrs = ['left', 'right', 'centerX']
        const verAttrs = ['top', 'bottom', 'centerY']
        return styleArr.reduce((s1, s2) => {
            // 避免同时出现同类布局属性
            // 如 left: 0 / centerX: 0
            Object.keys(s2).map((newKey: string) => {
                [horAttrs, verAttrs].map((attrs) => {
                    if (attrs.includes(newKey)) {
                        attrs.map((attr) => delete s1[attr])
                    }
                })
            })
            return extend(true, s1, s2)
        }, {})
    }
    private styleDiff(oldStyle: Layout.style, newStyle: Layout.style, replaceStyle: boolean) {
        const diff: string[] = []
        if (newStyle) {
            // 新增或者修改
            forin(newStyle, (key: string, value: any) => {
                // 全替换
                if (replaceStyle || oldStyle[key] !== value) {
                    diff.push(key)
                }
            })

            // 删除
            if (replaceStyle) {
                forin(oldStyle, (key: string) => {
                    if (!newStyle[key]) diff.push(key)
                })
            }
        }
        return diff
    }
    // 合并 style
    private mergeStyle(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 {
            this.style = this.extendStyle(this.style, newStyle)
        }
    }
    //  判断 style 中的属性是否被修改
    // 用于性能优化，当值不变时，直接复用原值
    private styleHasChanged(...changeKeyArr: string[]) {
        // 当父级发生修改时
        if (this.container.refresh) return true

        // 当自身属性改变时
        for (let i = 0; i < changeKeyArr.length; i++) {
            const key = changeKeyArr[i]
            if (this.styleDiffPool.includes(key)) {
                return true
            }
        }
        return false
    }
    private sizeHasChanged() {
        return this.styleHasChanged('width', 'height', 'content', 'backgroundImage')
    }
    // 从父级继承属性
    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 (attrs.concat('center').includes(value)) {
                        const attr = value === 'center' ? attrs[1] : value
                        store[attr] = 0
                    } else if (+value || +value === 0) {
                        store[attrs[0]] = +value
                    }
                }
            }
            _handle('textAlign', ['left', 'centerX', 'right'])
            _handle('textJustify', ['top', 'centerY', 'bottom'])
        }

        return styleFromParent
    }
    
    // 更新父级状态
    public refreshContainer() {
        const parent = this.container ? this.container.element : this.target.parent 
        this.container = this.transform.container(parent as Layout.element)
        // 标识父级更新
        this.container.refresh = true
    }
    private setVisible() {
        if (this.styleHasChanged('visible')) {
            this.target.visible = !(String(this.style.visible) === 'false')
        }
    }
    /*
    *   ----------
    *   公共API
    *   ----------
    */
    // 设置缩放
    public setScale(scale: Layout.style['scale'] = { x: 1, y: 1 }) {
        const { target, initScale } = this
        const { x: scaleX, y: scaleY } = scale as Layout.point
        target.scale.set(initScale.x * scaleX, initScale.y * scaleY)

        // 获取当前元素的真实缩放值
        this.realScale = this.transform.actualScale(target)
    }

    public appendTo(container: any, renderable: boolean = true) {
        if (container && container.addChild) {
            if (this.target.zIndex) {
                container.sortableChildren = true
            }
            container.addChild(this.target)
            // 减少无效渲染
            if (renderable) {
                this.render(container)
            }
        } else {
            error(`the container is not exsit!`)
        }
        return this.target
    }

    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) {
            if (child.zIndex) {
                parent.sortableChildren = true
            }
            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 = (style.anchor || { x: .5, y: .5 }) as Layout.point
        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 = this.extendStyle(this.style, style)
        let size = this.transform.elementSize(target, _style, container)
        
        let posStyle = _style
        if (target['type'] === 'text') {
            size = this.fixTextSize(size)
            
            // 文字需要继承 textAlign / textJustify 位置属性
            const { horPos = {}, verPos = {} } = this.styleFromParent
            posStyle = this.extendStyle(horPos, verPos, _style)
        }
        // 位置
        const anchor = (_style['anchor'] || { x: .5, y: .5 }) as Layout.point
        const pos = this.transform.elementPos(size, posStyle, container)
        const rlayout = extend(size, pos)
        return this.transform.rlayout2pixilayout(container, anchor, rlayout)
    }

    public getStyle(key ?: string, options ?: {
        refreshStyle?: boolean,
    }) {
        if (options && options.refreshStyle) this.updateStyle()
        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)
    })
}
