/* Copyright (c) 2015-2019, NVIDIA CORPORATION.  All rights reserved.
 *
 * NVIDIA CORPORATION and its licensors retain all intellectual property
 * and proprietary rights in and to this software, related documentation
 * and any modifications thereto.  Any use, reproduction, disclosure or
 * distribution of this software and related documentation without an express
 * license agreement from NVIDIA CORPORATION is strictly prohibited.
 */

#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "main.h"
#include "check_version.h"
#include "capture.h"
#include "save.h"
#include "composite.h"
#include "display.h"
#include "err_handler.h"
#include "os_common.h"

#define IDLE_TIME 100000

/* Quit flag. Out of context structure for sig handling */
static volatile NvMediaBool *quit_flag;
static volatile NvMediaBool *suspend_flag;
static volatile NvMediaBool *resume_flag;
static NvMainContext *mainCtxPtr;

static void
SigHandler(int signum)
{
    signal(SIGINT, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGKILL, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    signal(SIGHUP, SIG_IGN);

    *quit_flag = NVMEDIA_TRUE;

    signal(SIGINT, SIG_DFL);
    signal(SIGTERM, SIG_DFL);
    signal(SIGQUIT, SIG_DFL);
    signal(SIGKILL, SIG_DFL);
    signal(SIGSTOP, SIG_DFL);
    signal(SIGHUP, SIG_DFL);
}

static void
SuspendSigHandler(int signum)
{
    *resume_flag = NVMEDIA_FALSE;
    *suspend_flag = NVMEDIA_TRUE;
    struct timespec start, finish;
    long seconds, nanoseconds;
    double miliseconds;

    // prevent compile time optimization here and ensure writes are done
    asm volatile("DMB ST": : :"memory");

    clock_gettime(CLOCK_REALTIME, &start);
    CaptureSuspend(mainCtxPtr);
    clock_gettime(CLOCK_REALTIME, &finish);

    // measuring time for suspend
    seconds = finish.tv_sec - start.tv_sec;
    nanoseconds = finish.tv_nsec - start.tv_nsec;

    if (start.tv_nsec > finish.tv_nsec) {
        --seconds;
        nanoseconds += 1000000000;
    }
    miliseconds = seconds * 1000.0 + nanoseconds / 1000000.0;

    LOG_INFO("Total time for suspend is %f miliseconds\n", miliseconds);
    *suspend_flag = NVMEDIA_FALSE;
}

static void
ResumeSigHandler(int signum)
{
    *resume_flag = NVMEDIA_TRUE;
    *suspend_flag = NVMEDIA_FALSE;
    struct timespec start, finish;
    long seconds, nanoseconds;
    double miliseconds;

    // prevent compile time optimization here and ensure writes are done
    asm volatile("DMB ST": : :"memory");

    clock_gettime(CLOCK_REALTIME, &start);
    CaptureResume(mainCtxPtr);
    clock_gettime(CLOCK_REALTIME, &finish);

    //measuring time for resume
    seconds = finish.tv_sec - start.tv_sec;
    nanoseconds = finish.tv_nsec - start.tv_nsec;

    if (start.tv_nsec > finish.tv_nsec) {
        --seconds;
        nanoseconds += 1000000000;
    }
    miliseconds = seconds*1000.0 + nanoseconds/1000000.0;

    LOG_INFO("Total time for resume is %f miliseconds\n", miliseconds);
    *resume_flag = NVMEDIA_FALSE;
}

static void
SigSuspendSetup(void)
{
    struct sigaction action;

    memset(&action, 0, sizeof(action));
    action.sa_handler = SuspendSigHandler;

    sigaction(SIGUSR1, &action, NULL);
}

static void
SigResumeSetup(void)
{
    struct sigaction action;

    memset(&action, 0, sizeof(action));
    action.sa_handler = ResumeSigHandler;

    sigaction(SIGUSR2, &action, NULL);
}

static void
SigSetup(void)
{
    struct sigaction action;

    memset(&action, 0, sizeof(action));
    action.sa_handler = SigHandler;

    sigaction(SIGINT, &action, NULL);
    sigaction(SIGTERM, &action, NULL);
    sigaction(SIGQUIT, &action, NULL);
    sigaction(SIGKILL, &action, NULL);
    sigaction(SIGSTOP, &action, NULL);
    sigaction(SIGHUP, &action, NULL);
}

static int
ExecuteNextCommand(NvMainContext *ctx) {
    char input[256] = { 0 };

    if ((*resume_flag == NVMEDIA_TRUE) || (*suspend_flag == NVMEDIA_TRUE))
        return 0;

    if (!fgets(input, 256, stdin)) {
        if (*quit_flag != NVMEDIA_TRUE) {
            LOG_WARN("%s: Either failed to read command or suspend/resume happened\n", __func__);
        }
    }

    /* Remove new line character */
    if (input[strlen(input) - 1] == '\n')
        input[strlen(input) - 1] = '\0';


    if (!strcasecmp(input, "q") || !strcasecmp(input, "quit")) {
        *quit_flag = NVMEDIA_TRUE;
        return 0;
    } else if (!strcasecmp(input, "h") || !strcasecmp(input, "help")) {
        PrintUsage();
        return 0;
    } else if (!strcasecmp(input, "")) {
        return 0;
    } else {
        LOG_ERR("%s: Unknown runtime command type encountered\n",
                __func__);
        return -1;
    }

    return 0;
}

int main(int argc,
         char *argv[])
{
    TestArgs allArgs;
    NvMainContext mainCtx;
    sigset_t set;
    int status;
    mainCtxPtr = &mainCtx;

    /* prepare an empty signal set */
    status = sigemptyset(&set);

    if (0 != status) {
        LOG_ERR("%s: Failed to empty signal set\n");
        return -1;
    }

    /* add signals to be blocked */
    sigaddset(&set, SIGINT);
    sigaddset(&set, SIGTERM);
    sigaddset(&set, SIGQUIT);
    sigaddset(&set, SIGKILL);
    sigaddset(&set, SIGSTOP);
    sigaddset(&set, SIGHUP);
    sigaddset(&set, SIGUSR1);
    sigaddset(&set, SIGUSR2);

    /* block all signals, the new threads created will inherit the signal mask */
    status = pthread_sigmask(SIG_BLOCK, &set, NULL);

    if (0 != status) {
        LOG_ERR("%s: Failed to blocks signals\n");
        return -1;
    }

    memset(&allArgs, 0, sizeof(TestArgs));
    memset(&mainCtx, 0 , sizeof(NvMainContext));

    if (CheckModulesVersion() != NVMEDIA_STATUS_OK) {
        return -1;
    }

    if (IsFailed(ParseArgs(argc, argv, &allArgs))) {
        return -1;
    }

    quit_flag = &mainCtx.quit;
    suspend_flag = &mainCtx.suspend;
    resume_flag = &mainCtx.resume;
    SigSetup();
    SigSuspendSetup();
    SigResumeSetup();

    /* Initialize context */
    mainCtx.testArgs = &allArgs;

    /* Initialize all the components */
    if (CaptureInit(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to Initialize Capture\n", __func__);
        goto done;
    }

    if (SaveInit(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to Initialize Save\n", __func__);
        goto done;
    }

    if (CompositeInit(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to Initialize Composite\n", __func__);
        goto done;
    }

    if (DisplayInit(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to Initialize Display\n", __func__);
        goto done;
    }

    if (ErrHandlerInit(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: Failed to Initialize ErrHandler\n", __func__);
        goto done;
    }

    /* Call Proc for each component */
    if (CaptureProc(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: CaptureProc Failed\n", __func__);
        goto done;
    }

    if (SaveProc(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: SaveProc Failed\n", __func__);
        goto done;
    }

    if (CompositeProc(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: CompositeProc Failed\n", __func__);
        goto done;
    }

    if (DisplayProc(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: DisplayProc Failed\n", __func__);
        goto done;
    }

    if (ErrHandlerProc(&mainCtx) != NVMEDIA_STATUS_OK) {
        LOG_ERR("%s: ErHandlerProc Failed\n", __func__);
        goto done;
    }

    /* unblock the signals, they will be handled only by the main thread */
    status = pthread_sigmask(SIG_UNBLOCK, &set, NULL);
    if (0 != status) {
        LOG_ERR("%s: Failed to unblock signals\n", __func__);
        goto done;
    }

    LOG_MSG("Type \"h\" for a list of options\n");

    while (!mainCtx.quit) {
        if (!allArgs.numFrames.isUsed) {
            LOG_MSG("-");
            ExecuteNextCommand(&mainCtx);
        } else {
            nvsleep(IDLE_TIME);
        }
    }

done:
    /* Stop HW engines */
    DisplaySuspend(&mainCtx);
    CompositeSuspend(&mainCtx);
    SaveSuspend(&mainCtx);
    /* ErrHandler should be suspended before Capture Thread */
    ErrHandlerSuspend(&mainCtx);
    CaptureSuspend(&mainCtx);

    /* Free SW context */
    DisplayFini(&mainCtx);
    CompositeFini(&mainCtx);
    SaveFini(&mainCtx);
    ErrHandlerFini(&mainCtx);
    CaptureFini(&mainCtx);
    return 0;
}
