/*
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.
*/
#define VCOS_LOG_CATEGORY (&khrn_client_log)
#include "interface/khronos/common/khrn_int_common.h"

#include "interface/khronos/common/khrn_client_check_types.h"
#include "interface/khronos/common/khrn_client.h"
#include "interface/khronos/common/khrn_client_rpc.h"
#include "interface/khronos/egl/egl_client_config.h"
#include "interface/khronos/glxx/glxx_client.h"

#if defined(V3D_LEAN)
#include "interface/khronos/common/khrn_int_misc_impl.h"
#endif

#if EGL_KHR_sync
#include "interface/khronos/ext/egl_khr_sync_client.h"
#endif

#if EGL_BRCM_perf_monitor
#include "interface/khronos/ext/egl_brcm_perf_monitor_client.h"
#endif

#if EGL_BRCM_driver_monitor
#include "interface/khronos/ext/egl_brcm_driver_monitor_client.h"
#endif

#ifdef RPC_LIBRARY
#include "middleware/dlloader/dlfcn.h"
#include "applications/vmcs/khronos/khronos_server.h"
#endif

VCOS_LOG_CAT_T khrn_client_log = VCOS_LOG_INIT("khrn_client", VCOS_LOG_WARN);

/*
   void client_try_unload_server(CLIENT_PROCESS_STATE_T *process)

   Preconditions:

   -

   Postconditions:

   -

   Invariants preserved:

   -

   Invariants used:

   -
*/

void client_try_unload_server(CLIENT_PROCESS_STATE_T *process)
{
   if (/* some context is current */
      (process->context_current_count != 0) ||
      /* egl is initialised */
      process->inited) {
      return;
   }

   /*
      Prompt the server to unload Khronos VLL if it can,
      and wait until it is done
   */
#ifdef RPC_LIBRARY   //TODO: not thread safe
   if (process->connected) {
      const KHRONOS_FUNC_TABLE_T *func_table;
      func_table = khronos_server_lock_func_table(client_library_get_connection());
      if (func_table != NULL)
      {
         func_table->khrn_misc_try_unload_impl();
         khronos_server_disconnect();
      }
      khronos_server_unlock_func_table();

      process->connected = false;
   }
#else
   {
      CLIENT_THREAD_STATE_T *thread = CLIENT_GET_THREAD_STATE();
      RPC_INT_RES(RPC_CALL0_RES(khrn_misc_try_unload_impl, thread, KHRNMISCTRYUNLOAD_ID)); // return unimportant - read is just to cause blocking
   }
#endif
}

/*
   void client_process_state_init(CLIENT_PROCESS_STATE_T *state)

   Implementation notes:

   Does nothing if already initialised.

   Preconditions:

   process is a valid pointer
   Thread owns EGL lock
   client_process_state_term must be called at some point after calling this function if we return true.

   Postconditions:

   Either:
   - an error occurred and false is returned, or
   - process->inited is true

   Invariants preserved:

   All invariants on CLIENT_PROCESS_STATE_T
   (CLIENT_PROCESS_STATE_INITED_SANITY)

   Invariants used:

   -
*/

bool client_process_state_init(CLIENT_PROCESS_STATE_T *process)
{
   if (!process->inited) {
      if (!khrn_pointer_map_init(&process->contexts, 64))
         return false;

      if (!khrn_pointer_map_init(&process->surfaces, 64))
      {
         khrn_pointer_map_term(&process->contexts);
         return false;
      }
      if (!khrn_pointer_map_init(&process->windows, 64))
      {
         khrn_pointer_map_term(&process->contexts);
         khrn_pointer_map_term(&process->surfaces);
         return false;
      }

#if EGL_KHR_sync
      if (!khrn_pointer_map_init(&process->syncs, 64))
      {
         khrn_pointer_map_term(&process->contexts);
         khrn_pointer_map_term(&process->surfaces);
         khrn_pointer_map_term(&process->windows);
         return false;
      }
#endif
#if EGL_BRCM_global_image && EGL_KHR_image
      khrn_global_image_map_init(&process->global_image_egl_images, 8);
#endif
      process->next_surface = 1;
      process->next_context = 1;
#if EGL_KHR_sync
      process->next_sync = 0x80000001;
#endif
#if EGL_BRCM_global_image && EGL_KHR_image
      process->next_global_image_egl_image = 1 << 31;
#endif

#if EGL_BRCM_perf_monitor
      process->perf_monitor_inited = false;
#endif

#ifdef RPC_LIBRARY
      if (!process->connected) {
         process->khrn_connection.make_current_func = client_library_send_make_current;
         khronos_server_lock_func_table(&process->khrn_connection);
         khronos_server_connect(&process->khrn_connection);
         khronos_server_unlock_func_table();
         RPC_CALL0(khrn_misc_startup_impl, NULL, no_id);

         process->connected = true;
      }
#endif

      process->inited = true;
   }

#ifndef ABSTRACT_PLATFORM
#if defined(ANDROID) && !defined (ANDROID_HWCOMPOSER)
   egl_config_install_configs(1); // T-format configs
#else
   egl_config_install_configs(0); // RSO configs
#endif
#endif

   return true;
}

#include "interface/khronos/common/khrn_client_cr.c"

/*
   void client_process_state_term(CLIENT_PROCESS_STATE_T *process)

   Implementation notes:

   Does nothing if already terminated.

   Preconditions:

   process is a valid pointer
   Thread owns EGL lock

   Postconditions:

   process->inited is false.

   Invariants preserved:

   (EGL_CONTEXT_IS_DESTROYED)
   (CLIENT_PROCESS_STATE_INITED_SANITY)

   Invariants used:

   -
*/

void client_process_state_term(CLIENT_PROCESS_STATE_T *process)
{
   if (process->inited) {
      khrn_pointer_map_iterate(&process->contexts, callback_destroy_context, NULL);
      khrn_pointer_map_term(&process->contexts);

      khrn_pointer_map_iterate(&process->surfaces, callback_destroy_surface, NULL);
      khrn_pointer_map_term(&process->surfaces);

      khrn_pointer_map_term(&process->windows);

#if EGL_KHR_sync
      egl_sync_destroy_all(&process->syncs);
      khrn_pointer_map_term(&process->syncs);
#endif

#if EGL_BRCM_global_image && EGL_KHR_image
      khrn_global_image_map_term(&process->global_image_egl_images);
#endif

#if EGL_BRCM_perf_monitor
      egl_perf_monitor_term(process);
#endif

#if EGL_BRCM_driver_monitor
      egl_driver_monitor_term(process);
#endif

      process->inited = false;
   }
}

CLIENT_PROCESS_STATE_T client_process_state = {
#ifdef RPC_LIBRARY
   false, /* not connected */
#endif
   0, /* nothing current */
   false}; /* not inited */

void client_thread_state_init(CLIENT_THREAD_STATE_T *state)
{
   state->error = EGL_SUCCESS;

   state->bound_api = EGL_OPENGL_ES_API;

   state->opengl.context = 0;
   state->opengl.draw = 0;
   state->opengl.read = 0;

   state->openvg.context = 0;
   state->openvg.draw = 0;
   state->openvg.read = 0;

   state->high_priority = false;

   state->merge_pos = 0;
   state->merge_end = 0;

	state->glgeterror_hack = 0;
	state->async_error_notification = false;
}

void client_thread_state_term(CLIENT_THREAD_STATE_T *state)
{
   // TODO: termination
   platform_term_rpc( state );
}

EGL_CONTEXT_T *client_egl_get_context(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLContext ctx)
{
   EGL_CONTEXT_T *context = (EGL_CONTEXT_T *)khrn_pointer_map_lookup(&process->contexts, (uint32_t)(size_t)ctx);

   vcos_assert(!context || !context->is_destroyed);

   if (!context)
      thread->error = EGL_BAD_CONTEXT;

   return context;
}

EGL_SURFACE_T *client_egl_get_surface(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLSurface surf)
{
   EGL_SURFACE_T *surface = (EGL_SURFACE_T *)khrn_pointer_map_lookup(&process->surfaces, (uint32_t)(size_t)surf);

   vcos_assert (!surface || !surface->is_destroyed);

   if (!surface)
      thread->error = EGL_BAD_SURFACE;

#if EGL_KHR_lock_surface
   if (surface && surface->is_locked) {
      thread->error = EGL_BAD_ACCESS;
      surface = NULL;
   }
#endif

   return surface;
}

/*
   We don't actually insist that the surface is locked. But unlike client_egl_get_surface, we don't throw an
   error if it isn't.
*/
EGL_SURFACE_T *client_egl_get_locked_surface(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLSurface surf)
{
   EGL_SURFACE_T *surface = (EGL_SURFACE_T *)khrn_pointer_map_lookup(&process->surfaces, (uint32_t)(size_t)surf);

   vcos_assert (!surface || !surface->is_destroyed);

   if (!surface)
      thread->error = EGL_BAD_SURFACE;

   return surface;
}

/*
 * just return if we've seen window before
 */
EGLNativeWindowType client_egl_get_window(CLIENT_THREAD_STATE_T *thread, CLIENT_PROCESS_STATE_T *process, EGLNativeWindowType window)
{
   EGLNativeWindowType win = (EGLNativeWindowType)khrn_pointer_map_lookup(&process->windows, (uint32_t)(size_t)window);

   return win;
}

static uint32_t convert_gltype(EGL_CONTEXT_TYPE_T type)
{
   switch (type) {
   case OPENGL_ES_11: return EGL_SERVER_GL11;
   case OPENGL_ES_20: return EGL_SERVER_GL20;
   default:           UNREACHABLE(); return 0;
   }
}

void client_send_make_current(CLIENT_THREAD_STATE_T *thread)
{
   uint64_t pid                  = rpc_get_client_id(thread);
   uint32_t gltype               = thread->opengl.context ? convert_gltype(thread->opengl.context->type) : 0;
   EGL_GL_CONTEXT_ID_T servergl  = thread->opengl.context ? thread->opengl.context->servercontext : EGL_SERVER_NO_GL_CONTEXT;
   EGL_SURFACE_ID_T servergldraw = thread->opengl.draw    ? thread->opengl.draw->serverbuffer     : EGL_SERVER_NO_SURFACE;
   EGL_SURFACE_ID_T serverglread = thread->opengl.read    ? thread->opengl.read->serverbuffer     : EGL_SERVER_NO_SURFACE;
   EGL_VG_CONTEXT_ID_T servervg  = thread->openvg.context ? thread->openvg.context->servercontext : EGL_SERVER_NO_VG_CONTEXT;
   EGL_SURFACE_ID_T servervgsurf = thread->openvg.draw    ? thread->openvg.draw->serverbuffer     : EGL_SERVER_NO_SURFACE;

   /*
      if the size of this call in the merge buffer changes,
      CLIENT_MAKE_CURRENT_SIZE in khrn_client.h should be updated
   */

   if (!thread->opengl.context || !thread->opengl.draw)
   {
      vcos_log_trace("Send null make current %x %x",
                 (unsigned int)(char *)thread->opengl.context, (unsigned int)(char *)thread->opengl.draw);
   }
   else
   {
      vcos_log_trace("Send make current %d[%d %s%s] %d[%d %d%s]",
            (int)thread->opengl.context->name,
            thread->opengl.context->servercontext,
            thread->opengl.context->is_current ? " C" : "",
            thread->opengl.context->is_destroyed ? " D" : "",
            (int)thread->opengl.draw->name,
            thread->opengl.draw->serverbuffer,
            thread->opengl.draw->context_binding_count,
            thread->opengl.draw->is_destroyed ? " D" : "");
   }

   RPC_CALL8_MAKECURRENT(eglIntMakeCurrent_impl,
                         thread,
                         EGLINTMAKECURRENT_ID,
                         RPC_UINT((uint32_t)pid),
                         RPC_UINT((uint32_t)(pid >> 32)),
                         RPC_UINT(gltype),
                         RPC_UINT(servergl),
                         RPC_UINT(servergldraw),
                         RPC_UINT(serverglread),
                         RPC_UINT(servervg),
                         RPC_UINT(servervgsurf));
}

PLATFORM_TLS_T client_tls;
PLATFORM_MUTEX_T client_mutex;
#ifdef CLIENT_THREAD_IS_PROCESS
PLATFORM_TLS_T client_tls_process;
PLATFORM_TLS_T client_tls_mutex;
#endif

bool client_process_attach()
{
   KHR_STATUS_T status;
   status = platform_tls_create(&client_tls);
   if (status != KHR_SUCCESS) {
      return false;
   }

#ifdef CLIENT_THREAD_IS_PROCESS
   status = platform_tls_create(&client_tls_process);
   if (status != KHR_SUCCESS) {
      return false;
   }

   status = platform_tls_create(&client_tls_mutex);
   if (status != KHR_SUCCESS) {
      return false;
   }
#endif

   status = platform_mutex_create(&client_mutex);

   if (status != KHR_SUCCESS) {
      platform_tls_destroy(client_tls);
      return false;
   }
   if (!RPC_INIT()) {
      platform_mutex_destroy(&client_mutex);
      platform_tls_destroy(client_tls);
      return false;
   }
   return true;
}

bool client_thread_attach()
{
   CLIENT_THREAD_STATE_T *state = (CLIENT_THREAD_STATE_T *)khrn_platform_malloc(sizeof(CLIENT_THREAD_STATE_T), "CLIENT_THREAD_STATE_T");

   if (!state)
      return false;

   client_thread_state_init(state);

   platform_tls_set(client_tls, state);

#ifdef CLIENT_THREAD_IS_PROCESS
	{  //add mutex into thread's tls
		KHR_STATUS_T status;
		PLATFORM_MUTEX_T *local_mutex = (PLATFORM_MUTEX_T*)vcos_tls_get(client_tls_mutex);

		if (!local_mutex)
		{
			local_mutex = (PLATFORM_MUTEX_T*)khrn_platform_malloc(sizeof(PLATFORM_MUTEX_T),"thread mutex");
			if (!local_mutex)
				return false;

			status = platform_mutex_create(local_mutex);
			if (status != KHR_SUCCESS) {
				khrn_platform_free(local_mutex);
				return false;
			}

			vcos_tls_set(client_tls_mutex,local_mutex);
		}
	}
#endif

#ifndef RPC_LIBRARY    //TODO
   client_send_make_current(state);
#endif
   return true;
}

void client_thread_detach(void *dummy)
{
   CLIENT_THREAD_STATE_T *state = CLIENT_GET_THREAD_STATE();
   UNUSED(dummy);

   platform_tls_remove(client_tls);
   client_thread_state_term(state);

   khrn_platform_free(state);
   platform_maybe_free_process();

#ifdef CLIENT_THREAD_IS_PROCESS
	{
		CLIENT_PROCESS_STATE_T *process = CLIENT_GET_PROCESS_STATE();
   	khrn_platform_free(process);
		platform_tls_remove(client_tls_process);
	}

	{
		PLATFORM_MUTEX_T *local_mutex = (PLATFORM_MUTEX_T*)vcos_tls_get(client_tls_mutex);
		vcos_assert(local_mutex);

		platform_mutex_destroy(local_mutex);

		khrn_platform_free(local_mutex);
		platform_tls_remove(client_tls_mutex);
	}
#endif
}

void client_process_detach()
{
   RPC_TERM();
   platform_tls_destroy(client_tls);
   platform_mutex_destroy(&client_mutex);

#ifdef CLIENT_THREAD_IS_PROCESS
	platform_tls_destroy(client_tls_process);
#endif
}

#ifdef RPC_LIBRARY
KHRONOS_SERVER_CONNECTION_T *client_library_get_connection(void)
{
   return &client_process_state.khrn_connection;
}

#endif

#if defined(RPC_LIBRARY) || defined(RPC_DIRECT_MULTI)

void client_library_send_make_current(const KHRONOS_FUNC_TABLE_T *func_table)
{
   CLIENT_THREAD_STATE_T *thread = CLIENT_GET_THREAD_STATE();
   if (thread->opengl.context || thread->openvg.context)
   {
      uint64_t pid                  = rpc_get_client_id(thread);
      uint32_t gltype               = thread->opengl.context ? convert_gltype(thread->opengl.context->type) : 0;
      EGL_GL_CONTEXT_ID_T servergl  = thread->opengl.context ? thread->opengl.context->servercontext : EGL_SERVER_NO_GL_CONTEXT;
      EGL_SURFACE_ID_T servergldraw = thread->opengl.draw    ? thread->opengl.draw->serverbuffer     : EGL_SERVER_NO_SURFACE;
      EGL_SURFACE_ID_T serverglread = thread->opengl.read    ? thread->opengl.read->serverbuffer     : EGL_SERVER_NO_SURFACE;
      EGL_VG_CONTEXT_ID_T servervg  = thread->openvg.context ? thread->openvg.context->servercontext : EGL_SERVER_NO_VG_CONTEXT;
      EGL_SURFACE_ID_T servervgsurf = thread->openvg.draw    ? thread->openvg.draw->serverbuffer     : EGL_SERVER_NO_SURFACE;

      /*
         if the size of this call in the merge buffer changes,
         CLIENT_MAKE_CURRENT_SIZE in khrn_client.h should be updated
      */

      func_table->eglIntMakeCurrent_impl(
                (uint32_t)pid,
                (uint32_t)(pid >> 32),
                gltype,
                servergl,
                servergldraw,
                serverglread,
                servervg,
                servervgsurf);
   }
}

#endif

#ifdef GL_GET_ERROR_ASYNC
static void callback_set_error(KHRN_POINTER_MAP_T *map, uint32_t key, void *value, void *data)
{
   EGL_CONTEXT_T *context = (EGL_CONTEXT_T *)value;

   UNUSED(map);
   UNUSED_NDEBUG(key);

   vcos_assert( context != NULL );
   vcos_assert((uintptr_t)key == (uintptr_t)context->name);

   if (context->servercontext == *((uint32_t *)data)){
      CLIENT_THREAD_STATE_T *thread = context->thread;
      /* todo: VG */
      if (thread && IS_OPENGLES_11_OR_20(thread)) {
         vcos_log_error("GL OOM context %d", context->servercontext);
         glxx_set_error(GLXX_GET_CLIENT_STATE(thread), GL_OUT_OF_MEMORY);
      }
   }
}

void client_set_error(uint32_t server_context_name)
{
   CLIENT_PROCESS_STATE_T *process;
   CLIENT_LOCK();
   process = CLIENT_GET_PROCESS_STATE();
   khrn_pointer_map_iterate(&process->contexts, callback_set_error, &server_context_name);
   CLIENT_UNLOCK();
}
#endif
