/* Typemap definitions, to allow SWIG to properly handle 'char**' data types. */

%typemap(in) char ** {
  using namespace lldb_private;
  /* Check if is a list  */
  if (PythonList::Check($input)) {
    PythonList list(PyRefType::Borrowed, $input);
    int size = list.GetSize();
    int i = 0;
    $1 = (char**)malloc((size+1)*sizeof(char*));
    for (i = 0; i < size; i++) {
      PythonString py_str = list.GetItemAtIndex(i).AsType<PythonString>();
      if (!py_str.IsAllocated()) {
        PyErr_SetString(PyExc_TypeError,"list must contain strings");
        free($1);
        return nullptr;
      }

      $1[i] = const_cast<char*>(py_str.GetString().data());
    }
    $1[i] = 0;
  } else if ($input == Py_None) {
    $1 =  NULL;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(typecheck) char ** {
  /* Check if is a list  */
  $1 = 1;
  using namespace lldb_private;
  if (PythonList::Check($input)) {
    PythonList list(PyRefType::Borrowed, $input);
    int size = list.GetSize();
    int i = 0;
    for (i = 0; i < size; i++) {
      PythonString s = list.GetItemAtIndex(i).AsType<PythonString>();
      if (!s.IsAllocated()) { $1 = 0; }
    }
  }
  else
  {
    $1 = ( ($input == Py_None) ? 1 : 0);
  }
}

%typemap(freearg) char** {
  free((char *) $1);
}

%typemap(out) char** {
  int len;
  int i;
  len = 0;
  while ($1[len]) len++;
  using namespace lldb_private;
  PythonList list(len);
  for (i = 0; i < len; i++)
    list.SetItemAtIndex(i, PythonString($1[i]));
  $result = list.release();
}


%typemap(in) lldb::tid_t {
  using namespace lldb_private;
  if (PythonInteger::Check($input))
  {
    PythonInteger py_int(PyRefType::Borrowed, $input);
    $1 = static_cast<lldb::tid_t>(py_int.GetInteger());
  }
  else
  {
    PyErr_SetString(PyExc_ValueError, "Expecting an integer");
    return nullptr;
  }
}

/* Typemap definitions to allow SWIG to properly handle char buffer. */

// typemap for a char buffer
// See also SBThread::GetStopDescription.
%typemap(in) (char *dst, size_t dst_len) {
   if (!PyInt_Check($input)) {
       PyErr_SetString(PyExc_ValueError, "Expecting an integer");
       return NULL;
   }
   $2 = PyInt_AsLong($input);
   if ($2 <= 0) {
       PyErr_SetString(PyExc_ValueError, "Positive integer expected");
       return NULL;
   }
   $1 = (char *) malloc($2);
}
// SBProcess::ReadCStringFromMemory() uses a void*, but needs to be treated
// as char data instead of byte data.
%typemap(in) (void *char_buf, size_t size) = (char *dst, size_t dst_len);

// Return the char buffer.  Discarding any previous return result
// See also SBThread::GetStopDescription.
%typemap(argout) (char *dst, size_t dst_len) {
   Py_XDECREF($result);   /* Blow away any previous result */
   if (result == 0) {
      $result = Py_None;
      Py_INCREF($result);
   } else {
      llvm::StringRef ref(static_cast<const char*>($1), result);
      lldb_private::PythonString string(ref);
      $result = string.release();
   }
   free($1);
}
// SBProcess::ReadCStringFromMemory() uses a void*, but needs to be treated
// as char data instead of byte data.
%typemap(argout) (void *char_buf, size_t size) = (char *dst, size_t dst_len);


// typemap for an outgoing buffer
// See also SBEvent::SBEvent(uint32_t event, const char *cstr, uint32_t cstr_len).
%typemap(in) (const char *cstr, uint32_t cstr_len) {
   using namespace lldb_private;
   if (PythonString::Check($input)) {
      PythonString str(PyRefType::Borrowed, $input);
      $1 = (char*)str.GetString().data();
      $2 = str.GetSize();
   }
   else if(PythonByteArray::Check($input)) {
      PythonByteArray bytearray(PyRefType::Borrowed, $input);
      $1 = (char*)bytearray.GetBytes().data();
      $2 = bytearray.GetSize();
   }
   else if (PythonBytes::Check($input)) {
      PythonBytes bytes(PyRefType::Borrowed, $input);
      $1 = (char*)bytes.GetBytes().data();
      $2 = bytes.GetSize();
   }
   else {
      PyErr_SetString(PyExc_ValueError, "Expecting a string");
      return NULL;
   }
}
// Ditto for SBProcess::PutSTDIN(const char *src, size_t src_len).
%typemap(in) (const char *src, size_t src_len) {
   using namespace lldb_private;
   if (PythonString::Check($input)) {
      PythonString str(PyRefType::Borrowed, $input);
      $1 = (char*)str.GetString().data();
      $2 = str.GetSize();
   }
   else if(PythonByteArray::Check($input)) {
      PythonByteArray bytearray(PyRefType::Borrowed, $input);
      $1 = (char*)bytearray.GetBytes().data();
      $2 = bytearray.GetSize();
   }
   else if (PythonBytes::Check($input)) {
      PythonBytes bytes(PyRefType::Borrowed, $input);
      $1 = (char*)bytes.GetBytes().data();
      $2 = bytes.GetSize();
   }
   else {
      PyErr_SetString(PyExc_ValueError, "Expecting a string");
      return NULL;
   }
}
// And SBProcess::WriteMemory.
%typemap(in) (const void *buf, size_t size) {
   using namespace lldb_private;
   if (PythonString::Check($input)) {
      PythonString str(PyRefType::Borrowed, $input);
      $1 = (void*)str.GetString().data();
      $2 = str.GetSize();
   }
   else if(PythonByteArray::Check($input)) {
      PythonByteArray bytearray(PyRefType::Borrowed, $input);
      $1 = (void*)bytearray.GetBytes().data();
      $2 = bytearray.GetSize();
   }
   else if (PythonBytes::Check($input)) {
      PythonBytes bytes(PyRefType::Borrowed, $input);
      $1 = (void*)bytes.GetBytes().data();
      $2 = bytes.GetSize();
   }
   else {
      PyErr_SetString(PyExc_ValueError, "Expecting a buffer");
      return NULL;
   }
}

// For SBDebugger::DispatchInput
%typemap(in) (const void *data, size_t data_len) {
   using namespace lldb_private;
   if (PythonString::Check($input)) {
      PythonString str(PyRefType::Borrowed, $input);
      $1 = (void*)str.GetString().data();
      $2 = str.GetSize();
   }
   else if(PythonByteArray::Check($input)) {
      PythonByteArray bytearray(PyRefType::Borrowed, $input);
      $1 = (void*)bytearray.GetBytes().data();
      $2 = bytearray.GetSize();
   }
   else if (PythonBytes::Check($input)) {
      PythonBytes bytes(PyRefType::Borrowed, $input);
      $1 = (void*)bytes.GetBytes().data();
      $2 = bytes.GetSize();
   }
   else {
      PyErr_SetString(PyExc_ValueError, "Expecting a buffer");
      return NULL;
   }
}

// typemap for an incoming buffer
// See also SBProcess::ReadMemory.
%typemap(in) (void *buf, size_t size) {
   if (PyInt_Check($input)) {
      $2 = PyInt_AsLong($input);
   } else if (PyLong_Check($input)) {
      $2 = PyLong_AsLong($input);
   } else {
      PyErr_SetString(PyExc_ValueError, "Expecting an integer or long object");
      return NULL;
   }
   if ($2 <= 0) {
       PyErr_SetString(PyExc_ValueError, "Positive integer expected");
       return NULL;
   }
   $1 = (void *) malloc($2);
}

// Return the buffer.  Discarding any previous return result
// See also SBProcess::ReadMemory.
%typemap(argout) (void *buf, size_t size) {
   Py_XDECREF($result);   /* Blow away any previous result */
   if (result == 0) {
      $result = Py_None;
      Py_INCREF($result);
   } else {
      lldb_private::PythonBytes bytes(static_cast<const uint8_t*>($1), result);
      $result = bytes.release();
   }
   free($1);
}

// these typemaps allow Python users to pass list objects
// and have them turn into C++ arrays (this is useful, for instance
// when creating SBData objects from lists of numbers)
%typemap(in) (uint64_t* array, size_t array_len) {
  /* Check if is a list  */
  if (PyList_Check($input)) {
    int size = PyList_Size($input);
    int i = 0;
    $2 = size;
    $1 = (uint64_t*) malloc(size * sizeof(uint64_t));
    for (i = 0; i < size; i++) {
      PyObject *o = PyList_GetItem($input,i);
      if (PyInt_Check(o)) {
        $1[i] = PyInt_AsLong(o);
      }
      else if (PyLong_Check(o)) {
        $1[i] = PyLong_AsUnsignedLongLong(o);
      }
      else {
        PyErr_SetString(PyExc_TypeError,"list must contain numbers");
        free($1);
        return NULL;
      }

      if (PyErr_Occurred()) {
        free($1);
        return NULL;
      }
    }
  } else if ($input == Py_None) {
    $1 =  NULL;
    $2 = 0;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(freearg) (uint64_t* array, size_t array_len) {
  free($1);
}

%typemap(in) (uint32_t* array, size_t array_len) {
  /* Check if is a list  */
  if (PyList_Check($input)) {
    int size = PyList_Size($input);
    int i = 0;
    $2 = size;
    $1 = (uint32_t*) malloc(size * sizeof(uint32_t));
    for (i = 0; i < size; i++) {
      PyObject *o = PyList_GetItem($input,i);
      if (PyInt_Check(o)) {
        $1[i] = PyInt_AsLong(o);
      }
      else if (PyLong_Check(o)) {
        $1[i] = PyLong_AsUnsignedLong(o);
      }
      else {
        PyErr_SetString(PyExc_TypeError,"list must contain numbers");
        free($1);
        return NULL;
      }

      if (PyErr_Occurred()) {
        free($1);
        return NULL;
      }
    }
  } else if ($input == Py_None) {
    $1 =  NULL;
    $2 = 0;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(freearg) (uint32_t* array, size_t array_len) {
  free($1);
}

%typemap(in) (int64_t* array, size_t array_len) {
  /* Check if is a list  */
  if (PyList_Check($input)) {
    int size = PyList_Size($input);
    int i = 0;
    $2 = size;
    $1 = (int64_t*) malloc(size * sizeof(int64_t));
    for (i = 0; i < size; i++) {
      PyObject *o = PyList_GetItem($input,i);
      if (PyInt_Check(o)) {
        $1[i] = PyInt_AsLong(o);
      }
      else if (PyLong_Check(o)) {
        $1[i] = PyLong_AsLongLong(o);
      }
      else {
        PyErr_SetString(PyExc_TypeError,"list must contain numbers");
        free($1);
        return NULL;
      }

      if (PyErr_Occurred()) {
        free($1);
        return NULL;
      }
    }
  } else if ($input == Py_None) {
    $1 =  NULL;
    $2 = 0;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(freearg) (int64_t* array, size_t array_len) {
  free($1);
}

%typemap(in) (int32_t* array, size_t array_len) {
  /* Check if is a list  */
  if (PyList_Check($input)) {
    int size = PyList_Size($input);
    int i = 0;
    $2 = size;
    $1 = (int32_t*) malloc(size * sizeof(int32_t));
    for (i = 0; i < size; i++) {
      PyObject *o = PyList_GetItem($input,i);
      if (PyInt_Check(o)) {
        $1[i] = PyInt_AsLong(o);
      }
      else if (PyLong_Check(o)) {
        $1[i] = PyLong_AsLong(o);
      }
      else {
        PyErr_SetString(PyExc_TypeError,"list must contain numbers");
        free($1);
        return NULL;
      }

      if (PyErr_Occurred()) {
        free($1);
        return NULL;
      }
    }
  } else if ($input == Py_None) {
    $1 =  NULL;
    $2 = 0;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(freearg) (int32_t* array, size_t array_len) {
  free($1);
}

%typemap(in) (double* array, size_t array_len) {
  /* Check if is a list  */
  if (PyList_Check($input)) {
    int size = PyList_Size($input);
    int i = 0;
    $2 = size;
    $1 = (double*) malloc(size * sizeof(double));
    for (i = 0; i < size; i++) {
      PyObject *o = PyList_GetItem($input,i);
      if (PyFloat_Check(o)) {
        $1[i] = PyFloat_AsDouble(o);
      }
      else {
        PyErr_SetString(PyExc_TypeError,"list must contain floating-point numbers");
        free($1);
        return NULL;
      }
    }
  } else if ($input == Py_None) {
    $1 =  NULL;
    $2 = 0;
  } else {
    PyErr_SetString(PyExc_TypeError,"not a list");
    return NULL;
  }
}

%typemap(freearg) (double* array, size_t array_len) {
  free($1);
}

// these typemaps wrap SBModule::GetVersion() from requiring a memory buffer
// to the more Pythonic style where a list is returned and no previous allocation
// is necessary - this will break if more than 50 versions are ever returned
%typemap(typecheck) (uint32_t *versions, uint32_t num_versions) {
    $1 = ($input == Py_None ? 1 : 0);
}

%typemap(in, numinputs=0) (uint32_t *versions) {
    $1 = (uint32_t*)malloc(sizeof(uint32_t) * 50);
}

%typemap(in, numinputs=0) (uint32_t num_versions) {
    $1 = 50;
}

%typemap(argout) (uint32_t *versions, uint32_t num_versions) {
    uint32_t count = result;
    if (count >= $2)
        count = $2;
    PyObject* list = PyList_New(count);
    for (uint32_t j = 0; j < count; j++)
    {
        if ($1[j] < UINT32_MAX)
        {
            PyObject* item = PyInt_FromLong($1[j]);
            int ok = PyList_SetItem(list,j,item);
            if (ok != 0)
            {
                $result = Py_None;
                break;
            }
        }
        else
            break;
    }
    $result = list;
}

%typemap(freearg) (uint32_t *versions) {
    free($1);
}


// For Log::LogOutputCallback
%typemap(in) (lldb::LogOutputCallback log_callback, void *baton) {
  if (!($input == Py_None || PyCallable_Check(reinterpret_cast<PyObject*>($input)))) {
    PyErr_SetString(PyExc_TypeError, "Need a callable object or None!");
    return NULL;
  }

  // FIXME (filcab): We can't currently check if our callback is already
  // LLDBSwigPythonCallPythonLogOutputCallback (to DECREF the previous
  // baton) nor can we just remove all traces of a callback, if we want to
  // revert to a file logging mechanism.

  // Don't lose the callback reference
  Py_INCREF($input);
  $1 = LLDBSwigPythonCallPythonLogOutputCallback;
  $2 = $input;
}

%typemap(typecheck) (lldb::LogOutputCallback log_callback, void *baton) {
  $1 = $input == Py_None;
  $1 = $1 || PyCallable_Check(reinterpret_cast<PyObject*>($input));
}

%typemap(in) FILE * {
   using namespace lldb_private;
   if ($input == Py_None)
      $1 = nullptr;
   else if (!lldb_private::PythonFile::Check($input)) {
      int fd = PyObject_AsFileDescriptor($input);
      PythonObject py_input(PyRefType::Borrowed, $input);
      PythonString py_mode = py_input.GetAttributeValue("mode").AsType<PythonString>();

      if (-1 != fd && py_mode.IsValid()) {
         FILE *f;
         if ((f = fdopen(fd, py_mode.GetString().str().c_str())))
            $1 = f;
         else
            PyErr_SetString(PyExc_TypeError, strerror(errno));
      } else {
         PyErr_SetString(PyExc_TypeError,"not a file-like object");
         return nullptr;
      }
   }
   else
   {
      PythonFile py_file(PyRefType::Borrowed, $input);
      File file;
      if (!py_file.GetUnderlyingFile(file))
         return nullptr;

      $1 = file.GetStream();
      if ($1)
         file.Clear();
    }
}

%typemap(out) FILE * {
   char mode[4] = {0};
#ifdef __APPLE__
   int i = 0;
   if ($1)
   {
       short flags = $1->_flags;

       if (flags & __SRD)
          mode[i++] = 'r';
       else if (flags & __SWR)
          mode[i++] = 'w';
       else // if (flags & __SRW)
          mode[i++] = 'a';
    }
#endif
   using namespace lldb_private;
   File file($1, false);
   PythonFile py_file(file, mode);
   $result = py_file.release();
   if (!$result)
   {
       $result = Py_None;
       Py_INCREF(Py_None);
   }
}

%typemap(in) (const char* string, int len) {
    using namespace lldb_private;
    if ($input == Py_None)
    {
        $1 = NULL;
        $2 = 0;
    }
    else if (PythonString::Check($input))
    {
        PythonString py_str(PyRefType::Borrowed, $input);
        llvm::StringRef str = py_str.GetString();
        $1 = const_cast<char*>(str.data());
        $2 = str.size();
        // In Python 2, if $input is a PyUnicode object then this
        // will trigger a Unicode -> String conversion, in which
        // case the `PythonString` will now own the PyString.  Thus
        // if it goes out of scope, the data will be deleted.  The
        // only way to avoid this is to leak the Python object in
        // that case.  Note that if there was no conversion, then
        // releasing the string will not leak anything, since we
        // created this as a borrowed reference.
        py_str.release();
    }
    else
    {
        PyErr_SetString(PyExc_TypeError,"not a string-like object");
        return NULL;
    }
}
