//
//  PQGPUTools.swift
//  GPUImage
//
//  Created by ak on 2020/8/13.
//  Copyright © 2020 Sunset Lake Software LLC. All rights reserved.
//

import Foundation
import BFCommonKit

#if GLES
    import OpenGLES
#else
    import OpenGL.GL3
#endif

/// 旋转角度模式
enum NXGPUImageRotationMode: Int {
    case noRotationTextureCoordinates
    case rotateLeftTextureCoordinates
    case rotateRightTextureCoordinates
    case verticalFlipTextureCoordinates
    case horizontalFlipTextureCoordinates
    case rotateRightVerticalFlipTextureCoordinates
    case rotateRightHorizontalFlipTextureCoordinates
    case rotate180TextureCoordinates
    case rotate270TextureCoordinates
}

open class PQGPUImageTools: NSObject {
    class func NXGenerateVBO(for vertices: [GLfloat]) -> GLuint {
        // 1. 创建VBO得到vboId
        var newBuffer: GLuint = 0
        glGenBuffers(1, &newBuffer)
        // 2. 根据id绑定VBO
        glBindBuffer(GLenum(GL_ARRAY_BUFFER), newBuffer)
        // 3. 分配VBO需要的缓存大小
        glBufferData(GLenum(GL_ARRAY_BUFFER), MemoryLayout<GLfloat>.size * vertices.count, vertices, GLenum(GL_STATIC_DRAW))
        // 4. 解绑VBO
        glBindBuffer(GLenum(GL_ARRAY_BUFFER), 0)
        return newBuffer
    }

    // 删除 VBO
    class func deleteVBO(_ vbo: GLuint) {
        var deletedVBO = vbo
        glDeleteBuffers(1, &deletedVBO)
    }

    // 图片转纹理集数据 XXXXX 使用方法后 返回的 imageTexture 要清空纹理,注意不清空显存会暴增     glDeleteTextures(1,&imageTexture)
    class func setupTexture(image: CGImage) -> GLuint {
        BFLog(2, message: "图片转纹理集数据 =====width :\(image.width) height: \(image.height)")

        let widthOfImage = GLint(image.width)
        let heightOfImage = GLint(image.height)

        let smoothlyScaleOutput: Bool = true
//        let orientation: ImageOrientation = .portrait
        var textureID: GLuint = 0
        autoreleasepool {
            // If passed an empty image reference, CGContextDrawImage will fail in future versions of the SDK.
            //              guard((widthOfImage > 0) && (heightOfImage > 0)) else { throw PictureInputError.zeroSizedImageError }

            var widthToUseForTexture = widthOfImage
            var heightToUseForTexture = heightOfImage
            var shouldRedrawUsingCoreGraphics = false

            // 1 For now, deal with images larger than the maximum texture size by resizing to be within that limit
            let scaledImageSizeToFitOnGPU = GLSize(sharedImageProcessingContext.sizeThatFitsWithinATextureForSize(Size(width: Float(widthOfImage), height: Float(heightOfImage))))
            if scaledImageSizeToFitOnGPU.width != widthOfImage, scaledImageSizeToFitOnGPU.height != heightOfImage {
                widthToUseForTexture = scaledImageSizeToFitOnGPU.width
                heightToUseForTexture = scaledImageSizeToFitOnGPU.height
                shouldRedrawUsingCoreGraphics = true
            }

            if smoothlyScaleOutput {
                // In order to use mipmaps, you need to provide power-of-two textures, so convert to the next largest power of two and stretch to fill
                let powerClosestToWidth = ceil(log2(Float(widthToUseForTexture)))
                let powerClosestToHeight = ceil(log2(Float(heightToUseForTexture)))

                widthToUseForTexture = GLint(round(pow(2.0, powerClosestToWidth)))
                heightToUseForTexture = GLint(round(pow(2.0, powerClosestToHeight)))
                shouldRedrawUsingCoreGraphics = true
            }

            var imageData: UnsafeMutablePointer<GLubyte>!
            var dataFromImageDataProvider: CFData!
            var format = GL_BGRA
            // 2 image 格式
            if !shouldRedrawUsingCoreGraphics {
                /* Check that the memory layout is compatible with GL, as we cannot use glPixelStore to
                 * tell GL about the memory layout with GLES.
                 */
                if (image.bytesPerRow != image.width * 4) || (image.bitsPerPixel != 32) || (image.bitsPerComponent != 8) {
                    shouldRedrawUsingCoreGraphics = true
                } else {
                    /* Check that the bitmap pixel format is compatible with GL */
                    let bitmapInfo = image.bitmapInfo
                    if bitmapInfo.contains(.floatComponents) {
                        /* We don't support float components for use directly in GL */
                        shouldRedrawUsingCoreGraphics = true
                    } else {
                        let alphaInfo = CGImageAlphaInfo(rawValue: bitmapInfo.rawValue & CGBitmapInfo.alphaInfoMask.rawValue)
                        if bitmapInfo.contains(.byteOrder32Little) {
                            /* Little endian, for alpha-first we can use this bitmap directly in GL */
                            if alphaInfo != CGImageAlphaInfo.premultipliedFirst, alphaInfo != CGImageAlphaInfo.first, alphaInfo != CGImageAlphaInfo.noneSkipFirst {
                                shouldRedrawUsingCoreGraphics = true
                            }
                        } else if bitmapInfo.contains(CGBitmapInfo()) || bitmapInfo.contains(.byteOrder32Big) {
                            /* Big endian, for alpha-last we can use this bitmap directly in GL */
                            if alphaInfo != CGImageAlphaInfo.premultipliedLast, alphaInfo != CGImageAlphaInfo.last, alphaInfo != CGImageAlphaInfo.noneSkipLast {
                                shouldRedrawUsingCoreGraphics = true
                            } else {
                                /* Can access directly using GL_RGBA pixel format */
                                format = GL_RGBA
                            }
                        }
                    }
                }
            }
            BFLog(2, message: "图片转纹理集数据2222 =====")

            //        sharedImageProcessingContext.runOperationSynchronously{
            //    CFAbsoluteTime elapsedTime, startTime = CFAbsoluteTimeGetCurrent();

            if shouldRedrawUsingCoreGraphics {
                // For resized or incompatible image: redraw
                // 分配内存

                imageData = UnsafeMutablePointer<GLubyte>.allocate(capacity: Int(widthToUseForTexture * heightToUseForTexture) * 4)

                let genericRGBColorspace = CGColorSpaceCreateDeviceRGB()

                let imageContext = CGContext(data: imageData, width: Int(widthToUseForTexture), height: Int(heightToUseForTexture), bitsPerComponent: 8, bytesPerRow: Int(widthToUseForTexture) * 4, space: genericRGBColorspace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue)
                // CGContextSetBlendMode(imageContext, kCGBlendModeCopy); // From Technical Q&A QA1708: http://developer.apple.com/library/ios/#qa/qa1708/_index.html
                imageContext?.draw(image, in: CGRect(x: 0.0, y: 0.0, width: CGFloat(widthToUseForTexture), height: CGFloat(heightToUseForTexture)))

            } else {
                // Access the raw image bytes directly
                let data = image.dataProvider?.data
                dataFromImageDataProvider = data
                imageData = UnsafeMutablePointer<GLubyte>(mutating: CFDataGetBytePtr(dataFromImageDataProvider))
            }
            BFLog(2, message: "图片转纹理集数据333333 =====")

            glEnable(GLenum(GL_TEXTURE_2D))
            BFLog(2, message: "图片转纹理集数据44444 =====")

            /**
             *  GL_TEXTURE_2D表示操作2D纹理
             *  创建纹理对象，
             *  绑定纹理对象，
             */

            glGenTextures(1, &textureID)
            BFLog(2, message: "图片转纹理集数据5555 =====\(textureID)")

            glBindTexture(GLenum(GL_TEXTURE_2D), textureID)
            BFLog(2, message: "图片转纹理集数据6666 =====\(textureID)")

            /**
             *  纹理过滤函数
             *  图象从纹理图象空间映射到帧缓冲图象空间(映射需要重新构造纹理图像,这样就会造成应用到多边形上的图像失真),
             *  这时就可用glTexParmeteri()函数来确定如何把纹理象素映射成像素.
             *  如何把图像从纹理图像空间映射到帧缓冲图像空间（即如何把纹理像素映射成像素）
             */
            glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_S), GL_CLAMP_TO_EDGE) // S方向上的贴图模式
            glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_WRAP_T), GL_CLAMP_TO_EDGE) // T方向上的贴图模式
            // 线性过滤：使用距离当前渲染像素中心最近的4个纹理像素加权平均值
            glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MIN_FILTER), GL_LINEAR)
            glTexParameteri(GLenum(GL_TEXTURE_2D), GLenum(GL_TEXTURE_MAG_FILTER), GL_LINEAR)

            /**
             *  将图像数据传递给到GL_TEXTURE_2D中, 因其于textureID纹理对象已经绑定，所以即传递给了textureID纹理对象中。
             *  glTexImage2d会将图像数据从CPU内存通过PCIE上传到GPU内存。
             *  不使用PBO时它是一个阻塞CPU的函数，数据量大会卡。
             */
            /* 载入纹理
             * 参数1：纹理模式，GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
             * 参数2：加载的层次，一般设置为0
             * 参数3：纹理的颜色值GL_RGBA
             * 参数4：宽
             * 参数5：高
             * 参数6：border，边界宽度
             * 参数7：format
             * 参数8：type
             * 参数9：纹理数据
             */
            BFLog(2, message: "载入纹理 =====")
            BFLog(2, message: "GL_TEXTURE_2D =====\(GL_TEXTURE_2D)")
            BFLog(2, message: "GL_RGBA =====\(GL_RGBA)")
            BFLog(2, message: "widthOfImage =====\(widthOfImage)")
            BFLog(2, message: "heightOfImage =====\(heightOfImage)")
            BFLog(2, message: "GL_UNSIGNED_BYTE =====\(GL_UNSIGNED_BYTE)")
            BFLog(2, message: "imageData =====\(String(describing: imageData))")
            BFLog(2, message: "GLenum(GL_TEXTURE_2D) =====\(GLenum(GL_TEXTURE_2D))")
            BFLog(2, message: "GLenum(format) =====\(GLenum(format))")
            BFLog(2, message: "GLenum(GL_UNSIGNED_BYTE) =====\(GLenum(GL_UNSIGNED_BYTE))")

            glTexImage2D(GLenum(GL_TEXTURE_2D), 0, GL_RGBA, widthToUseForTexture, heightToUseForTexture, 0, GLenum(format), GLenum(GL_UNSIGNED_BYTE), imageData)

            // 结束后要做清理
            BFLog(2, message: "结束后要做清理 =====")
            BFLog(2, message: "GLenum(GL_TEXTURE_2D) =====\(GLenum(GL_TEXTURE_2D))")

            glBindTexture(GLenum(GL_TEXTURE_2D), 0) // 解绑

            BFLog(2, message: "结束后要做清理1111111 =====")
            imageData.deallocate()

            BFLog(2, message: "textureID =====\(textureID)")
        }

        return textureID
    }

    /// 相素坐标 TO 顶点坐标
    /// - Parameter viewSize: 画布大小 也是 renderview 的大小
    /// - Parameter _bounds: 显示纹理的大小和位置
    class func computeVertices(viewSize: CGSize, _bounds: CGRect) -> [GLfloat] {
        // 左上x， 左上y， 右上x， 右上y， 左下x， 左下y， 右下x， 右下y
        let originalVertex: [CGFloat] = [
            _bounds.origin.x,
            _bounds.origin.y,
            _bounds.origin.x + _bounds.size.width,
            _bounds.origin.y,
            _bounds.origin.x,
            _bounds.origin.y + _bounds.size.height,
            _bounds.origin.x + _bounds.size.width,
            _bounds.origin.y + _bounds.size.height,
        ]

        // 贴纸中心
        let cx: CGFloat = _bounds.origin.x + _bounds.size.width / 2.0
        let cy: CGFloat = _bounds.origin.y + _bounds.size.height / 2.0

        // 贴纸坐标移到贴纸中心->transform变换->贴纸坐标还原->贴纸坐标归一化到[-1,1]
        let tmpTransform: CGAffineTransform = CGAffineTransform.identity

        var _vertices: [GLfloat] = Array(repeating: GLfloat(), count: 8)

        for i in [0, 2, 4, 6] {
            _vertices[i] = GLfloat(((tmpTransform.tx + (originalVertex[i] - cx) * tmpTransform.a + (originalVertex[i + 1] - cy) * tmpTransform.c) + cx) / viewSize.width * 2 - 1)
            _vertices[i + 1] = GLfloat(((tmpTransform.ty + (originalVertex[i] - cx) * tmpTransform.b + (originalVertex[i + 1] - cy) * tmpTransform.d) + cy) / viewSize.height * 2 - 1)
        }

        return _vertices
    }

    // 裁剪纹理
    class func cropBuffer(cropSizeInPixels: Size?, locationOfCropInPixels: Position?, inputSize: GLSize!) -> (Size, Position) {
        let finalCropSize: GLSize
        let normalizedOffsetFromOrigin: Position

        if let cropSize = cropSizeInPixels, let locationOfCrop = locationOfCropInPixels {
            let glCropSize = GLSize(cropSize)
            finalCropSize = GLSize(width: min(inputSize.width, glCropSize.width), height: min(inputSize.height, glCropSize.height))
            normalizedOffsetFromOrigin = Position(locationOfCrop.x / Float(inputSize.width), locationOfCrop.y / Float(inputSize.height))
        } else if let cropSize = cropSizeInPixels {
            let glCropSize = GLSize(cropSize)
            finalCropSize = GLSize(width: min(inputSize.width, glCropSize.width), height: min(inputSize.height, glCropSize.height))
            normalizedOffsetFromOrigin = Position(Float(inputSize.width / 2 - finalCropSize.width / 2) / Float(inputSize.width), Float(inputSize.height / 2 - finalCropSize.height / 2) / Float(inputSize.height))
        } else {
            finalCropSize = inputSize
            normalizedOffsetFromOrigin = Position.zero
        }
        let normalizedCropSize = Size(width: Float(finalCropSize.width) / Float(inputSize.width), height: Float(finalCropSize.height) / Float(inputSize.height))

        return (normalizedCropSize, normalizedOffsetFromOrigin)
    }

    /// 旋转纹理坐标
    /// - Parameter model: 旋转角度
    class func textureCoordinatesForRotation(model: NXGPUImageRotationMode, oldTextureCoordinates: [GLfloat]) -> [GLfloat] {
        switch model {
        case .noRotationTextureCoordinates:
            return oldTextureCoordinates
        case .rotateLeftTextureCoordinates:
            return [oldTextureCoordinates[2], oldTextureCoordinates[3], // 2
                    oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[0], oldTextureCoordinates[1], // 1
                    oldTextureCoordinates[4], oldTextureCoordinates[5]] // 3
        case .rotateRightTextureCoordinates:
            return [oldTextureCoordinates[4], oldTextureCoordinates[5], // 3
                    oldTextureCoordinates[0], oldTextureCoordinates[1], // 1
                    oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[2], oldTextureCoordinates[3]] // 2

        case .rotate180TextureCoordinates:
            return [oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[4], oldTextureCoordinates[5], // 3
                    oldTextureCoordinates[2], oldTextureCoordinates[3], // 2
                    oldTextureCoordinates[0], oldTextureCoordinates[1]] // 1

        case .rotate270TextureCoordinates:
            return [oldTextureCoordinates[4], oldTextureCoordinates[5], // 3
                    oldTextureCoordinates[0], oldTextureCoordinates[1], // 1
                    oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[2], oldTextureCoordinates[3]] // 2

        case .verticalFlipTextureCoordinates:
            return [oldTextureCoordinates[4], oldTextureCoordinates[5], // 3
                    oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[0], oldTextureCoordinates[1], // 1
                    oldTextureCoordinates[2], oldTextureCoordinates[3]] // 2

        case .horizontalFlipTextureCoordinates:
            return [oldTextureCoordinates[2], oldTextureCoordinates[3], // 2
                    oldTextureCoordinates[0], oldTextureCoordinates[1], // 1
                    oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[4], oldTextureCoordinates[5]] // 3
        case .rotateRightVerticalFlipTextureCoordinates:
            return [oldTextureCoordinates[0], oldTextureCoordinates[1], // 1
                    oldTextureCoordinates[4], oldTextureCoordinates[5], // 3
                    oldTextureCoordinates[2], oldTextureCoordinates[3], // 2
                    oldTextureCoordinates[6], oldTextureCoordinates[7]] // 4
        case .rotateRightHorizontalFlipTextureCoordinates:
            return [oldTextureCoordinates[6], oldTextureCoordinates[7], // 4
                    oldTextureCoordinates[2], oldTextureCoordinates[3], // 2
                    oldTextureCoordinates[4], oldTextureCoordinates[5], // 3
                    oldTextureCoordinates[0], oldTextureCoordinates[1]] // 1
//        default:
//            return oldTextureCoordinates
        }
    }

    // 取纹理坐标
    class func getTextureCoordinates(sticker: PQEditVisionTrackMaterialsModel, textureSize: CGSize, rotationMode: NXGPUImageRotationMode = .noRotationTextureCoordinates, cannvasSize: GLSize = GLSize(width: 1080, height: 1080)) -> [GLfloat] {
        var textureCoordinates: [GLfloat] = [
            0.0, 0.0, // 1 bottom left
            1.0, 0.0, // 2 bottom right
            0.0, 1.0, // 3 top left
            1.0, 1.0, // 4 top right
        ]
//        case aspectFitStr = "complete" // 完整显示(有黑边)
//        case aspectFillStr = "full" // 铺满
        // 1,complete完整显示 2，素材比例和画布比例一致 都不进行裁剪
        if sticker.canvasFillType == stickerContentMode.aspectFitStr.rawValue || textureSize.width / textureSize.height == CGFloat(CGFloat(cannvasSize.width) / CGFloat(cannvasSize.height)) {
            textureCoordinates = textureCoordinatesForRotation(model: rotationMode, oldTextureCoordinates: textureCoordinates)
        } else {
            // 是否为竖屏
            let isTexturePortrait: Bool = Int(textureSize.height) >= Int(textureSize.width)
            let isCannvasPortrait: Bool = Int(cannvasSize.height) > Int(cannvasSize.width)
            // 裁剪大小
            // 1裁左右
            var cropSize: Size = Size(width: Float(textureSize.height) * Float(Float(cannvasSize.width) / Float(cannvasSize.height)), height: Float(textureSize.height))
            // 2裁上下
            if !isCannvasPortrait && isTexturePortrait || (isTexturePortrait && cannvasSize.height == cannvasSize.width) || !isCannvasPortrait && textureSize.width == textureSize.height {
                cropSize = Size(width: Float(textureSize.width), height: Float(textureSize.width) * Float(Float(cannvasSize.height) / Float(cannvasSize.width)))
            }

            // 裁剪位置
            let cropPosition: Position = Position(Float((textureSize.width - CGFloat(cropSize.width)) / 2), Float((textureSize.height - CGFloat(cropSize.height)) / 2))

            let (cropSizee, postion) = cropBuffer(cropSizeInPixels: cropSize, locationOfCropInPixels: cropPosition, inputSize: GLSize(width: GLint(textureSize.width), height: GLint(textureSize.height)))
//            isTexturePortrait = false
            if isTexturePortrait, !isCannvasPortrait {
                textureCoordinates = [
                    0.0, postion.y,
                    1.0, postion.y,
                    0.0, postion.y + cropSizee.height,
                    1.0, postion.y + cropSizee.height,
                ]
            } else {
                textureCoordinates = [
                    postion.x, postion.y,
                    postion.x + cropSizee.width, postion.y,
                    postion.x, 1.0,
                    postion.x + cropSizee.width, 1.0,
                ]
            }
            //[-0.0005214146, 0.0, 0.9994786, 0.0, -0.0005214146, 1.0, 0.9994786, 1.0]
            for i in 0 ..< textureCoordinates.count{
                if(textureCoordinates[i] < 0){
                    textureCoordinates[i] = 0
                }
            }
             

            textureCoordinates = textureCoordinatesForRotation(model: rotationMode, oldTextureCoordinates: textureCoordinates)
        }

        return textureCoordinates
    }

    // 取顶点坐标
    class func getVerticesPoint(sticker: PQEditVisionTrackMaterialsModel, textureSize: CGSize, rotationMode _: NXGPUImageRotationMode = .noRotationTextureCoordinates, cannvasSize: GLSize = GLSize(width: 1080, height: 1080)) -> [GLfloat] {
        var vertices: [GLfloat] = [
            -1.0, -1.0,
            1.0, -1.0,
            -1.0, 1.0,
            1.0, 1.0,
        ]

        if sticker.canvasFillType == stickerContentMode.aspectFitStr.rawValue {
            var maxWidth: CGFloat = CGFloat(cannvasSize.width)
            var maxHeight: CGFloat = CGFloat(cannvasSize.height)
            if textureSize.width > textureSize.height { // 按照宽 来缩放
                var imageScale: CGFloat = CGFloat(cannvasSize.width) / textureSize.width

                maxHeight = textureSize.height * imageScale

                if maxHeight > CGFloat(cannvasSize.height) {
                    imageScale = CGFloat(cannvasSize.height) / maxHeight

                    maxWidth = maxWidth * imageScale
                    maxHeight = maxHeight * imageScale
                }

            } else if textureSize.width < textureSize.height { // 按照高来缩放
                var imageScale: CGFloat = CGFloat(cannvasSize.height) / textureSize.height

                maxWidth = textureSize.width * imageScale
                if maxWidth > CGFloat(cannvasSize.width) {
                    imageScale = CGFloat(cannvasSize.width) / maxWidth

                    maxWidth = maxWidth * imageScale
                    maxHeight = maxHeight * imageScale
                }

            } else if textureSize.width == textureSize.height { // 原素材为正方形都不缩放
                if maxWidth >= maxHeight {
                    maxWidth = maxHeight
                } else {
                    maxHeight = maxWidth
                }
            }

            let bounds = CGRect(x: (CGFloat(cannvasSize.width) - maxWidth) / 2, y: (CGFloat(cannvasSize.height) - maxHeight) / 2, width: maxWidth, height: maxHeight)

            let cgsizeCanvars = CGSize(width: CGFloat(cannvasSize.width), height: CGFloat(cannvasSize.height))
            vertices = PQGPUImageTools.computeVertices(viewSize: cgsizeCanvars, _bounds: bounds)
        }

        return vertices
    }
}
