import { _decorator, log, math, UITransform } from 'cc';
import { YXCollectionView, YXEdgeInsets, YXIndexPath, YXLayout, YXLayoutAttributes } from './yx-collection-view';
const { ccclass, property } = _decorator;

const _rectOut = new math.Rect()

/**
 * 流式布局
 * 支持水平/垂直方向排列滚动
 */
export class YXFlowLayout extends YXLayout {

    /**
     * 是否开启分页滚动效果
     */
    pagingEnabled: boolean = false

    /**
     * @bug 如果节点大小差距很大，可能会导致计算屏幕内节点时不准确，出现节点不被正确添加到滚动视图上的问题 (使用瀑布流布局时问题明显)
     * @fix 可以通过此属性，追加屏幕显示的节点数量
     * 设置这个值会在检查是否可见的节点时，尝试检查更多的可能处于屏幕外的节点，具体设置多少要根据实际情况调试，一般如果都是正常大小的节点，不需要考虑这个配置
     * 设置负值会检查所有的节点
     */
    extraVisibleCount: number = 0

    /**
     * 元素大小
     */
    itemSize: math.Size | ((indexPath: YXIndexPath, layout: YXFlowLayout, collectionView: YXCollectionView) => math.Size) = new math.Size(100, 100)
    getItemSize(): math.Size {
        if (this.itemSize instanceof Function == false) {
            return this.itemSize
        }
        throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取，请检查自己的布局逻辑并谨慎的通过动态配置自己获取，注意避免死循环");
    }

    /**
     * 元素之间垂直间距
     */
    verticalSpacing: number | ((section: number, layout: YXFlowLayout, collectionView: YXCollectionView) => number) = 0
    getVerticalSpacing(): number {
        if (this.verticalSpacing instanceof Function == false) {
            return this.verticalSpacing
        }
        throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取，请检查自己的布局逻辑并谨慎的通过动态配置自己获取，注意避免死循环");
    }

    /**
     * 元素之间水平间距
     */
    horizontalSpacing: number | ((section: number, layout: YXFlowLayout, collectionView: YXCollectionView) => number) = 0
    getHorizontalSpacing(): number {
        if (this.horizontalSpacing instanceof Function == false) {
            return this.horizontalSpacing
        }
        throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取，请检查自己的布局逻辑并谨慎的通过动态配置自己获取，注意避免死循环");
    }

    /**
     * 边距
     */
    sectionInset: YXEdgeInsets | ((section: number, layout: YXFlowLayout, collectionView: YXCollectionView) => YXEdgeInsets) = YXEdgeInsets.ZERO
    getSectionInset(): YXEdgeInsets {
        if (this.sectionInset instanceof Function == false) {
            return this.sectionInset
        }
        throw new Error("YXFlowLayout: 动态配置的布局参数不支持直接获取，请检查自己的布局逻辑并谨慎的通过动态配置自己获取，注意避免死循环");
    }

    prepare(collectionView: YXCollectionView): void {
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
            this._horizontal(collectionView)
            return
        }
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.VERTICAL) {
            this._vertical(collectionView)
            return
        }
    }

    initOffset(collectionView: YXCollectionView): void {
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
            collectionView.scrollView.scrollToLeft(0)
            return
        }
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.VERTICAL) {
            collectionView.scrollView.scrollToTop(0)
            return
        }
    }

    targetOffset(collectionView: YXCollectionView, touchMoveVelocity: math.Vec3, startOffset: math.Vec2): { offset: math.Vec2; time: number; } {
        if (this.pagingEnabled == false) {
            return null
        }
        let offset = collectionView.scrollView.getScrollOffset()
        offset.x = - offset.x
        let threshold = 0.2
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.HORIZONTAL) {
            let idx = Math.round(offset.x / collectionView.scrollView.view.width)
            let r = touchMoveVelocity.x / collectionView.scrollView.view.width
            if (startOffset && Math.abs(r) >= threshold) {
                idx = Math.round(startOffset.x / collectionView.scrollView.view.width) + (r > 0 ? -1 : 1)
            }
            offset.x = idx * collectionView.scrollView.view.width
        }
        if (collectionView.scrollDirection == YXCollectionView.ScrollDirection.VERTICAL) {
            let idx = Math.round(offset.y / collectionView.scrollView.view.height)
            let r = touchMoveVelocity.y / collectionView.scrollView.view.height
            if (startOffset && Math.abs(r) >= threshold) {
                idx = Math.round(startOffset.y / collectionView.scrollView.view.height) + (r > 0 ? 1 : -1)
            }
            offset.y = idx * collectionView.scrollView.view.height
        }
        return { offset: offset, time: 0.25 }
    }

    layoutAttributesForElementsInRect(rect: math.Rect, collectionView: YXCollectionView): YXLayoutAttributes[] {
        if (this.extraVisibleCount < 0) {
            return super.layoutAttributesForElementsInRect(rect, collectionView)
        }

        // 二分先查出大概位置
        let midIdx = -1
        let left = 0
        let right = this.attributes.length - 1

        while (left <= right && right >= 0) {
            let mid = left + (right - left) / 2
            mid = Math.floor(mid)
            let attr = this.attributes[mid]
            if (rect.intersects(attr.frame)) {
                midIdx = mid
                break
            }
            if (rect.yMax < attr.frame.yMin || rect.xMax < attr.frame.xMin) {
                right = mid - 1
            } else {
                left = mid + 1
            }
        }
        if (midIdx < 0) {
            return super.layoutAttributesForElementsInRect(rect, collectionView)
        }

        let result = []
        result.push(this.attributes[midIdx])

        // 往前检查
        let startIdx = midIdx
        while (startIdx > 0) {
            let idx = startIdx - 1
            let attr = this.attributes[idx]
            if (rect.intersects(attr.frame) == false) {
                break
            }
            result.push(attr)
            startIdx = idx
        }

        // 追加检查
        let extra_left = this.extraVisibleCount
        while (extra_left > 0) {
            let idx = startIdx - 1
            if (idx < 0) { break }
            let attr = this.attributes[idx]
            if (rect.intersects(attr.frame)) { result.push(attr) }
            startIdx = idx
            extra_left--
        }

        // 往后检查
        let endIdx = midIdx
        while (endIdx < this.attributes.length - 1) {
            let idx = endIdx + 1
            let attr = this.attributes[idx]
            if (rect.intersects(attr.frame) == false) {
                break
            }
            result.push(attr)
            endIdx = idx
        }

        // 追加检查
        let extra_right = this.extraVisibleCount
        while (extra_right > 0) {
            let idx = endIdx + 1
            if (idx >= this.attributes.length) { break }
            let attr = this.attributes[idx]
            if (rect.intersects(attr.frame)) { result.push(attr) }
            endIdx = idx
            extra_right--
        }

        return result
    }

    layoutAttributesForItemAtIndexPath(indexPath: YXIndexPath, collectionView: YXCollectionView): YXLayoutAttributes {
        let left = 0
        let right = this.attributes.length - 1

        while (left <= right && right >= 0) {
            let mid = left + (right - left) / 2
            mid = Math.floor(mid)
            let attr = this.attributes[mid]
            if (attr.indexPath.equals(indexPath)) {
                return attr
            }
            if (attr.indexPath.section < indexPath.section || (attr.indexPath.section == indexPath.section && attr.indexPath.item < indexPath.item)) {
                left = mid + 1
            } else {
                right = mid - 1
            }
        }
        return super.layoutAttributesForItemAtIndexPath(indexPath, collectionView)
    }

    private _horizontal(collectionView: YXCollectionView) {
        collectionView.scrollView.horizontal = true
        collectionView.scrollView.vertical = false
        let contentSize = collectionView.node.getComponent(UITransform).contentSize.clone()
        let allAttributes: YXLayoutAttributes[] = []

        let numberOfSections = collectionView.numberOfSections instanceof Function ? collectionView.numberOfSections(collectionView) : collectionView.numberOfSections

        let sectionMaxX = 0
        for (let section = 0; section < numberOfSections; section++) {
            let numberOfItems = collectionView.numberOfItems instanceof Function ? collectionView.numberOfItems(section, collectionView) : collectionView.numberOfItems
            let verticalSpacing = this.verticalSpacing instanceof Function ? this.verticalSpacing(section, this, collectionView) : this.verticalSpacing
            let horizontalSpacing = this.horizontalSpacing instanceof Function ? this.horizontalSpacing(section, this, collectionView) : this.horizontalSpacing
            let sectionInset = this.sectionInset instanceof Function ? this.sectionInset(section, this, collectionView) : this.sectionInset

            sectionMaxX += sectionInset.left

            let whole = new _yx_flow_layout_whole()
            whole.verticalSpacing = verticalSpacing
            whole.horizontalSpacing = horizontalSpacing
            whole.sectionInset = sectionInset
            whole.offset = sectionMaxX
            whole.attrs = []
            whole.containerWidth = 0
            whole.containerHeight = contentSize.height

            for (let item = 0; item < numberOfItems; item++) {
                let indexPath = new YXIndexPath(section, item)
                let itemSize = this.itemSize instanceof Function ? this.itemSize(indexPath, this, collectionView) : this.itemSize

                let attr = whole.layout_horizontal_item(indexPath, itemSize)
                if (attr == null) {
                    // 返回 null 表示摆不下了，需要换列排列
                    whole.offset = whole.offset + whole.containerWidth + horizontalSpacing
                    whole.containerWidth = 0
                    whole.attrs = []
                    attr = whole.layout_horizontal_item(indexPath, itemSize)
                }
                if (attr) {
                    allAttributes.push(attr)
                }
                sectionMaxX = Math.max(sectionMaxX, whole.offset + whole.containerWidth)
            }
            sectionMaxX += sectionInset.right
        }

        this.attributes = allAttributes
        contentSize.width = Math.max(contentSize.width, sectionMaxX)
        this.contentSize = contentSize
    }

    private _vertical(collectionView: YXCollectionView) {
        collectionView.scrollView.horizontal = false
        collectionView.scrollView.vertical = true
        let contentSize = collectionView.node.getComponent(UITransform).contentSize.clone()
        let allAttributes: YXLayoutAttributes[] = []

        let numberOfSections = collectionView.numberOfSections instanceof Function ? collectionView.numberOfSections(collectionView) : collectionView.numberOfSections

        let sectionMaxY = 0
        for (let section = 0; section < numberOfSections; section++) {
            let numberOfItems = collectionView.numberOfItems instanceof Function ? collectionView.numberOfItems(section, collectionView) : collectionView.numberOfItems
            let verticalSpacing = this.verticalSpacing instanceof Function ? this.verticalSpacing(section, this, collectionView) : this.verticalSpacing
            let horizontalSpacing = this.horizontalSpacing instanceof Function ? this.horizontalSpacing(section, this, collectionView) : this.horizontalSpacing
            let sectionInset = this.sectionInset instanceof Function ? this.sectionInset(section, this, collectionView) : this.sectionInset

            sectionMaxY += sectionInset.top

            let whole = new _yx_flow_layout_whole()
            whole.verticalSpacing = verticalSpacing
            whole.horizontalSpacing = horizontalSpacing
            whole.sectionInset = sectionInset
            whole.offset = sectionMaxY
            whole.attrs = []
            whole.containerWidth = contentSize.width
            whole.containerHeight = 0

            for (let item = 0; item < numberOfItems; item++) {
                let indexPath = new YXIndexPath(section, item)
                let itemSize = this.itemSize instanceof Function ? this.itemSize(indexPath, this, collectionView) : this.itemSize

                let attr = whole.layout_vertical_item(indexPath, itemSize)
                if (attr == null) {
                    // 返回 null 表示摆不下了，需要换行排列
                    whole.offset = whole.offset + whole.containerHeight + verticalSpacing
                    whole.containerHeight = 0
                    whole.attrs = []
                    attr = whole.layout_vertical_item(indexPath, itemSize)
                }
                if (attr) {
                    allAttributes.push(attr)
                }
                sectionMaxY = Math.max(sectionMaxY, whole.offset + whole.containerHeight)
            }
            sectionMaxY += sectionInset.bottom
        }

        this.attributes = allAttributes
        contentSize.height = Math.max(contentSize.height, sectionMaxY)
        this.contentSize = contentSize
    }
}

/**
 * 这个类用来实现紧凑流式布局  
 * 目前版本使用的策略: 贪心算法，遍历取当前整体内最优的位置  
 * GPT: 水平/垂直扫描 和 贪心算法 是解决这种矩形排列问题的简单有效方法。它们通过从当前矩形的位置出发，逐步扫描周围空白区域来寻找新矩形的放置位置。虽然这种方法比较直观且易于实现，但在复杂情况下可能不是最优解。对于更高效的排列策略，可能需要结合更多的启发式方法或优化算法。  
 * @todo 优化排列算法 (如果能想到更好的排列算法的话)  
 */
class _yx_flow_layout_whole {

    /**
     * 当前这块内容的起始位置
     */
    offset: number

    /**
     * flow layout 部分参数
     */
    verticalSpacing: number
    horizontalSpacing: number
    sectionInset: YXEdgeInsets

    /**
     * 容器相关的参数
     */
    containerWidth: number
    containerHeight: number

    /**
     * 这块内容区域目前已经摆放的节点
     */
    attrs: YXLayoutAttributes[] = []

    /**
     * 检查传进来的位置是否跟当前这块内容的其他节点重叠
     */
    intersects(rect: math.Rect) {
        for (let index = 0; index < this.attrs.length; index++) {
            const element = this.attrs[index];
            math.Rect.intersection(_rectOut, element.frame, rect)
            if (_rectOut.width * _rectOut.height > 0) {
                return true
            }
        }
        return false
    }

    /**
     * 垂直方向列表的节点的排列规则
     */
    layout_vertical_item(indexPath: YXIndexPath, itemSize: math.Size) {
        if (this.attrs.length <= 0) {
            let attributes = new YXLayoutAttributes()
            attributes.indexPath = indexPath
            attributes.frame = new math.Rect(this.sectionInset.left, this.offset, itemSize.width, itemSize.height)
            this.attrs.push(attributes)
            this.containerHeight = Math.max(this.containerHeight, attributes.frame.height)
            return attributes
        }

        const frame = new math.Rect()
        frame.size = itemSize

        // 检查所有节点的右边
        for (let index = 0; index < this.attrs.length; index++) {
            const element = this.attrs[index];
            frame.x = element.frame.xMax + this.horizontalSpacing
            frame.y = element.frame.y
            if (frame.xMax <= (this.containerWidth - this.sectionInset.right)) {
                if (this.intersects(frame) == false) {
                    let attributes = new YXLayoutAttributes()
                    attributes.indexPath = indexPath
                    attributes.frame = frame
                    this.attrs.push(attributes)
                    this.containerHeight = Math.max(this.containerHeight, attributes.frame.height)
                    return attributes
                }
            }
        }

        // 检测所有节点的下边
        for (let index = 0; index < this.attrs.length; index++) {
            const element = this.attrs[index];
            frame.x = element.frame.x
            frame.y = element.frame.yMax + this.verticalSpacing
            if (frame.yMax <= (this.offset + this.containerHeight)) {
                if (this.intersects(frame) == false) {
                    let attributes = new YXLayoutAttributes()
                    attributes.indexPath = indexPath
                    attributes.frame = frame
                    this.attrs.push(attributes)
                    this.containerHeight = Math.max(this.containerHeight, attributes.frame.height)
                    return attributes
                }
            }
        }

        // 走到这里表示这块内容区域已经摆不下了
        return null
    }

    /**
     * 水平方向列表的节点的排列规则
     */
    layout_horizontal_item(indexPath: YXIndexPath, itemSize: math.Size) {
        if (this.attrs.length <= 0) {
            let attributes = new YXLayoutAttributes()
            attributes.indexPath = indexPath
            attributes.frame = new math.Rect(this.offset, this.sectionInset.top, itemSize.width, itemSize.height)
            this.attrs.push(attributes)
            this.containerWidth = Math.max(this.containerWidth, attributes.frame.width)
            return attributes
        }

        const frame = new math.Rect()
        frame.size = itemSize

        // 检测所有节点的下边
        for (let index = 0; index < this.attrs.length; index++) {
            const element = this.attrs[index];
            frame.x = element.frame.x
            frame.y = element.frame.yMax + this.verticalSpacing
            if (frame.yMax <= (this.containerHeight - this.sectionInset.bottom)) {
                if (this.intersects(frame) == false) {
                    let attributes = new YXLayoutAttributes()
                    attributes.indexPath = indexPath
                    attributes.frame = frame
                    this.attrs.push(attributes)
                    this.containerWidth = Math.max(this.containerWidth, attributes.frame.width)
                    return attributes
                }
            }
        }

        // 检查所有节点的右边
        for (let index = 0; index < this.attrs.length; index++) {
            const element = this.attrs[index];
            frame.x = element.frame.xMax + this.horizontalSpacing
            frame.y = element.frame.y
            if (frame.xMax <= (this.offset + this.containerWidth)) {
                if (this.intersects(frame) == false) {
                    let attributes = new YXLayoutAttributes()
                    attributes.indexPath = indexPath
                    attributes.frame = frame
                    this.attrs.push(attributes)
                    this.containerWidth = Math.max(this.containerWidth, attributes.frame.width)
                    return attributes
                }
            }
        }

        // 走到这里表示这块内容区域已经摆不下了
        return null
    }
}