/* Stuff for distributing the commands from a shm client to the wineserver 
 *
 * Copyright 2004 TransGaming Technologies
 *
 */

#include <stdio.h>
#include <unistd.h>

#include "request.h"
#include "server_memory.h"
#include "object.h"


struct distributed_commands
{
    struct object       obj;            /* object header */
    context_fd          client_pipe_fd; /* client end of pipe */ 
};


START_FILE_STATIC_VARIABLES(distributed)
  struct distributed_commands* distributed_command;
END_FILE_STATIC_VARIABLES(distributed)


static void get_distributed_msg(void);
static int send_modify_select_to_wineserver( struct object* obj, int events, int action );
static void distributed_commands_dump( struct object *obj, int verbose );
static void distributed_commands_poll_event( struct object *obj, int event );
static void distributed_commands_destroy( struct object *obj );

static const struct object_ops distributed_commands_ops =
{
    sizeof(struct distributed_commands),  /* size */
    distributed_commands_dump,            /* dump */
    no_add_queue,                         /* add_queue */
    NULL,                                 /* remove_queue */
    NULL,                                 /* signaled */
    NULL,                                 /* satisfied */
    NULL,                                 /* get_poll_events */
    distributed_commands_poll_event,      /* poll_event */
    no_get_fd,                            /* get_fd */
#if defined( CONTEXTUAL_FD )
    no_get_ctx_fd,                        /* get_ctx_fd */
#endif
    no_flush,                             /* flush */
    no_get_file_info,                     /* get_file_info */
    NULL,                                 /* queue_async */
    distributed_commands_destroy          /* destroy */
};
const struct object_ops* get_this_process_distributed_commands_ops(void)
  { return &distributed_commands_ops; }

static void distributed_commands_destroy( struct object *obj )
{
    assert( obj->ops == distributed_commands_ops_tag );
}

/* handle a socket event */
static void distributed_commands_poll_event( struct object *obj, int event )
{
    struct distributed_commands* command = (struct distributed_commands*)obj;
    assert( obj->ops == distributed_commands_ops_tag );

    assert( command == SERVER_STATIC_VARIABLE( distributed, distributed_command ) );  /* there is only one master socket */

    if (event & (POLLERR | POLLHUP))
    {
        /* this is not supposed to happen */
        if( debug_level ) fprintf( stderr, "wineserver: Error on distributed command: %x\n", event );
        release_object( obj );
    }
    else if (event & POLLIN)
    {
        get_distributed_msg();
    }
}

static void distributed_commands_dump( struct object *obj, int verbose )
{
    struct distributed_commands* command = (struct distributed_commands*)obj;
    assert( obj->ops == distributed_commands_ops_tag );
    fprintf( stderr, "DO fd=%d ctx_fd=%u\n",
                    get_object_fd_for_server( &command->obj ),
                    command->client_pipe_fd );
}

void create_distributed_command(void)
{
    int pipefd[2];
  
    if( pipe( pipefd ) != 0 )
    {
        fatal_perror( "Unable to create distributed pipe: " );
    }

    if (!(SERVER_STATIC_VARIABLE( distributed, distributed_command ) = alloc_object( distributed_commands_ops_tag, pipefd[0] )))
        fatal_error( "out of memory\n" );
    set_select_events( &SERVER_STATIC_VARIABLE( distributed, distributed_command )->obj, POLLIN, NULL );

    init_context_fd( &SERVER_STATIC_VARIABLE( distributed, distributed_command )->client_pipe_fd, pipefd[1] );
    grab_context_fd( SERVER_STATIC_VARIABLE( distributed, distributed_command )->client_pipe_fd, NULL );
}

void destroy_distributed_command(void)
{
    release_context_fd( SERVER_STATIC_VARIABLE( distributed, distributed_command )->client_pipe_fd, NULL, NULL );
}

enum msg_type { TIMEOUT_MSG, SELECT_MSG };

struct send_timeout_msg
{
/* There is no content since there is no guarantee that it hasn't already
 * expired. */    
};

enum select_action { SELECT_CHANGE, SELECT_REMOVE, SELECT_ADD };

struct send_select_msg
{
    int action;
    struct object* obj;
    int events;
};

struct send_msg
{
    int type;
    void* thread; 
    union
    {
        struct send_timeout_msg tm;
        struct send_select_msg sm;
    } u;
};

/* Sending this message to the wineserver is really just to make sure
 * that the wineserver knows about it. We've already added it to the 
 * wineserver timeout list, but the wineserver will not be aware until
 * the next time it sets up a poll call.
 */
int send_timeout_to_wineserver( struct timeval *when, timeout_callback func,
                                 void *private)
{
    context_fd ctx_fd = SERVER_STATIC_VARIABLE( distributed, distributed_command )->client_pipe_fd;
    int fd;
    struct send_msg msg;
    int ret;

assert( !is_wineserver );

    fd = get_context_fd( ctx_fd );

    if( fd == -1 ) fatal_error( "send_timeout_to_wineserver\n" );

    msg.type = TIMEOUT_MSG;
    msg.thread = current;

    if( debug_level )
        fprintf( stderr, "Sending timeout msg to wineserver from %p\n",
                  current );

    ret = write( fd, &msg, sizeof( msg ) );

    if( ret != sizeof( msg ) ) fatal_perror( "send_timeout_to_wineserver: " );

    return 1;
}

int send_remove_select_to_wineserver( struct object* obj )
{
   fprintf( stderr, "send_remove_select_to_wineserver: Expect problems. This should be synchronous but isn't\n" );
   return send_modify_select_to_wineserver( obj, -1, SELECT_REMOVE );
}

int send_select_change_to_wineserver( struct object* obj, int events )
{
   return send_modify_select_to_wineserver( obj, events, SELECT_CHANGE );
}

static int
send_modify_select_to_wineserver( struct object* obj, int events, int action )
                       
{
    context_fd ctx_fd = SERVER_STATIC_VARIABLE( distributed, distributed_command )->client_pipe_fd;
    int fd;
    struct send_msg msg;
    int ret;

assert( !is_wineserver );

    fd = get_context_fd( ctx_fd );
    if( fd == -1 ) fatal_error( "send_timeout_to_wineserver\n" );

    msg.type = SELECT_MSG;
    msg.thread = current;
    msg.u.sm.obj = obj;
    msg.u.sm.events = events;
    msg.u.sm.action = action;

    if( debug_level )
        fprintf( stderr, "0x%08x: Sending select change msg to wineserver\n",
                          (unsigned int)current );

    ret = write( fd, &msg, sizeof( msg ) );

    if( ret != sizeof( msg ) ) fatal_perror( "send_timeout_to_wineserver: " );

    return 1;
}

static void get_distributed_msg(void)
{
    struct send_msg msg;
    int fd;
    int ret;

    fd = get_object_fd( &SERVER_STATIC_VARIABLE( distributed, distributed_command )->obj );

    ret = read( fd, &msg, sizeof( msg ) );

    if( ret != sizeof( msg ) ) fatal_perror( "get_distributed_msg: " );

    switch (msg.type)
    {
        case TIMEOUT_MSG:
            /* Nothing to do for this. We've woken up the wineserver so it will reread the
             * timeout list. */
            if( debug_level ) fprintf( stderr, "distributed command: Got timeout msg\n" );

            break;

        case SELECT_MSG:
            /* Handle the request to do something to the select list */
            fprintf( stderr, "distributed command: Got set select msg action=%d obj=%p events=%d\n",
                             msg.u.sm.action, msg.u.sm.obj, msg.u.sm.events );

            if( msg.u.sm.action == SELECT_CHANGE )
            {
                set_select_events( msg.u.sm.obj, msg.u.sm.events, NULL );
            }
            else if ( msg.u.sm.action == SELECT_REMOVE )
            {
                remove_select_user( msg.u.sm.obj );
            }
            else if ( msg.u.sm.action == SELECT_ADD )
            {
                add_select_user( msg.u.sm.obj );
            }
            else
            {
                fatal_error( "distributed command: Invalid set select msg action %d\n", msg.u.sm.action );
            }

            break;

        default:
            fatal_error( "get_distributed_msg unknown msg %d\n", msg.type );
            break;
    }
}

