/*
 * 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.
 *
 * The Original Code is Copyright (C) 2012 Blender Foundation.
 * All rights reserved.
 */

/** \file
 * \ingroup pybmesh
 *
 * This file defines the types for 'BMesh.select_history'
 * sequence and iterator.
 *
 * select_history is very loosely based on pythons set() type,
 * since items can only exist once. however they do have an order.
 */

#include <Python.h>

#include "BLI_listbase.h"
#include "BLI_utildefines.h"

#include "bmesh.h"

#include "bmesh_py_types.h"
#include "bmesh_py_types_select.h"

#include "../generic/py_capi_utils.h"
#include "../generic/python_utildefines.h"

PyDoc_STRVAR(bpy_bmeditselseq_active_doc,
             "The last selected element or None (read-only).\n\n:type: :class:`BMVert`, "
             ":class:`BMEdge` or :class:`BMFace`");
static PyObject *bpy_bmeditselseq_active_get(BPy_BMEditSelSeq *self, void *UNUSED(closure))
{
  BMEditSelection *ese;
  BPY_BM_CHECK_OBJ(self);

  if ((ese = self->bm->selected.last)) {
    return BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head);
  }

  Py_RETURN_NONE;
}

static PyGetSetDef bpy_bmeditselseq_getseters[] = {
    {"active",
     (getter)bpy_bmeditselseq_active_get,
     (setter)NULL,
     bpy_bmeditselseq_active_doc,
     NULL},
    {NULL, NULL, NULL, NULL, NULL} /* Sentinel */
};

PyDoc_STRVAR(bpy_bmeditselseq_validate_doc,
             ".. method:: validate()\n"
             "\n"
             "   Ensures all elements in the selection history are selected.\n");
static PyObject *bpy_bmeditselseq_validate(BPy_BMEditSelSeq *self)
{
  BPY_BM_CHECK_OBJ(self);
  BM_select_history_validate(self->bm);
  Py_RETURN_NONE;
}

PyDoc_STRVAR(bpy_bmeditselseq_clear_doc,
             ".. method:: clear()\n"
             "\n"
             "   Empties the selection history.\n");
static PyObject *bpy_bmeditselseq_clear(BPy_BMEditSelSeq *self)
{
  BPY_BM_CHECK_OBJ(self);
  BM_select_history_clear(self->bm);
  Py_RETURN_NONE;
}

PyDoc_STRVAR(
    bpy_bmeditselseq_add_doc,
    ".. method:: add(element)\n"
    "\n"
    "   Add an element to the selection history (no action taken if its already added).\n");
static PyObject *bpy_bmeditselseq_add(BPy_BMEditSelSeq *self, BPy_BMElem *value)
{
  BPY_BM_CHECK_OBJ(self);

  if ((BPy_BMVert_Check(value) || BPy_BMEdge_Check(value) || BPy_BMFace_Check(value)) == false) {
    PyErr_Format(
        PyExc_TypeError, "Expected a BMVert/BMedge/BMFace not a %.200s", Py_TYPE(value)->tp_name);
    return NULL;
  }

  BPY_BM_CHECK_SOURCE_OBJ(self->bm, "select_history.add()", value);

  BM_select_history_store(self->bm, value->ele);

  Py_RETURN_NONE;
}

PyDoc_STRVAR(bpy_bmeditselseq_remove_doc,
             ".. method:: remove(element)\n"
             "\n"
             "   Remove an element from the selection history.\n");
static PyObject *bpy_bmeditselseq_remove(BPy_BMEditSelSeq *self, BPy_BMElem *value)
{
  BPY_BM_CHECK_OBJ(self);

  if ((BPy_BMVert_Check(value) || BPy_BMEdge_Check(value) || BPy_BMFace_Check(value)) == false) {
    PyErr_Format(
        PyExc_TypeError, "Expected a BMVert/BMedge/BMFace not a %.200s", Py_TYPE(value)->tp_name);
    return NULL;
  }

  BPY_BM_CHECK_SOURCE_OBJ(self->bm, "select_history.remove()", value);

  if (BM_select_history_remove(self->bm, value->ele) == false) {
    PyErr_SetString(PyExc_ValueError, "Element not found in selection history");
    return NULL;
  }

  Py_RETURN_NONE;
}

PyDoc_STRVAR(
    bpy_bmeditselseq_discard_doc,
    ".. method:: discard(element)\n"
    "\n"
    "   Discard an element from the selection history.\n"
    "\n"
    "   Like remove but doesn't raise an error when the elements not in the selection list.\n");
static PyObject *bpy_bmeditselseq_discard(BPy_BMEditSelSeq *self, BPy_BMElem *value)
{
  BPY_BM_CHECK_OBJ(self);

  if ((BPy_BMVert_Check(value) || BPy_BMEdge_Check(value) || BPy_BMFace_Check(value)) == false) {
    PyErr_Format(
        PyExc_TypeError, "Expected a BMVert/BMedge/BMFace not a %.200s", Py_TYPE(value)->tp_name);
    return NULL;
  }

  BPY_BM_CHECK_SOURCE_OBJ(self->bm, "select_history.discard()", value);

  BM_select_history_remove(self->bm, value->ele);

  Py_RETURN_NONE;
}

static struct PyMethodDef bpy_bmeditselseq_methods[] = {
    {"validate",
     (PyCFunction)bpy_bmeditselseq_validate,
     METH_NOARGS,
     bpy_bmeditselseq_validate_doc},
    {"clear", (PyCFunction)bpy_bmeditselseq_clear, METH_NOARGS, bpy_bmeditselseq_clear_doc},

    {"add", (PyCFunction)bpy_bmeditselseq_add, METH_O, bpy_bmeditselseq_add_doc},
    {"remove", (PyCFunction)bpy_bmeditselseq_remove, METH_O, bpy_bmeditselseq_remove_doc},
    {"discard", (PyCFunction)bpy_bmeditselseq_discard, METH_O, bpy_bmeditselseq_discard_doc},
    {NULL, NULL, 0, NULL},
};

/* Sequences
 * ========= */

static Py_ssize_t bpy_bmeditselseq_length(BPy_BMEditSelSeq *self)
{
  BPY_BM_CHECK_INT(self);

  return BLI_listbase_count(&self->bm->selected);
}

static PyObject *bpy_bmeditselseq_subscript_int(BPy_BMEditSelSeq *self, int keynum)
{
  BMEditSelection *ese;

  BPY_BM_CHECK_OBJ(self);

  if (keynum < 0) {
    ese = BLI_rfindlink(&self->bm->selected, -1 - keynum);
  }
  else {
    ese = BLI_findlink(&self->bm->selected, keynum);
  }

  if (ese) {
    return BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head);
  }

  PyErr_Format(PyExc_IndexError, "BMElemSeq[index]: index %d out of range", keynum);
  return NULL;
}

static PyObject *bpy_bmeditselseq_subscript_slice(BPy_BMEditSelSeq *self,
                                                  Py_ssize_t start,
                                                  Py_ssize_t stop)
{
  int count = 0;

  PyObject *list;
  BMEditSelection *ese;

  BPY_BM_CHECK_OBJ(self);

  list = PyList_New(0);

  /* First loop up-until the start. */
  for (ese = self->bm->selected.first; ese; ese = ese->next) {
    if (count == start) {
      break;
    }
    count++;
  }

  /* Add items until stop. */
  for (; ese; ese = ese->next) {
    PyList_APPEND(list, BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head));
    count++;
    if (count == stop) {
      break;
    }
  }

  return list;
}

static PyObject *bpy_bmeditselseq_subscript(BPy_BMEditSelSeq *self, PyObject *key)
{
  /* don't need error check here */
  if (PyIndex_Check(key)) {
    const Py_ssize_t i = PyNumber_AsSsize_t(key, PyExc_IndexError);
    if (i == -1 && PyErr_Occurred()) {
      return NULL;
    }
    return bpy_bmeditselseq_subscript_int(self, i);
  }
  if (PySlice_Check(key)) {
    PySliceObject *key_slice = (PySliceObject *)key;
    Py_ssize_t step = 1;

    if (key_slice->step != Py_None && !_PyEval_SliceIndex(key, &step)) {
      return NULL;
    }
    if (step != 1) {
      PyErr_SetString(PyExc_TypeError, "BMElemSeq[slice]: slice steps not supported");
      return NULL;
    }
    if (key_slice->start == Py_None && key_slice->stop == Py_None) {
      return bpy_bmeditselseq_subscript_slice(self, 0, PY_SSIZE_T_MAX);
    }

    Py_ssize_t start = 0, stop = PY_SSIZE_T_MAX;

    /* avoid PySlice_GetIndicesEx because it needs to know the length ahead of time. */
    if (key_slice->start != Py_None && !_PyEval_SliceIndex(key_slice->start, &start)) {
      return NULL;
    }
    if (key_slice->stop != Py_None && !_PyEval_SliceIndex(key_slice->stop, &stop)) {
      return NULL;
    }

    if (start < 0 || stop < 0) {
      /* only get the length for negative values */
      const Py_ssize_t len = bpy_bmeditselseq_length(self);
      if (start < 0) {
        start += len;
        CLAMP_MIN(start, 0);
      }
      if (stop < 0) {
        stop += len;
        CLAMP_MIN(stop, 0);
      }
    }

    if (stop - start <= 0) {
      return PyList_New(0);
    }

    return bpy_bmeditselseq_subscript_slice(self, start, stop);
  }

  PyErr_SetString(PyExc_AttributeError, "BMElemSeq[key]: invalid key, key must be an int");
  return NULL;
}

static int bpy_bmeditselseq_contains(BPy_BMEditSelSeq *self, PyObject *value)
{
  BPy_BMElem *value_bm_ele;

  BPY_BM_CHECK_INT(self);

  value_bm_ele = (BPy_BMElem *)value;
  if (value_bm_ele->bm == self->bm) {
    return BM_select_history_check(self->bm, value_bm_ele->ele);
  }

  return 0;
}

static PySequenceMethods bpy_bmeditselseq_as_sequence = {
    (lenfunc)bpy_bmeditselseq_length, /* sq_length */
    NULL,                             /* sq_concat */
    NULL,                             /* sq_repeat */
    (ssizeargfunc)bpy_bmeditselseq_subscript_int,
    /* sq_item */                          /* Only set this so PySequence_Check() returns True */
    NULL,                                  /* sq_slice */
    (ssizeobjargproc)NULL,                 /* sq_ass_item */
    NULL,                                  /* *was* sq_ass_slice */
    (objobjproc)bpy_bmeditselseq_contains, /* sq_contains */
    (binaryfunc)NULL,                      /* sq_inplace_concat */
    (ssizeargfunc)NULL,                    /* sq_inplace_repeat */
};

static PyMappingMethods bpy_bmeditselseq_as_mapping = {
    (lenfunc)bpy_bmeditselseq_length,       /* mp_length */
    (binaryfunc)bpy_bmeditselseq_subscript, /* mp_subscript */
    (objobjargproc)NULL,                    /* mp_ass_subscript */
};

/* Iterator
 * -------- */

static PyObject *bpy_bmeditselseq_iter(BPy_BMEditSelSeq *self)
{
  BPy_BMEditSelIter *py_iter;

  BPY_BM_CHECK_OBJ(self);
  py_iter = (BPy_BMEditSelIter *)BPy_BMEditSelIter_CreatePyObject(self->bm);
  py_iter->ese = self->bm->selected.first;
  return (PyObject *)py_iter;
}

static PyObject *bpy_bmeditseliter_next(BPy_BMEditSelIter *self)
{
  BMEditSelection *ese = self->ese;
  if (ese == NULL) {
    PyErr_SetNone(PyExc_StopIteration);
    return NULL;
  }

  self->ese = ese->next;
  return (PyObject *)BPy_BMElem_CreatePyObject(self->bm, &ese->ele->head);
}

PyTypeObject BPy_BMEditSelSeq_Type;
PyTypeObject BPy_BMEditSelIter_Type;

PyObject *BPy_BMEditSel_CreatePyObject(BMesh *bm)
{
  BPy_BMEditSelSeq *self = PyObject_New(BPy_BMEditSelSeq, &BPy_BMEditSelSeq_Type);
  self->bm = bm;
  /* caller must initialize 'iter' member */
  return (PyObject *)self;
}

PyObject *BPy_BMEditSelIter_CreatePyObject(BMesh *bm)
{
  BPy_BMEditSelIter *self = PyObject_New(BPy_BMEditSelIter, &BPy_BMEditSelIter_Type);
  self->bm = bm;
  /* caller must initialize 'iter' member */
  return (PyObject *)self;
}

void BPy_BM_init_types_select(void)
{
  BPy_BMEditSelSeq_Type.tp_basicsize = sizeof(BPy_BMEditSelSeq);
  BPy_BMEditSelIter_Type.tp_basicsize = sizeof(BPy_BMEditSelIter);

  BPy_BMEditSelSeq_Type.tp_name = "BMEditSelSeq";
  BPy_BMEditSelIter_Type.tp_name = "BMEditSelIter";

  BPy_BMEditSelSeq_Type.tp_doc = NULL; /* todo */
  BPy_BMEditSelIter_Type.tp_doc = NULL;

  BPy_BMEditSelSeq_Type.tp_repr = (reprfunc)NULL;
  BPy_BMEditSelIter_Type.tp_repr = (reprfunc)NULL;

  BPy_BMEditSelSeq_Type.tp_getset = bpy_bmeditselseq_getseters;
  BPy_BMEditSelIter_Type.tp_getset = NULL;

  BPy_BMEditSelSeq_Type.tp_methods = bpy_bmeditselseq_methods;
  BPy_BMEditSelIter_Type.tp_methods = NULL;

  BPy_BMEditSelSeq_Type.tp_as_sequence = &bpy_bmeditselseq_as_sequence;

  BPy_BMEditSelSeq_Type.tp_as_mapping = &bpy_bmeditselseq_as_mapping;

  BPy_BMEditSelSeq_Type.tp_iter = (getiterfunc)bpy_bmeditselseq_iter;

  /* only 1 iteratir so far */
  BPy_BMEditSelIter_Type.tp_iternext = (iternextfunc)bpy_bmeditseliter_next;

  BPy_BMEditSelSeq_Type.tp_dealloc = NULL;   //(destructor)bpy_bmeditselseq_dealloc;
  BPy_BMEditSelIter_Type.tp_dealloc = NULL;  //(destructor)bpy_bmvert_dealloc;

  BPy_BMEditSelSeq_Type.tp_flags = Py_TPFLAGS_DEFAULT;
  BPy_BMEditSelIter_Type.tp_flags = Py_TPFLAGS_DEFAULT;

  PyType_Ready(&BPy_BMEditSelSeq_Type);
  PyType_Ready(&BPy_BMEditSelIter_Type);
}

/* utility function */

/**
 * \note doesn't actually check selection.
 */
int BPy_BMEditSel_Assign(BPy_BMesh *self, PyObject *value)
{
  BMesh *bm;
  Py_ssize_t value_len;
  Py_ssize_t i;
  BMElem **value_array = NULL;

  BPY_BM_CHECK_INT(self);

  bm = self->bm;

  value_array = BPy_BMElem_PySeq_As_Array(&bm,
                                          value,
                                          0,
                                          PY_SSIZE_T_MAX,
                                          &value_len,
                                          BM_VERT | BM_EDGE | BM_FACE,
                                          true,
                                          true,
                                          "BMesh.select_history = value");

  if (value_array == NULL) {
    return -1;
  }

  BM_select_history_clear(bm);

  for (i = 0; i < value_len; i++) {
    BM_select_history_store_notest(bm, value_array[i]);
  }

  PyMem_FREE(value_array);
  return 0;
}
