#include "evas_common.h"
#include "evas_private.h"
#include "evas_cs.h"

#ifdef LKDEBUG
EAPI Eina_Bool lockdebug = EINA_FALSE;
EAPI int lockmax = 0;
#endif

static int _evas_init_count = 0;
int _evas_log_dom_global = -1;
EAPI int
evas_init(void)
{
   if (++_evas_init_count != 1)
     return _evas_init_count;

#ifdef LKDEBUG
   if (getenv("EVAS_LOCK_DEBUG"))
      {
         lockdebug = EINA_TRUE;
         lockmax = atoi(getenv("EVAS_LOCK_DEBUG"));
      }
#endif
   
#ifdef HAVE_EVIL
   if (!evil_init())
     return --_evas_init_count;
#endif

   if (!eina_init())
     goto shutdown_evil;

   _evas_log_dom_global = eina_log_domain_register
     ("evas_main", EVAS_DEFAULT_LOG_COLOR);
   if (_evas_log_dom_global < 0)
     {
        EINA_LOG_ERR("Can not create a module log domain.");
        goto shutdown_eina;
     }

   evas_module_init();
#ifdef BUILD_ASYNC_EVENTS
   if (!evas_async_events_init())
     goto shutdown_module;
#endif
#ifdef EVAS_CSERVE
   if (getenv("EVAS_CSERVE")) evas_cserve_init();
#endif
#ifdef BUILD_ASYNC_PRELOAD
   _evas_preload_thread_init();
#endif
#ifdef EVAS_FRAME_QUEUING
   evas_common_frameq_init();
#endif

   return _evas_init_count;

#ifdef BUILD_ASYNC_EVENTS
 shutdown_module:
   evas_module_shutdown();
   eina_log_domain_unregister(_evas_log_dom_global);
#endif
 shutdown_eina:
   eina_shutdown();
 shutdown_evil:
#ifdef HAVE_EVIL
   evil_shutdown();
#endif

   return --_evas_init_count;
}

EAPI int
evas_shutdown(void)
{
   if (--_evas_init_count != 0)
     return _evas_init_count;

#ifdef EVAS_FRAME_QUEUING
   if (evas_common_frameq_enabled())
     {
        evas_common_frameq_finish();
        evas_common_frameq_destroy();
     }
#endif
#ifdef BUILD_ASYNC_EVENTS
   _evas_preload_thread_shutdown();
#endif
#ifdef EVAS_CSERVE
   if (getenv("EVAS_CSERVE")) evas_cserve_shutdown();
#endif
#ifdef BUILD_ASYNC_EVENTS
   evas_async_events_shutdown();
#endif
   evas_font_dir_cache_free();
   evas_common_shutdown();
   evas_module_shutdown();
   eina_log_domain_unregister(_evas_log_dom_global);
   eina_shutdown();
#ifdef HAVE_EVIL
   evil_shutdown();
#endif

   return _evas_init_count;
}


EAPI Evas *
evas_new(void)
{
   Evas *e;

   e = calloc(1, sizeof(Evas));
   if (!e) return NULL;

   e->magic = MAGIC_EVAS;
   e->output.render_method = RENDER_METHOD_INVALID;
   e->viewport.w = 1;
   e->viewport.h = 1;
   e->hinting = EVAS_FONT_HINTING_BYTECODE;
   e->name_hash = eina_hash_string_superfast_new(NULL);

#define EVAS_ARRAY_SET(E, Array)		\
   eina_array_step_set(&E->Array, sizeof (E->Array), 256);

   EVAS_ARRAY_SET(e, delete_objects);
   EVAS_ARRAY_SET(e, active_objects);
   EVAS_ARRAY_SET(e, restack_objects);
   EVAS_ARRAY_SET(e, render_objects);
   EVAS_ARRAY_SET(e, pending_objects);
   EVAS_ARRAY_SET(e, obscuring_objects);
   EVAS_ARRAY_SET(e, temporary_objects);
   EVAS_ARRAY_SET(e, calculate_objects);
   EVAS_ARRAY_SET(e, clip_changes);

#undef EVAS_ARRAY_SET

   return e;
}

EAPI void
evas_free(Evas *e)
{
   Eina_Rectangle *r;
   Evas_Layer *lay;
   int i;
   int del;

   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();

#ifdef EVAS_FRAME_QUEUING
   evas_common_frameq_flush();
#endif

   if (e->walking_list == 0) evas_render_idle_flush(e);
   
   if (e->walking_list > 0) return;

   if (e->callbacks)
     {
	if (e->callbacks->deletions_waiting) return;

	e->callbacks->deletions_waiting = 0;
	evas_event_callback_list_post_free(&e->callbacks->callbacks);
	if (!e->callbacks->callbacks)
	  {
	     free(e->callbacks);
	     e->callbacks = NULL;
	  }

	_evas_post_event_callback_free(e);
     }
   
   del = 1;
   e->walking_list++;
   e->cleanup = 1;
   while (del)
     {
	del = 0;
	EINA_INLIST_FOREACH(e->layers, lay)
	  {
	     Evas_Object *o;

	     evas_layer_pre_free(lay);

	     EINA_INLIST_FOREACH(lay->objects, o)
	       {
		  if ((o->callbacks) && (o->callbacks->walking_list))
		    {
		       /* Defer free */
		       e->delete_me = 1;
		       e->walking_list--;
		       return;
		    }
		  if (!o->delete_me)
		    del = 1;
	       }
	  }
     }
   EINA_INLIST_FOREACH(e->layers, lay)
     evas_layer_free_objects(lay);
   evas_layer_clean(e);

   e->walking_list--;

   evas_font_path_clear(e);
   e->pointer.object.in = eina_list_free(e->pointer.object.in);

   if (e->name_hash) eina_hash_free(e->name_hash);
   e->name_hash = NULL;

   EINA_LIST_FREE(e->damages, r)
     eina_rectangle_free(r);
   EINA_LIST_FREE(e->obscures, r)
     eina_rectangle_free(r);

   evas_fonts_zero_free(e);
   
   evas_event_callback_all_del(e);
   evas_event_callback_cleanup(e);

   if (e->engine.func)
     {
	e->engine.func->context_free(e->engine.data.output, e->engine.data.context);
	e->engine.func->output_free(e->engine.data.output);
	e->engine.func->info_free(e, e->engine.info);
     }

   for (i = 0; i < e->modifiers.mod.count; i++)
     free(e->modifiers.mod.list[i]);
   if (e->modifiers.mod.list) free(e->modifiers.mod.list);

   for (i = 0; i < e->locks.lock.count; i++)
     free(e->locks.lock.list[i]);
   if (e->locks.lock.list) free(e->locks.lock.list);

   if (e->engine.module) evas_module_unref(e->engine.module);

   eina_array_flush(&e->delete_objects);
   eina_array_flush(&e->active_objects);
   eina_array_flush(&e->restack_objects);
   eina_array_flush(&e->render_objects);
   eina_array_flush(&e->pending_objects);
   eina_array_flush(&e->obscuring_objects);
   eina_array_flush(&e->temporary_objects);
   eina_array_flush(&e->calculate_objects);
   eina_array_flush(&e->clip_changes);

   e->magic = 0;
   free(e);
}

EAPI void
evas_output_method_set(Evas *e, int render_method)
{
   Evas_Module *em;

   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();

   /* if our engine to set it to is invalid - abort */
   if (render_method == RENDER_METHOD_INVALID) return;
   /* if the engine is already set up - abort */
   if (e->output.render_method != RENDER_METHOD_INVALID) return;
   /* Request the right engine. */
   em = evas_module_engine_get(render_method);
   if (!em) return ;
   if (em->id_engine != render_method) return;
   if (!evas_module_load(em)) return;

   /* set the correct render */
   e->output.render_method = render_method;
   e->engine.func = (em->functions);
   evas_module_use(em);
   if (e->engine.module) evas_module_unref(e->engine.module);
   e->engine.module = em;
   evas_module_ref(em);
   /* get the engine info struct */
   if (e->engine.func->info) e->engine.info = e->engine.func->info(e);
   return;
}

EAPI int
evas_output_method_get(const Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return RENDER_METHOD_INVALID;
   MAGIC_CHECK_END();

   return e->output.render_method;
}

EAPI Evas_Engine_Info *
evas_engine_info_get(const Evas *e)
{
   Evas_Engine_Info *info;

   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return NULL;
   MAGIC_CHECK_END();

   if (!e->engine.info) return NULL;

   info = e->engine.info;
   ((Evas *)e)->engine.info_magic = info->magic;

   return info;
}

EAPI Eina_Bool
evas_engine_info_set(Evas *e, Evas_Engine_Info *info)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return EINA_FALSE;
   MAGIC_CHECK_END();
   if (!info) return EINA_FALSE;
   if (info != e->engine.info) return EINA_FALSE;
   if (info->magic != e->engine.info_magic) return EINA_FALSE;
   return (Eina_Bool)e->engine.func->setup(e, info);
}

EAPI void
evas_output_size_set(Evas *e, int w, int h)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();

   if ((w == e->output.w) && (h == e->output.h)) return;
   if (w < 1) w = 1;
   if (h < 1) h = 1;

#ifdef EVAS_FRAME_QUEUING
   evas_common_frameq_flush();
#endif

   e->output.w = w;
   e->output.h = h;
   e->output.changed = 1;
   e->output_validity++;
   e->changed = 1;
   evas_render_invalidate(e);
}

EAPI void
evas_output_size_get(const Evas *e, int *w, int *h)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   if (w) *w = 0;
   if (h) *h = 0;
   return;
   MAGIC_CHECK_END();

   if (w) *w = e->output.w;
   if (h) *h = e->output.h;
}

EAPI void
evas_output_viewport_set(Evas *e, Evas_Coord x, Evas_Coord y, Evas_Coord w, Evas_Coord h)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();

   if ((x == e->viewport.x) && (y == e->viewport.y) &&
       (w == e->viewport.w) && (h == e->viewport.h)) return;
   if (w <= 0) return;
   if (h <= 0) return;
   if ((x != 0) || (y != 0))
     {
	ERR("Compat error. viewport x,y != 0,0 not supported");
	x = 0;
	y = 0;
     }
   e->viewport.x = x;
   e->viewport.y = y;
   e->viewport.w = w;
   e->viewport.h = h;
   e->viewport.changed = 1;
   e->output_validity++;
   e->changed = 1;
}

EAPI void
evas_output_viewport_get(const Evas *e, Evas_Coord *x, Evas_Coord *y, Evas_Coord *w, Evas_Coord *h)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   if (x) *x = 0;
   if (y) *y = 0;
   if (w) *w = 0;
   if (h) *h = 0;
   return;
   MAGIC_CHECK_END();

   if (x) *x = e->viewport.x;
   if (y) *y = e->viewport.y;
   if (w) *w = e->viewport.w;
   if (h) *h = e->viewport.h;
}

EAPI Evas_Coord
evas_coord_screen_x_to_world(const Evas *e, int x)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   if (e->output.w == e->viewport.w) return e->viewport.x + x;
   return (long long)e->viewport.x + (((long long)x * (long long)e->viewport.w) / (long long)e->output.w);
}

EAPI Evas_Coord
evas_coord_screen_y_to_world(const Evas *e, int y)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   if (e->output.h == e->viewport.h) return e->viewport.y + y;
   return (long long)e->viewport.y + (((long long)y * (long long)e->viewport.h) / (long long)e->output.h);
}

EAPI int
evas_coord_world_x_to_screen(const Evas *e, Evas_Coord x)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   if (e->output.w == e->viewport.w) return x - e->viewport.x;
   return (int)((((long long)x - (long long)e->viewport.x) * (long long)e->output.w) /  (long long)e->viewport.w);
}

EAPI int
evas_coord_world_y_to_screen(const Evas *e, Evas_Coord y)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   if (e->output.h == e->viewport.h) return y - e->viewport.y;
   return (int)((((long long)y - (long long)e->viewport.y) * (long long)e->output.h) /  (long long)e->viewport.h);
}

EAPI int
evas_render_method_lookup(const char *name)
{
   Evas_Module *em;

   if (!name) return RENDER_METHOD_INVALID;
   /* search on the engines list for the name */
   em = evas_module_find_type(EVAS_MODULE_TYPE_ENGINE, name);
   if (!em) return RENDER_METHOD_INVALID;

   return em->id_engine;
}

EAPI Eina_List *
evas_render_method_list(void)
{
   Eina_List *methods = NULL;

   /* FIXME: get from modules - this is currently coded-in */
#ifdef BUILD_ENGINE_SOFTWARE_GDI
   methods = eina_list_append(methods, "software_gdi");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_DDRAW
   methods = eina_list_append(methods, "software_ddraw");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_16_DDRAW
   methods = eina_list_append(methods, "software_16_ddraw");
#endif
#ifdef BUILD_ENGINE_DIRECT3D
   methods = eina_list_append(methods, "direct3d");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_16_WINCE
   methods = eina_list_append(methods, "software_16_wince");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_X11
   methods = eina_list_append(methods, "software_x11");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_16_X11
   methods = eina_list_append(methods, "software_16_x11");
#endif
#ifdef BUILD_ENGINE_GL_X11
   methods = eina_list_append(methods, "gl_x11");
#endif
#ifdef BUILD_ENGINE_DIRECTFB
   methods = eina_list_append(methods, "directfb");
#endif
#ifdef BUILD_ENGINE_FB
   methods = eina_list_append(methods, "fb");
#endif
#ifdef BUILD_ENGINE_BUFFER
   methods = eina_list_append(methods, "buffer");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_WIN32_GDI
   methods = eina_list_append(methods, "software_win32_gdi");
#endif
#ifdef BUILD_ENGINE_SOFTWARE_SDL
   methods = eina_list_append(methods, "software_sdl");
#endif

   return methods;
}

EAPI void
evas_render_method_list_free(Eina_List *list)
{
   eina_list_free(list);
}

EAPI Eina_Bool
evas_object_image_extension_can_load_get(const char *file)
{
   const char *tmp;
   Eina_Bool result;

   tmp = eina_stringshare_add(file);
   result = evas_common_extension_can_load_get(tmp);
   eina_stringshare_del(tmp);

   return result;
}

EAPI Eina_Bool
evas_object_image_extension_can_load_fast_get(const char *file)
{
   return evas_common_extension_can_load_get(file);
}

EAPI void
evas_pointer_output_xy_get(const Evas *e, int *x, int *y)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   if (x) *x = 0;
   if (y) *y = 0;
   return;
   MAGIC_CHECK_END();
   if (x) *x = e->pointer.x;
   if (y) *y = e->pointer.y;
}

EAPI void
evas_pointer_canvas_xy_get(const Evas *e, Evas_Coord *x, Evas_Coord *y)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   if (x) *x = 0;
   if (y) *y = 0;
   return;
   MAGIC_CHECK_END();
   if (x) *x = e->pointer.x;
   if (y) *y = e->pointer.y;
}

EAPI int
evas_pointer_button_down_mask_get(const Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   return (int)e->pointer.button;
}

EAPI Eina_Bool
evas_pointer_inside_get(const Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   return (int)e->pointer.inside;
}

EAPI void
evas_data_attach_set(Evas *e, void *data)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();
   e->attach_data = data;
}

EAPI void *
evas_data_attach_get(const Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return NULL;
   MAGIC_CHECK_END();
   return e->attach_data;
}

EAPI void
evas_focus_in(Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();
   if (e->focus) return;
   e->focus = 1;
   evas_event_callback_call(e, EVAS_CALLBACK_CANVAS_FOCUS_IN, NULL);
}

EAPI void
evas_focus_out(Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return;
   MAGIC_CHECK_END();
   if (!e->focus) return;
   e->focus = 0;
   evas_event_callback_call(e, EVAS_CALLBACK_CANVAS_FOCUS_OUT, NULL);
}

EAPI Eina_Bool
evas_focus_state_get(const Evas *e)
{
   MAGIC_CHECK(e, Evas, MAGIC_EVAS);
   return 0;
   MAGIC_CHECK_END();
   return e->focus;
}

EAPI void
evas_nochange_push(Evas *e)
{
   e->nochange++;
}

EAPI void
evas_nochange_pop(Evas *e)
{
   e->nochange--;
}

void
_evas_walk(Evas *e)
{
   e->walking_list++;
}

void
_evas_unwalk(Evas *e)
{
   e->walking_list--;
   if ((e->walking_list == 0) && (e->delete_me)) evas_free(e);
}

EAPI const char *
evas_load_error_str(Evas_Load_Error error)
{
   switch (error)
     {
      case EVAS_LOAD_ERROR_NONE:
	 return "No error on load";
      case EVAS_LOAD_ERROR_GENERIC:
	 return "A non-specific error occurred";
      case EVAS_LOAD_ERROR_DOES_NOT_EXIST:
	 return "File (or file path) does not exist";
      case EVAS_LOAD_ERROR_PERMISSION_DENIED:
	 return "Permission deinied to an existing file (or path)";
      case EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED:
	 return "Allocation of resources failure prevented load";
      case EVAS_LOAD_ERROR_CORRUPT_FILE:
	 return "File corrupt (but was detected as a known format)";
      case EVAS_LOAD_ERROR_UNKNOWN_FORMAT:
	 return "File is not a known format";
      default:
	 return "Unknown error";
     }
}

EAPI void
evas_color_hsv_to_rgb(float h, float s, float v, int *r, int *g, int *b)
{
   evas_common_convert_color_hsv_to_rgb(h, s, v, r, g, b);
}

EAPI void
evas_color_rgb_to_hsv(int r, int g, int b, float *h, float *s, float *v)
{
   evas_common_convert_color_rgb_to_hsv(r, g, b, h, s, v);
}

EAPI void
evas_color_argb_premul(int a, int *r, int *g, int *b)
{
   evas_common_convert_color_argb_premul(a, r, g, b);
}

EAPI void
evas_color_argb_unpremul(int a, int *r, int *g, int *b)
{
   evas_common_convert_color_argb_unpremul(a, r, g, b);
}

EAPI void
evas_data_argb_premul(unsigned int *data, unsigned int len)
{
   if (!data || (len < 1)) return;
   evas_common_convert_argb_premul(data, len);
}

EAPI void
evas_data_argb_unpremul(unsigned int *data, unsigned int len)
{
   if (!data || (len < 1)) return;
   evas_common_convert_argb_unpremul(data, len);
}
