/*
 *   MediaMVP Server Library
 *
 *   (C) 2003 Dominic Morris
 *
 *   $Id: gui.c,v 1.26 2004/09/16 19:37:04 dom Exp $
 *   $Date: 2004/09/16 19:37:04 $
 *
 *
 *   Handles the GUI...
 */

#define NEED_MSGS
#include "libmvp_internal.h"
#include "gui.h"




struct _gui {
    application_t  application;      /* Controlling application spec */
    dongle_t      *dongle;
    mutex_t        mutex;
    struct event   event;
    int            fd;
    int            state;
    uint8_t        send_update;      /* Send an update out when necessary */
    char           inbuf[1024];
    int            inbufstart;
};

static void       gui_accept(int fd, short event, void *arg);
static void       gui_read(int fd, short event, void *arg);
static void       rfb_send_data(gui_t *gui);
static int        rfb_running(gui_t *gui, unsigned char *buf, int len);
static void       rfb_send_screen(gui_t *gui);
static int        deflateit(Byte *uncompr, uLong uncomprLen, Byte *compr, uLong comprLen) ;



static struct event listen_event;
static int          listen_sock;


static mvprfbmsg_t  *rdc_msgs[RDC_MESSAGE_MAX];
static mvprfbmsg_t  *rdc_msgs_22012[RDC_MESSAGE_MAX];

#define DEBUG_TIMESTAMP debug_timestamp()

static char buf[30];
static const char * debug_timestamp()
{
   struct timeval tv;
   gettimeofday(&tv, NULL);
   snprintf(buf,30,"[TS: %ld.%03ld]", tv.tv_sec, (tv.tv_usec+500)/1000);
   return buf;
}


int gui_init()
{

    if ( (listen_sock = tcp_listen(NULL,initparams->gui_port,0) ) == -1 ) {
        Dprintf(ERROR,"Cannot listen to port %d\n",initparams->gui_port);
        return -1;
    }

    event_set(&listen_event,listen_sock,EV_READ,gui_accept,&listen_event);

    event_add(&listen_event,NULL);

    Dprintf(INFO,"Listening on port %d for GUI connection\n",initparams->gui_port);

    /* Set up some structures */
    rdc_msgs[RDC_PLAY] = &rfb_media_play;
    rdc_msgs[RDC_PAUSE] = &rfb_media_pause;
    rdc_msgs[RDC_STOP] = &rfb_media_stop;
    rdc_msgs[RDC_REWIND] = &rfb_media_rewind;
    rdc_msgs[RDC_FORWARD] = &rfb_media_forward;
    rdc_msgs[RDC_VOLUP] = &rfb_media_volup;
    rdc_msgs[RDC_VOLDOWN] = &rfb_media_voldown;
    rdc_msgs[RDC_MENU] = &rfb_media_menu;
    rdc_msgs[RDC_MUTE] = &rfb_media_mute;
    rdc_msgs[RDC_SETTINGS] = &rfb_media_settings;
    rdc_msgs[RDC_DISPLAY_ON] = &rfb_media_displayon;
    rdc_msgs[RDC_DISPLAY_OFF] = &rfb_media_displayoff;
    rdc_msgs[RDC_SKIP] = &rfb_media_stop; // not very nice to user but safe
    rdc_msgs[RDC_BACK] = &rfb_media_stop; // not very nice to user but safe
    rdc_msgs[RDC_SEEK_PERCENT] = &rfb_media_stop; // not very nice to user but safe
    rdc_msgs[RFB_PONG]  = &rfb_ping;

    rdc_msgs_22012[RDC_PLAY] = &rfb_media_play_22012;
    rdc_msgs_22012[RDC_PAUSE] = &rfb_media_pause_22012;
    rdc_msgs_22012[RDC_STOP] = &rfb_media_stop_22012;
    rdc_msgs_22012[RDC_REWIND] = &rfb_media_rewind_22012;
    rdc_msgs_22012[RDC_FORWARD] = &rfb_media_forward_22012;
    rdc_msgs_22012[RDC_VOLUP] = &rfb_media_volup_22012;
    rdc_msgs_22012[RDC_VOLDOWN] = &rfb_media_voldown_22012;
    rdc_msgs_22012[RDC_MENU] = &rfb_media_menu_22012;
    rdc_msgs_22012[RDC_MUTE] = &rfb_media_mute_22012;
    rdc_msgs_22012[RDC_SETTINGS] = &rfb_media_settings_22012;
    rdc_msgs_22012[RDC_DISPLAY_ON] = &rfb_media_displayon_22012;
    rdc_msgs_22012[RDC_DISPLAY_OFF] = &rfb_media_displayoff_22012;
    rdc_msgs_22012[RDC_SKIP] = &rfb_media_skip_22012;
    rdc_msgs_22012[RDC_BACK] = &rfb_media_back_22012;
    rdc_msgs_22012[RDC_SEEK_PERCENT] = &rfb_media_percent_22012;
    rdc_msgs_22012[RFB_PONG]  = &rfb_ping;



    return 0;
}

void gui_exit()
{
    event_del(&listen_event);
    close(listen_sock);
}

static gui_t *new_gui(dongle_t *dongle)
{
    gui_t  *gui;

    gui = (gui_t*)calloc(1,sizeof(*gui));
    if ( ( initparams->open(dongle,&gui->application) ) < 0 ) {
        free(gui);
        return NULL;
    }

    /* Now set up the rest of the parameters */
    dongle->gui = gui;
    gui->dongle = dongle;
    gui->state  = VNC_VERSION;
    gui->inbufstart = 0;
    gui->send_update = TRUE;

    mutex_init(&gui->mutex,NULL);
   
    return gui;
}

void delete_gui(gui_t *gui)
{
    event_del(&gui->event);
    close(gui->fd);
    shutdown(gui->fd,SHUT_RDWR);
    gui->application.close(&gui->application);

    mutex_destroy(&gui->mutex);
    free(gui);
}

static void gui_accept(int fd, short event, void *arg)
{
    struct event  *ev = (struct event*)arg;
    dongle_t      *dongle;
    gui_t         *gui;
    char          *hostname;
    int            cfd;

    /* Reschedule event */
    event_add(ev,NULL);


    if ( ( cfd = tcp_accept(fd,&hostname) ) == -1 ) {
        return;
    }
    Dprintf(INFO,"Accepted GUI connection from host %s\n",hostname);
    dongle = dongle_return(hostname);

    if ( dongle->gui ) {
        dongle_close(dongle);
        dongle = dongle_return(hostname);
    }

  
    /* Create a new application, if we fail, then abort */
    if ( ( gui = new_gui(dongle) ) == NULL ) {
        dongle_close(dongle);
        close(cfd);
        shutdown(cfd,SHUT_RDWR);
        return;
    }

    gui->fd = cfd;

    event_set(&gui->event,cfd,EV_READ,gui_read,gui);
    event_add(&gui->event,NULL);

    rfb_send_data(gui);      /* Send out the protocol info */
}



static void gui_read(int fd, short event, void *arg)
{
    gui_t       *gui = (gui_t*)arg;
    int          r,diff = 0 ;

    /* Reschedule event */
    event_add(&gui->event,NULL);

    r = read(fd,gui->inbuf + gui->inbufstart,sizeof(gui->inbuf)-gui->inbufstart);

    /* Socket closed/error */
    if ( r == 0 || r == -1 ) {
        dongle_close(gui->dongle);
        return;
    }

    /* We only expect to get one message a time */

    gui->inbufstart += r;

    while ( gui->inbufstart > 0 ) {
        switch( gui->state ) {
        case VNC_VERSION:
        case VNC_VERSION2:
            if ( gui->inbufstart >= 12 ) {
                gui->state = VNC_AUTH;
                diff = 12;
            } else {
                diff = 0;
            }
            break;
        case VNC_AUTH:
        case VNC_AUTH2:
            diff = 1;     /* Ignore auth stuff */
            gui->state = VNC_INIT;
            break;
        case VNC_RUNNING:
            diff = rfb_running(gui,(unsigned char*)gui->inbuf,gui->inbufstart);
            break;
        default:
            break;
        }
        if ( diff <= 0 ) {
            break;
        } else if ( diff > 0 ) {
            int  rem = gui->inbufstart - diff;
            if ( rem >= 0 ) {
                memmove(gui->inbuf,gui->inbuf+diff,rem);
                gui->inbufstart = rem;
            } else {
                gui->inbufstart = 0;
            }
        }

        /* Send out pending messages */
        if ( diff != -1 ) {
            rfb_send_data(gui);
        }

        Dprintf(DEBUG, "%s finished processing.\n", DEBUG_TIMESTAMP);
    }



}

/** \internal
 *  \brief Handle incoming RFB messages from the client
 *
 * \param gui Handle to the gui channel
 * \param buf Incoming message buffer
 * \param len Length of the incoming message buffer
 *
 * \return Number of bytes processed
 */
static int rfb_running(gui_t *gui, unsigned char *buf, int len)
{
    uint8_t  *ptr = buf + 2;
    int       diff = 0;

    Dprintf(DEBUG, "%s: processing %d\n", DEBUG_TIMESTAMP, buf[0]);
    switch ( buf[0] ) {
    case RFB_SET_PIXEL_FORMAT:
        if ( len >= 20 ) {
            uint8_t   bpp;
            uint8_t   depth;
            uint8_t   endian;
            uint8_t   truecolour;
            uint16_t  redmax;
            uint16_t  greenmax;
            uint16_t  bluemax;
            uint8_t   redshift;
            uint8_t   greenshift;
            uint8_t   blueshift;

            ptr += 2;  /* Skip padding */

            bpp = *ptr++;
            depth = *ptr++;
            endian = *ptr++;
            truecolour = *ptr++;
            BUF_TO_INT16(redmax,ptr);
            BUF_TO_INT16(greenmax,ptr);
            BUF_TO_INT16(bluemax,ptr);
            redshift = *ptr++;
            greenshift = *ptr++;
            blueshift = *ptr++;

            Dprintf(DEBUG,"Received SetPixelFormat bpp = %d depth = %d endian = %d truecolour = %d redmax = %d greenmax = %d bluemax = %d redshift = %d greenshift = %d blueshift = %d\n",
                   bpp,depth,endian,truecolour,
                   redmax,greenmax,bluemax,
                   redshift,greenshift,blueshift);
            diff = 20;
        }
        break;
    case RFB_FIX_COLORMAP_ENTRIES:
        /* Shouldn't get here */
        break;
    case RFB_SET_ENCODINGS:
        if ( len >= 4 ) {
            uint16_t  num;
            uint32_t  type;
            int       i;

            BUF_TO_INT16(num,ptr);
            Dprintf(DEBUG,"Client supports %d encodings: ",num);
            if ( len >= 4 + ( num * 4 ) ) {
                for ( i = 0 ; i < num; i++ ) {
                    BUF_TO_INT32(type,ptr);
                    Dprintf(DEBUG,"type %d%s",type,i < num - 1 ? ", " : "\n");
                }
                diff = 4 + ( num * 4);
            } else {
                diff = 0;
            }
        }
        break;
    case RFB_FB_UPDATE_REQ:
        if ( len >= 10 ) {
            uint16_t    width;
            uint16_t    depth;
            uint16_t    x;
            uint16_t    y;

            BUF_TO_INT16(x,ptr);
            BUF_TO_INT16(y,ptr);
            BUF_TO_INT16(width,ptr);
            BUF_TO_INT16(depth,ptr);

            Dprintf(DEBUG,"Received %supdate request for (%d,%d) -> (%d,%d)\n",
                    buf[1] ? "incremental " :"",
                   x,y,
                   x+width,y+depth);
            diff = 10;
            gui->send_update = TRUE;
        }
        break;
    case RFB_KEY_EVENT:
        if ( len >= 8 ) {
            uint32_t   key;
            int        code;

            ptr = buf + 4;
            BUF_TO_INT32(key,ptr);

            code = key_code(key);


            if ( code != keyNone ) {
                Dprintf(DEBUG,"Received IR key %s\n",key_string(key));

                /* If power key was pressed, shut everything down.. */
                if ( code == keyPower ) {
                    dongle_close(gui->dongle);
                    return -1;
                }

                /* Just pass it back to a higher level */
                gui->application.keypress(&gui->application,code);
            }

            diff = 8;
        }
        break;
    case RFB_POINTER_EVENT:
        if ( len >= 6 ) {
            diff = 6;
        }
        break;
    case RFB_CLIENT_CUT_TEXT:
        if ( len >= 8 ) {
            uint32_t  tlen;

            ptr = buf + 4;

            BUF_TO_INT32(tlen,ptr);

            Dprintf(DEBUG,"Received client cuttext of length %d\n",tlen);

            if ( (uint32_t)len >= 8 + tlen ) {
                diff = 8 + tlen;
            }
        }
        break;
    case RFB_RDC_ACK:
        if ( len >= 10 ) {
            if ( initparams->dongle_version < 21365 ) {
                diff = 10;
            } else {
                diff = 34;
            }

            switch ( buf[1] ) {
            case RDC_SETTINGS:       /* This really is the only one we need to catch */
                if ( buf[7] != 0x00 ) {
                    if ( diff + buf[7] >= len ) {

                        /* Save those settings */
                        gui->dongle->tvmode = buf[diff+1];
                        /* output mode seems to be supplied with -1 - quite why I've no idea! */
                        gui->dongle->videooutput = buf[diff+2];
                        if ( initparams->dongle_version < 21330 ) {
                            gui->dongle->videooutput--;
                        }
                        gui->dongle->flickermode = buf[diff + 3];
                        gui->dongle->aspectratio = buf[diff + 4];
                        Dprintf(DEBUG,"TVmode %d output = %d flicker = %d aspect = %d\n",
                               gui->dongle->tvmode,
                               gui->dongle->videooutput,
                               gui->dongle->flickermode,
                               gui->dongle->aspectratio);
                        diff += buf[7];
                    } else {
                        diff = 0;  /* Not had all the data yet */
                    }
                }
                break;
            default:
                /* Kick ack events up to a higher level so we can do multiple plays */
                gui->application.ack(&gui->application,buf[1],buf,len);

                break;
            }
        }
        break;
    case RFB_PING:
        if ( len >= 2 ) {
            diff = 2;
            gui_send_message(gui,RFB_PONG);
        }
        break;
    default:
        dump_hex(INFO,buf,len);

    }

    return diff;

}


static void rfb_send_data(gui_t *gui)
{
    unsigned char  buf[100];
    unsigned char *ptr;
    int   len;


    switch ( gui->state ) {
    case VNC_VERSION:
        len = sprintf((char*)buf,"RFB %03d.%03d\012",RFB_MAJOR,RFB_MINOR);
        mutex_lock(&gui->mutex);
        write(gui->fd,buf,len);
        mutex_unlock(&gui->mutex);
        break;
    case VNC_AUTH:
        buf[0] = 0;
        buf[1] = 0;
        buf[2] = 0;
        buf[3] = RFB_AUTH_NONE;
        mutex_lock(&gui->mutex);
        write(gui->fd,buf,4);
        mutex_unlock(&gui->mutex);
        break;
    case VNC_INIT:
        ptr = buf;
        INT16_TO_BUF(gui->application.screen_width,ptr);
        INT16_TO_BUF(gui->application.screen_depth,ptr);
        *ptr++ = 24;    /* bpp */
        *ptr++ = 24;    /* depth */
        *ptr++ = 0;     /* big endian */
        *ptr++ = 0;     /* true colour */
        INT16_TO_BUF(7,ptr);  /* red max */
        INT16_TO_BUF(7,ptr);  /* green max */
        INT16_TO_BUF(3,ptr);  /* blue max */
        *ptr++ = 0;     /* red shift */
        *ptr++ = 3;     /* green shift */
        *ptr++ = 6;     /* blue shift */
        *ptr++ = 0;     /* padding */
        *ptr++ = 0;     /* padding */
        *ptr++ = 0;     /* padding */
        len = strlen("rfbwindows");
        INT32_TO_BUF(len,ptr);
        strcpy((char*)ptr,"rfbwindows");
        ptr += len;
        mutex_lock(&gui->mutex);
        write(gui->fd,buf,ptr - buf);
        mutex_unlock(&gui->mutex);
        gui_send_settings(gui,RDC_SETTINGS_GET);
        gui->state = VNC_RUNNING;
        break;
    case VNC_RUNNING:
        if ( gui->send_update ) {
            rfb_send_screen(gui);
        }
        break;
    }
}

/** \internal
 *  \brief Send a screen update to the client
 *
 * \param gui Handle to the gui channel
 *
 * We have to translate our rgb map into yuvy, compress it and then
 * send it down the rfb channel
 */
static void rfb_send_screen(gui_t *gui)
{
    uint8_t        buf[100];
    uint8_t       *surface;
    uint8_t       *comp;
    uint8_t       *ptr = buf;
    int            yuvsize;
    int            gz;

    if ( ( surface = gui->application.get_yvuy_surface(&gui->application,&yuvsize) ) == NULL ) {
        return;
    }

    gui->send_update = FALSE;
    Dprintf(DEBUG,"%s Sending update size %d\n", DEBUG_TIMESTAMP,yuvsize);
    comp = (uint8_t*)calloc(yuvsize,1);

    *ptr++ = RFB_FB_UPDATE;
    *ptr++ = 0xCC;            /* Pad */
    INT16_TO_BUF(1,ptr);      /* Number rectangles */
    INT16_TO_BUF((gui->application.screen_width - gui->application.gui_width ) / 2,ptr);
    INT16_TO_BUF((gui->application.screen_depth - gui->application.gui_depth ) / 2,ptr);

    INT16_TO_BUF(gui->application.gui_width,ptr);      /* width */
    INT16_TO_BUF(gui->application.gui_depth,ptr);      /* depth */
    INT32_TO_BUF(7,ptr);      /* The encoding type used by HCW... */


    gz = deflateit(surface,yuvsize,comp,yuvsize);
    INT32_TO_BUF(gz,ptr);
    INT16_TO_BUF(0,ptr);        /* colour format, 0 = yuv */
    INT16_TO_BUF(2,ptr);        /* bytes per pixel */

    mutex_lock(&gui->mutex);
    write(gui->fd,buf,ptr - buf);
    write(gui->fd,comp,gz);
    mutex_unlock(&gui->mutex);

    free(comp);
    free(surface);

}

#define CHECK_ERR(err, msg) { \
    if (err != Z_OK) { \
        Dprintf(ERROR, "libz error: %s error: %d\n", msg, err); \
        exit(1); \
    } \
}

/** \internal
 *  \brief deflate the yuvy buffer
 *
 *  \param uncompr The raw buffer
 *  \param uncomprLen Length of the raw buffer
 *  \param compr Buffer to place compressed raw buffer
 *  \param comprLen Length of destination buffer
 *
 *  Shamelessly stolen from a libz example
 */
static int deflateit(Byte *uncompr, uLong uncomprLen, Byte *compr, uLong comprLen)
{
    z_stream c_stream; /* compression stream */
    int err;

    c_stream.zalloc = (alloc_func)0;
    c_stream.zfree = (free_func)0;
    c_stream.opaque = (voidpf)0;

    err = deflateInit(&c_stream, Z_DEFAULT_COMPRESSION);
    CHECK_ERR(err, "deflateInit");

    c_stream.next_out = compr;
    c_stream.avail_out = (uInt)comprLen;

    c_stream.next_in = uncompr;
    c_stream.avail_in = (uInt)uncomprLen;
    err = deflate(&c_stream, Z_FINISH);
    // CHECK_ERR(err, "deflate");



    err = deflateEnd(&c_stream);
    CHECK_ERR(err, "deflateEnd");

    return c_stream.total_out;
}


/** \internal
 *  \brief Force a a screen update to the client
 *
 *  \param gui Handle to the gui channel 
 */
void gui_send_screen(gui_t *gui)
{
    rfb_send_screen(gui);
}


/** \internal
 *  \brief Send a filename to the client down the RFB channel
 *
 * \param gui Handle to the gui channel
 * \param name Filename to be send (may well be http:// form as well)
 *
 * The rfb_media_filename message is unusual in that we need to nadger
 * it before the client will accept it.
 *
 */
void gui_send_play(gui_t *gui, char *name)
{
    unsigned  char  buf[FILENAME_MAX + 34];
    mvprfbmsg_t    *msg;
    int             len = strlen(name);

    Dprintf(DEBUG, "%s Sending message %s\n", DEBUG_TIMESTAMP, name);

    if ( initparams->dongle_version < 21365 ) {
        msg = &rfb_media_play;
    } else {
        msg = &rfb_media_play_22012;
    }

    memcpy(buf,msg->msg,msg->len);

    buf[8] = len;        /* What happens here if path > 256?!?! */

    strcpy((char*)(buf + msg->len),name);

    mutex_lock(&gui->mutex);
    write(gui->fd,buf,len + msg->len);
    mutex_unlock(&gui->mutex);
}

/** \internal
 *  \brief Send a message to the client down the RFB channel
 *
 * \param gui Handle to the gui channel
 * \param msgtype Message type to send
 *
 */
void gui_send_message(gui_t *gui, int msgtype)
{
    mvprfbmsg_t **msglist;
    mvprfbmsg_t  *msg;

    Dprintf(DEBUG, "%s Sending message %d\n", DEBUG_TIMESTAMP, msgtype);

    if ( initparams->dongle_version < 21365 ) {
        msglist = rdc_msgs;
    } else {
        msglist = rdc_msgs_22012;
    }

    msg = msglist[msgtype];

    mutex_lock(&gui->mutex);
    write(gui->fd,msg->msg,msg->len);
    mutex_unlock(&gui->mutex);
}

void gui_send_percent(gui_t *gui, int msgtype, int percent)
{
    unsigned char buf[256];
    mvprfbmsg_t **msglist;
    mvprfbmsg_t  *msg;

    Dprintf(DEBUG, "%s Sending message %d, %d\n", DEBUG_TIMESTAMP, msgtype, percent);

    if ( initparams->dongle_version < 21365 ) {
        msglist = rdc_msgs;
    } else {
        msglist = rdc_msgs_22012;
    }

    msg = msglist[msgtype];

    memcpy(buf,msg->msg,msg->len);

    buf[19] = percent;

    mutex_lock(&gui->mutex);
    write(gui->fd,buf,msg->len);
    mutex_unlock(&gui->mutex);
}


/** \internal
 *  \brief Send the settings to the client down the RFB channel
 *
 *  \param gui Handle to the gui channel
 */
void gui_send_settings(gui_t *gui, int subcommand)
{
    unsigned char    buf[256];
    mvprfbmsg_t     *msg;
    int              len;

    if ( initparams->dongle_version < 21365 ) {
        msg = &rfb_media_settings;
    } else {
        msg = &rfb_media_settings_22012;
    }

    len = msg->len;
    memcpy(buf,msg->msg,len);

    buf[len + 0] = subcommand;
    buf[len + 1] = gui->dongle->tvmode;
    buf[len + 2] = gui->dongle->videooutput;
    buf[len + 3] = gui->dongle->flickermode;
    buf[len + 4] = gui->dongle->aspectratio;
    buf[len + 5] = buf[len + 6] = buf[len + 7] = 0;
    buf[len + 8] = buf[len + 9] = buf[len + 10] = buf[len + 11] = 0;

    mutex_lock(&gui->mutex);
    write(gui->fd,buf,len + 12);
    mutex_unlock(&gui->mutex);
}




/*
 * Local Variables:
 *  indent-tabs-mode:nil
 *  require-final-newline:t
 *  c-basic-offset: 4
 * End:
 */
