import CoreImage
import Foundation
import Metal

public enum StillCaptureErrors: Error {
    case InputError
    case RenderError
}

public class StillCaptureUtility {
    public static func png(texture: MTLTexture?, url: URL) throws {
        guard
            let texture = texture,
            let ciImage = CIImage(mtlTexture: texture, options: nil),
            let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
            let device = Adamantium.sharedDevice
        else { throw StillCaptureErrors.InputError }

        /// Metal's coordinate system has an origin at the top left
        /// CoreImage has an origin at the bottom left.
        /// Transform the coordinates otherwise the image will appear flipped
        // TODO: convert this to single CIFilter using Metal.
        var imageToFlip = ciImage
        imageToFlip = imageToFlip.transformed(by: CGAffineTransform(scaleX: 1, y: -1))
        imageToFlip = imageToFlip.transformed(by: CGAffineTransform(translationX: 0, y: CGFloat(texture.height)))
        imageToFlip = imageToFlip.applyingFilter("CISRGBToneCurveToLinear")
        let context = CIContext(mtlDevice: device)

        do {
            try context.writePNGRepresentation(
                of: imageToFlip,
                to: url,
                format: .BGRA8,
                colorSpace: colorSpace,
                options: [:]
            )
        } catch {
            throw StillCaptureErrors.RenderError
        }
    }

    public static func png(texture: MTLTexture?) throws -> URL {
        guard
            let texture = texture,
            let ciImage = CIImage(mtlTexture: texture, options: nil),
            let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
            let device = Adamantium.sharedDevice
        else { throw StillCaptureErrors.InputError }

        /// Metal's coordinate system has an origin at the top left
        /// CoreImage has an origin at the bottom left.
        /// Transform the coordinates otherwise the image will appear flipped
        // TODO: convert this to single CIFilter using Metal.
        var imageToFlip = ciImage
        imageToFlip = imageToFlip.transformed(by: CGAffineTransform(scaleX: 1, y: -1))
        imageToFlip = imageToFlip.transformed(by: CGAffineTransform(translationX: 0, y: CGFloat(texture.height)))
        imageToFlip = imageToFlip.applyingFilter("CISRGBToneCurveToLinear")

        let url = Adamantium.createRandomFileURLInDocuments("png") ?? FileManager.default.temporaryDirectory.appendingPathComponent("new_image.png")
        let context = CIContext(mtlDevice: device)

        do {
            if FileManager.default.fileExists(atPath: url.absoluteString) {
                try FileManager.default.removeItem(at: url)
            }
            try context.writePNGRepresentation(
                of: imageToFlip,
                to: url,
                format: .BGRA8,
                colorSpace: colorSpace,
                options: [:]
            )
            return url
        } catch {
            throw StillCaptureErrors.RenderError
        }
    }

    public static func ciImage(texture: MTLTexture?) throws -> CIImage? {
        guard let texture, let device = Adamantium.sharedDevice else { throw StillCaptureErrors.InputError }
        return try StillCaptureUtility.metalTextureToColorSpaceCorrectedCIImage(texture)
    }

    public static func cgImage(texture: MTLTexture?) throws -> CGImage? {
        guard let texture, let device = Adamantium.sharedDevice else { throw StillCaptureErrors.InputError }
        let ciImage = try StillCaptureUtility.metalTextureToColorSpaceCorrectedCIImage(texture)
        let context = CIContext(mtlDevice: device)
        return context.createCGImage(ciImage, from: ciImage.extent)
    }
}

private extension StillCaptureUtility {
    static func metalTextureToColorSpaceCorrectedCIImage(_ texture: MTLTexture?) throws -> CIImage {
        guard
            let texture = texture,
            let ciImage = CIImage(mtlTexture: texture, options: nil),
            let colorSpace = CGColorSpace(name: CGColorSpace.sRGB),
            let device = Adamantium.sharedDevice
        else { throw StillCaptureErrors.InputError }

        /// Metal's coordinate system has an origin at the top left
        /// CoreImage has an origin at the bottom left.
        /// Transform the coordinates otherwise the image will appear flipped
        // TODO: convert this to single CIFilter using Metal.
        var imageToFlip = ciImage
        imageToFlip = imageToFlip.transformed(by: CGAffineTransform(scaleX: 1, y: -1))
        imageToFlip = imageToFlip.transformed(by: CGAffineTransform(translationX: 0, y: CGFloat(texture.height)))
        imageToFlip = imageToFlip.applyingFilter("CISRGBToneCurveToLinear")

        return imageToFlip
    }
}
