/* Copyright (C) 2007-2008 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
*/

/* this file implements the support of the new 'hardware control'
 * qemud communication channel, which is used by libs/hardware on
 * the system image to communicate with the emulator program for
 * emulating the following:
 *
 *   - power management
 *   - led(s) brightness
 *   - vibrator
 *   - flashlight
 */
#include "android_hw_control.h"
#include "cbuffer.h"
#include "android_qemud.h"
#include "android_utils.h"
#include <stdio.h>

#define  D(...)  VERBOSE_PRINT(hw_control,__VA_ARGS__)

/* define T_ACTIVE to 1 to debug transport communications */
#define  T_ACTIVE  0

#if T_ACTIVE
#define  T(...)  VERBOSE_PRINT(hw_control,__VA_ARGS__)
#else
#define  T(...)   ((void)0)
#endif

static void*                  hw_control_client;
static AndroidHwControlFuncs  hw_control_funcs;

#define  BUFFER_SIZE  512

typedef struct {
    CharDriverState*  cs;
    int               overflow;
    int               wanted;
    CBuffer           input[1];
    char              input_0[ BUFFER_SIZE ];
    /* note: 1 more byte to zero-terminate the query */
    char              query[ BUFFER_SIZE+1 ];
} HwControl;

/* forward */
static void  hw_control_do_query( HwControl*  h,
                                  uint8_t*    query,
                                  int         querylen );

static void
hw_control_init( HwControl*  h, CharDriverState*  cs )
{
    h->cs       = cs;
    h->overflow = 0;
    h->wanted   = 0;
    cbuffer_reset( h->input,  h->input_0,  sizeof h->input_0 );
}

static int
hw_control_can_read( void*  _hw )
{
    HwControl*  h = _hw;
    return cbuffer_write_avail( h->input );
}

static void
hw_control_read( void*  _hw, const uint8_t*  data, int  len )
{
    HwControl*  h     = _hw;
    CBuffer*    input = h->input;

    T("%s: %4d '%.*s'", __FUNCTION__, len, len, data);

    cbuffer_write( input, data, len );

    while ( input->count > 0 )
    {
        /* skip over unwanted data, if any */
        while (h->overflow > 0) {
            uint8_t*  dummy;
            int       avail = cbuffer_read_peek( input, &dummy );

            if (avail == 0)
                return;

            if (avail > h->overflow)
                avail = h->overflow;

            cbuffer_read_step( input, avail );
            h->overflow -= avail;
        }

        /* all incoming messages are made of a 4-byte hexchar sequence giving */
        /* the length of the following payload                                */
        if (h->wanted == 0)
        {
            char  header[4];
            int   len;

            if (input->count < 4)
                return;

            cbuffer_read( input, header, 4 );
            len = hex2int( header, 4 );
            if (len >= 0) {
                /* if the message is too long, skip it */
                if (len > input->size) {
                    T("%s: skipping oversized message (%d > %d)",
                    __FUNCTION__, len, input->size);
                    h->overflow = len;
                } else {
                    T("%s: waiting for %d bytes", __FUNCTION__, len);
                    h->wanted = len;
                }
            }
        }
        else
        {
            if (input->count < h->wanted)
                break;

            cbuffer_read( input, h->query, h->wanted );
            h->query[h->wanted] = 0;
            hw_control_do_query( h, h->query, h->wanted );
            h->wanted = 0;
        }
    }
}


static char*
if_starts_with( uint8_t*  buf, int buflen, const char*  prefix )
{
    int  prefixlen = strlen(prefix);

    if (buflen < prefixlen || memcmp(buf, prefix, prefixlen))
        return NULL;

    return (char*)buf + prefixlen;
}


static void
hw_control_do_query( HwControl*  h,
                     uint8_t*    query,
                     int         querylen )
{
    uint8_t*   q;

    D("%s: query %4d '%.*s'", __FUNCTION__, querylen, querylen, query );

    q = if_starts_with( query, querylen, "power:light:brightness:" );
    if (q != NULL) {
        if (hw_control_funcs.light_brightness) {
            char*  qq = strchr(q, ':');
            int    value;
            if (qq == NULL) {
                D("%s: badly formatted", __FUNCTION__ );
                return;
            }
            *qq++ = 0;
            value = atoi(qq);
            hw_control_funcs.light_brightness( hw_control_client, q, value );
        }
        return;
    }
}


void
android_hw_control_init( void*  opaque, const AndroidHwControlFuncs*  funcs )
{
    static CharDriverState*   hw_control_cs;
    static HwControl          hwstate[1];

    if (hw_control_cs == NULL) {
        CharDriverState*  cs;
        if ( android_qemud_get_channel( ANDROID_QEMUD_CONTROL, &cs ) < 0 ) {
            derror( "could not create hardware control charpipe" );
            exit(1);
        }

        hw_control_cs = cs;
        hw_control_init( hwstate, cs );
        qemu_chr_add_read_handler( cs, hw_control_can_read, hw_control_read, hwstate );

        D("%s: hw-control char pipe initialized", __FUNCTION__);
    }
    hw_control_client = opaque;
    hw_control_funcs  = funcs[0];
}
