/*
 * Server main select() loop
 *
 * Copyright (C) 1998 Alexandre Julliard
 */
#include "config.h"

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/poll.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#include <unistd.h>

#include "object.h"
#include "thread.h"
#include "server_memory.h"
#include "request.h"
#include "process.h"
#include "cdrom_eject.h"


struct timeout_user
{
    struct timeout_user  *next;       /* next in sorted timeout list */
    struct timeout_user  *prev;       /* prev in sorted timeout list */
    struct timeval        when;       /* timeout expiry (absolute time) */
    timeout_callback      callback;   /* callback function */
    void                 *private;    /* callback private data */
#if !defined(NO_SHARED_MEMORY)
    struct thread* setting_thread;    /* Thread which set the timeout (NULL = wineserver) */
#endif
};

/* Per process variables */
int alarming;
/* End per process variables */

START_FILE_STATIC_VARIABLES( select )
  struct object **poll_users;          /* users array */
  struct pollfd *pollfd;               /* poll fd array */
  int nb_users;                        /* count of array entries actually in use */
  int active_users;                    /* current number of active users */
  int allocated_users;                 /* count of allocated entries in the array */
  struct object **freelist;            /* list of free entries in the array */
                                                                                                                                                                    
  struct timeout_user *timeout_head;   /* sorted timeouts list head */ // PH: FIXME: reg mem?
  struct timeout_user *timeout_tail;   /* sorted timeouts list tail */ // PH: FIXME: reg mem?
END_FILE_STATIC_VARIABLES( select )


/* add a user and return an opaque handle to it, or -1 on failure */
int add_select_user( struct object *obj )
{
    int ret;

/* PH: FIXME: Unless we can figure out a way to deal with the poll list stuff. */
assert( is_wineserver );

    if (SERVER_STATIC_VARIABLE( select, freelist ))
    {
        ret = SERVER_STATIC_VARIABLE( select, freelist ) - SERVER_STATIC_VARIABLE( select, poll_users );
        SERVER_STATIC_VARIABLE( select, freelist ) = (struct object **)SERVER_STATIC_VARIABLE( select, poll_users[ret] );
    }
    else
    {
        if (SERVER_STATIC_VARIABLE( select, nb_users ) == SERVER_STATIC_VARIABLE( select, allocated_users ))
        {
            struct object **newusers;
            struct pollfd *newpoll;
            int new_count = SERVER_STATIC_VARIABLE( select, allocated_users ) ? (SERVER_STATIC_VARIABLE( select, allocated_users ) + SERVER_STATIC_VARIABLE( select, allocated_users ) / 2) : 16;
            if (!(newusers = mem_realloc( SERVER_STATIC_VARIABLE( select, poll_users ), new_count * sizeof(*SERVER_STATIC_VARIABLE( select, poll_users )) ))) return -1;
            if (!(newpoll = mem_realloc( SERVER_STATIC_VARIABLE( select, pollfd ), new_count * sizeof(*SERVER_STATIC_VARIABLE( select, pollfd )) )))
            {
                if (SERVER_STATIC_VARIABLE( select, allocated_users ))
                    SERVER_STATIC_VARIABLE( select, poll_users ) = newusers;
                else
                    mem_free( newusers );
                obj->select = -1;
                return -1;
            }
            SERVER_STATIC_VARIABLE( select, poll_users ) = newusers;
            SERVER_STATIC_VARIABLE( select, pollfd ) = newpoll;
            SERVER_STATIC_VARIABLE( select, allocated_users ) = new_count;
        }
        ret = SERVER_STATIC_VARIABLE( select, nb_users )++;
    }

    /* PH: NOTE: This is actually setting up the pollfd for regular use. */
    /*           Making this local is the right way to do it... but we need to be able to 
     *           reference back through poll_users[ret]->select. Does this mean that we need to
     *           actually keep pollfd in sync somehow. 
     * Q1: Do we need to keep the sizes in sync?
     *     A: No. We only need to be able to relate obj->select to an entry.
     * Q1a: How to do so? Or is keeping the sizes in sync the only way?
     *
     *
     * Q2: Should we be modifying pollfd here?
     *     A: We need to modify the wineserver pollfd for the fact that we
     *        can get an EPIPE or similar through a POLLHUP/POLLERR. For this reason
     *        we need to have something waiting on it at all times (ie windows handling underlying
     *        problems).
     * Q2a: How to do initiate contextual fds from a client thread?
     * Q2b: How do ensure that we always have a listener to each fd?
     *     A: If !is_wineserver then kick the wineserver? However we still have a race and can't protect the shared memory.
     *
     *
     * Q3: Do we have the same problems with timeouts?
     *
     *
     * Q4:
     * NOTE: Only wineserver can add_select_user - this will ensure that we have something always listening. (*undesirable*)
     *       Only wineserver can remove_select_user (*undesirable*)
     *       set_select_event - ??
     *       check_select_events - ??
     */
    SERVER_STATIC_VARIABLE( select, pollfd[ret].fd ) = get_object_fd( obj );
    SERVER_STATIC_VARIABLE( select, pollfd[ret].events ) = 0;
    SERVER_STATIC_VARIABLE( select, pollfd[ret].revents ) = 0;

    SERVER_STATIC_VARIABLE( select, poll_users[ret] ) = obj;
    obj->select = ret;
    SERVER_STATIC_VARIABLE( select, active_users )++;
    return ret;
}

/* remove an object from the select list and close its fd */
void remove_select_user( struct object *obj )
{
    int user = obj->select;
    assert( user >= 0 );
    assert( SERVER_STATIC_VARIABLE( select, poll_users[user] ) == obj );


/* PH: FIXME: Unless we can figure out a way to deal with the poll list stuff. */
    if( !is_wineserver )
    { 
         send_remove_select_to_wineserver( obj );
         fprintf( stderr, "remove_select_user badness\n" );
         return;
    }

    /* Only subtract from active users if the fd wasn't already -1.
       This can be done from set_select_events as well, 
       so we be sure not to double decrement. */
    if (SERVER_STATIC_VARIABLE( select, pollfd[user].fd ) != -1)
    {
       SERVER_STATIC_VARIABLE( select, active_users )--;
    }
    SERVER_STATIC_VARIABLE( select, pollfd[user].fd ) = -1;
    SERVER_STATIC_VARIABLE( select, pollfd[user].events ) = 0;
    SERVER_STATIC_VARIABLE( select, pollfd[user].revents ) = 0;
    SERVER_STATIC_VARIABLE( select, poll_users[user] ) = (struct object *)SERVER_STATIC_VARIABLE( select, freelist );
    SERVER_STATIC_VARIABLE( select, freelist ) = &SERVER_STATIC_VARIABLE( select, poll_users[user] );
    obj->select = -1;
}

/* change the fd and events of an object */
void change_select_fd( struct object *obj, int fd, int events )
{
    int user = obj->select;

/* PH: FIXME: Unless we can figure out a way to deal with the poll list stuff. */
if( !is_wineserver ) { fprintf( stderr, "change_select_fd crashing\n" ); *(int*)0x89 = 89; }
assert( is_wineserver );

    assert( SERVER_STATIC_VARIABLE( select, poll_users[user] ) == obj );
    SERVER_STATIC_VARIABLE( select, pollfd[user].fd ) = fd;
    SERVER_STATIC_VARIABLE( select, pollfd[user].events ) = events;
    set_object_fd( obj, fd );
}

/* set the events that select waits for on this fd */
void set_select_events( struct object *obj, int events, struct thread* thread )
{
extern int is_select;
extern int is_wakeup;
extern int is_thread_death;

    int user = obj->select;
    assert( SERVER_STATIC_VARIABLE( select, poll_users[user] ) == obj );

    if( thread == NULL )
    {
        // assert( is_wineserver );
        if( !is_wineserver )
        {
            send_select_change_to_wineserver( obj, events );
            return;
        }
    }

    if( thread )
    {
       struct pollfd* pollfd_array = (struct pollfd*)thread->pollfd_array;
       int   old_count = thread->pollfd_array_size;

       /* It's probably not a big deal to be changing a poll list if we're waking
        * the thread up... or if this is a select call as presumably the client is
        * potentially going to be rereading the poll lists.
        */
       if( !is_select && !is_wakeup && !is_thread_death )
       {
           info_msg( ALWAYS, thread, 
                      "Bad stuff: client ignore setting select events for %p to %x\n",
                      obj, events );
       }

       /* Figure out the size of the pollfd and expand if necessary */
       if( SERVER_STATIC_VARIABLE( select, allocated_users ) > thread->pollfd_array_size )
       {
           pollfd_array = mem_realloc( pollfd_array, sizeof( *pollfd_array ) * SERVER_STATIC_VARIABLE( select, allocated_users ) );

           /* Initialize the new stuff. */
           for( ; thread->pollfd_array_size < SERVER_STATIC_VARIABLE( select, allocated_users ); thread->pollfd_array_size++ )
           {
               pollfd_array[ thread->pollfd_array_size ].fd      = -1;
               pollfd_array[ thread->pollfd_array_size ].events  = 0;
               pollfd_array[ thread->pollfd_array_size ].revents = 0;
           }

           if( debug_level )
               fprintf( stderr, "Expanding pollfd to %d from %d\n", thread->pollfd_array_size, old_count );
       }

       if( events == -1 || events == 0 )  /* stop waiting on this fd completely */
       {
           if( pollfd_array[ user ].fd != -1 )assert( thread->active_users-- );

           pollfd_array[ user ].fd     = -1;
           pollfd_array[ user ].events = 0;
           pollfd_array[ user ].revents = 0;

           if( debug_level )
               fprintf( stderr, "Turn off %d. %d users remaining\n", user, thread->active_users );
       }
       else
       {
           int new_fd = get_object_fd( obj );
           int fd_changed = ( pollfd_array[ user ].fd != new_fd );
           if( pollfd_array[ user ].fd == -1 ) thread->active_users++;
           pollfd_array[ user ].fd      = new_fd;
           pollfd_array[ user ].events  = events; 
           pollfd_array[ user ].revents = 0; 
           info_msg( WITH_DEBUG, current, "User %d. *fd* %s %d. Events 0x%x. Now %d users\n",
                                user, fd_changed ? "changed to" : "still", pollfd_array[ user ].fd,
                                events, thread->active_users );
       }

       thread->pollfd_array = pollfd_array;

       return;
    }

    /* Wineserver case */
    if (events == -1)  /* stop waiting on this fd completely */
    {
        /* Only subtract from our active users if the fd wasn't already -1. 
           This can be done from remove_select_user as well, 
           so we be sure not to double decrement. */
        if (SERVER_STATIC_VARIABLE( select, pollfd[user].fd ) != -1)
        {
            SERVER_STATIC_VARIABLE( select, active_users )--;
        }
        SERVER_STATIC_VARIABLE( select, pollfd[user].fd ) = -1;
        SERVER_STATIC_VARIABLE( select, pollfd[user].events ) = 0;
        SERVER_STATIC_VARIABLE( select, pollfd[user].revents ) = 0;
    }
    else if (SERVER_STATIC_VARIABLE( select, pollfd[user].fd ) != -1) SERVER_STATIC_VARIABLE( select, pollfd[user].events ) = events;
}

/* check if events are pending */
int check_select_events( int fd, int events )
{
    struct pollfd pfd;

    pfd.fd     = fd;
    pfd.events = events;
    return poll( &pfd, 1, 0 ) > 0;
}

void update_pollfd_for_thread( void* pollfd, int* pollfds, int count )
{
    struct shm_pollfd* pfd = (struct shm_pollfd*)pollfd;
    unsigned int i;

    /* PH: FIXME: Could optimize by packing object array to the number of real fds we have */
    for( i = 1; i <= count; i++ )
    {
        int select = pfd->objects[i]->select;

        /* Is there actually something to wait for that we should care about? */
        if( select != -1 && select < current->pollfd_array_size )
        {
            pfd->fd_array[i].fd = current->pollfd_array[select].fd;
            pfd->fd_array[i].events = current->pollfd_array[select].events;
            pfd->fd_array[i].revents = 0;
        }
        else
        {
            pfd->fd_array[i].fd = -1;
            pfd->fd_array[i].events = 0;
            pfd->fd_array[i].revents = 0;
        }
    }

    *pollfds = count;
}

/* add a timeout user */
struct timeout_user *add_timeout_user( struct timeval *when, timeout_callback func, void *private )
{
    struct timeout_user *user;
    struct timeout_user *pos;
    struct timeout_user** timeout_head;
    struct timeout_user** timeout_tail;
    int count;

    extern int is_select;

    if (!(user = mem_alloc( sizeof(*user) ))) return NULL;
    user->when     = *when;
    user->callback = func;
    user->private  = private;

    if( is_wineserver || !is_select )
    {
       timeout_head = &SERVER_STATIC_VARIABLE( select, timeout_head );
       timeout_tail = &SERVER_STATIC_VARIABLE( select, timeout_tail );
       user->setting_thread = NULL;
    }
    else
    {
       timeout_head = &current->timeout_head;
       timeout_tail = &current->timeout_tail;
       user->setting_thread = current;
    }

    if( debug_level )
        fprintf( stderr, " Adding %p to %p/%p %p/%p\n",
                             user, timeout_head, *timeout_head,
                             timeout_tail, *timeout_tail );

    /* Now insert it in the linked list */

    for (count = 0, pos = *timeout_head; pos; pos = pos->next, count++)
        if (!time_before( &pos->when, when )) break;

    /* If we're adding to the wineserver's timeout list, but we're not the wineserver
     * we need to ensure that the timeout will happen. So, if we're adding to the front
     * of the timeout list we need to make sure the wineserver handles everything correctly.
     */
    if( !count && !is_wineserver && !is_select ) 
    {
        send_timeout_to_wineserver( when, func, private );
        if( debug_level ) fprintf( stderr, "AHAHAH. Not poll!\n" ); 
    }

    if (pos)  /* insert it before 'pos' */
    {
        if ((user->prev = pos->prev)) user->prev->next = user;
        else *timeout_head = user;
        user->next = pos;
        pos->prev = user;
    }
    else  /* insert it at the tail */
    {
        user->next = NULL;
        if (*timeout_tail) (*timeout_tail)->next = user;
        else *timeout_head = user;
        user->prev = *timeout_tail;
        *timeout_tail = user;
    }

    return user;
}

/* remove a timeout user */
void remove_timeout_user( struct timeout_user *user )
{
    struct timeout_user** timeout_head;
    struct timeout_user** timeout_tail;

    if( user->setting_thread == NULL )
    {
       /* Removing something from the wineserver timeout list isn't a big deal.
        * When the wineserver times out it will just perform a noop. To inform the
        * wineserver that there's a change to the list it would have to get woken 
        * up and do real work, so this is just as good.
        */
       timeout_head = &SERVER_STATIC_VARIABLE( select, timeout_head );
       timeout_tail = &SERVER_STATIC_VARIABLE( select, timeout_tail );
    }
    else
    {
       timeout_head = &user->setting_thread->timeout_head;
       timeout_tail = &user->setting_thread->timeout_tail;
    }

    if( debug_level ) 
       fprintf( stderr, "Removing %p to %p/%p %p/%p\n", user, timeout_head, *timeout_head, timeout_tail, *timeout_tail );

    if (user->next) user->next->prev = user->prev;
    else *timeout_tail = user->prev;

    if (user->prev) user->prev->next = user->next;
    else *timeout_head = user->next;

    mem_free( user );
}

/* add a timeout in milliseconds to an absolute time */
void add_timeout( struct timeval *when, int timeout )
{
    if (timeout)
    {
        long sec = timeout / 1000;
        if ((when->tv_usec += (timeout - 1000*sec) * 1000) >= 1000000)
        {
            when->tv_usec -= 1000000;
            when->tv_sec++;
        }
        when->tv_sec += sec;
    }
}

/* handle the next expired timeout */
static void handle_timeout(struct timeout_user** timeout_head, struct timeout_user** timeout_tail)
{
    struct timeout_user *user = *timeout_head;
    *timeout_head = user->next;

    info_msg( WITH_DEBUG, current, "Calling timeout func %p(%d) %p/%p %p/%p\n",
                           user->callback, is_wineserver,
                           timeout_head, *timeout_head, timeout_tail, *timeout_tail );


    if (user->next) user->next->prev = user->prev;
    else *timeout_tail = user->prev;

    get_callback( user->callback )( user->private );

    if( debug_level )
        fprintf( stderr, "handle_timeout callback completed\n" );

    mem_free( user );
}

int shm_handle_timeout(void)
{
    struct timeval now;
    struct timeout_user** timeout_head;
    struct timeout_user** timeout_tail;
    int count = 0;

    if( debug_level )
        fprintf( stderr, "PH: NOTE: Handling timeout user for %p\n", current );

    timeout_head = &current->timeout_head;
    timeout_tail = &current->timeout_tail;

    gettimeofday( &now, NULL );
    while (*timeout_head)
    {
        if (!time_before( &now, &(*timeout_head)->when )) 
        {
            handle_timeout( timeout_head, timeout_tail );
            count++;
        }
        else break;
    }
    return count;
}

/* PH: FIXME: Handlers are server (space?) only */
/* SIGHUP handler */
static void sighup_handler()
{
#ifdef DEBUG_OBJECTS
    dump_objects();
#endif
}

/* SIGTERM handler */
static void sigterm_handler()
{
    close_registry();
    exit(1);
}

/* SIGALRM handler */
static void sigalrm_handler()
{
    alarming++;
}

/* SIGUSR2 handler - used for force eject */
static void sigusr2_handler()
{
    signal_eject_cdrom( );
}

/* server main loop PH: FIXME: server only */
void select_loop(void)
{
    int ret;
    sigset_t sigset;
    struct sigaction action;

    /* block the signals we use */
    sigemptyset( &sigset );
    sigaddset( &sigset, SIGCHLD );
    sigaddset( &sigset, SIGHUP );
    sigaddset( &sigset, SIGINT );
    sigaddset( &sigset, SIGQUIT );
    sigaddset( &sigset, SIGTERM );
    sigaddset( &sigset, SIGUSR2 );
    sigprocmask( SIG_BLOCK, &sigset, NULL );

    /* set the handlers */
    action.sa_mask = sigset;
    action.sa_flags = 0;
    action.sa_handler = sigchld_handler;
    sigaction( SIGCHLD, &action, NULL );
    action.sa_handler = sighup_handler;
    sigaction( SIGHUP, &action, NULL );
    action.sa_handler = sigterm_handler;
    sigaction( SIGINT, &action, NULL );
    sigaction( SIGQUIT, &action, NULL );
    sigaction( SIGTERM, &action, NULL );
    action.sa_handler = sigalrm_handler;
    sigaction( SIGALRM, &action, NULL );
    action.sa_handler = sigusr2_handler;
    sigaction (SIGUSR2, &action, NULL);

    while (SERVER_STATIC_VARIABLE( select, active_users ))
    {
        long diff = -1;
        
        if (SERVER_STATIC_VARIABLE( select, timeout_head ))
        {
            struct timeval now;
            gettimeofday( &now, NULL );
            while (SERVER_STATIC_VARIABLE( select, timeout_head ))
            {
                if (!time_before( &now, &SERVER_STATIC_VARIABLE( select, timeout_head->when ) ))
                   handle_timeout( &SERVER_STATIC_VARIABLE( select, timeout_head ), &SERVER_STATIC_VARIABLE( select, timeout_tail ));
                else
                {
                    diff = (SERVER_STATIC_VARIABLE( select, timeout_head->when.tv_sec ) - now.tv_sec) * 1000
                            + (SERVER_STATIC_VARIABLE( select, timeout_head->when.tv_usec ) - now.tv_usec) / 1000;
                    break;
                }
            }
        }

        release_shm_server_semaphore();

	// PH: FIXME: Move into release_shm_server?
        sigprocmask( SIG_UNBLOCK, &sigset, NULL );

        /* PH: FIXME: We now have a problem with this! Make a copy of pollfd and nb_users. */
        /* Note: we assume that the signal handlers do not manipulate the pollfd array
         *       or the timeout list, otherwise there is a race here.
         */
        ret = poll( SERVER_STATIC_VARIABLE( select, pollfd ), SERVER_STATIC_VARIABLE( select, nb_users ), diff );

	// PH: FIXME: Move into acquire_shm_server?
        acquire_shm_server_semaphore_exclusive(); 

        /* In some sense there is a race here. We could acquire the sema and then get killed */
        /* However, since we don't handle that correctly yet it doesn't matter much. */
        sigprocmask( SIG_BLOCK, &sigset, NULL );

        if (ret > 0)
        {
            int i;
            for (i = 0; i < SERVER_STATIC_VARIABLE( select, nb_users ); i++)
            {
                if (SERVER_STATIC_VARIABLE( select, pollfd[i].revents ))
                {
                    const struct object_ops* ops = get_ops(SERVER_STATIC_VARIABLE( select, poll_users[i]->ops));
		    if( (SERVER_STATIC_VARIABLE( select, pollfd[i].revents ) & POLLNVAL) == 0 )
                        ops->poll_event( SERVER_STATIC_VARIABLE( select, poll_users[i] ), SERVER_STATIC_VARIABLE( select, pollfd[i].revents ) );
                    if (!--ret) break;
                }
            }
        }
    }
}

void shm_poll_hit( const struct shm_pollfd* pfd, unsigned int hits, unsigned int size )
{
    int i;
    const struct pollfd* pollfd_copy = pfd->fd_array;

    for( i=1; hits && i <= size; i++ )
    {
        int revents = pollfd_copy[i].revents;
        if( revents )
        {
            struct object* obj = (struct object*)pfd->objects[i];
            const struct object_ops* ops = get_ops(obj->ops);
            //fprintf( stderr, "Poll hit on %d with revents=0x%x\n", i, revents );
            if( (revents & POLLNVAL) == 0 )
                ops->poll_event( obj, revents );
            hits--;
        }
    }

    if( hits ) fprintf( stderr, "ERROR. Not all hits resolved\n" );
}
