/*
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

/** \file
 * \ingroup wm
 */

#include "BLI_ghash.h"
#include "BLI_utildefines.h"

#include "BKE_context.h"

#include "MEM_guardedalloc.h"

#include "RNA_access.h"
#include "RNA_define.h"

#include "WM_api.h"
#include "WM_types.h"

/* only for own init/exit calls (wm_gizmogrouptype_init/wm_gizmogrouptype_free) */
#include "wm.h"

/* own includes */
#include "wm_gizmo_intern.h"
#include "wm_gizmo_wmapi.h"

/** \name GizmoGroup Type Append
 *
 * \note This follows conventions from #WM_operatortype_find #WM_operatortype_append & friends.
 * \{ */

static GHash *global_gizmogrouptype_hash = NULL;

wmGizmoGroupType *WM_gizmogrouptype_find(const char *idname, bool quiet)
{
  if (idname[0]) {
    wmGizmoGroupType *gzgt;

    gzgt = BLI_ghash_lookup(global_gizmogrouptype_hash, idname);
    if (gzgt) {
      return gzgt;
    }

    if (!quiet) {
      printf("search for unknown gizmo group '%s'\n", idname);
    }
  }
  else {
    if (!quiet) {
      printf("search for empty gizmo group\n");
    }
  }

  return NULL;
}

/* caller must free */
void WM_gizmogrouptype_iter(GHashIterator *ghi)
{
  BLI_ghashIterator_init(ghi, global_gizmogrouptype_hash);
}

static wmGizmoGroupType *wm_gizmogrouptype_append__begin(void)
{
  wmGizmoGroupType *gzgt = MEM_callocN(sizeof(wmGizmoGroupType), "gizmogrouptype");
  gzgt->srna = RNA_def_struct_ptr(&BLENDER_RNA, "", &RNA_GizmoGroupProperties);
#if 0
  /* Set the default i18n context now, so that opfunc can redefine it if needed! */
  RNA_def_struct_translation_context(ot->srna, BLT_I18NCONTEXT_OPERATOR_DEFAULT);
  ot->translation_context = BLT_I18NCONTEXT_OPERATOR_DEFAULT;
#endif
  return gzgt;
}
static void wm_gizmogrouptype_append__end(wmGizmoGroupType *gzgt)
{
  BLI_assert(gzgt->name != NULL);
  BLI_assert(gzgt->idname != NULL);

  RNA_def_struct_identifier(&BLENDER_RNA, gzgt->srna, gzgt->idname);

  gzgt->type_update_flag |= WM_GIZMOMAPTYPE_KEYMAP_INIT;

  /* if not set, use default */
  if (gzgt->setup_keymap == NULL) {
    if (gzgt->flag & WM_GIZMOGROUPTYPE_SELECT) {
      gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic_select;
    }
    else {
      gzgt->setup_keymap = WM_gizmogroup_setup_keymap_generic;
    }
  }

  BLI_ghash_insert(global_gizmogrouptype_hash, (void *)gzgt->idname, gzgt);
}

wmGizmoGroupType *WM_gizmogrouptype_append(void (*wtfunc)(struct wmGizmoGroupType *))
{
  wmGizmoGroupType *gzgt = wm_gizmogrouptype_append__begin();
  wtfunc(gzgt);
  wm_gizmogrouptype_append__end(gzgt);
  return gzgt;
}

wmGizmoGroupType *WM_gizmogrouptype_append_ptr(void (*wtfunc)(struct wmGizmoGroupType *, void *),
                                               void *userdata)
{
  wmGizmoGroupType *gzgt = wm_gizmogrouptype_append__begin();
  wtfunc(gzgt, userdata);
  wm_gizmogrouptype_append__end(gzgt);
  return gzgt;
}

/**
 * Append and insert into a gizmo typemap.
 * This is most common for C gizmos which are enabled by default.
 */
wmGizmoGroupTypeRef *WM_gizmogrouptype_append_and_link(wmGizmoMapType *gzmap_type,
                                                       void (*wtfunc)(struct wmGizmoGroupType *))
{
  wmGizmoGroupType *gzgt = WM_gizmogrouptype_append(wtfunc);

  gzgt->gzmap_params.spaceid = gzmap_type->spaceid;
  gzgt->gzmap_params.regionid = gzmap_type->regionid;

  return WM_gizmomaptype_group_link_ptr(gzmap_type, gzgt);
}

/**
 * Free but don't remove from ghash.
 */
static void gizmogrouptype_free(wmGizmoGroupType *gzgt)
{
  if (gzgt->rna_ext.srna) { /* python gizmo group, allocs own string */
    MEM_freeN((void *)gzgt->idname);
  }

  MEM_freeN(gzgt);
}

void WM_gizmogrouptype_free_ptr(wmGizmoGroupType *gzgt)
{
  BLI_assert(gzgt == WM_gizmogrouptype_find(gzgt->idname, false));

  BLI_ghash_remove(global_gizmogrouptype_hash, gzgt->idname, NULL, NULL);

  gizmogrouptype_free(gzgt);

  /* XXX, TODO, update the world! */
}

bool WM_gizmogrouptype_free(const char *idname)
{
  wmGizmoGroupType *gzgt = BLI_ghash_lookup(global_gizmogrouptype_hash, idname);

  if (gzgt == NULL) {
    return false;
  }

  WM_gizmogrouptype_free_ptr(gzgt);

  return true;
}

static void wm_gizmogrouptype_ghash_free_cb(wmGizmoGroupType *gzgt)
{
  gizmogrouptype_free(gzgt);
}

void wm_gizmogrouptype_free(void)
{
  BLI_ghash_free(
      global_gizmogrouptype_hash, NULL, (GHashValFreeFP)wm_gizmogrouptype_ghash_free_cb);
  global_gizmogrouptype_hash = NULL;
}

/* called on initialize WM_init() */
void wm_gizmogrouptype_init(void)
{
  /* reserve size is set based on blender default setup */
  global_gizmogrouptype_hash = BLI_ghash_str_new_ex("wm_gizmogrouptype_init gh", 128);
}

/** \} */
