/*
 * ctrl.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 <string.h>
#include <unistd.h>
#include <pthread.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <sys/ioctl.h>

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

/* Codec Engine headers */
#include <xdc/std.h>
#include <ti/sdo/ce/Engine.h>

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

/* Keyboard command prompt */
#define COMMAND_PROMPT       "Command [ 'help' for usage ] > "

/* The 0-7 transparency value to use for the OSD */
#define OSD_TRANSPARENCY     0x55
#define MAX_TRANSPARENCY     0x77
#define MIN_TRANSPARENCY     0x11
#define INC_TRANSPARENCY     0x11
#define NO_TRANSPARENCY      0x0

/* The levels of initialization */
#define OSDINITIALIZED       0x1
#define ENGINEOPENED         0x2
#define MSP430LIBINITIALIZED 0x4
#define UICREATED            0x8

/* Local function prototypes */
static int  setOsdBuffer(int osdFd, int idx);
static int  osdInit(char *displays[]);
static int  osdExit(int osdFd);
static int  setOsdTransparency(unsigned char trans);
static int  waitForVsync(int fd);
static int  getArmCpuLoad(int *procLoad, int *cpuLoad);
static int  keyAction(enum msp430lib_keycode key, int displayIdx,
                      int *osdTransPtr);
static void drawDynamicData(Engine_Handle hEngine, int osdFd, int *time,
                            int *displayIdx, int *workingIdx);

/******************************************************************************
 * setOsdBuffer
 ******************************************************************************/
static int setOsdBuffer(int osdFd, int idx)
{
    struct fb_var_screeninfo vInfo;

    if (ioctl(osdFd, FBIOGET_VSCREENINFO, &vInfo) == -1) {
        ERR("Failed FBIOGET_VSCREENINFO (%s)\n", strerror(errno));
        return FAILURE;
    }

    vInfo.yoffset = vInfo.yres * idx;
    if (ioctl(osdFd, FBIOPAN_DISPLAY, &vInfo) == -1) {
        ERR("Failed FBIOPAN_DISPLAY (%s)\n", strerror(errno));
        return FAILURE;
    }

    return SUCCESS;
}

/******************************************************************************
 * osdInit
 ******************************************************************************/
static int osdInit(char *displays[])
{
    struct fb_var_screeninfo varInfo;
    int                      fd;
    int                      size;

    /* Open the OSD device */
    fd = open(OSD_DEVICE, O_RDWR);

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

    if (ioctl(fd, FBIOGET_VSCREENINFO, &varInfo) == -1) {
        ERR("Failed ioctl FBIOGET_VSCREENINFO on %s\n", OSD_DEVICE);
        return FAILURE;
    }

    /* Try the requested size */
    varInfo.xres = D1_WIDTH;
    varInfo.yres = D1_HEIGHT;
    varInfo.bits_per_pixel = SCREEN_BPP;

    if (ioctl(fd, FBIOPUT_VSCREENINFO, &varInfo) == -1) {
        ERR("Failed ioctl FBIOPUT_VSCREENINFO on %s\n", OSD_DEVICE);
        return FAILURE;
    }

    if (varInfo.xres != D1_WIDTH ||
        varInfo.yres != D1_HEIGHT ||
        varInfo.bits_per_pixel != SCREEN_BPP) {
        ERR("Failed to get the requested screen size: %dx%d at %d bpp\n",
            D1_WIDTH, D1_HEIGHT, SCREEN_BPP);
        return FAILURE;
    }

    size = varInfo.xres * varInfo.yres * varInfo.bits_per_pixel / 8;

    /* Map the frame buffers to user space */
    displays[0] = (char *) mmap(NULL, size * NUM_BUFS,
                                   PROT_READ | PROT_WRITE,
                                   MAP_SHARED, fd, 0);

    if (displays[0] == MAP_FAILED) {
        ERR("Failed mmap on %s\n", OSD_DEVICE);
        return FAILURE;
    }

    displays[1] = displays[0] + size;

    setOsdBuffer(fd, 0);

    return fd;
}

/******************************************************************************
 * osdExit
 ******************************************************************************/
static int osdExit(int osdFd)
{
    setOsdBuffer(osdFd, 0);
    close(osdFd);

    return SUCCESS;
}

/******************************************************************************
 * setOsdTransparency
 ******************************************************************************/
static int setOsdTransparency(unsigned char trans)
{
    struct fb_var_screeninfo vInfo;
    unsigned short          *attrDisplay;
    int                      attrSize;
    int                      fd;

    /* Open the attribute device */
    fd = open(ATTR_DEVICE, O_RDWR);

    if (fd == -1) {
        ERR("Failed to open attribute window %s\n", ATTR_DEVICE);
        return FAILURE;
    }

    if (ioctl(fd, FBIOGET_VSCREENINFO, &vInfo) == -1) {
        ERR("Error reading variable information.\n");
        return FAILURE;
    }

    /* One nibble per pixel */
    attrSize = vInfo.xres_virtual * vInfo.yres / 2; 

    attrDisplay = (unsigned short *) mmap(NULL, attrSize,
                                          PROT_READ | PROT_WRITE,
                                          MAP_SHARED, fd, 0);
    if (attrDisplay == MAP_FAILED) {
        ERR("Failed mmap on %s\n", ATTR_DEVICE);
        return FAILURE;
    }

    /* Fill the window with the new attribute value */
    memset(attrDisplay, trans, attrSize);

    munmap(attrDisplay, attrSize);
    close(fd);

    return SUCCESS;
}

/******************************************************************************
 * waitForVsync
 ******************************************************************************/
static int waitForVsync(int fd)
{
    int dummy;

    /* Wait for vertical sync */
    if (ioctl(fd, FBIO_WAITFORVSYNC, &dummy) == -1) {
        ERR("Failed FBIO_WAITFORVSYNC (%s)\n", strerror(errno));
        return FAILURE;
    }

    return SUCCESS;
}

/******************************************************************************
 * getArmCpuLoad
 ******************************************************************************/
static int getArmCpuLoad(int *procLoad, int *cpuLoad)
{
    static unsigned long prevIdle     = 0;
    static unsigned long prevTotal    = 0;
    static unsigned long prevProc     = 0;
    int                  cpuLoadFound = FALSE;
    unsigned long        user, nice, sys, idle, total, proc;
    unsigned long        uTime, sTime, cuTime, csTime;
    unsigned long        deltaTotal, deltaIdle, deltaProc;
    char                 textBuf[4];
    FILE                *fptr;

    /* Read the overall system information */
    fptr = fopen("/proc/stat", "r");

    if (fptr == NULL) {
        ERR("/proc/stat not found. Is the /proc filesystem mounted?\n");
        return FAILURE;
    }

    /* Scan the file line by line */
    while (fscanf(fptr, "%4s %lu %lu %lu %lu %*[^\n]", textBuf,
                  &user, &nice, &sys, &idle) != EOF) {
        if (strcmp(textBuf, "cpu") == 0) {
            cpuLoadFound = TRUE;
            break;
        }
    }

    if (fclose(fptr) != 0) {
        return FAILURE;
    }

    if (!cpuLoadFound) {
        return FAILURE;
    }

    /* Read the current process information */
    fptr = fopen("/proc/self/stat", "r");

    if (fptr == NULL) {
        ERR("/proc/self/stat not found. Is the /proc filesystem mounted?\n");
        return FAILURE;
    }

    if (fscanf(fptr, "%*d %*s %*s %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d %lu "
                     "%lu %lu %lu", &uTime, &sTime, &cuTime, &csTime) != 4) {
        ERR("Failed to get process load information.\n");
        fclose(fptr);
        return FAILURE;
    }

    if (fclose(fptr) != 0) {
        return FAILURE;
    }

    total = user + nice + sys + idle;
    proc = uTime + sTime + cuTime + csTime; 

    /* Check if this is the first time, if so init the prev values */
    if (prevIdle == 0 && prevTotal == 0 && prevProc == 0) {
        prevIdle = idle;
        prevTotal = total;
        prevProc = proc; 
        return SUCCESS;
    }

    deltaIdle = idle - prevIdle;
    deltaTotal = total - prevTotal;
    deltaProc = proc - prevProc; 

    prevIdle = idle;
    prevTotal = total;
    prevProc = proc;

    *cpuLoad = 100 - deltaIdle * 100 / deltaTotal;
    *procLoad = deltaProc * 100 / deltaTotal;

    return SUCCESS;
}

/******************************************************************************
 * drawDynamicData
 ******************************************************************************/
static void drawDynamicData(Engine_Handle hEngine, int osdFd, int *time,
                            int *displayIdx, int *workingIdx)
{
    static unsigned long  firstTime = 0;
    static unsigned long  prevTime;
    unsigned long         newTime;
    unsigned long         deltaTime;
    struct timeval        tv;
    struct tm            *timePassed;
    time_t                spentTime;
    char                  tempString[9];
    int                   procLoad;
    int                   armLoad;
    int                   dspLoad;
    int                   fps;
    float                 fpsf;

    *time = -1;

    if (gettimeofday(&tv, NULL) == -1) {
        ERR("Failed to get os time\n");
        return;
    }

    newTime = tv.tv_sec * 1000 + tv.tv_usec / 1000;
    if (!firstTime) {
        firstTime = newTime;
        prevTime = newTime;
        return;
    }

    /* Only update OSD every second */
    deltaTime = newTime - prevTime;
    if (deltaTime <= 1000) {
        return;
    }

    prevTime = newTime;

    spentTime = (newTime - firstTime) / 1000;
    if (spentTime <= 0) {
        return;
    }

    *time = spentTime;

    fpsf         = gblGetAndResetFrames() * 1000.0 / deltaTime;
    fps          = fpsf + 0.5;

    uiClearScreen(COLUMN_2, 0, COLUMN_3 - COLUMN_2, yScale(220), *workingIdx);

    if (getArmCpuLoad(&procLoad, &armLoad) != FAILURE) {
        sprintf(tempString, "%d%%", procLoad);
        uiDrawText(tempString, COLUMN_2, ROW_1, *workingIdx); 
    }
    else {
        ERR("Failed to get ARM CPU load\n");
    }

    dspLoad = Engine_getCpuLoad(hEngine);

    if (dspLoad > -1) {
        sprintf(tempString, "%d%%", dspLoad); 
        uiDrawText(tempString, COLUMN_2, ROW_2, *workingIdx); 
    }
    else {
        ERR("Failed to get DSP CPU load\n");
    }

    sprintf(tempString, "%d fps", fps);
    uiDrawText(tempString, COLUMN_2, ROW_3, *workingIdx); 

    timePassed = localtime(&spentTime);
    if (timePassed == NULL) {
        return;
    }

    sprintf(tempString, "%.2d:%.2d:%.2d", timePassed->tm_hour,
                                          timePassed->tm_min,
                                          timePassed->tm_sec);
    uiDrawText(tempString, COLUMN_2, ROW_5, *workingIdx); 

    *displayIdx = (*displayIdx + 1) % NUM_BUFS;
    *workingIdx = (*workingIdx + 1) % NUM_BUFS;

    setOsdBuffer(osdFd, *displayIdx);

    waitForVsync(osdFd);
}

/******************************************************************************
 * keyAction
 ******************************************************************************/
static int keyAction(enum msp430lib_keycode key, int displayIdx,
                     int *osdTransPtr)
{
    static int osdVisible = TRUE;

    switch(key) {
        case MSP430LIB_KEYCODE_OK:              
        case MSP430LIB_KEYCODE_PLAY:
            DBG("Play button pressed\n");
            gblSetPlay(TRUE);

            if (uiPressButton(BTNIDX_CTRLPLAY, displayIdx) == FAILURE) {
                return FAILURE;
            }
            break;

        case MSP430LIB_KEYCODE_PAUSE:
            DBG("Pause button pressed\n");
            gblSetPlay(FALSE);

            if (uiPressButton(BTNIDX_CTRLPAUSE, displayIdx) == FAILURE) {
                return FAILURE;
            }
            break;

        case MSP430LIB_KEYCODE_STOP:
            DBG("Stop button pressed, quitting demo..\n");
            gblSetQuit();
            break;

        case MSP430LIB_KEYCODE_VOLINC:
            DBG("Volume inc button pressed\n");
            if (*osdTransPtr < MAX_TRANSPARENCY && osdVisible) {
                *osdTransPtr += INC_TRANSPARENCY;
                setOsdTransparency(*osdTransPtr);
            }

            if (uiPressButton(BTNIDX_NAVPLUS, displayIdx) == FAILURE) {
                return FAILURE;
            }
            break;

        case MSP430LIB_KEYCODE_VOLDEC:
            DBG("Volume dec button pressed\n");
            if (*osdTransPtr > MIN_TRANSPARENCY && osdVisible) {
                *osdTransPtr -= INC_TRANSPARENCY;
                setOsdTransparency(*osdTransPtr);
            }

            if (uiPressButton(BTNIDX_NAVMINUS, displayIdx) == FAILURE) {
                return FAILURE;
            }
            break;

        case MSP430LIB_KEYCODE_INFOSELECT:
            DBG("Info/select command received.\n");
            if (osdVisible) {
                setOsdTransparency(NO_TRANSPARENCY);
                osdVisible = FALSE;
            }
            else {
                setOsdTransparency(*osdTransPtr);
                osdVisible = TRUE;
            }   
            break;

        default:
            DBG("Unknown button pressed.\n");
            if (uiPressButton(BTNIDX_WRONGPRESSED, displayIdx) == FAILURE) {
                return FAILURE;
            }
    }

    return SUCCESS;
}

/******************************************************************************
 * getKbdCommand
 ******************************************************************************/
int getKbdCommand(enum msp430lib_keycode *keyPtr)
{
    struct timeval tv;
    fd_set         fds;
    int            ret;
    char           string[80];

    FD_ZERO(&fds);
    FD_SET(fileno(stdin), &fds);

    /* Timeout of 0 means polling */
    tv.tv_sec = 0;
    tv.tv_usec = 0;

    ret = select(FD_SETSIZE, &fds, NULL, NULL, &tv);

    if (ret == -1) {
        ERR("Select failed on stdin (%s)\n", strerror(errno));
        return FAILURE;
    }

    if (ret == 0) {
        return SUCCESS;
    }

    if (FD_ISSET(fileno(stdin), &fds)) {
        if (fgets(string, 80, stdin) == NULL) {
            return FAILURE;
        }

        /* Remove the end of line */
        strtok(string, "\n");

        /* Assign corresponding msp430lib key */
        if (strcmp(string, "play") == 0) {
            *keyPtr = MSP430LIB_KEYCODE_PLAY;
        }
        else if (strcmp(string, "pause") == 0) {
            *keyPtr = MSP430LIB_KEYCODE_PAUSE;
        }
        else if (strcmp(string, "stop") == 0) {
            *keyPtr = MSP430LIB_KEYCODE_STOP;
        }
        else if (strcmp(string, "inc") == 0) {
            *keyPtr = MSP430LIB_KEYCODE_VOLDEC;
        }
        else if (strcmp(string, "dec") == 0) {
            *keyPtr = MSP430LIB_KEYCODE_VOLINC;
        }
        else if (strcmp(string, "hide") == 0) {
            *keyPtr = MSP430LIB_KEYCODE_INFOSELECT;
        }
        else if (strcmp(string, "help") == 0) {
            printf("\nAvailable commands:\n"
                   "    play  - Play video\n"
                   "    pause - Pause video\n"
                   "    stop  - Quit demo\n"
                   "    inc   - Increase OSD transparency\n"
                   "    dec   - Decrease OSD transparency\n"
                   "    hide  - Show / hide the OSD\n"
                   "    help  - Show this help screen\n\n");
        }
        else {
            printf("Unknown command: [ %s ]\n", string);
        }

        if (*keyPtr != MSP430LIB_KEYCODE_STOP) {
            printf(COMMAND_PROMPT);
            fflush(stdout);
        }
        else {
            printf("\n");
        }
    }

    return SUCCESS;
}

/******************************************************************************
 * ctrlThrFxn
 ******************************************************************************/
void *ctrlThrFxn(void *arg)
{
    Engine_Handle          hEngine          = NULL;
    unsigned int           initMask         = 0;
    CtrlEnv               *envp             = (CtrlEnv *) arg;
    int                    osdTransparency  = OSD_TRANSPARENCY;
    void                  *status           = THREAD_SUCCESS;
    int                    displayIdx       = 0;
    int                    workingIdx       = 1;
    char                  *osdDisplays[NUM_BUFS];
    enum msp430lib_keycode key;
    UIParams               uiParams;
    int                    osdFd;
    int                    timeSpent;

    /* Initialize the OSD */
    osdFd = osdInit(osdDisplays);

    if (osdFd == FAILURE) {
        cleanup(THREAD_FAILURE);
    }

    DBG("OSD successfully initialized\n");

    initMask |= OSDINITIALIZED;

    /* Initialize the OSD transparency */
    if (setOsdTransparency(osdTransparency) == FAILURE) {
        cleanup(THREAD_FAILURE);
    }

    DBG("OSD transparency initialized\n");

    /* Reset, load, and start DSP Engine */
    hEngine = Engine_open(ENGINE_NAME, NULL, NULL);

    if (hEngine == NULL) {
        ERR("Failed to open codec engine %s\n", ENGINE_NAME);
        cleanup(THREAD_FAILURE);
    }

    DBG("Codec Engine opened in control thread\n");

    initMask |= ENGINEOPENED;

    /* Initialize the MSP430 library to be able to receive IR commands */
    if (msp430lib_init() == MSP430LIB_FAILURE) {
        ERR("Failed to initialize msp430lib.\n");
        cleanup(THREAD_FAILURE);
    }

    DBG("MSP430 library initialized\n");

    initMask |= MSP430LIBINITIALIZED;

    /* Create the user interface */
    uiParams.imageWidth  = envp->imageWidth;
    uiParams.imageHeight = envp->imageHeight;
    uiParams.passThrough = envp->passThrough;
    uiParams.osdDisplays = osdDisplays;

    if (uiCreate(&uiParams) == FAILURE) {
        cleanup(THREAD_FAILURE);
    }

    DBG("User interface created\n");

    initMask |= UICREATED;

    /* Signal that initialization is done and wait for other threads */
    Rendezvous_meet(envp->hRendezvous);

    /* Initialize the ARM cpu load */
    getArmCpuLoad(NULL, NULL);

    if (envp->keyboard) {
        printf(COMMAND_PROMPT);
        fflush(stdout);
    }

    DBG("Entering control main loop.\n");
    while (!gblGetQuit()) {
        /* Draw the cpu load on the OSD */
        drawDynamicData(hEngine, osdFd, &timeSpent, &displayIdx, &workingIdx);

        /* Has the demo timelimit been hit? */
        if (envp->time > FOREVER && timeSpent >= envp->time) {
            breakLoop(THREAD_SUCCESS);
        }

        /* See if an IR remote key has been pressed */
        if (msp430lib_get_ir_key(&key) == MSP430LIB_FAILURE) {
            DBG("Failed to get IR value.\n");
        }

        if (envp->keyboard) {
            if (getKbdCommand(&key) == FAILURE) {
                breakLoop(THREAD_FAILURE);
            }
        }

        /* If an IR key had been pressed, service it */
        if (key != 0) {
            if (keyAction(key, displayIdx, &osdTransparency) == FAILURE) {
                breakLoop(THREAD_FAILURE);
            }
        }

        usleep(REMOTECONTROLLATENCY);
    }

cleanup:
    /* Make sure the other threads aren't waiting for init to complete */
    Rendezvous_force(envp->hRendezvous);

    /* Clean up the control thread */
    if (initMask & UICREATED) {
        uiDelete();
    }

    if (initMask & MSP430LIBINITIALIZED) {
        msp430lib_exit();
    }

    if (initMask & ENGINEOPENED) {
        Engine_close(hEngine);
    }

    if (initMask & OSDINITIALIZED) {
        osdExit(osdFd);
    }

    return status;
}
