/*
 *   MediaMVP Server
 *
 *   (C) 2004 Dominic Morris
 *
 *   $Id: program.c,v 1.3 2004/04/25 15:52:53 dom Exp $
 *   $Date: 2004/04/25 15:52:53 $
 *
 *
 *   Implements the skeleton application
 * 
 */


#include "program.h"

#ifdef VDR_PLUGIN
#include "vdrurl.h"
#include "vdrrecurl.h"
#endif


typedef struct {
    void    *param;
    int    (*ack_func)(int acktype, void *param, uint8_t *buf, int len);
} ackhandle_t;

typedef struct {
    void    *param;
    int    (*key_func)(void *menu,int keycode); /* Key press callback */
} keyhandle_t;


struct _program {
    dongle_t     *dongle;

    struct event *evtimer;      /* To disable the screensaver */


    /* These values are used to replicate the previous configuration
       of the original app.c
    */
    render_t     *render;
    keyhandle_t   menus;
    keyhandle_t   media;
    ackhandle_t   menus_ack;
    ackhandle_t   media_ack;
    menuapp_t    *menuapp;
};



static int      program_open(dongle_t *dongle, application_t *application);
static void     program_close(application_t *application);
static uint8_t *program_get_surface(application_t *application, int *size);
static void     program_keypress(application_t *application, int keycode);
static void     program_ack(application_t *application, int ackcode, unsigned char *buf, int buflen);
static int      program_authorise(char *mac_address, uint32_t ipaddr, int connected);
static void     program_dirty_cb(int val, short event, void *arg);


#ifndef VDR_PLUGIN
static int      c_gui_width      = 640;   
static int      c_gui_depth      = 480;   
static int      c_screen_width   = 720;   
static int      c_screen_depth   = 576;  
static char     c_screensaver    = 0; 
static char    *c_config_dir     = NULL;
#endif

#ifndef VDR_PLUGIN
void program_config()
{
    iniparse_add(0,"main:width","Width of the MVP gui",OPT_INT,&c_gui_width);
    iniparse_add(0,"main:depth","Height of the MVP gui",OPT_INT,&c_gui_depth);
    iniparse_add(0,"main:screenwidth","Total width of the MVP display",OPT_INT,&c_screen_width);
    iniparse_add(0,"main:screendepth","Total depth of the MVP display",OPT_INT,&c_screen_depth);
    iniparse_add(0,"main:screensaver","Enable the onboard screensaver",OPT_BOOL,&c_screensaver);
    iniparse_add('c',"main:configdirectory","Directory where all configuration files can be found",OPT_STR,&c_config_dir);
    menuapp_config();
}
#endif


void program_init(mvpinit_t *init)
{
    /* Set up the initial callbacks */
    init->open = program_open;
    init->authorise = program_authorise;
    
#ifdef VDR_PLUGIN
   cMediamvpVdrURL::Init();
   cMediamvpVdrRecURL::Init();
#endif
}

/** \brief Populate the provide application with our callbacks
 *
 *  \param dongle The dongle this is being setup for
 *  \param application Parameter block to populate
 *
 *  \return 0 on success, -1 on failure
 *
 *  This function is called via a callback from the library.
 *  We allocate our surface here and startup our menu system here.
 */
static int program_open(dongle_t *dongle, application_t *application)
{
    program_t   *program = (program_t *) calloc(1,sizeof(*program));

    /* We should pick up appropriate parameters for the dongle here, or
       alternatively refuse the connection?
    */

    program->dongle = dongle;

    /* Create a new render_t - this is an 8bpp plane */
    if ( ( program->render = new_render(c_gui_width,c_gui_depth) ) == NULL ) {
        free(program);
        return -1;
    }

    application->data = (void *) program;

    /* Set up the appropriate callback functions */
    application->close = program_close;
    application->get_yvuy_surface = program_get_surface;
    application->keypress = program_keypress;
    application->ack = program_ack;

    application->gui_width = c_gui_width;
    application->gui_depth = c_gui_depth;
    application->screen_width = c_screen_width;
    application->screen_depth = c_screen_depth;

    if ( c_screensaver == 0 ) {
        struct timeval tv;
        program->evtimer = (struct event *) malloc(sizeof(struct event));
        evtimer_set(program->evtimer,program_dirty_cb,program);
        tv.tv_sec = 15;
        tv.tv_usec = 0;
        evtimer_add(program->evtimer,&tv);
    } else {
        program->evtimer = NULL;
    }
    
    /* Finally, startup our application */
    program->menuapp = new_menuapp(dongle,program->render,program);


    return 0;
}

/** \brief Delete the application
 *
 *  \param application Application to delete for
 */
static void program_close(application_t *application)
{
    program_t   *program = (program_t *)application->data;

    if ( program->evtimer ) {
        event_del(program->evtimer);
        free(program->evtimer);
    }


    delete_render(program->render);

    free(program);
}

/** \brief Return the yvuy surface
 *
 *  \param application
 *  \param size Pointer to place the surface size
 *
 *  \return a rendered surface, or NULL if it's not been updated recently
 */
static uint8_t *program_get_surface(application_t *application, int *size)
{
    program_t  *program = (program_t *)application->data;

    return (uint8_t *) render_rgb2yvuy(program->render,size);  
}

/** \brief Handle a keypress
 *
 *  \param application
 *  \param keycode Key code from the MVP
 *
 */
static void program_keypress(application_t *application, int keycode)
{
    program_t  *program = (program_t *)application->data;

    if ( program->media.key_func ) {
        /* Now pass the key onto the menu stuff if necessary */
        if ( (program->media.key_func)(program->media.param,keycode) == 0 ) {
            if ( program->menus.key_func ) {
                (program->menus.key_func)(program->menus.param,keycode);
            }
        }
    } else if ( program->menus.key_func ) {
        (program->menus.key_func)(program->menus.param,keycode);
    }
}

/** \brief Handle an ack
 *
 *  \param application
 *  \param ackcode Type of ack
 *  \param buf Ack message
 *  \param buflen Length of ack message
 */
static void program_ack(application_t *application, int ackcode, unsigned char *buf, int buflen)
{
    program_t  *program = (program_t *)application->data;

    if ( program->media_ack.ack_func ) {
        /* Now pass the key onto the menu stuff if necessary */
        if ( (program->media_ack.ack_func)(ackcode,program->media_ack.param,buf,buflen) == 0 ) {
            if ( program->menus_ack.ack_func ) {
                (program->menus_ack.ack_func)(ackcode,program->menus_ack.param,buf,buflen);
            }
        }
    } else if ( program->menus_ack.ack_func ) {
        (program->menus_ack.ack_func)(ackcode,program->menus_ack.param,buf,buflen);
    } else {
        Dprintf(INFO,"Can't deal with MEDIA_ACK type %d\n",buf[1]);
    }
}

/** \brief Register key handling functions
 *
 *  \param app Handle the the app channel
 *  \param types REGISTER_MENU or REGISTER_MEDIA
 *  \param key_func Function to call back with key code
 *  \param param Generic pointer to pass back to the callback function
 *
 *  We have two sets of functions for handling keys - one whilst in the
 *  menu, and a second for playing media
 */
void program_register_keys(program_t *program,int type, 
                       int (*key_func)(void *param,int keycode), void *param)
{
    if ( type == REGISTER_MENU ) {
        program->menus.key_func = key_func;
        program->menus.param = param;
    } else {
        program->media.key_func = key_func;
        program->media.param = param;
    }
}


/** \brief Register ack handling functions
 *
 *  \param app Handle the the app channel
 *  \param types REGISTER_MENU or REGISTER_MEDIA
 *  \param key_func Function to call back with the ack
 *  \param param Generic pointer to pass back to the callback function
 *
 *  We have two sets of functions for handling acks - one whilst in the
 *  menu, and a second for playing media
 */
void program_register_ack(program_t *program, int type,
                      int    (*ack_func)(int acktype, void *param, unsigned char
 *buf, int len), void  *param)
{
    if ( type == REGISTER_MENU ) {
        program->menus_ack.ack_func = ack_func;
        program->menus_ack.param = param;
    } else {
        program->media_ack.ack_func = ack_func;
        program->media_ack.param = param;
    }
}

/** \brief Check to see whether we should permit this dongle to connect
 *  
 *  \param mac_address The mac address of the dongle trying to connect
 *  \param ipaddr      The ipv4 ip address of the dongle trying to connect
 *  \param connected   Number of dongles already connected
 *
 *  \return 0 do not allow to connect, 1 permit to connect
 *
 *  This is a callback from the library and is called when the service
 *  locator message is received by the server
 */
static int program_authorise(char *mac_address, uint32_t ipaddr, int connected)
{
    /* We always permit dongles to connect */
    return 1;
}


/** \brief A callback to ensure that the screensaver doesn't kick in
 *
 *  \param val
 *  \param event
 *  \param arg
 *
 *  This is called via the event loop and simply marks the render
 *  surface as dirty so that updates will be sent out, this prevents
 *  the onboard screensaver from kicking in
 */
static void program_dirty_cb(int val, short event, void *arg)
{
    program_t     *program = (program_t *)arg;
    struct timeval tv;

    render_mark_dirty(program->render);

    tv.tv_sec = 15;
    tv.tv_usec = 0;
    evtimer_add(program->evtimer,&tv);
}

/** \brief Expand a filename to get the config directory. 
 *
 *  \param filename Filename to expand
 *
 *  If the filename begins with a '/' then assume that it is already
 *  expanded
 */
char *filename_expand(char *filename)
{
    static char  buf[FILENAME_MAX+1];

    if ( filename[0] == '/' ) {
        return filename;
    }
#ifdef VDR_PLUGIN
    snprintf(buf,sizeof(buf),"%s/%s",cPluginMediamvp::ConfigDirectory(),filename);
#else
    snprintf(buf,sizeof(buf),"%s%s%s",c_config_dir ? c_config_dir : "",
             c_config_dir ? "/" : "",
             filename);
#endif
    return buf;
}
