//=============================================================================================================================
//
// Copyright (c) 2015-2022 VisionStar Information Technology (Shanghai) Co., Ltd. All Rights Reserved.
// EasyAR is the registered trademark or trademark of VisionStar Information Technology (Shanghai) Co., Ltd in China
// and other countries for the augmented reality technology developed by VisionStar Information Technology (Shanghai) Co., Ltd.
//
//=============================================================================================================================

import Foundation
import OpenGLES
import EasyARSwift

internal class HelloAR {
    private var scheduler: DelayedCallbackScheduler
    private var camera: CameraDevice? = nil
    private var tracker: ImageTracker? = nil
    private var throttler: InputFrameThrottler? = nil
    private var outputFrameFork: OutputFrameFork? = nil
    private var outputFrameBuffer: OutputFrameBuffer? = nil
    private var i2FAdapter: InputFrameToFeedbackFrameAdapter? = nil
    private var bgRenderer: Renderer? = nil
    private var boxRenderer: BoxRenderer? = nil
    private var previousInputFrameIndex: Int32 = -1

    public init() {
        scheduler = DelayedCallbackScheduler()
    }
    public func getScheduler() -> DelayedCallbackScheduler {
        return scheduler
    }

    private func loadFromImage(_ tracker: ImageTracker, _ path: String, _ name: String) -> Void {
        let imageTarget = ImageTarget.createFromImageFile(path, StorageType.Assets, name, "", "", 1.0)
        if (imageTarget != nil) {
            tracker.loadTarget(imageTarget!, scheduler, { (target, status) in
                print("load target (\(status)): \(target.name()) (\(target.runtimeID()))")
            })
        }
    }

    public func recreate_context() {
        bgRenderer = nil
        boxRenderer = nil
        previousInputFrameIndex = -1
        bgRenderer = Renderer()
        boxRenderer = BoxRenderer()
    }

    public func initialize() -> Bool {
        recreate_context()

        throttler = InputFrameThrottler.create()
        i2FAdapter = InputFrameToFeedbackFrameAdapter.create()
        outputFrameFork = OutputFrameFork.create(2)
        outputFrameBuffer = OutputFrameBuffer.create()

        camera = CameraDevice()
        if (!camera!.openWithPreferredType(CameraDeviceType.Back)) { return false }
        _ = camera!.setSize(Vec2I(1280, 960))
        _ = camera!.setFocusMode(CameraDeviceFocusMode.Continousauto)

        tracker = ImageTracker.create()
        loadFromImage(tracker!, "sightplus/argame00.jpg", "argame00")
        loadFromImage(tracker!, "sightplus/argame01.jpg", "argame01")
        loadFromImage(tracker!, "sightplus/argame02.jpg", "argame02")
        loadFromImage(tracker!, "sightplus/argame03.jpg", "argame03")
        loadFromImage(tracker!, "idback.jpg", "idback")
        loadFromImage(tracker!, "namecard.jpg", "namecard")

        camera!.inputFrameSource().connect(throttler!.input())
        throttler!.output().connect(i2FAdapter!.input())
        i2FAdapter!.output().connect(tracker!.feedbackFrameSink())
        tracker!.outputFrameSource().connect(outputFrameFork!.input())
        outputFrameFork!.output(0).connect(outputFrameBuffer!.input())

        outputFrameBuffer!.signalOutput().connect(throttler!.signalInput())
        outputFrameFork!.output(1).connect(i2FAdapter!.sideInput())

        //CameraDevice and rendering each require an additional buffer
        camera!.setBufferCapacity(throttler!.bufferRequirement() + i2FAdapter!.bufferRequirement() + outputFrameBuffer!.bufferRequirement() + tracker!.bufferRequirement() + 2);

        return start()
    }

    public func dispose() -> Void {
        tracker = nil
        boxRenderer = nil
        bgRenderer = nil
        camera = nil
        throttler = nil
        outputFrameFork = nil
        outputFrameBuffer = nil
        i2FAdapter = nil
    }

    public func start() -> Bool {
        var status = true
        if (camera != nil) {
            status = status && camera!.start()
        } else {
            status = false
        }
        if (tracker != nil) {
            status = status && tracker!.start()
        } else {
            status = false
        }
        return status
    }

    public func stop() -> Void {
        if (tracker != nil) {
            tracker!.stop()
        }
        if (camera != nil) {
            camera!.stop()
        }
    }

    public func render(_ width: Int32, _ height: Int32, _ screenRotation: Int32) -> Void {
        while (scheduler.runOne())
        {
        }

        glViewport(0, 0, width, height)
        glClearColor(0.0, 0.0, 0.0, 1.0)
        glClear(GLbitfield(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT))

        let oFrame = outputFrameBuffer!.peek()
        if (oFrame == nil) { return }
        let frame = oFrame!
        if (!frame.inputFrame().hasCameraParameters()) { return; }
        let cameraParameters = frame.inputFrame().cameraParameters()
        let viewport_aspect_ratio = Float(width) / Float(height)
        let projection = cameraParameters.projection(0.01, 1000, viewport_aspect_ratio, screenRotation, true, false)
        let imageProjection = cameraParameters.imageProjection(viewport_aspect_ratio, screenRotation, true, false)
        let image = frame.inputFrame().image()

        if (frame.inputFrame().index() != previousInputFrameIndex) {
            bgRenderer?.upload(image.format(), image.width(), image.height(), image.pixelWidth(), image.pixelHeight(), image.buffer().data()!)
            previousInputFrameIndex = frame.inputFrame().index()
        }
        bgRenderer!.render(imageProjection)
        for result in frame.results() {
            if (result == nil) { continue }
            let imageTrackerResult = result! as? ImageTrackerResult
            if (imageTrackerResult == nil) { continue }
            for targetInstance in imageTrackerResult!.targetInstances() {
                let status = targetInstance.status()
                let target = targetInstance.target()
                let imagetarget = target as? ImageTarget
                if (imagetarget == nil) { continue }
                let targetSize = Vec2F(imagetarget!.scale(), imagetarget!.scale() / imagetarget!.aspectRatio())
                boxRenderer!.render(projection, targetInstance.pose(), targetSize)
            }
        }
    }
}
