//=============================================================================================================================
//
// 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 "helloar.h"

#import "BoxRenderer.h"
#import "BGRenderer.h"

#import <easyar/types.oc.h>
#import <easyar/buffer.oc.h>
#import <easyar/arkitcamera.oc.h>
#import <easyar/frame.oc.h>
#import <easyar/callbackscheduler.oc.h>
#import <easyar/dataflow.oc.h>
#import <easyar/cameraparameters.oc.h>
#import <easyar/image.oc.h>

#include <OpenGLES/ES3/gl.h>

easyar_DelayedCallbackScheduler * scheduler;
easyar_ARKitCameraDevice * camera;
BGRenderer * bgRenderer;
BoxRenderer * boxRenderer;
easyar_InputFrameToOutputFrameAdapter * i2OAdapter;
easyar_OutputFrameBuffer * outputFrameBuffer;
int previousInputFrameIndex = -1;

void createScheduler()
{
    scheduler = [easyar_DelayedCallbackScheduler create];
}
easyar_DelayedCallbackScheduler * getScheduler()
{
    return scheduler;
}

void recreate_context()
{
    bgRenderer = nil;
    boxRenderer = nil;
    previousInputFrameIndex = -1;
    bgRenderer = [BGRenderer create];
    boxRenderer = [BoxRenderer create];
}

void initialize()
{
    recreate_context();

    outputFrameBuffer = [easyar_OutputFrameBuffer create];
    i2OAdapter = [easyar_InputFrameToOutputFrameAdapter create];

    camera = [easyar_ARKitCameraDevice create];
    [camera setFocusMode:easyar_ARKitCameraDeviceFocusMode_Auto];

    [[camera inputFrameSource] connect:[i2OAdapter input]];
    [[i2OAdapter output] connect:[outputFrameBuffer input]];

    //CameraDevice and rendering each require an additional buffer
    [camera setBufferCapacity: [outputFrameBuffer bufferRequirement] + 2];
}

void finalize()
{
    boxRenderer = nil;
    bgRenderer = nil;
    camera = nil;
    outputFrameBuffer = nil;
    i2OAdapter = nil;
    scheduler = nil;
}

BOOL start()
{
    BOOL status = YES;
    if (camera != nil) {
        status &= [camera start];
    } else {
        status = NO;
    }
    return status;
}

void stop()
{
    if (camera != nil) {
        [camera stop];
    }
}

easyar_Matrix44F* gluInvertMatrix(easyar_Matrix44F * matrix)
{
    float m[16] = {};
    float invOut[16] = {};
    NSArray<NSNumber *> * data = matrix.data;
    int index = 0;
    for (NSNumber * num in  data) {
        m[index] = num.floatValue;
        index++;
    }

    float inv[16], det;
    int i;

    inv[0] = m[5]  * m[10] * m[15] -
    m[5]  * m[11] * m[14] -
    m[9]  * m[6]  * m[15] +
    m[9]  * m[7]  * m[14] +
    m[13] * m[6]  * m[11] -
    m[13] * m[7]  * m[10];

    inv[4] = -m[4]  * m[10] * m[15] +
    m[4]  * m[11] * m[14] +
    m[8]  * m[6]  * m[15] -
    m[8]  * m[7]  * m[14] -
    m[12] * m[6]  * m[11] +
    m[12] * m[7]  * m[10];

    inv[8] = m[4]  * m[9] * m[15] -
    m[4]  * m[11] * m[13] -
    m[8]  * m[5] * m[15] +
    m[8]  * m[7] * m[13] +
    m[12] * m[5] * m[11] -
    m[12] * m[7] * m[9];

    inv[12] = -m[4]  * m[9] * m[14] +
    m[4]  * m[10] * m[13] +
    m[8]  * m[5] * m[14] -
    m[8]  * m[6] * m[13] -
    m[12] * m[5] * m[10] +
    m[12] * m[6] * m[9];

    inv[1] = -m[1]  * m[10] * m[15] +
    m[1]  * m[11] * m[14] +
    m[9]  * m[2] * m[15] -
    m[9]  * m[3] * m[14] -
    m[13] * m[2] * m[11] +
    m[13] * m[3] * m[10];

    inv[5] = m[0]  * m[10] * m[15] -
    m[0]  * m[11] * m[14] -
    m[8]  * m[2] * m[15] +
    m[8]  * m[3] * m[14] +
    m[12] * m[2] * m[11] -
    m[12] * m[3] * m[10];

    inv[9] = -m[0]  * m[9] * m[15] +
    m[0]  * m[11] * m[13] +
    m[8]  * m[1] * m[15] -
    m[8]  * m[3] * m[13] -
    m[12] * m[1] * m[11] +
    m[12] * m[3] * m[9];

    inv[13] = m[0]  * m[9] * m[14] -
    m[0]  * m[10] * m[13] -
    m[8]  * m[1] * m[14] +
    m[8]  * m[2] * m[13] +
    m[12] * m[1] * m[10] -
    m[12] * m[2] * m[9];

    inv[2] = m[1]  * m[6] * m[15] -
    m[1]  * m[7] * m[14] -
    m[5]  * m[2] * m[15] +
    m[5]  * m[3] * m[14] +
    m[13] * m[2] * m[7] -
    m[13] * m[3] * m[6];

    inv[6] = -m[0]  * m[6] * m[15] +
    m[0]  * m[7] * m[14] +
    m[4]  * m[2] * m[15] -
    m[4]  * m[3] * m[14] -
    m[12] * m[2] * m[7] +
    m[12] * m[3] * m[6];

    inv[10] = m[0]  * m[5] * m[15] -
    m[0]  * m[7] * m[13] -
    m[4]  * m[1] * m[15] +
    m[4]  * m[3] * m[13] +
    m[12] * m[1] * m[7] -
    m[12] * m[3] * m[5];

    inv[14] = -m[0]  * m[5] * m[14] +
    m[0]  * m[6] * m[13] +
    m[4]  * m[1] * m[14] -
    m[4]  * m[2] * m[13] -
    m[12] * m[1] * m[6] +
    m[12] * m[2] * m[5];

    inv[3] = -m[1] * m[6] * m[11] +
    m[1] * m[7] * m[10] +
    m[5] * m[2] * m[11] -
    m[5] * m[3] * m[10] -
    m[9] * m[2] * m[7] +
    m[9] * m[3] * m[6];

    inv[7] = m[0] * m[6] * m[11] -
    m[0] * m[7] * m[10] -
    m[4] * m[2] * m[11] +
    m[4] * m[3] * m[10] +
    m[8] * m[2] * m[7] -
    m[8] * m[3] * m[6];

    inv[11] = -m[0] * m[5] * m[11] +
    m[0] * m[7] * m[9] +
    m[4] * m[1] * m[11] -
    m[4] * m[3] * m[9] -
    m[8] * m[1] * m[7] +
    m[8] * m[3] * m[5];

    inv[15] = m[0] * m[5] * m[10] -
    m[0] * m[6] * m[9] -
    m[4] * m[1] * m[10] +
    m[4] * m[2] * m[9] +
    m[8] * m[1] * m[6] -
    m[8] * m[2] * m[5];

    det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12];

    if (det == 0)
        return false;

    det = 1.0 / det;

    for (i = 0; i < 16; i++)
        invOut[i] = inv[i] * det;

    return [easyar_Matrix44F create:@[@(invOut[0]), @(invOut[1]),@(invOut[2]),@(invOut[3]),@(invOut[4]),@(invOut[5]),@(invOut[6]),@(invOut[7]),@(invOut[8]),@(invOut[9]),@(invOut[10]),@(invOut[11]),@(invOut[12]),@(invOut[13]),@(invOut[14]),@(invOut[15])]];
}

void render(int width, int height, int screenRotation)
{
    while ([scheduler runOne])
    {
    }
    
    glViewport(0, 0, width, height);
    glClearColor(0.f, 0.f, 0.f, 1.f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    easyar_OutputFrame * oFrame = [outputFrameBuffer peek];
    if (oFrame == nil){ return; }
    easyar_InputFrame * iFrame = [oFrame inputFrame];
    if (iFrame == nil) { return; }
    if (![iFrame hasCameraParameters]) { return; }
    easyar_CameraParameters * cameraParameters = [iFrame cameraParameters];
    float viewport_aspect_ratio = (float)width / (float)height;
    easyar_Matrix44F * projection = [cameraParameters projection:0.01f farPlane:1000.f viewportAspectRatio:viewport_aspect_ratio screenRotation:screenRotation combiningFlip:true manualHorizontalFlip:false];
    easyar_Matrix44F * imageProjection = [cameraParameters imageProjection:viewport_aspect_ratio screenRotation:screenRotation combiningFlip:true manualHorizontalFlip:false];
    easyar_Image * image = [iFrame image];

    if ([iFrame index] != previousInputFrameIndex) {
        [bgRenderer upload:[image format] width:[image width] height:[image height] pixelWidth:[image pixelWidth] pixelHeight:[image pixelHeight] bufferData:[[image buffer] data]];
        previousInputFrameIndex = [iFrame index];
    }
    [bgRenderer render:imageProjection];

    if ([iFrame trackingStatus] != easyar_MotionTrackingStatus_NotTracking) {
        if (boxRenderer != nil) {
            easyar_Matrix44F * transform = [iFrame cameraTransform];
            [boxRenderer render:projection cameraview: gluInvertMatrix(transform) size:[easyar_Vec3F create:@[@0.2, @0.2, @0.2]]];
        }
    }
}
