/*
Copyright (c) 2012, Broadcom Europe Ltd
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the copyright holder nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef KHRN_CLIENT_H
#define KHRN_CLIENT_H

typedef struct CLIENT_PROCESS_STATE CLIENT_PROCESS_STATE_T;
typedef struct CLIENT_THREAD_STATE CLIENT_THREAD_STATE_T;

#include "interface/khronos/common/khrn_client_platform.h"

#include "interface/khronos/egl/egl_client_context.h"
#include "interface/khronos/egl/egl_client_surface.h"
#include "interface/khronos/include/EGL/eglext.h"

#include "interface/khronos/common/khrn_client_pointermap.h"

#ifdef RPC_LIBRARY
#include "middleware/khronos/common/khrn_misc.h"
#include "applications/vmcs/khronos/khronos_server.h"
#elif defined(RPC_DIRECT_MULTI)
#include "middleware/khronos/common/khrn_misc.h"
#endif

/* must be after EGL/eglext.h */
#if EGL_BRCM_global_image && EGL_KHR_image
   #include "interface/khronos/common/khrn_client_global_image_map.h"
#endif

extern void client_try_unload_server(CLIENT_PROCESS_STATE_T *process);

/*
   per-thread state

   - EGL error
   - EGL bound API
   - EGL context and surfaces for each API

   - RPC merge buffer
*/

#define MERGE_BUFFER_SIZE  4080

typedef struct {
   EGL_CONTEXT_T *context;
   EGL_SURFACE_T *draw;
   EGL_SURFACE_T *read;
} EGL_CURRENT_T;

struct CLIENT_THREAD_STATE {
   /*
      error

      Invariant:
      (CLIENT_THREAD_STATE_ERROR)
      error is one of
         EGL_SUCCESS
         EGL_NOT_INITIALIZED
         EGL_BAD_ACCESS
         EGL_BAD_ALLOC
         EGL_BAD_ATTRIBUTE
         EGL_BAD_CONTEXT
         EGL_BAD_CONFIG
         EGL_BAD_CURRENT SURFACE
         EGL_BAD_DISPLAY
         EGL_BAD_SURFACE
         EGL_BAD_MATCH
         EGL_BAD_PARAMETER
         EGL_BAD_NATIVE PIXMAP
         EGL_BAD_NATIVE WINDOW
         EGL_CONTEXT_LOST
   */
   EGLint error;

   EGLenum bound_api;

   /*
      handles to current display, context and surfaces for each API

      Availability:

      Thread owns EGL lock
   */

   EGL_CURRENT_T opengl;
   EGL_CURRENT_T openvg;

   /*
      rpc stuff
   */

   bool high_priority;

   uint8_t merge_buffer[MERGE_BUFFER_SIZE];

   uint32_t merge_pos;
   uint32_t merge_end;

	/* Try to reduce impact of repeated consecutive glGetError() calls */
	int32_t glgeterror_hack;
	bool async_error_notification;
};

extern void client_thread_state_init(CLIENT_THREAD_STATE_T *state);
extern void client_thread_state_term(CLIENT_THREAD_STATE_T *state);

extern PLATFORM_TLS_T client_tls;

/*
   CLIENT_GET_THREAD_STATE

   Implementation notes:

   TODO: make sure this gets code-reviewed

   Preconditions:

   -

   Postconditions:

   Result is a valid pointer to a thread-local CLIENT_THREAD_STATE_T structure.

   Invariants preserved:

   -

   Invariants used:

   -
*/

static INLINE CLIENT_THREAD_STATE_T *CLIENT_GET_THREAD_STATE(void)
{
	CLIENT_THREAD_STATE_T *tls;
	tls = (CLIENT_THREAD_STATE_T *)platform_tls_get(client_tls);
	if( tls && tls->glgeterror_hack ) {
		tls->glgeterror_hack--;
	}
   return tls;
}

static INLINE CLIENT_THREAD_STATE_T *CLIENT_GET_CHECK_THREAD_STATE(void)
{
   return (CLIENT_THREAD_STATE_T *)platform_tls_get_check(client_tls);
}

/*
   per-process state

   - EGL initialization stage
   - EGL contexts and surfaces
   - EGL counters
*/

struct CLIENT_PROCESS_STATE {
#ifdef RPC_LIBRARY
   /*
   called khronos_server_connect? this is valid even if !inited
   */
   bool connected;
#endif

   /*
   number of current contexts across all threads in this process. this is valid
   even if !inited
   */
   uint32_t context_current_count;

   /*
   inited

   Specifies whether the structure has been initialised and all of the other members are valid.
   inited is true between eglInitialise/eglTerminate. threads can still have
   things current when !inited

   Invariants:
   (CLIENT_PROCESS_STATE_INITED_SANITY)
   Only client_process_state_init/client_process_state_term modify this value
   */
   bool inited;

   /*
   contexts

   A map from context id (EGLContext) to EGL_CONTEXT_T
   TODO: use pointers as key rather than integers

   Validity: inited is true

   Invariants:
   (CLIENT_PROCESS_STATE_CONTEXTS)
   If id is a key in contexts:
      contexts[id].name == id
      contexts[id].is_destroyed == false
   */
   KHRN_POINTER_MAP_T contexts;

   /*
   surfaces

   A map from context id (EGLContext) to EGL_SURFACE_T

   Validity: inited is true

   Invariants:
   (CLIENT_PROCESS_STATE_SURFACES)
   If id is a key in surfaces:
      surfaces[id].name == id
      surfaces[id].is_destroyed == false
   */
   KHRN_POINTER_MAP_T surfaces;

   /*
    * some platforms (e.g. Android) need to maintain a list of
    * known windows
    */
   KHRN_POINTER_MAP_T windows;

#if EGL_KHR_sync

   /*
   syncs

   Validity: inited is true
   */
   KHRN_POINTER_MAP_T syncs;
#endif
#if EGL_BRCM_global_image && EGL_KHR_image
   KHRN_GLOBAL_IMAGE_MAP_T global_image_egl_images;
#endif

   /*
      next_surface

      Implementation notes:
      TODO: these could theoretically overflow

      Validity: inited is true

      Invariant:
      (CLIENT_PROCESS_STATE_NEXT_SURFACE)
      next_surface is greater than every key in surfaces
      next_surface >= 1
   */
   uint32_t next_surface;
   /*
      next_context

      Validity: inited is true
   */
   uint32_t next_context;
#if EGL_KHR_sync
   /*
      next_sync

      Validity: inited is true
   */
   uint32_t next_sync;
#endif
#if EGL_BRCM_global_image && EGL_KHR_image
   uint32_t next_global_image_egl_image;
#endif

#if EGL_BRCM_perf_monitor
   /*
      perf_monitor_inited

      Validity: inited is true
   */
   bool perf_monitor_inited;
#endif

#if EGL_BRCM_driver_monitor
   /*
      driver_monitor_inited

      Validity: inited is true
   */
   bool driver_monitor_inited;
#endif

#ifdef RPC_LIBRARY
   KHRONOS_SERVER_CONNECTION_T khrn_connection;
#endif
};

extern bool client_process_state_init(CLIENT_PROCESS_STATE_T *process);
extern void client_process_state_term(CLIENT_PROCESS_STATE_T *process);

extern CLIENT_PROCESS_STATE_T client_process_state;

/*
   CLIENT_GET_PROCESS_STATE()

   Returns the process-global CLIENT_PROCESS_STATE_T object.
*/
#ifdef CLIENT_THREAD_IS_PROCESS
extern PLATFORM_TLS_T client_tls_process;
extern PLATFORM_TLS_T client_tls_mutex;
extern void *platform_tls_get_process(PLATFORM_TLS_T tls);
#endif
static INLINE CLIENT_PROCESS_STATE_T *CLIENT_GET_PROCESS_STATE(void)
{
#ifdef CLIENT_THREAD_IS_PROCESS
	//each thread has its own client_process_state
	return (CLIENT_PROCESS_STATE_T *)platform_tls_get_process(client_tls_process);
#else
   return &client_process_state;
#endif
}

/*
   exposed bits of EGL
*/

CLIENT_PROCESS_STATE_T *client_egl_get_process_state(CLIENT_THREAD_STATE_T *thread, EGLDisplay dpy, EGLBoolean check_inited);
EGL_CONTEXT_T *client_egl_get_context(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLContext ctx);
EGL_SURFACE_T *client_egl_get_surface(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLSurface surf);
EGL_SURFACE_T *client_egl_get_locked_surface(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLSurface surf);

EGLNativeWindowType client_egl_get_window(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLNativeWindowType window);
/*
   client state
*/

#define CLIENT_MAKE_CURRENT_SIZE 36 /* RPC_CALL8 */
extern void client_send_make_current(CLIENT_THREAD_STATE_T *thread);

extern void client_set_error(uint32_t server_context_name);
/*
   big giant lock
*/

extern PLATFORM_MUTEX_T client_mutex;

/*
   CLIENT_LOCK()

   Acquires EGL lock.

   Implementation notes:

   TODO make sure this gets reviewed

   Preconditions:

   TODO: check mutex hierarchy methodology
   Mutex: >(MUTEX_EGL_LOCK)
   Is being called from a function which _always_ subsequently calls CLIENT_UNLOCK()

   Postconditions:

   Mutex: (MUTEX_EGL_LOCK)
   Thread owns EGL lock
*/

static INLINE void CLIENT_LOCK(void)
{
      platform_client_lock();
}

/*
   CLIENT_UNLOCK()

   Releases EGL lock.

   Implementation notes:

   TODO make sure this gets reviewed

   Preconditions:

   Mutex: (MUTEX_EGL_LOCK)
   Thread owns EGL lock
   Is being called from a function which has _always_ previously called CLIENT_LOCK()

   Postconditions:
   Mutex: >(MUTEX_EGL_LOCK)
*/

static INLINE void CLIENT_UNLOCK(void)
{
    platform_client_release();
}

/*
   bool CLIENT_LOCK_AND_GET_STATES(EGLDisplay dpy, CLIENT_THREAD_STATE_T **thread, CLIENT_PROCESS_STATE_T **process)

   Try to acquire EGL lock and get thread and process state.

   Implementation notes:

   TODO make sure this gets reviewed

   Preconditions:

   thread is a valid pointer to a thread*
   process is a valid pointer to a process*
   Mutex: >(MUTEX_EGL_LOCK)
   Is being called from a function which calls CLIENT_UNLOCK() if we return true

   Postconditions:

   The following conditions cause error to assume the specified value

      EGL_BAD_DISPLAY               An EGLDisplay argument does not name a valid EGLDisplay
      EGL_NOT_INITIALIZED           EGL is not initialized for the specified display.

   if more than one condition holds, the first error is generated.

   Either:
      Mutex: (MUTEX_EGL_LOCK)
      Thread owns EGL lock
      result is true
   Or:
      Nothing changes
      result is false
*/

static INLINE bool CLIENT_LOCK_AND_GET_STATES(EGLDisplay dpy, CLIENT_THREAD_STATE_T **thread, CLIENT_PROCESS_STATE_T **process)
{
   *thread = CLIENT_GET_THREAD_STATE();
   CLIENT_LOCK();
   *process = client_egl_get_process_state(*thread, dpy, EGL_TRUE);
   if (*process != NULL)
      return true;
   else
   {
      CLIENT_UNLOCK();
      return false;
   }
}

/*
   process and thread attach/detach hooks
*/

#ifdef __cplusplus
extern "C" {
#endif
extern bool client_process_attach(void);
extern bool client_thread_attach(void);
extern void client_thread_detach(void *dummy);
extern void client_process_detach(void);

#ifdef RPC_LIBRARY
extern KHRONOS_SERVER_CONNECTION_T *client_library_get_connection(void);
extern void client_library_send_make_current(const KHRONOS_FUNC_TABLE_T *func_table);
#elif defined(RPC_DIRECT_MULTI)
extern void client_library_send_make_current(const KHRONOS_FUNC_TABLE_T *func_table);
#endif

#ifdef __cplusplus
}
#endif

#endif
