/**
 * PANDA 3D SOFTWARE
 * Copyright (c) Carnegie Mellon University.  All rights reserved.
 *
 * All use of this software is subject to the terms of the revised BSD
 * license.  You should have received a copy of this license along
 * with this source code in a file named "LICENSE."
 *
 * @file renderState_ext.cxx
 * @author CFSworks
 * @date 2014-03-31
 */

#include "renderState_ext.h"

#ifdef HAVE_PYTHON

/**
 * Returns a RenderState with a given number of attributes set.
 */
CPT(RenderState) Extension<RenderState>::
make(PyObject *args, PyObject *kwds) {
  extern struct Dtool_PyTypedObject Dtool_RenderAttrib;

  Py_ssize_t num_attribs = PyTuple_GET_SIZE(args);

  // Check for the override keyword argument.
  PyObject *py_override = nullptr;
  if (kwds != nullptr && PyDict_GET_SIZE(kwds) > 0) {
    if (PyDict_GET_SIZE(kwds) > 1) {
      Dtool_Raise_TypeError("RenderState.make() received an unexpected keyword argument");
      return nullptr;
    }

    PyObject *key;
    Py_ssize_t ppos = 0;
    if (!PyDict_Next(kwds, &ppos, &key, &py_override)) {
      return nullptr;
    }

    // We got the item, we just need to make sure that it had the right key.
    if (!PyUnicode_CheckExact(key) || PyUnicode_CompareWithASCIIString(key, "override") != 0) {
      Dtool_Raise_TypeError("RenderState.make() received an unexpected keyword argument");
      return nullptr;
    }

    if (!PyLong_Check(py_override)) {
      Dtool_Raise_TypeError("RenderState.make() override argument should be int");
      return nullptr;
    }
  }
  else if (num_attribs > 1 && PyLong_Check(PyTuple_GET_ITEM(args, num_attribs - 1))) {
    // It was specified as last positional argument.
    py_override = PyTuple_GET_ITEM(args, --num_attribs);
  }

  int override = 0;
  if (py_override != nullptr) {
    override = _PyLong_AsInt(py_override);
    if (override == -1 && PyErr_Occurred()) {
      return nullptr;
    }
  }

  const RenderAttrib **attribs = (const RenderAttrib **)alloca(sizeof(RenderAttrib *) * num_attribs);
  for (Py_ssize_t i = 0; i < num_attribs; ++i) {
    PyObject *arg = PyTuple_GET_ITEM(args, i);
    if (!DtoolInstance_GetPointer(arg, attribs[i], Dtool_RenderAttrib)) {
      Dtool_Raise_ArgTypeError(arg, i, "RenderState.make", "RenderAttrib");
      return nullptr;
    }
  }
  return RenderState::make(attribs, num_attribs, override);
}

/**
 * Returns a list of 2-tuples that represents the composition cache.  For each
 * tuple in the list, the first element is the source render, and the second
 * is the result render.  If both are None, there is no entry in the cache at
 * that slot.
 *
 * In general, a->compose(source) == result.
 *
 * This has no practical value other than for examining the cache for
 * performance analysis.
 */
PyObject *Extension<RenderState>::
get_composition_cache() const {
  extern struct Dtool_PyTypedObject Dtool_RenderState;
  LightReMutexHolder holder(*RenderState::_states_lock);
  size_t cache_size = _this->_composition_cache.get_num_entries();
  PyObject *list = PyList_New(cache_size);

  for (size_t i = 0; i < cache_size; ++i) {
    PyObject *tuple = PyTuple_New(2);
    PyObject *a, *b;
    const RenderState *source = _this->_composition_cache.get_key(i);
    if (source == nullptr) {
      a = Py_NewRef(Py_None);
    } else {
      source->ref();
      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState,
                                      true, true, source->get_type_index());
    }
    const RenderState *result = _this->_composition_cache.get_data(i)._result;
    if (result == nullptr) {
      b = Py_NewRef(Py_None);
    } else {
      result->ref();
      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState,
                                      true, true, result->get_type_index());
    }
    PyTuple_SET_ITEM(tuple, 0, a);
    PyTuple_SET_ITEM(tuple, 1, b);

    PyList_SET_ITEM(list, i, tuple);
  }

  return list;
}

/**
 * Returns a list of 2-tuples that represents the invert_composition cache.
 * For each tuple in the list, the first element is the source render, and the
 * second is the result render.  If both are None, there is no entry in the
 * cache at that slot.
 *
 * In general, a->invert_compose(source) == result.
 *
 * This has no practical value other than for examining the cache for
 * performance analysis.
 */
PyObject *Extension<RenderState>::
get_invert_composition_cache() const {
  extern struct Dtool_PyTypedObject Dtool_RenderState;
  LightReMutexHolder holder(*RenderState::_states_lock);
  size_t cache_size = _this->_invert_composition_cache.get_num_entries();
  PyObject *list = PyList_New(cache_size);

  for (size_t i = 0; i < cache_size; ++i) {
    PyObject *tuple = PyTuple_New(2);
    PyObject *a, *b;
    const RenderState *source = _this->_invert_composition_cache.get_key(i);
    if (source == nullptr) {
      a = Py_NewRef(Py_None);
    } else {
      source->ref();
      a = DTool_CreatePyInstanceTyped((void *)source, Dtool_RenderState,
                                      true, true, source->get_type_index());
    }
    const RenderState *result = _this->_invert_composition_cache.get_data(i)._result;
    if (result == nullptr) {
      b = Py_NewRef(Py_None);
    } else {
      result->ref();
      b = DTool_CreatePyInstanceTyped((void *)result, Dtool_RenderState,
                                      true, true, result->get_type_index());
    }
    PyTuple_SET_ITEM(tuple, 0, a);
    PyTuple_SET_ITEM(tuple, 1, b);

    PyList_SET_ITEM(list, i, tuple);
  }

  return list;
}

/**
 * Returns a list of all of the RenderState objects in the state cache.  The
 * order of elements in this cache is arbitrary.
 */
PyObject *Extension<RenderState>::
get_states() {
  extern struct Dtool_PyTypedObject Dtool_RenderState;
  LightReMutexHolder holder(*RenderState::_states_lock);

  size_t num_states = RenderState::_states.get_num_entries();
  PyObject *list = PyList_New(num_states);
  size_t i = 0;

  size_t size = RenderState::_states.get_num_entries();
  for (size_t si = 0; si < size; ++si) {
    const RenderState *state = RenderState::_states.get_key(si);
    state->ref();
    PyObject *a =
      DTool_CreatePyInstanceTyped((void *)state, Dtool_RenderState,
                                  true, true, state->get_type_index());
    nassertr(i < num_states, list);
    PyList_SET_ITEM(list, i, a);
    ++i;
  }
  nassertr(i == num_states, list);
  return list;
}

/**
 * Returns a list of all of the "unused" RenderState objects in the state
 * cache.  See get_num_unused_states().
 */
PyObject *Extension<RenderState>::
get_unused_states() {
  extern struct Dtool_PyTypedObject Dtool_RenderState;
  LightReMutexHolder holder(*RenderState::_states_lock);

  PyObject *list = PyList_New(0);
  size_t size = RenderState::_states.get_num_entries();
  for (size_t si = 0; si < size; ++si) {
    const RenderState *state = RenderState::_states.get_key(si);
    if (state->get_cache_ref_count() == state->get_ref_count()) {
      state->ref();
      PyObject *a =
        DTool_CreatePyInstanceTyped((void *)state, Dtool_RenderState,
                                    true, true, state->get_type_index());
      PyList_Append(list, a);
      Py_DECREF(a);
    }
  }
  return list;
}

#endif  // HAVE_PYTHON
