/*
 * Server-side objects
 * These are the server equivalent of K32OBJ
 *
 * Copyright (C) 1998 Alexandre Julliard
 */

#include <assert.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "thread.h"
#include "process.h"
#include "unicode.h"
#include "mem_malloc.h"
#include "server_memory.h"
#include "object.h"
#include "request.h"

#define CONTEXT_DEBUG WITH_EXTREME_DEBUG

#define NAME_HASH_SIZE 37

START_FILE_STATIC_VARIABLES(object)
  struct object_name *names[NAME_HASH_SIZE];
#ifdef DEBUG_OBJECTS
  struct object *first;
#endif
  struct context_entry* server_context_fd_list;
  unsigned int server_context_fd_list_size;
END_FILE_STATIC_VARIABLES(object)

static void close_context_fd_internal( context_fd* ctx_fd, struct process* process );

static void grow_context_fd_list( context_fd ctx_fd, struct context_entry** list, unsigned int* size )
{
    struct context_entry* context_fd_list = *list;
    unsigned int context_fd_list_size = *size;

    /* Grow list */
    if( ctx_fd >= context_fd_list_size )
    {
      unsigned int orig_size;
      unsigned int i;

      /* Grow the list */
      orig_size = context_fd_list_size;

      context_fd_list_size = max( context_fd_list_size+20, ctx_fd+1 );
      context_fd_list = mem_realloc( context_fd_list, sizeof( *context_fd_list ) * context_fd_list_size );

      for( i = orig_size; i < context_fd_list_size; i++ )
      {
        context_fd_list[i].fd = -1;
        context_fd_list[i].valid = 0;
        context_fd_list[i].refcount = 0;
      }

      *list = context_fd_list;
      *size = context_fd_list_size;

      info_msg( CONTEXT_DEBUG, current, "Growing ctx fd list @ %p to %u \n", *list, *size );
    }

}

void grab_context_fd( context_fd ctx_fd, struct process* process )
{
    struct context_entry* context_fd_list;
    unsigned int context_fd_list_size;

    if( ctx_fd == INVALID_CTX_FD ) return;
 
    if( process == NULL )
    {
        grow_context_fd_list( ctx_fd, &SERVER_STATIC_VARIABLE( object, server_context_fd_list),
                                  &SERVER_STATIC_VARIABLE( object, server_context_fd_list_size ) );
        context_fd_list = SERVER_STATIC_VARIABLE( object, server_context_fd_list );
        context_fd_list_size = SERVER_STATIC_VARIABLE( object, server_context_fd_list_size );
    }
    else
    {
        grow_context_fd_list( ctx_fd, &process->context_fd_list, &process->context_fd_list_size );
        context_fd_list = process->context_fd_list;
        context_fd_list_size = process->context_fd_list_size;
    }

    /* This entry doesn't need to be valid yet, nor does the fd need to be set. We just haven't
     * translated the appropriate fd if this is the case.
     */
    context_fd_list[ ctx_fd ].refcount++;
    if( debug_level >= CONTEXT_DEBUG )
       fprintf( stderr, "grab_context_fd: *ctx fd* %u in %p is now %u\n",
                  ctx_fd, process, context_fd_list[ ctx_fd ].refcount );
}

void release_context_fd( context_fd ctx_fd, struct process* process, int* fd )
{
    struct context_entry* context_fd_list;
    unsigned int context_fd_list_size;

    if( fd ) *fd = -1;

    if( ctx_fd == INVALID_CTX_FD ) return;
 
    if( process == NULL )
    {
        context_fd_list = SERVER_STATIC_VARIABLE( object, server_context_fd_list );
        context_fd_list_size = SERVER_STATIC_VARIABLE( object, server_context_fd_list_size );
    }
    else
    {
        context_fd_list = process->context_fd_list;
        context_fd_list_size = process->context_fd_list_size;
    }

    /* NOTE: the ctx fd does not need to be valid when dropping a reference */
    assert( ctx_fd < context_fd_list_size );
    assert( context_fd_list[ ctx_fd ].refcount );

    --context_fd_list[ ctx_fd ].refcount;
    if( debug_level >= CONTEXT_DEBUG )
        fprintf( stderr, "release_context_fd: *ctx fd* %u in %p is now %u\n",
                            ctx_fd, process, context_fd_list[ ctx_fd ].refcount );

    if( context_fd_list[ ctx_fd ].refcount != 0 ) return;

    if( fd ) *fd = context_fd_list[ ctx_fd ].fd;
    close_context_fd_internal( &ctx_fd, process );
}


/* process == NULL means use present context otherwise specific context */
static int get_context_fd_internal( context_fd ctx_fd, struct process* process, int translate )
{
#if defined( CONTEXTUAL_FD )
  int fd;
  struct context_entry* context_fd_list = SERVER_STATIC_VARIABLE( object, server_context_fd_list );
  unsigned int context_fd_list_size = SERVER_STATIC_VARIABLE( object, server_context_fd_list_size );

  /* Is there something set? */
  if( ctx_fd == INVALID_CTX_FD ) return -1;


  if( process || !is_wineserver )
  {
    if( ctx_fd >= current->process->context_fd_list_size )
    {
      grow_context_fd_list( ctx_fd, &current->process->context_fd_list,
                                     &current->process->context_fd_list_size );
    }

    context_fd_list = current->process->context_fd_list;
    context_fd_list_size = current->process->context_fd_list_size;

    if( ( !context_fd_list[ ctx_fd ].valid ) && 
        ( context_fd_list[ ctx_fd ].fd != -1 ) )
    {
       /* PH: FIXME: This is probably a temp fix */
       if( debug_level >= CONTEXT_DEBUG )
           fprintf( stderr, "Invalidating entry %u *fd* %d\n", ctx_fd, context_fd_list[ ctx_fd ].fd );
       close( context_fd_list[ ctx_fd ].fd );
       context_fd_list[ ctx_fd ].fd = -1;
    }

    if( context_fd_list[ ctx_fd ].fd == -1)
    {
        if( translate )
        {
            context_fd_list[ ctx_fd ].fd = translate_server_fd( ctx_fd ); 
            context_fd_list[ ctx_fd ].valid = 1;
        }
    }
  }

  /* fd_list is always valid in server context under correctly behaving program */
  fd = context_fd_list[ ctx_fd ].fd;

  info_msg( CONTEXT_DEBUG, current, "get: ctx_fd == %d (%d) (process=%p wineserver=%d)\n",
               ctx_fd, fd, process, is_wineserver );

  return fd;
#else
  return ctx_fd;
#endif
}

int get_context_fd( context_fd ctx_fd )
{
    return get_context_fd_internal( ctx_fd, NULL, 1 );
}

int get_context_untranslated_fd( context_fd ctx_fd, struct process* process )
{
    return get_context_fd_internal( ctx_fd, process, 0 );
}


int get_object_fd( const struct object* obj )
{
  return get_context_fd( obj->fd );
}

int get_object_fd_for_server( const struct object* obj )
{
  assert( is_wineserver );

  return get_object_fd( obj );
}

int get_object_fd_for_client( const struct object* obj )
{
  assert( !is_wineserver );

  return get_object_fd( obj );
}

void init_context_fd( context_fd* ctx_fd, int fd )
{
#if defined( CONTEXTUAL_FD )
  *ctx_fd = INVALID_CTX_FD;

  if( fd != -1 ) set_context_fd( ctx_fd, fd );
#else
  *ctx_fd = fd;
#endif
}

void init_object_fd( struct object* obj, int fd )
{
  init_context_fd( &obj->fd, fd );
}

static int invalidate_context_fd( struct process* process, void* data )
{
  context_fd ctx_fd = (context_fd)data;

  if( ( ctx_fd < process->context_fd_list_size ) &&
      ( process->context_fd_list[ ctx_fd ].fd != -1 ) )
  {
    if( debug_level >= CONTEXT_DEBUG )
        fprintf( stderr, "Invalidating *ctx fd* %u for %p\n", ctx_fd, process );
    process->context_fd_list[ ctx_fd ].valid = 0;
  }

  return 0;
}

static void close_context_fd_internal( context_fd* ctx_fd, struct process* process )
{
#if defined( CONTEXTUAL_FD )
  struct context_entry* context_fd_list = SERVER_STATIC_VARIABLE( object, server_context_fd_list );

  if( *ctx_fd == INVALID_CTX_FD ) return;

  info_msg( CONTEXT_DEBUG, current, "Closing (%d/%p) *context fd* %u\n",
                                       is_wineserver, process, *ctx_fd );

  if( process || !is_wineserver )
  {
    context_fd_list = current->process->context_fd_list;
  }
  else
  {
    /* Walk all processes invalidating their copy */
    enum_processes( invalidate_context_fd, (void*)*ctx_fd );
  }

  if( context_fd_list[ *ctx_fd ].fd == -1 ) return;

  /* Don't actually close unless wineserver ... or in same context */
  if( (!process && is_wineserver) || (process == current->process && !is_wineserver) )
  {
      if( debug_level >= CONTEXT_DEBUG )
          fprintf( stderr, "Actually closing *ctx fd* %u *fd* %d, process=%p/%d\n",
                             *ctx_fd, context_fd_list[ *ctx_fd ].fd, process, is_wineserver );
      assert( context_fd_list[ *ctx_fd ].refcount == 0 );
      close( context_fd_list[ *ctx_fd ].fd );
  }

  context_fd_list[ *ctx_fd ].fd = -1;
  context_fd_list[ *ctx_fd ].valid = 0;
  *ctx_fd = -1;
#else
  close( *ctx_fd );
  *ctx_fd = -1;
#endif
}

void close_context_fd( context_fd* ctx_fd )
{
  close_context_fd_internal( ctx_fd, NULL );
}

void close_object_fd( struct object* obj )
{
  close_context_fd( &obj->fd );
}

void set_context_fd( context_fd* ctx_fd, int fd )
{
#if defined( CONTEXTUAL_FD )
  unsigned int i;

  assert( is_wineserver );

  /* Find an index */
  if( *ctx_fd != INVALID_CTX_FD )
  {
    i = *ctx_fd;
  }
  else
  {
    for( i=0; SERVER_STATIC_VARIABLE( object, server_context_fd_list_size ) && 
              i < SERVER_STATIC_VARIABLE( object, server_context_fd_list_size );
         i++ )
    {
      /* Find an unused one */
      if( SERVER_STATIC_VARIABLE( object, server_context_fd_list[i].fd ) == -1 &&
          !SERVER_STATIC_VARIABLE( object, server_context_fd_list[i].valid )) break;
    }

    if( i == SERVER_STATIC_VARIABLE( object, server_context_fd_list_size ) || 
             !SERVER_STATIC_VARIABLE( object, server_context_fd_list ) )
    {
      grow_context_fd_list( *ctx_fd, &SERVER_STATIC_VARIABLE( object, server_context_fd_list ),
                              &SERVER_STATIC_VARIABLE( object, server_context_fd_list_size ) );
    }

    *ctx_fd = i;

    assert( !SERVER_STATIC_VARIABLE( object, server_context_fd_list[i].valid ) );
  }

  SERVER_STATIC_VARIABLE( object, server_context_fd_list[i].fd ) = fd;
  SERVER_STATIC_VARIABLE( object, server_context_fd_list[i].valid ) = 1;

  info_msg( CONTEXT_DEBUG, current, "set_context_fd: Setting *ctx fd* %u to %d with ref %d (%p,%d). \n",
                                       i, fd, SERVER_STATIC_VARIABLE( object, server_context_fd_list[i].refcount ),
                                       SERVER_STATIC_VARIABLE( object, server_context_fd_list ),
                                       SERVER_STATIC_VARIABLE( object, server_context_fd_list_size ) );

#else
  /* is this valid for contextual? */
  *ctx_fd = fd;
#endif
}

void set_context_fd_for_process( context_fd ctx_fd, int fd, struct process* process )
{
    if( ctx_fd == INVALID_CTX_FD ) return;

    grow_context_fd_list( ctx_fd, &process->context_fd_list, &process->context_fd_list_size );

    info_msg( CONTEXT_DEBUG, current,
              "set_context_fd_for_process: Setting *ctx fd* %u from *fd* %d (refcount %u) in process %p\n",
                 ctx_fd, process->context_fd_list[ ctx_fd ].fd, process->context_fd_list[ ctx_fd ].refcount, process );

    process->context_fd_list[ ctx_fd ].fd    = fd;
    process->context_fd_list[ ctx_fd ].valid = 1;

    info_msg( CONTEXT_DEBUG, current,
              "set_context_fd_for_process: Setting *ctx fd* %u to *fd* %d (refcount %u) in process %p\n",
                 ctx_fd, fd, process->context_fd_list[ ctx_fd ].refcount, process );
}


void set_object_fd( struct object* obj, int fd )
{
  set_context_fd( &obj->fd, fd );
}


/*****************************************************************/

struct object_name
{
    struct object_name *next;
    struct object_name *prev;
    struct object      *obj;
    size_t              len;
    WCHAR               name[1];
};


#ifdef DEBUG_OBJECTS
void dump_objects(void)
{
    struct object *ptr = SERVER_STATIC_VARIABLE( object, first );
    while (ptr)
    {
        fprintf( stderr, "%p:%d: ", ptr, ptr->refcount );
        get_ops(ptr->ops)->dump( ptr, 1 );
        ptr = ptr->next;
    }
}
#endif /* DEBUG_OBJECTS */

/*****************************************************************/

/* malloc replacement */
void *mem_alloc_no_init( size_t size )
{
    void *ptr;
                                                                                                                                                                    
    if( SERVER_GLOBAL_VARIABLE(shared_memory_server) )
    {
        ptr = shm_mem_malloc( size );
    }
    else
    {
        ptr = malloc( size );
    }
                                                                                                                                                                    
    if (!ptr) set_error( STATUS_NO_MEMORY );
                                                                                                                                                                    
    return ptr;
}

/* malloc replacement */
void* mem_alloc( size_t size )
{
    void *ptr = mem_alloc_no_init( size );

#ifdef DEBUG_OBJECTS
    if (ptr) memset( ptr, 0xA5, size );
#endif

    return ptr;
}

/* Free replacement */
void mem_free( void* ptr )
{
    if( SERVER_GLOBAL_VARIABLE(shared_memory_server) )
    {
       shm_mem_free( ptr );
    }
    else
    {
        free( ptr );
    }
}

/* realloc replacement */
void* mem_realloc( void* ptr, size_t size )
{
    if( SERVER_GLOBAL_VARIABLE(shared_memory_server) )
    {
        ptr = shm_mem_realloc( ptr, size );
    }
    else
    {
        ptr = realloc( ptr, size );
    }

    if( !ptr ) set_error( STATUS_NO_MEMORY );

    return ptr;
}

/* calloc replacement */
void* mem_calloc( size_t nmemb, size_t size )
{
    void* ptr;

    if( SERVER_GLOBAL_VARIABLE(shared_memory_server) )
    {
        ptr = shm_mem_calloc( nmemb, size );
    }
    else
    {
        ptr = calloc( nmemb, size );
    }

    if( !ptr ) set_error( STATUS_NO_MEMORY );

    return ptr;
}

/* duplicate a block of memory */
void* mem_dup( const void* data, size_t len )
{
    void *ptr = mem_alloc_no_init( len );

    if (ptr) memcpy( ptr, data, len );

    return ptr;
}


/* strdup replacement */
char* mem_strdup( const char* str )
{
  const int len = strlen(str);

  return mem_dup( str, len+1 );
}


/*****************************************************************/

static int get_name_hash( const WCHAR *name, size_t len )
{
    WCHAR hash = 0;
    len /= sizeof(WCHAR);
    while (len--) hash ^= *name++;
    return hash % NAME_HASH_SIZE;
}

/* allocate a name for an object */
static struct object_name *alloc_name( const WCHAR *name, size_t len )
{
    struct object_name *ptr;

    if ((ptr = mem_alloc( sizeof(*ptr) + len - sizeof(ptr->name) )))
    {
        ptr->len = len;
        memcpy( ptr->name, name, len );
    }
    return ptr;
}

/* free the name of an object */
static void free_name( struct object *obj )
{
    struct object_name *ptr = obj->name;
    if (ptr->next) ptr->next->prev = ptr->prev;
    if (ptr->prev) ptr->prev->next = ptr->next;
    else
    {
        int hash;
        for (hash = 0; hash < NAME_HASH_SIZE; hash++)
            if (SERVER_STATIC_VARIABLE( object, names[hash] ) == ptr)
            {
                SERVER_STATIC_VARIABLE( object, names[hash] ) = ptr->next;
                break;
            }
    }
    mem_free( ptr );
}

/* set the name of an existing object */
static void set_object_name( struct object *obj, struct object_name *ptr )
{
    int hash = get_name_hash( ptr->name, ptr->len );

/* PH: FIXME: Are we getting many collisions? We only seem to have 37 bins for names */
    if ((ptr->next = SERVER_STATIC_VARIABLE( object, names[hash] )) != NULL) ptr->next->prev = ptr;
    ptr->obj = obj;
    ptr->prev = NULL;
    SERVER_STATIC_VARIABLE( object, names[hash] ) = ptr;
    assert( !obj->name );
    obj->name = ptr;
}

/* allocate and initialize an object */
/* if the function fails the fd is closed */
void *alloc_object( const struct object_ops *ops, int fd )
{
    struct object *obj = mem_alloc( get_ops(ops)->size );
    if (obj)
    {
        obj->refcount = 1;
        init_object_fd( obj, fd );
        grab_context_fd( obj->fd, NULL );
        obj->select   = -1;
        obj->ops      = ops;
        obj->head     = NULL;
        obj->tail     = NULL;
        obj->name     = NULL;
        if ((fd != -1) && (add_select_user( obj ) == -1))
        {
            close( fd );
            mem_free( obj );
            return NULL;
        }
#ifdef DEBUG_OBJECTS
        obj->prev = NULL;
        if ((obj->next = SERVER_STATIC_VARIABLE( object, first )) != NULL) obj->next->prev = obj;
        SERVER_STATIC_VARIABLE( object, first ) = obj;
#endif
        return obj;
    }
    if (fd != -1) close( fd );
    return NULL;
}

void *create_named_object( const struct object_ops *ops, const WCHAR *name, size_t len )
{
    struct object *obj;
    struct object_name *name_ptr;

    if (!name || !len) return alloc_object( ops, -1 );
    if (!(name_ptr = alloc_name( name, len ))) return NULL;

    if ((obj = find_object( name_ptr->name, name_ptr->len )))
    {
        mem_free( name_ptr );  /* we no longer need it */
        if (obj->ops == ops)
        {
            set_error( STATUS_OBJECT_NAME_COLLISION );
            return obj;
        }
        set_error( STATUS_OBJECT_TYPE_MISMATCH );
        return NULL;
    }
    if ((obj = alloc_object( ops, -1 )))
    {
        set_object_name( obj, name_ptr );
        clear_error();
    }
    else mem_free( name_ptr );
    return obj;
}

/* dump the name of an object to stderr */
void dump_object_name( struct object *obj )
{
    if (!obj->name) fprintf( stderr, "name=\"\"" );
    else
    {
        fprintf( stderr, "name=L\"" );
        dump_strW( obj->name->name, obj->name->len/sizeof(WCHAR), stderr, "\"\"" );
        fputc( '\"', stderr );
    }
}

/* grab an object (i.e. increment its refcount) and return the object */
struct object *grab_object( void *ptr )
{
    struct object *obj = (struct object *)ptr;
    assert( (obj->refcount < INT_MAX) && (obj->refcount > 0) );
    obj->refcount++;
    return obj;
}

/* release an object (i.e. decrement its refcount) */
unsigned int release_object( void *ptr )
{
    unsigned int endcount;
    struct object *obj = (struct object *)ptr;
    assert( obj->refcount );
    endcount = --obj->refcount;
    if (!endcount)
    {
        /* if the refcount is 0, nobody can be in the wait queue */
        assert( !obj->head );
        assert( !obj->tail );
        if (obj->name) free_name( obj );
        if (obj->select != -1) remove_select_user( obj );
        release_context_fd( obj->fd, NULL, NULL );
        /* Destroy after releasing everything related to ctx */    
        get_ops(obj->ops)->destroy( obj );
#ifdef DEBUG_OBJECTS
        if (obj->next) obj->next->prev = obj->prev;
        if (obj->prev) obj->prev->next = obj->next;
        else SERVER_STATIC_VARIABLE( object, first ) = obj->next;
        memset( obj, 0xaa, get_ops(obj->ops)->size );
#endif
        mem_free( obj );
        //fprintf( stderr, "PH: obj %p is now at %u\n", ptr, endcount );
    }

    return endcount;
}

/* find an object by its name; the refcount is incremented */
struct object *find_object( const WCHAR *name, size_t len )
{
    struct object_name *ptr;

    if (!name || !len) return NULL;
    for (ptr = SERVER_STATIC_VARIABLE( object, names[ get_name_hash( name, len ) ] ); ptr; ptr = ptr->next)
    {
        if (ptr->len != len) continue;
        if (!memcmp( ptr->name, name, len )) return grab_object( ptr->obj );
    }
    return NULL;
}

/* functions for unimplemented/default object operations */

int no_add_queue( struct object *obj, struct wait_queue_entry *entry )
{
    set_error( STATUS_OBJECT_TYPE_MISMATCH );
    return 0;
}

int no_satisfied( struct object *obj, struct thread *thread )
{
    return 0;  /* not abandoned */
}

int no_get_fd( struct object *obj )
{
    set_error( STATUS_OBJECT_TYPE_MISMATCH );
    return -1;
}

context_fd no_get_ctx_fd( struct object *obj )
{
    return INVALID_CTX_FD;
}


int no_flush( struct object *obj )
{
    set_error( STATUS_OBJECT_TYPE_MISMATCH );
    return 0;
}

int no_get_file_info( struct object *obj, struct get_file_info_reply *info, int *flags )
{
    set_error( STATUS_OBJECT_TYPE_MISMATCH );
    *flags = 0;
    return FD_TYPE_INVALID;
}

void no_destroy( struct object *obj )
{
}

static int is_in_poll_list( const struct object* obj, const struct thread* thread )
{
    const struct wait_queue_entry* entry = obj->head;

    if( entry == NULL ) return 0;

    do
    {
        if( entry->poll_thread == thread )
        {
            if( debug_level > 2 )
                fprintf( stderr, "Found %p in queue for thread %p\n", obj, thread );
            return 1;
        }
        entry = entry->prev;
    } while( entry != obj->head );

    return 0;
}

/* default add_queue() routine for objects that poll() on an fd */
int default_poll_add_queue( struct object *obj, struct wait_queue_entry *entry )
{
    if ( !is_in_poll_list( obj, entry->poll_thread ) )  /* first on the queue */
        set_select_events( obj, get_ops(obj->ops)->get_poll_events( obj ), entry->poll_thread );
    add_queue( obj, entry );
    return 1;
}

/* default remove_queue() routine for objects that poll() on an fd */
void default_poll_remove_queue( struct object *obj, struct wait_queue_entry *entry )
{
    grab_object(obj);
    remove_queue( obj, entry );
    if (!is_in_poll_list( obj, entry->poll_thread ))  /* last on the queue is gone */
        set_select_events( obj, 0, entry->poll_thread );
    release_object( obj );
}

/* default signaled() routine for objects that poll() on an fd */
int default_poll_signaled( struct object *obj, struct wait_queue_entry *entry )
{
    int events = get_ops(obj->ops)->get_poll_events( obj );

    if (check_select_events( get_object_fd( obj ), events ))
    {
        /* stop waiting on select() if we are signaled */
        set_select_events( obj, 0, entry->poll_thread );
        return 1;
    }
    /* restart waiting on select() if we are no longer signaled */
    if (is_in_poll_list( obj, entry->poll_thread )) // PH: FIXME: Doesn't quite work.
        set_select_events( obj, events, entry->poll_thread );
    return 0;
}

/* default handler for poll() events */
void default_poll_event( struct object *obj, int event )
{
    /* an error occurred, stop polling this fd to avoid busy-looping */
    if (event & (POLLERR | POLLHUP)) set_select_events( obj, -1, NULL /* PH: FIXME: */ );
    wake_up( obj, 0 );
}
