/*
   This file is part of darktable,
   copyright (c) 2015 Jeremy Rosen
   copyright (c) 2015 tobias ellinghaus

   darktable is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   darktable is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with darktable.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "control/control.h"
#include "lua/call.h"
#include "lua/modules.h"
#include "lua/types.h"
#include "lua/widget/common.h"
#include "stdarg.h"
/**
  TODO
  use name to save/restore states as pref like other widgets
  have a way to save presets
  luastorage can't save presets
dt_ui_section_label : make new lua widget
widget names : implement for CSS ?
  */

#pragma GCC diagnostic ignored "-Wshadow"

dt_lua_widget_type_t widget_type = {
  .name = "widget",
  .gui_init = NULL,
  .gui_cleanup = NULL,
  .alloc_size = sizeof(dt_lua_widget_t),
  .parent = NULL
};


static void cleanup_widget_sub(lua_State *L,dt_lua_widget_type_t*widget_type,lua_widget widget);
static void cleanup_widget_sub(lua_State *L,dt_lua_widget_type_t*widget_type,lua_widget widget) {
  if(widget_type->parent) 
    cleanup_widget_sub(L,widget_type->parent,widget);
  if(widget_type->gui_cleanup) {
    widget_type->gui_cleanup(L,widget);
  }
}

static void init_widget_sub(lua_State *L,dt_lua_widget_type_t*widget_type);
static void init_widget_sub(lua_State *L,dt_lua_widget_type_t*widget_type) {
  if(widget_type->parent) 
    init_widget_sub(L,widget_type->parent);
  if(widget_type->gui_init) 
    widget_type->gui_init(L);
}

static void on_destroy(GtkWidget *widget, gpointer user_data)
{
}

static gboolean on_destroy_wrapper(gpointer user_data)
{
  gtk_widget_destroy((GtkWidget*) user_data);
  return false;
}

static int widget_gc(lua_State *L)
{
  lua_widget lwidget;
  luaA_to(L,lua_widget,&lwidget,1);
  if(!lwidget) return 0; // object has been destroyed
  if(gtk_widget_get_parent(lwidget->widget)) {
    luaL_error(L,"Destroying a widget which is still parented, this should never happen (%s at %p)\n",lwidget->type->name,lwidget);
  }
  cleanup_widget_sub(L,lwidget->type,lwidget);
  dt_lua_widget_unbind(L,lwidget);
  // no need to drop, the pointer table is weak and the widget is already being GC, so it's not in the table anymore
  //dt_lua_type_gpointer_drop(L,lwidget);
  //dt_lua_type_gpointer_drop(L,lwidget->widget);
  g_idle_add(on_destroy_wrapper,lwidget->widget);
  free(lwidget);
  return 0;
}

static int get_widget_params(lua_State *L)
{
  struct dt_lua_widget_type_t *widget_type = lua_touserdata(L, lua_upvalueindex(1));
  if(G_TYPE_IS_ABSTRACT(widget_type->gtk_type)){
    luaL_error(L,"Trying to create a widget of an abstract type : %s\n",widget_type->name);
  }
  lua_widget widget= malloc(widget_type->alloc_size);
  widget->widget = gtk_widget_new(widget_type->gtk_type,NULL);
  gtk_widget_show(widget->widget);// widgets are invisible by default
  g_object_ref_sink(widget->widget);
  widget->type = widget_type;
  luaA_push_type(L,widget_type->associated_type,&widget);
  dt_lua_type_gpointer_alias_type(L,widget_type->associated_type,widget,widget->widget);
  init_widget_sub(L,widget_type);

  luaL_getmetafield(L,-1,"__gtk_signals");
  lua_pushnil(L); /* first key */
  while(lua_next(L, -2) != 0)
  {
    g_signal_connect(widget->widget, lua_tostring(L,-2), G_CALLBACK(lua_touserdata(L,-1)), widget);
    lua_pop(L,1);
  }
  lua_pop(L,1);
  g_signal_connect(widget->widget,"destroy",G_CALLBACK(on_destroy),widget);
  return 1;
}



luaA_Type dt_lua_init_widget_type_type(lua_State *L, dt_lua_widget_type_t* widget_type,const char* lua_type,GType gtk_type)
{
  luaA_Type type_id = dt_lua_init_gpointer_type_type(L,luaA_type_add(L,lua_type,sizeof(gpointer)));
  widget_type->associated_type = type_id;
  widget_type->gtk_type = gtk_type;
  dt_lua_type_register_parent_type(L, type_id, widget_type->parent->associated_type);

  lua_newtable(L);
  dt_lua_type_setmetafield_type(L,type_id,"__gtk_signals");
  // add to the table
  lua_pushlightuserdata(L, widget_type);
  lua_pushcclosure(L, get_widget_params, 1);
  dt_lua_gtk_wrap(L);
  dt_lua_module_entry_new(L, -1, "widget", widget_type->name);
  lua_pop(L, 1);
  return type_id;
};



static int new_widget(lua_State *L)
{
  const char *entry_name = luaL_checkstring(L, 1);
  dt_lua_module_entry_push(L, "widget", entry_name);
  lua_insert(L,2);
  lua_call(L, lua_gettop(L)-2, 1);
  return 1;
}

void dt_lua_widget_set_callback(lua_State *L,int index,const char* name)
{
  luaL_argcheck(L, dt_lua_isa(L, index, lua_widget), index, "lua_widget expected");
  luaL_checktype(L,-1,LUA_TFUNCTION);
  lua_getuservalue(L,index);
  lua_pushvalue(L,-2);
  lua_setfield(L,-2,name);
  lua_pop(L,2);
}

void dt_lua_widget_get_callback(lua_State *L,int index,const char* name)
{
  luaL_argcheck(L, dt_lua_isa(L, index, lua_widget), index, "lua_widget expected");
  lua_getuservalue(L,index);
  lua_getfield(L,-1,name);
  lua_remove(L,-2);
}

int dt_lua_widget_trigger_callback(lua_State *L)
{
  int nargs = lua_gettop(L) -2;
  lua_widget widget;
  luaA_to(L,lua_widget,&widget,1);
  const char* name = lua_tostring(L,2);
  lua_getuservalue(L,1);
  lua_getfield(L,-1,name);
  if(!lua_isnil(L,-1)) {
    lua_pushvalue(L,1);
    for(int i = 0 ; i < nargs ; i++) {
      lua_pushvalue(L,i+3);
    }
    dt_lua_treated_pcall(L,nargs+1,0);
    dt_lua_redraw_screen();
  }
  return 0;
}

static int reset_member(lua_State *L)
{
  if(lua_gettop(L) > 2) {
    dt_lua_widget_set_callback(L,1,"reset");
    return 0;
  }
  dt_lua_widget_get_callback(L,1,"reset");
  return 1;
}


static int tooltip_member(lua_State *L)
{
  lua_widget widget;
  luaA_to(L,lua_widget,&widget,1);
  if(lua_gettop(L) > 2) {
    if(lua_isnil(L,3)) {
      gtk_widget_set_tooltip_text(widget->widget,NULL);
    } else {
      const char * text = luaL_checkstring(L,3);
      gtk_widget_set_tooltip_text(widget->widget,text);
    }
    return 0;
  }
  char* result = gtk_widget_get_tooltip_text(widget->widget);
  lua_pushstring(L,result);
  free(result);
  return 1;
}

static int sensitive_member(lua_State *L)
{
  lua_widget widget;
  luaA_to(L,lua_widget,&widget,1);
  if(lua_gettop(L) > 2) {
    gboolean value = lua_toboolean(L,3);
    gtk_widget_set_sensitive(widget->widget,value);
    return 0;
  }
  gboolean result = gtk_widget_get_sensitive(widget->widget);
  lua_pushboolean(L,result);
  return 1;
}

int dt_lua_widget_tostring_member(lua_State *L)
{
  lua_widget widget;
  luaA_to(L, lua_widget, &widget, 1);
  lua_pushstring(L, G_OBJECT_TYPE_NAME(widget->widget));
  return 1;
}

static int gtk_signal_member(lua_State *L)
{

  const char *signal = lua_tostring(L,lua_upvalueindex(1));
  if(lua_gettop(L) > 2) {
    dt_lua_widget_set_callback(L,1,signal);
    return 0;
  }
  dt_lua_widget_get_callback(L,1,signal);
  return 1;
}

void dt_lua_widget_register_gtk_callback_type(lua_State *L,luaA_Type type_id,const char* signal_name, const char* lua_name,GCallback callback) 
{
  lua_pushstring(L,signal_name);
  lua_pushcclosure(L,gtk_signal_member,1);
  dt_lua_type_register_type(L, type_id, lua_name);

  luaL_newmetatable(L, luaA_typename(L, type_id));
  lua_getfield(L,-1,"__gtk_signals");
  lua_pushlightuserdata(L,callback);
  lua_setfield(L,-2,signal_name);
  lua_pop(L,2);
  
}

int widget_call(lua_State *L)
{
  lua_pushnil(L); /* first key */
  while(lua_next(L, 2) != 0)
  {
    lua_pushvalue(L,-2);
    lua_pushvalue(L,-2);
    lua_settable(L,1);
    lua_pop(L,1);
  }
  lua_pop(L,1);
  return 1;
}

void dt_lua_widget_bind(lua_State *L, lua_widget widget)
{
  /* check that widget isn't already parented */
  if(gtk_widget_get_parent (widget->widget) != NULL) {
    luaL_error(L,"Attempting to bind a widget which already has a parent\n");
  }

  /* store it as a toplevel widget */
  lua_getfield(L, LUA_REGISTRYINDEX,"dt_lua_widget_bind_table");
  lua_pushlightuserdata(L,widget);
  luaA_push(L,lua_widget,&widget);
  lua_settable(L,-3);
  lua_pop(L,1);
}

void dt_lua_widget_unbind(lua_State *L, lua_widget widget)
{
  lua_getfield(L, LUA_REGISTRYINDEX,"dt_lua_widget_bind_table");
  lua_pushlightuserdata(L,widget);
  lua_pushnil(L);
  lua_settable(L,-3);
  lua_pop(L,1);
}


int dt_lua_init_widget(lua_State* L)
{

  lua_newtable(L);
  lua_setfield(L, LUA_REGISTRYINDEX,"dt_lua_widget_bind_table");

  dt_lua_module_new(L,"widget");

  widget_type.associated_type = dt_lua_init_gpointer_type(L,lua_widget);
  lua_pushcfunction(L,tooltip_member);
  dt_lua_gtk_wrap(L);
  dt_lua_type_register(L, lua_widget, "tooltip");
  lua_pushcfunction(L,widget_gc);
  dt_lua_gtk_wrap(L);
  dt_lua_type_setmetafield(L,lua_widget,"__gc");
  lua_pushcfunction(L,reset_member);
  dt_lua_type_register(L, lua_widget, "reset_callback");
  lua_pushcfunction(L,widget_call);
  dt_lua_type_setmetafield(L,lua_widget,"__call");
  lua_pushcfunction(L,sensitive_member);
  dt_lua_gtk_wrap(L);
  dt_lua_type_register(L, lua_widget, "sensitive");
  lua_pushcfunction(L, dt_lua_widget_tostring_member);
  dt_lua_gtk_wrap(L);
  dt_lua_type_setmetafield(L,lua_widget,"__tostring");

  dt_lua_init_widget_container(L);

  dt_lua_init_widget_box(L);
  dt_lua_init_widget_button(L);
  dt_lua_init_widget_check_button(L);
  dt_lua_init_widget_combobox(L);
  dt_lua_init_widget_label(L);
  dt_lua_init_widget_section_label(L);
  dt_lua_init_widget_entry(L);
  dt_lua_init_widget_file_chooser_button(L);
  dt_lua_init_widget_separator(L);
  dt_lua_init_widget_slider(L);
  dt_lua_init_widget_stack(L);
  dt_lua_init_widget_text_view(L);

  dt_lua_push_darktable_lib(L);
  lua_pushstring(L, "new_widget");
  lua_pushcfunction(L, &new_widget);
  lua_settable(L, -3);
  lua_pop(L, 1);
  return 0;
}
// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.sh
// vim: shiftwidth=2 expandtab tabstop=2 cindent
// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
