import OpenGLES
import UIKit

// TODO: Find a way to warn people if they set this after the context has been created
var imageProcessingShareGroup: EAGLSharegroup?

var dispatchQueKeyValueCounter = 81

public class OpenGLContext: SerialDispatch {
    lazy var framebufferCache: FramebufferCache = {
        FramebufferCache(context: self)
    }()

    var shaderCache: [String: ShaderProgram] = [:]
    public let standardImageVBO: GLuint
    var textureVBOs: [Rotation: GLuint] = [:]

    let context: EAGLContext

    lazy var passthroughShader: ShaderProgram = {
        crashOnShaderCompileFailure("OpenGLContext") { try self.programForVertexShader(OneInputVertexShader, fragmentShader: PassthroughFragmentShader) }
    }()

    lazy var coreVideoTextureCache: CVOpenGLESTextureCache = {
        var newTextureCache: CVOpenGLESTextureCache?
        let err = CVOpenGLESTextureCacheCreate(kCFAllocatorDefault, nil, self.context, nil, &newTextureCache)
        return newTextureCache!
    }()

    public let serialDispatchQueue: DispatchQueue = DispatchQueue(label: "com.sunsetlakesoftware.GPUImage.processingQueue", qos: .userInitiated)
    public let dispatchQueueKey = DispatchSpecificKey<Int>()
    public let dispatchQueueKeyValue: Int

    // MARK: -

    // MARK: Initialization and teardown

    init() {
        dispatchQueueKeyValue = dispatchQueKeyValueCounter
        serialDispatchQueue.setSpecific(key: dispatchQueueKey, value: dispatchQueueKeyValue)
        dispatchQueKeyValueCounter += 1

        let generatedContext: EAGLContext?
        if let shareGroup = imageProcessingShareGroup {
            generatedContext = EAGLContext(api: .openGLES2, sharegroup: shareGroup)
        } else {
            generatedContext = EAGLContext(api: .openGLES2)
        }

        guard let concreteGeneratedContext = generatedContext else {
            fatalError("Unable to create an OpenGL ES 2.0 context. The GPUImage framework requires OpenGL ES 2.0 support to work.")
        }

        context = concreteGeneratedContext
        EAGLContext.setCurrent(concreteGeneratedContext)

        standardImageVBO = generateVBO(for: standardImageVertices)
        generateTextureVBOs()

        glDisable(GLenum(GL_DEPTH_TEST))
        glEnable(GLenum(GL_TEXTURE_2D))
    }

    // MARK: -

    // MARK: Rendering

    public func makeCurrentContext() {
        if EAGLContext.current() != context {
            EAGLContext.setCurrent(context)
        }
    }

    func presentBufferForDisplay() {
        context.presentRenderbuffer(Int(GL_RENDERBUFFER))
    }

    // MARK: -

    // MARK: Device capabilities

    func supportsTextureCaches() -> Bool {
        #if targetEnvironment(simulator)
            return false // Simulator glitches out on use of texture caches
        #else
            return true // Every iOS version and device that can run Swift can handle texture caches
        #endif
    }

    public var maximumTextureSizeForThisDevice: GLint { return _maximumTextureSizeForThisDevice }
    private lazy var _maximumTextureSizeForThisDevice: GLint = {
        self.openGLDeviceSettingForOption(GL_MAX_TEXTURE_SIZE)
    }()

    public var maximumTextureUnitsForThisDevice: GLint { return _maximumTextureUnitsForThisDevice }
    private lazy var _maximumTextureUnitsForThisDevice: GLint = {
        self.openGLDeviceSettingForOption(GL_MAX_TEXTURE_IMAGE_UNITS)
    }()

    public var maximumVaryingVectorsForThisDevice: GLint { return _maximumVaryingVectorsForThisDevice }
    private lazy var _maximumVaryingVectorsForThisDevice: GLint = {
        self.openGLDeviceSettingForOption(GL_MAX_VARYING_VECTORS)
    }()

    lazy var extensionString: String = {
        self.runOperationSynchronously {
            self.makeCurrentContext()
            return String(cString: unsafeBitCast(glGetString(GLenum(GL_EXTENSIONS)), to: UnsafePointer<CChar>.self))
        }
    }()
}
