%module csharp_exceptions

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

%include <exception.i>

%inline %{
  class Ex {
    const char *message;
  public:
    Ex(const char *msg) : message(msg) {}
    const char *what() { return message; }
  };
%}

%exception ThrowByValue() {
  try {
    $action
  } catch(Ex e) {
    SWIG_exception(SWIG_DivisionByZero, e.what());
  }
}

%exception ThrowByReference() {
  try {
    $action
  } catch(Ex &e) {
    SWIG_exception(SWIG_DivisionByZero, e.what());
  }
}

%csnothrowexception NoThrowException() {
  try {
    $action
  } catch(Ex) {
    // swallowed
  }
}

%inline %{
// %exception tests
void ThrowByValue()                                     { throw Ex("ThrowByValue"); }
void ThrowByReference()                                 { throw Ex("ThrowByReference"); }
// %csnothrowexception
void NoThrowException()                                 { throw Ex("NoThrowException"); }
// exception specifications
void ExceptionSpecificationValue() TESTCASE_THROW1(Ex)            { throw Ex("ExceptionSpecificationValue"); }
void ExceptionSpecificationReference() TESTCASE_THROW1(Ex&)       { throw Ex("ExceptionSpecificationReference"); }
void ExceptionSpecificationString() TESTCASE_THROW1(const char *) { throw "ExceptionSpecificationString"; }
void ExceptionSpecificationInteger() TESTCASE_THROW1(int)         { throw 20; }
%}

// test exceptions in the default typemaps

// null reference exceptions
%inline %{
void NullReference(Ex& e) {}
void NullValue(Ex e) {}
%}

// enums
%inline %{
enum TestEnum {TestEnumItem};
void ExceptionSpecificationEnumValue() TESTCASE_THROW1(TestEnum) { throw TestEnumItem; }
void ExceptionSpecificationEnumReference() TESTCASE_THROW1(TestEnum&) { throw TestEnumItem; }
%}

// std::string
%include <std_string.i>
%inline %{
void ExceptionSpecificationStdStringValue() TESTCASE_THROW1(std::string) { throw std::string("ExceptionSpecificationStdStringValue"); }
void ExceptionSpecificationStdStringReference() TESTCASE_THROW1(const std::string&) { throw std::string("ExceptionSpecificationStdStringReference"); }
void NullStdStringValue(std::string s) {}
void NullStdStringReference(std::string &s) {}
%}

// Memory leak check (The C++ exception stack was never unwound in the original approach to throwing exceptions from unmanaged code)
%exception MemoryLeakCheck() {
  Counter destructor_should_be_called;
  try {
    $action
  } catch(Ex e) {
    SWIG_exception(SWIG_DivisionByZero, e.what());
  }
}

%inline %{
struct Counter {
  static int count;
  Counter() { count++; }
  ~Counter() { count--; }
};
int Counter::count = 0;

void MemoryLeakCheck() {
  throw Ex("testing memory leaks when throwing exceptions");
}
%}

// test exception pending in the csconstruct typemap
%inline %{
struct constructor {
  constructor(std::string s) {}
  constructor() TESTCASE_THROW1(int) { throw 10; }
};
%}

// test exception pending in the csout typemaps
%typemap(out, canthrow=1) unsigned short ushorttest %{
  $result = $1;
  if ($result == 100) {
    SWIG_CSharpSetPendingException(SWIG_CSharpIndexOutOfRangeException, "don't like 100");
    return $null;
  }
%}
%inline %{
unsigned short ushorttest() { return 100; }
%}

// test exception pending in the csvarout/csvarin typemaps and canthrow attribute in unmanaged code typemaps
%typemap(check, canthrow=1) int numberin, int InOutStruct::staticnumberin %{
  if ($1 < 0) {
    SWIG_CSharpSetPendingException(SWIG_CSharpIndexOutOfRangeException, "too small");
    return $null;
  }
%}
%typemap(out, canthrow=1) int numberout, int InOutStruct::staticnumberout %{
  $result = $1;
  if ($result > 10) {
    SWIG_CSharpSetPendingException(SWIG_CSharpIndexOutOfRangeException, "too big");
    return $null;
  }
%}
%inline %{
  int numberin;
  int numberout;
  struct InOutStruct {
    int numberin;
    int numberout;
    static int staticnumberin;
    static int staticnumberout;
  };
  int InOutStruct::staticnumberin;
  int InOutStruct::staticnumberout;
%}

// test SWIG_exception macro - it must return from unmanaged code without executing any further unmanaged code
%typemap(check, canthrow=1) int macrotest {
  if ($1 < 0) {
    SWIG_exception(SWIG_IndexError, "testing SWIG_exception macro");
  }
}
%inline %{
  bool exception_macro_run_flag = false;
  void exceptionmacrotest(int macrotest) {
    exception_macro_run_flag = true;
  }
%}

// test all the types of exceptions
%typemap(check, canthrow=1) UnmanagedExceptions {
  switch($1) {
    case UnmanagedApplicationException:          SWIG_CSharpSetPendingException(SWIG_CSharpApplicationException,        "msg"); return $null; break;
    case UnmanagedArithmeticException:           SWIG_CSharpSetPendingException(SWIG_CSharpArithmeticException,         "msg"); return $null; break;
    case UnmanagedDivideByZeroException:         SWIG_CSharpSetPendingException(SWIG_CSharpDivideByZeroException,       "msg"); return $null; break;
    case UnmanagedIndexOutOfRangeException:      SWIG_CSharpSetPendingException(SWIG_CSharpIndexOutOfRangeException,    "msg"); return $null; break;
    case UnmanagedInvalidCastException:          SWIG_CSharpSetPendingException(SWIG_CSharpInvalidCastException,        "msg"); return $null; break;
    case UnmanagedInvalidOperationException:     SWIG_CSharpSetPendingException(SWIG_CSharpInvalidOperationException,   "msg"); return $null; break;
    case UnmanagedIOException:                   SWIG_CSharpSetPendingException(SWIG_CSharpIOException,                 "msg"); return $null; break;
    case UnmanagedNullReferenceException:        SWIG_CSharpSetPendingException(SWIG_CSharpNullReferenceException,      "msg"); return $null; break;
    case UnmanagedOutOfMemoryException:          SWIG_CSharpSetPendingException(SWIG_CSharpOutOfMemoryException,        "msg"); return $null; break;
    case UnmanagedOverflowException:             SWIG_CSharpSetPendingException(SWIG_CSharpOverflowException,           "msg"); return $null; break;
    case UnmanagedSystemException:               SWIG_CSharpSetPendingException(SWIG_CSharpSystemException,             "msg"); return $null; break;
    case UnmanagedArgumentException:             SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentException,           "msg", "parm"); return $null; break;
    case UnmanagedArgumentNullException:         SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentNullException,       "msg", "parm"); return $null; break;
    case UnmanagedArgumentOutOfRangeException:   SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentOutOfRangeException, "msg", "parm"); return $null; break;
  }
}
%inline %{
enum UnmanagedExceptions {
  UnmanagedApplicationException,
  UnmanagedArithmeticException,
  UnmanagedDivideByZeroException,
  UnmanagedIndexOutOfRangeException,
  UnmanagedInvalidCastException,
  UnmanagedInvalidOperationException,
  UnmanagedIOException,
  UnmanagedNullReferenceException,
  UnmanagedOutOfMemoryException,
  UnmanagedOverflowException,
  UnmanagedSystemException,
  UnmanagedArgumentException,
  UnmanagedArgumentNullException,
  UnmanagedArgumentOutOfRangeException
};

void check_exception(UnmanagedExceptions e) {
}
%}

// exceptions in multiple threads test
%exception ThrowsClass::ThrowException(long long input) {
  try {
    $action
  } catch (long long d) {
    char message[64];
    sprintf(message, "caught:%lld", d);
    SWIG_CSharpSetPendingExceptionArgument(SWIG_CSharpArgumentOutOfRangeException, message, "input");
  }
}
%inline %{
struct ThrowsClass {
  double dub;
  ThrowsClass(double d) : dub(d) {}
  long long ThrowException(long long input) {
    throw input;
    return input;
  }
};
%}

// test inner exceptions
%exception InnerExceptionTest() {
  try {
    $action
  } catch(Ex &e) {
    SWIG_CSharpSetPendingException(SWIG_CSharpApplicationException, e.what());
    SWIG_CSharpSetPendingException(SWIG_CSharpInvalidOperationException, "My OuterException message");
  }
}

%inline %{
void InnerExceptionTest() { throw Ex("My InnerException message"); }
%}

