// Test customizing slots when using the -builtin option

%module python_builtin

// throw is invalid in C++17 and later, only SWIG to use it
#define TESTCASE_THROW1(T1) throw(T1)
#define TESTCASE_THROW2(T1, T2) throw(T1, T2)
%{
#define TESTCASE_THROW1(T1)
#define TESTCASE_THROW2(T1, T2)
%}

%inline %{
#ifdef SWIGPYTHON_BUILTIN
bool is_python_builtin() { return true; }
#else
bool is_python_builtin() { return false; }
#endif
%}

// Test 0 for default tp_hash
%inline %{
struct ValueStruct {
  int value;
  ValueStruct(int value) : value(value) {}
  static ValueStruct *inout(ValueStruct *v) {
    return v;
  }
};
%}

// Test 1a for tp_hash
#if defined(SWIGPYTHON_BUILTIN)
%feature("python:tp_hash") SimpleValue "SimpleValueHashFunction"
#endif

%inline %{
struct SimpleValue {
  int value;
  SimpleValue(int value) : value(value) {}
};
%}

%{
#if PY_VERSION_HEX >= 0x03020000
Py_hash_t SimpleValueHashFunction(PyObject *v)
#else
long SimpleValueHashFunction(PyObject *v)
#endif
{
  SwigPyObject *sobj = (SwigPyObject *) v;
  SimpleValue *p = (SimpleValue *)sobj->ptr;
  return p->value;
}
hashfunc test_hashfunc_cast() {
    return SimpleValueHashFunction;
}
%}

// Test 1b for tp_hash
#if defined(SWIGPYTHON_BUILTIN)
%feature("python:slot", "tp_hash", functype="hashfunc") SimpleValue2::HashFunc;
#endif

%inline %{
struct SimpleValue2 {
  int value;
  SimpleValue2(int value) : value(value) {}
#if PY_VERSION_HEX >= 0x03020000
  typedef Py_hash_t HashType;
#else
  typedef long HashType;
#endif
  HashType HashFunc() { return (HashType)value; }
};
%}

// Test 2 for tp_hash
#if defined(SWIGPYTHON_BUILTIN)
%feature("python:slot", "tp_hash", functype="hashfunc") BadHashFunctionReturnType::bad_hash_function;
#endif

%inline %{
struct BadHashFunctionReturnType {
    static const char * bad_hash_function() {
      return "bad hash function";
    }
};
%}

// Test 3 for tp_hash
#if defined(SWIGPYTHON_BUILTIN)
%feature("python:slot", "tp_hash", functype="hashfunc") ExceptionHashFunction::exception_hash_function;
#endif

%catches(const char *) exception_hash_function;

%inline %{
#if PY_VERSION_HEX < 0x03020000
  #define Py_hash_t long
#endif
struct ExceptionHashFunction {
    static Py_hash_t exception_hash_function() {
      throw "oops";
    }
};
%}

// Test 4 for tp_dealloc (which is handled differently to other slots in the SWIG source)
#if defined(SWIGPYTHON_BUILTIN)
%feature("python:tp_dealloc") Dealloc1 "Dealloc1Destroyer"
%feature("python:tp_dealloc") Dealloc2 "Dealloc2Destroyer"
%feature("python:slot", "tp_dealloc", functype="destructor") Dealloc3::Destroyer;
#endif

%inline %{
static int Dealloc1CalledCount = 0;
static int Dealloc2CalledCount = 0;
static int Dealloc3CalledCount = 0;

struct Dealloc1 {
};
struct Dealloc2 {
  ~Dealloc2() {}
};
struct Dealloc3 {
  void Destroyer() {
    Dealloc3CalledCount++;
    delete this;
  }
};
%}

%{
void Dealloc1Destroyer(PyObject *v) {
  SwigPyObject *sobj = (SwigPyObject *) v;
  Dealloc1 *p = (Dealloc1 *)sobj->ptr;
  delete p;
  Dealloc1CalledCount++;
}
void Dealloc2Destroyer(PyObject *v) {
  SwigPyObject *sobj = (SwigPyObject *) v;
  Dealloc2 *p = (Dealloc2 *)sobj->ptr;
  delete p;
  Dealloc2CalledCount++;
}
%}

// Test 5 for python:compare feature
%feature("python:compare", "Py_LT") MyClass::lessThan;

%inline %{
  class MyClass {
  public:
    MyClass(int val = 0) : val(val) {}
    bool lessThan(const MyClass& other) const {
      less_than_counts++;
      return val < other.val;
    }
    int val;
    static int less_than_counts;
  };
  int MyClass::less_than_counts = 0;
%}

// Test 6 add in container __getitem__ to support basic sequence protocol
// Tests overloaded functions being used for more than one slot (mp_subscript and sq_item)
%include <exception.i>
%include <std_except.i>
%apply int {Py_ssize_t}
%typemap(in) PySliceObject * {
  if (!PySlice_Check($input))
    SWIG_exception(SWIG_TypeError, "in method '$symname', argument $argnum of type '$type'");
  $1 = (PySliceObject *)$input;
}
%typemap(typecheck,precedence=300) PySliceObject* {
  $1 = PySlice_Check($input);
}

%feature("python:slot", "mp_subscript", functype="binaryfunc") SimpleArray::__getitem__(PySliceObject *slice);
%feature("python:slot", "sq_item", functype="ssizeargfunc") SimpleArray::__getitem__(Py_ssize_t n);
%feature("python:slot", "sq_length", functype="lenfunc") SimpleArray::__len__;
%inline %{
  class SimpleArray {
    Py_ssize_t size;
    int numbers[5];
  public:
    SimpleArray(Py_ssize_t size) : size(size) {
      for (Py_ssize_t x = 0; x<size; ++x)
        numbers[x] = (int)x*10;
    }

    Py_ssize_t __len__() {
      return size;
    }

    int __getitem__(Py_ssize_t n) TESTCASE_THROW1(std::out_of_range) {
      if (n >= (int)size)
        throw std::out_of_range("Index too large");
      return numbers[n];
    }

    SimpleArray __getitem__(PySliceObject *slice) TESTCASE_THROW2(std::out_of_range, std::invalid_argument) {
      if (!PySlice_Check(slice))
        throw std::invalid_argument("Slice object expected");
      Py_ssize_t i, j, step;
#if PY_VERSION_HEX >= 0x03020000
      PySlice_GetIndices((PyObject *)slice, size, &i, &j, &step);
#else
      PySlice_GetIndices((PySliceObject *)slice, size, &i, &j, &step);
#endif
      if (step != 1)
        throw std::invalid_argument("Only a step size of 1 is implemented");

      {
        Py_ssize_t ii = i<0 ? 0 : i>=size ? size-1 : i;
        Py_ssize_t jj = j<0 ? 0 : j>=size ? size-1 : j;
        if (ii > jj)
          throw std::invalid_argument("getitem i should not be larger than j");
        SimpleArray n(jj-ii);
        for (Py_ssize_t x = 0; x<size; ++x)
          n.numbers[x] = numbers[x+ii];
        return n;
      }
    }
  };
%}

// Test 7 mapping to Python's pow
%pybinoperator(__pow__, ANumber::power, ternaryfunc, nb_power);

%inline %{
class ANumber {
  int num;
public:
  ANumber(int d = 0) : num(d) {}
  ANumber __pow__(const ANumber &other, const ANumber *x = 0) const {
    int val = (int)pow(num, other.num);
    val = x ? val % x->num : val;
    return ANumber(val);
  }
  int Value() const {
    return num;
  }
};
%}

// Test 8 https://github.com/swig/swig/pull/2771 __setitem__ for deleting item, uses C NULL
%feature("python:slot", "sq_item", functype="ssizeargfunc") GetSetItem::__getitem__;
%feature("python:slot", "sq_ass_item", functype="ssizeobjargproc") GetSetItem::__setitem__;
%feature("python:slot", "tp_call", functype="ternaryfunc") GetSetItem::__call__;
%typemap(default) PyObject* value {$1 = NULL;}

%inline %{
class GetSetItem {
public:
  int idx, value, args_count, kw_count;
  GetSetItem() : idx(), value(), args_count(), kw_count() { reset(); }
  int __getitem__(int idx) {
    this->idx = idx;
    return 123;
  }
  void __setitem__(int idx, PyObject* value) {
    this->idx = idx;
    this->value = value ? (int)PyInt_AsLong(value) : -11;
  }
  void __call__(PyObject* args, PyObject* kw) {
    this->args_count = args ? (int)PyTuple_Size(args) : -11;
    this->kw_count = kw ? (int)PyDict_Size(kw) : -11;
  }
  void reset() {
    idx = -100;
    value = -100;
    args_count = -100;
    kw_count = -100;
  }
};
%}
