/** \file
 * \brief EFL Clipboard
 *
 * See Copyright Notice in "iup.h"
 */

#include <stdlib.h>
#include <string.h>

#include "iup.h"

#include "iup_attrib.h"
#include "iup_str.h"
#include "iup_class.h"

#include "iupefl_drv.h"


static unsigned int eflClipboardGetSeatId(Eo* win)
{
  Eo* seat = efl_canvas_scene_seat_default_get(win);
  if (seat)
    return efl_input_device_seat_id_get(seat);
  return 0;
}

typedef struct {
  void* data;
  size_t size;
} eflClipboardResult;

static Eina_Value eflClipboardTextResolveCb(void* data, const Eina_Value value, const Eina_Future* dead_future)
{
  char** result_ptr = (char**)data;

  (void)dead_future;

  if (eina_value_type_get(&value) == EINA_VALUE_TYPE_CONTENT)
  {
    Eina_Content* content = eina_value_to_content(&value);
    if (content)
    {
      Eina_Slice slice = eina_content_data_get(content);
      if (slice.mem && slice.len > 0)
      {
        *result_ptr = (char*)malloc(slice.len + 1);
        if (*result_ptr)
        {
          memcpy(*result_ptr, slice.mem, slice.len);
          (*result_ptr)[slice.len] = '\0';
        }
      }
      eina_content_free(content);
    }
  }

  iupeflModalLoopQuit();
  return value;
}

static Eina_Value eflClipboardFormatResolveCb(void* data, const Eina_Value value, const Eina_Future* dead_future)
{
  eflClipboardResult* result = (eflClipboardResult*)data;

  (void)dead_future;

  if (eina_value_type_get(&value) == EINA_VALUE_TYPE_CONTENT)
  {
    Eina_Content* content = eina_value_to_content(&value);
    if (content)
    {
      Eina_Slice slice = eina_content_data_get(content);
      if (slice.mem && slice.len > 0)
      {
        result->data = malloc(slice.len);
        if (result->data)
        {
          memcpy(result->data, slice.mem, slice.len);
          result->size = slice.len;
        }
      }
      eina_content_free(content);
    }
  }

  iupeflModalLoopQuit();
  return value;
}

static int efl_clipboard_timed_out = 0;

static Eina_Bool eflClipboardTimeoutCb(void* data)
{
  (void)data;
  efl_clipboard_timed_out = 1;
  iupeflModalLoopQuit();
  return ECORE_CALLBACK_CANCEL;
}

static int eflClipboardSetTextAttrib(Ihandle* ih, const char* value)
{
  Eo* win;
  Eina_Content* content;
  Eina_Slice slice;

  (void)ih;

  win = iupeflGetMainWindow();
  if (!win)
    return 0;

  if (value && value[0])
  {
    unsigned int seat = eflClipboardGetSeatId(win);
    slice.mem = value;
    slice.len = strlen(value) + 1;
    content = eina_content_new(slice, "text/plain;charset=utf-8");
    if (content)
      efl_ui_selection_set(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, content, seat);
  }
  else
  {
    efl_ui_selection_clear(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, eflClipboardGetSeatId(win));
  }

  return 0;
}

static char* eflClipboardGetTextAttrib(Ihandle* ih)
{
  Eo* win;
  Eina_Future* future;
  Eina_Iterator* types;
  char* result = NULL;

  (void)ih;

  win = iupeflGetMainWindow();
  if (!win)
    return NULL;

  types = eina_carray_iterator_new((void*[]){ (void*)"text/plain;charset=utf-8", (void*)"text/plain", NULL });
  future = efl_ui_selection_get(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, eflClipboardGetSeatId(win), types);
  if (future)
  {
    eina_future_then(future, eflClipboardTextResolveCb, &result, NULL);
    iupeflModalLoopRun(NULL);
  }

  if (result)
  {
    char* ret = iupStrReturnStr(result);
    free(result);
    return ret;
  }

  return NULL;
}

/* EFL has_selection only checks if the buffer has any content,
   not whether the content is specifically text. */
static char* eflClipboardGetTextAvailableAttrib(Ihandle* ih)
{
  Eo* win;

  (void)ih;

  win = iupeflGetMainWindow();
  if (!win)
    return "NO";

  if (efl_ui_selection_has_selection(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, eflClipboardGetSeatId(win)))
    return "YES";

  return "NO";
}

static const char* eflClipboardGetFormatMimeType(Ihandle* ih)
{
  return iupAttribGetStr(ih, "FORMAT");
}

static int eflClipboardSetFormatDataAttrib(Ihandle* ih, const char* value)
{
  Eo* win;
  Eina_Content* content;
  Eina_Slice slice;
  const char* mime_type;
  int size;

  win = iupeflGetMainWindow();
  if (!win)
    return 0;

  if (!value)
  {
    efl_ui_selection_clear(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, eflClipboardGetSeatId(win));
    free(iupAttribGet(ih, "_IUP_CLIPBOARD_FORMAT_CACHE"));
    iupAttribSet(ih, "_IUP_CLIPBOARD_FORMAT_CACHE", NULL);
    iupAttribSetInt(ih, "_IUP_CLIPBOARD_FORMAT_CACHE_SIZE", 0);
    return 0;
  }

  mime_type = eflClipboardGetFormatMimeType(ih);
  if (!mime_type)
    return 0;

  size = iupAttribGetInt(ih, "FORMATDATASIZE");
  if (size <= 0)
    return 0;

  slice.mem = value;
  slice.len = size;
  content = eina_content_new(slice, mime_type);
  if (content)
    efl_ui_selection_set(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, content, eflClipboardGetSeatId(win));

  /* Cache locally to avoid X11 self-selection deadlock */
  {
    char* old_cache = (char*)iupAttribGet(ih, "_IUP_CLIPBOARD_FORMAT_CACHE");
    char* cache = (char*)malloc(size);
    if (cache)
    {
      memcpy(cache, value, size);
      iupAttribSet(ih, "_IUP_CLIPBOARD_FORMAT_CACHE", cache);
      iupAttribSetInt(ih, "_IUP_CLIPBOARD_FORMAT_CACHE_SIZE", size);
    }
    free(old_cache);
  }

  return 0;
}

static eflClipboardResult efl_clipboard_format_result;

static char* eflClipboardGetFormatDataAttrib(Ihandle* ih)
{
  Eo* win;
  Eina_Future* future;
  Eina_Iterator* types;
  const char* mime_type;
  eflClipboardResult *result = &efl_clipboard_format_result;

  result->data = NULL;
  result->size = 0;

  win = iupeflGetMainWindow();
  if (!win)
    return NULL;

  mime_type = eflClipboardGetFormatMimeType(ih);
  if (!mime_type)
    return NULL;

  /* Use local cache to avoid X11 self-selection deadlock */
  {
    const char* cached = iupAttribGet(ih, "_IUP_CLIPBOARD_FORMAT_CACHE");
    int cached_size = iupAttribGetInt(ih, "_IUP_CLIPBOARD_FORMAT_CACHE_SIZE");
    if (cached && cached_size > 0)
    {
      result->data = malloc(cached_size);
      if (result->data)
      {
        memcpy(result->data, cached, cached_size);
        result->size = cached_size;
      }
    }
  }

  if (!result->data)
  {
    types = eina_carray_iterator_new((void*[]){ (void*)mime_type, NULL });
    future = efl_ui_selection_get(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, eflClipboardGetSeatId(win), types);
    if (future)
    {
      efl_clipboard_timed_out = 0;
      Ecore_Timer* timeout = ecore_timer_add(2.0, eflClipboardTimeoutCb, NULL);
      eina_future_then(future, eflClipboardFormatResolveCb, result, NULL);
      iupeflModalLoopRun(NULL);
      if (!efl_clipboard_timed_out && timeout)
        ecore_timer_del(timeout);
    }
  }

  if (result->data)
  {
    void* ret = iupStrGetMemory((int)result->size + 1);
    memcpy(ret, result->data, result->size);
    ((char*)ret)[result->size] = 0;
    free(result->data);
    result->data = NULL;
    iupAttribSetInt(ih, "FORMATDATASIZE", (int)result->size);
    return ret;
  }

  return NULL;
}

static char* eflClipboardGetFormatDataStringAttrib(Ihandle* ih)
{
  char* data = eflClipboardGetFormatDataAttrib(ih);
  if (!data)
    return NULL;
  return iupStrReturnStr(data);
}

static int eflClipboardSetFormatDataStringAttrib(Ihandle* ih, const char* value)
{
  if (value)
  {
    int len = (int)strlen(value);
    iupAttribSetInt(ih, "FORMATDATASIZE", len + 1);
    return eflClipboardSetFormatDataAttrib(ih, value);
  }
  else
    return eflClipboardSetFormatDataAttrib(ih, NULL);
}

/* EFL has_selection only checks if the buffer has any content,
   not whether the specific format is available. */
static char* eflClipboardGetFormatAvailableAttrib(Ihandle* ih)
{
  Eo* win;

  win = iupeflGetMainWindow();
  if (!win)
    return "NO";

  if (efl_ui_selection_has_selection(win, EFL_UI_CNP_BUFFER_COPY_AND_PASTE, eflClipboardGetSeatId(win)))
    return "YES";

  return "NO";
}


Iclass* iupClipboardNewClass(void)
{
  Iclass* ic = iupClassNew(NULL);

  ic->name = "clipboard";
  ic->format = NULL;
  ic->nativetype = IUP_TYPEOTHER;
  ic->childtype = IUP_CHILDNONE;
  ic->is_interactive = 0;

  ic->New = iupClipboardNewClass;

  iupClassRegisterAttribute(ic, "TEXT", eflClipboardGetTextAttrib, eflClipboardSetTextAttrib, NULL, NULL, IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "TEXTAVAILABLE", eflClipboardGetTextAvailableAttrib, NULL, NULL, NULL, IUPAF_READONLY | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FORMAT", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FORMATAVAILABLE", eflClipboardGetFormatAvailableAttrib, NULL, NULL, NULL, IUPAF_READONLY | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "NATIVEIMAGE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_NO_STRING | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "IMAGE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_IHANDLENAME | IUPAF_WRITEONLY | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "IMAGEAVAILABLE", NULL, NULL, NULL, NULL, IUPAF_NOT_SUPPORTED | IUPAF_READONLY | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);

  iupClassRegisterAttribute(ic, "ADDFORMAT", NULL, NULL, NULL, NULL, IUPAF_WRITEONLY | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FORMATDATA", eflClipboardGetFormatDataAttrib, eflClipboardSetFormatDataAttrib, NULL, NULL, IUPAF_NO_STRING | IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FORMATDATASTRING", eflClipboardGetFormatDataStringAttrib, eflClipboardSetFormatDataStringAttrib, NULL, NULL, IUPAF_NOT_MAPPED | IUPAF_NO_INHERIT);
  iupClassRegisterAttribute(ic, "FORMATDATASIZE", NULL, NULL, NULL, NULL, IUPAF_NO_INHERIT);

  return ic;
}

IUP_API Ihandle* IupClipboard(void)
{
  return IupCreate("clipboard");
}
