/*
 * main.c
 *
 * ============================================================================
 * Copyright (c) Texas Instruments Inc 2005
 *
 * Use of this software is controlled by the terms and conditions found in the
 * license agreement under which this software has been supplied or provided.
 * ============================================================================
 */

/* Standard Linux headers */
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/ioctl.h>
#include <sys/resource.h>

/* Davinci specific kernel headers */
#include <video/davincifb.h>

/* Codec Engine headers */
#include <xdc/std.h>
#include <ti/sdo/ce/Engine.h>
#include <ti/sdo/ce/trace/gt.h>
#include <ti/sdo/ce/CERuntime.h>
#include <ti/sdo/ce/utils/trace/TraceUtil.h>

/* Demo headers */
#include <rendezvous.h>
#include "rotate_demo.h"
#include "video.h"
#include "ctrl.h"

/* The levels of initialization */
#define LOGSINITIALIZED     0x1
#define RENDEZVOUSOPENED    0x2
#define VIDEOTHREADCREATED  0x4

typedef struct Args {
    int imageWidth;
    int imageHeight;
    int passThrough;
    int svideoInput;
    int keyboard;
    int time;
} Args;

/* Global variable declarations for this application */
GlobalData      gbl = { 0, 1, 0, 0, NOSTD };

/******************************************************************************
 * usage
 ******************************************************************************/
static void usage(void)
{
    printf("Usage: rotate_demo [options]\n\n"
           "Options:\n"
           "-r | --resolution  Video resolution ('width'x'height') [720x480]\n"
           "-p | --passthrough Pass video through without encoding [off]\n"
           "-x | --svideo      Use s-video instead of composite video input\n"
           "-k | --keyboard    Enable keyboard interface\n"
           "-t | --time        Number of seconds to run the demo [infinite]\n"
           "-h | --help        Print this message\n\n");
}

/******************************************************************************
 * parseArgs
 ******************************************************************************/
static void parseArgs(int argc, char *argv[], Args *argsp)
{
    const char shortOptions[] = "r:b:pxkt:ih";
    const struct option longOptions[] = {
        {"resolution", required_argument, NULL, 'r'},
        {"passthrough", no_argument, NULL, 'p'},
        {"svideo", no_argument, NULL, 'x'},
        {"keyboard", no_argument, NULL, 'k'},
        {"time", required_argument, NULL, 't'},
        {"help", no_argument, NULL, 'h'},
        {0, 0, 0, 0}
    };
    int     index;
    int     c;
    int     imageWidth;
    int     imageHeight;

    for (;;) {
        c = getopt_long(argc, argv, shortOptions, longOptions, &index);

        if (c == -1) {
            break;
        }

        switch (c) {
            case 0:
                break;

            case 'r':
                if (sscanf(optarg, "%dx%d", &imageWidth,
                                            &imageHeight) != 2) {
                    ERR("Invalid resolution supplied (%s)\n", optarg);
                    usage();
                    exit(EXIT_FAILURE);
                }

                /* Sanity check resolution */
                if (imageWidth <= 0 || imageHeight <= 0 ||
                    imageWidth > D1_WIDTH || imageHeight > D1_HEIGHT) {
                    ERR("Video resolution must be maximum %dx%d\n",
                        D1_WIDTH, D1_HEIGHT);
                    exit(EXIT_FAILURE);
                }

                /* Only use multiples of 16 */
                argsp->imageWidth = imageWidth & ~0xf; 
                argsp->imageHeight = imageHeight & ~0xf; 
                break;

            case 'x':
                argsp->svideoInput = TRUE;
                break;

            case 'p':
                argsp->passThrough = TRUE;
                break;

            case 'k':
                argsp->keyboard = TRUE;
                break;

            case 't':
                argsp->time = atoi(optarg);
                break;

            case 'h':
                usage();
                exit(EXIT_SUCCESS);

            default:
                usage();
                exit(EXIT_FAILURE);
        }
    }
}

/******************************************************************************
 * detectVideoStandard
 ******************************************************************************/
static int detectVideoStandard(void)
{
    int fd;
    int std;

    /* Open video display device */
    fd = open(FBVID_DEVICE, O_RDWR);

    if (fd == -1) {
        ERR("Failed to open fb device %s (%s)\n", FBVID_DEVICE,
                                                  strerror(errno));
        return FAILURE;
    }

    /* Query the display device driver for video standard chosen */
    if (ioctl(fd, FBIO_GETSTD, &std) == -1) {
        ERR("Failed to get video standard from display device driver\n");
        return FAILURE;
    }

    if ((std >> 16) == 0x1) {
        DBG("NTSC selected\n");
        gblSetYFactor(NTSCSTD);
    }
    else {
        DBG("PAL selected\n");
        gblSetYFactor(PALSTD);
    }

    close(fd);

    return SUCCESS;
}

/******************************************************************************
 * main
 ******************************************************************************/
int main(int argc, char *argv[])
{
    unsigned int       initMask  = 0;
    int                status    = EXIT_SUCCESS;
    int                numThreads;
    struct sched_param schedParam;
    Rendezvous_Obj     rendezvous;
    pthread_t          videoThread;
    pthread_attr_t     attr;
    VideoEnv           videoEnv;
    CtrlEnv            ctrlEnv;
    void              *ret;
    Args               args = {
        720,
        480,
        FALSE,
        FALSE,
        FALSE,
        FOREVER
    };

    /* Detect PAL or NTSC */
    if (detectVideoStandard() == FAILURE) {
        cleanup(EXIT_FAILURE);
    }

    /* Parse the arguments given to the app and set the app environment */
    parseArgs(argc, argv, &args);

    printf("Rotate demo started.\n");

    /* Initialize the mutex which protects the global data */
    pthread_mutex_init(&gbl.mutex, NULL);

    /* Set the priority of this whole process to max (requires root) */
    setpriority(PRIO_PROCESS, 0, -20);

    /* Initialize Codec Engine runtime */
    CERuntime_init();

    DBG("Codec Engine initialized\n");

    /* Initialize the logs. Must be done after CERuntime_init() */
    TraceUtil_start(ENGINE_NAME);

    initMask |= LOGSINITIALIZED;

    DBG("Logging initialized\n");

    /* Open the object which synchronizes the thread initialization */
    numThreads = 3;
    Rendezvous_open(&rendezvous, numThreads);

    initMask |= RENDEZVOUSOPENED;

    DBG("Rendezvous opened for %d threads\n", numThreads);

    /* Initialize the thread attributes */
    if (pthread_attr_init(&attr)) {
        ERR("Failed to initialize thread attrs\n");
        cleanup(EXIT_FAILURE);
    }

    /* Force the thread to use custom scheduling attributes */
    if (pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED)) {
        ERR("Failed to set schedule inheritance attribute\n");
        cleanup(EXIT_FAILURE);
    }

    /* Set the thread to be fifo real time scheduled */
    if (pthread_attr_setschedpolicy(&attr, SCHED_FIFO)) {
        ERR("Failed to set FIFO scheduling policy\n");
        cleanup(EXIT_FAILURE);
    }

    /* Set the thread priority */
    schedParam.sched_priority = sched_get_priority_max(SCHED_FIFO);
    if (pthread_attr_setschedparam(&attr, &schedParam)) {
        ERR("Failed to set scheduler parameters\n");
        cleanup(EXIT_FAILURE);
    }

    /* Create the thread */
    videoEnv.hRendezvous  = &rendezvous;
    videoEnv.passThrough  = args.passThrough;
    videoEnv.svideoInput  = args.svideoInput;
    videoEnv.imageWidth   = args.imageWidth;
    videoEnv.imageHeight  = args.imageHeight;

    if (pthread_create(&videoThread, &attr, videoThrFxn, (void *) &videoEnv)) {
        ERR("Failed to create video thread\n");
        cleanup(EXIT_FAILURE);
    }

    initMask |= VIDEOTHREADCREATED;

    /* Main thread becomes the control thread */
    ctrlEnv.hRendezvous  = &rendezvous;
    ctrlEnv.keyboard     = args.keyboard;
    ctrlEnv.time         = args.time;
    ctrlEnv.imageWidth   = args.imageWidth;
    ctrlEnv.imageHeight  = args.imageHeight;
    ctrlEnv.passThrough  = args.passThrough;

    ret = ctrlThrFxn(&ctrlEnv);

    if (ret == THREAD_FAILURE) {
        status = EXIT_FAILURE;
    }

cleanup:
    /* Make sure the other threads aren't waiting for init to complete */
    Rendezvous_force(&rendezvous);

    /* Wait until the other threads terminate */
    if (initMask & VIDEOTHREADCREATED) {
        if (pthread_join(videoThread, &ret) == 0) {
            if (ret == THREAD_FAILURE) {
                status = EXIT_FAILURE;
            }
        }
    }

    if (initMask & RENDEZVOUSOPENED) {
        Rendezvous_close(&rendezvous);
    }

    if (initMask & LOGSINITIALIZED) {
        TraceUtil_stop();
    }

    /* Destroy the global mutex */
    pthread_mutex_destroy(&gbl.mutex);

    exit(status);
}
