// -*- Mode: C++; -*-
//                            Package   : omniORBpy
// pyValueType.cc             Created on: 2003/04/11
//                            Author    : Duncan Grisby (dgrisby)
//
//    Copyright (C) 2003-2015 Apasphere Ltd.
//
//    This file is part of the omniORBpy library
//
//    The omniORBpy library is free software; you can redistribute it
//    and/or modify it under the terms of the GNU Lesser General
//    Public License as published by the Free Software Foundation;
//    either version 2.1 of the License, or (at your option) any later
//    version.
//
//    This library 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 Lesser General Public License for more details.
//
//    You should have received a copy of the GNU Lesser General Public
//    License along with this library. If not, see http://www.gnu.org/licenses/
//
//
// Description:
//    ValueType support

#include <omnipy.h>
#include <pyThreadCache.h>

OMNI_USING_NAMESPACE(omni)

//
// Tag constants
//

#define CODEBASE_URL  1
#define REPOID_SINGLE 2
#define REPOID_LIST   6
#define REPOID_MASK   6
#define CHUNKED       8


//
// Value indirection trackers
//

class pyOutputValueTracker : public ValueIndirectionTracker {
public:
  inline pyOutputValueTracker() :
    magic_(MAGIC_), dict_(PyDict_New()), in_truncatable_(0)
  {
    omniORB::logs(25, "Create Python output value indirection tracker");
  }

  virtual ~pyOutputValueTracker()
  {
    omniORB::logs(25, "Delete Python output value indirection tracker");

    omnipyThreadCache::lock _t;
    Py_DECREF(dict_);
  }

  inline CORBA::Boolean valid()
  {
    return magic_ == MAGIC_;
  }

  omni::s_size_t addValue(PyObject* obj, omni::s_size_t current)
  {
    // Look to see if the value has been marshalled before. If so,
    // return its offset; if not, add it to the table and return -1.

    PyObject* key = PyLong_FromVoidPtr(obj); // id(obj)
    PyObject* val = PyDict_GetItem(dict_, key);

    if (val) {
      OMNIORB_ASSERT(Int_Check(val));
      omni::s_size_t pos = Int_AsSsize_t(val);
      Py_DECREF(key);
      return pos;
    }
    else {
      PyObject* val = Int_FromSsize_t(current);
      PyDict_SetItem(dict_, key, val);
      Py_DECREF(val);
      Py_DECREF(key);
      return -1;
    }
  }

  omni::s_size_t addValueBox(PyObject* d_o, PyObject* obj,
                             PyObject* repoId, omni::s_size_t current)
  {
    // Look to see if the value has been marshalled before. If so,
    // return its offset; if not, add it to the table and return -1.

    PyObject* key = PyLong_FromVoidPtr(obj); // id(obj)

    // Multiple distinct CORBA types map to the same Python types as
    // each other. If the caller passes a single Python object as two
    // valueboxes that should have different types, we must not send
    // an indirection. For cases where this sort of type overlap can
    // occur, we build a dict key as a tuple of kind and value id.
    CORBA::TCKind tk = (CORBA::TCKind)omniPy::descriptorToTK(d_o);

    while (tk == CORBA::tk_alias) {
      d_o = PyTuple_GET_ITEM(d_o, 3);
      tk  = (CORBA::TCKind)omniPy::descriptorToTK(d_o);
    }
    
    switch (tk) {
    case CORBA::tk_short:
    case CORBA::tk_long:
    case CORBA::tk_ushort:
    case CORBA::tk_ulong:
    case CORBA::tk_float:
    case CORBA::tk_double:
    case CORBA::tk_boolean:
    case CORBA::tk_char:
    case CORBA::tk_octet:
    case CORBA::tk_longlong:
    case CORBA::tk_ulonglong:
    case CORBA::tk_longdouble:
    case CORBA::tk_wchar:
    case CORBA::tk_wstring: // kept distinct from string
      {
        // Make a tuple of the form (id(obj), tk, repoId)
        Py_INCREF(repoId);
        PyObject* ktuple = PyTuple_New(3);
        PyTuple_SET_ITEM(ktuple, 0, key);
        PyTuple_SET_ITEM(ktuple, 1, Int_FromLong(tk));
        PyTuple_SET_ITEM(ktuple, 2, repoId);
        key = ktuple;
      }
      break;
    default:
      // Key of just id(obj) is sufficient. The default case is here
      // to avoid compiler warnings.
      break;
    }

    PyObject* val = PyDict_GetItem(dict_, key);

    if (val) {
      OMNIORB_ASSERT(Int_Check(val));
      omni::s_size_t pos = Int_AsSsize_t(val);
      Py_DECREF(key);
      return pos;
    }
    else {
      PyObject* val = Int_FromSsize_t(current);
      PyDict_SetItem(dict_, key, val);
      Py_DECREF(val);
      Py_DECREF(key);
      return -1;
    }
  }

  omni::s_size_t addRepoIds(PyObject* obj, omni::s_size_t current)
  {
    // Caller is marshalling a repoid or a list of repoids stored in a
    // tuple.
    PyObject* val = PyDict_GetItem(dict_, obj);

    if (val) {
      OMNIORB_ASSERT(Int_Check(val));
      omni::s_size_t pos = Int_AsSsize_t(val);
      return pos;
    }
    else {
      val = Int_FromSsize_t(current);
      PyDict_SetItem(dict_, obj, val);
      Py_DECREF(val);
      return -1;
    }
  }

  inline void startTruncatable() {
    in_truncatable_++;
  }
  inline void endTruncatable() {
    in_truncatable_--;
  }
  inline CORBA::Boolean inTruncatable() {
    return (in_truncatable_ > 0);
  }
  // Inside a truncatable value, repoIds of nested values must always
  // be sent, in case the receiver truncates the first instance of the
  // nested value away, but later receives an indirection to it.


private:
  static const CORBA::ULong MAGIC_;
  CORBA::ULong magic_;

  PyObject*    dict_;
  CORBA::ULong in_truncatable_;
};

const CORBA::ULong pyOutputValueTracker::MAGIC_ = 0x50594f56; // "PYOV"


class pyInputValueTracker : public ValueIndirectionTracker {
public:
  inline pyInputValueTracker() :
    magic_(MAGIC_), dict_(PyDict_New())
  {
    omniORB::logs(25, "Create Python input value indirection tracker");
  }

  virtual ~pyInputValueTracker()
  {
    omniORB::logs(25, "Delete Python input value indirection tracker");

    omnipyThreadCache::lock _t;
    Py_DECREF(dict_);
  }

  inline CORBA::Boolean valid()
  {
    return magic_ == MAGIC_;
  }
  
  void add(PyObject* obj, omni::s_size_t pos)
  {
    // Add record of an unmarshalled value.
    PyObject* key = Int_FromSsize_t(pos);
    PyDict_SetItem(dict_, key, obj);
    Py_DECREF(key);
  }

  PyObject* lookup(omni::s_size_t pos, CORBA::CompletionStatus completion)
  {
    // Lookup specified position for a previously unmarshalled value.
    // If the value has been previously unmarshalled, return a
    // duplicated reference to it. Otherwise, throw a MARSHAL
    // exception.
    PyObject* key = Int_FromSsize_t(pos);
    PyObject* ret = PyDict_GetItem(dict_, key);
    Py_DECREF(key);

    if (!ret)
      OMNIORB_THROW(MARSHAL, MARSHAL_InvalidIndirection, completion);

    Py_INCREF(ret);
    return ret;
  }

private:
  static const CORBA::ULong MAGIC_;
  CORBA::ULong magic_;

  PyObject*    dict_;
};

const CORBA::ULong pyInputValueTracker::MAGIC_ = 0x50594956; // "PYIV"


//
// Marshalling functions
//

static void validateMembers(PyObject* d_o, PyObject* a_o,
			    CORBA::CompletionStatus compstatus,
			    PyObject* track);

void
omniPy::
validateTypeValue(PyObject* d_o, PyObject* a_o,
		  CORBA::CompletionStatus compstatus,
		  PyObject* track)
{ // class, repoid, value name, valuemodifier, truncatable base repoids,
  // concrete base descr, [ member name, desc, visibility ] ...

  if (a_o == Py_None) // Nil value
    return;

  PyObject* idlRepoId = PyTuple_GET_ITEM(d_o, 2);

  // Detect cycles
  CORBA::Boolean track_alloc = 0;
  PyObject* key = PyTuple_New(2);
  Py_INCREF(idlRepoId);
  PyTuple_SET_ITEM(key, 0, PyLong_FromVoidPtr(a_o));
  PyTuple_SET_ITEM(key, 1, idlRepoId);

  if (track) {
    if (PyDict_GetItem(track, key)) {
      // Seen this value before.
      Py_DECREF(key);
      return;
    }
  }
  else {
    track_alloc = 1;
    track = PyDict_New();
  }
  PyDict_SetItem(track, key, Py_None);
  Py_DECREF(key);

  try {
    PyObject* actualRepoId = PyObject_GetAttr(a_o, omniPy::pyNP_RepositoryId);

    if (!actualRepoId) {
      PyErr_Clear();
      THROW_PY_BAD_PARAM(BAD_PARAM_WrongPythonType, compstatus,
			 omniPy::formatString("Expecting valuetype, got %r",
					      "O", a_o->ob_type));
    }

    omniPy::PyRefHolder actualRepoId_holder(actualRepoId);
  
    if (!omni::ptrStrMatch(String_AS_STRING(idlRepoId),
			   String_AS_STRING(actualRepoId))) {
      // Object doesn't match the value expected from the IDL. Check it
      // is derived from it.
      PyObject* cls = PyTuple_GET_ITEM(d_o, 1);

      if (!PyObject_IsInstance(a_o, cls)) {
	THROW_PY_BAD_PARAM(BAD_PARAM_WrongPythonType, compstatus,
			   omniPy::formatString("Valuetype %r is not "
						"a subclass of %r",
						"OO",
						a_o->ob_type,
						PyTuple_GET_ITEM(d_o, 3)));
      }
      d_o = PyDict_GetItem(omniPy::pyomniORBtypeMap, actualRepoId);
      if (!d_o) {
	THROW_PY_BAD_PARAM(BAD_PARAM_WrongPythonType, compstatus,
			   omniPy::formatString("Unknown valuetype repository "
						"id %r",
						"O", actualRepoId));
      }
      if (!PyTuple_Check(d_o) ||
	  Int_AS_LONG(PyTuple_GetItem(d_o, 0)) != CORBA::tk_value) {

	THROW_PY_BAD_PARAM(BAD_PARAM_WrongPythonType, compstatus,
			   omniPy::formatString("Repository id %r is not a "
						"valuetype",
						"O", actualRepoId));
      }
    }

    // Check value modifier
    PyObject*            pymod = PyTuple_GET_ITEM(d_o, 4);
    CORBA::ValueModifier mod   = Int_AS_LONG(pymod);

    if (mod == CORBA::VM_ABSTRACT) {
      THROW_PY_BAD_PARAM(BAD_PARAM_AttemptToMarshalAbstractValue, compstatus,
			   omniPy::formatString("Valuetype %r is abstract",
						"O", a_o->ob_type));
    }
    if (mod == CORBA::VM_CUSTOM)
      OMNIORB_THROW(NO_IMPLEMENT, NO_IMPLEMENT_Unsupported, compstatus);

    validateMembers(d_o, a_o, compstatus, track);

    if (track_alloc) {
      Py_DECREF(track);
    }
  }
  catch (...) {
    if (track_alloc) {
      Py_DECREF(track);
    }
    throw;
  }
}

static void validateMembers(PyObject* d_o, PyObject* a_o,
			    CORBA::CompletionStatus compstatus,
			    PyObject* track)
{
  PyObject* t_o = PyTuple_GET_ITEM(d_o, 0);
  OMNIORB_ASSERT(Int_Check(t_o) && Int_AS_LONG(t_o) == CORBA::tk_value);

  // Check base
  t_o = PyTuple_GET_ITEM(d_o, 6);
  if (PyTuple_Check(t_o))
    validateMembers(t_o, a_o, compstatus, track);

  // The descriptor has three times the number of value members, plus 7.
  int members = (PyTuple_GET_SIZE(d_o) - 7) / 3;

  PyObject* name;
  PyObject* value;

  int i, j;

  for (i=0,j=7; i < members; i++, j+=3) {
    name    = PyTuple_GET_ITEM(d_o, j); OMNIORB_ASSERT(String_Check(name));
    value   = PyObject_GetAttr(a_o, name);
    if (!value) {
      PyErr_Clear();
      THROW_PY_BAD_PARAM(BAD_PARAM_WrongPythonType, compstatus,
			 omniPy::formatString("Valuetype %r instance %r "
					      "has no %r member",
					      "OOO",
					      PyTuple_GET_ITEM(d_o, 3),
					      a_o->ob_type,
					      name));
    }
    omniPy::PyRefHolder h(value);
    try {
      omniPy::validateType(PyTuple_GET_ITEM(d_o, j+1), value,
			   compstatus, track);
    }
    catch (Py_BAD_PARAM& bp) {
      bp.add(omniPy::formatString("Valuetype %r member %r", "OO",
				  PyTuple_GET_ITEM(d_o, 3), name));
      throw;
    }
  }
}


void
omniPy::
validateTypeValueBox(PyObject* d_o, PyObject* a_o,
		     CORBA::CompletionStatus compstatus,
		     PyObject* track)
{ // class, repoid, valuebox name, boxed type desc

  if (a_o == Py_None) // Nil value
    return;

  try {
    omniPy::validateType(PyTuple_GET_ITEM(d_o, 4), a_o, compstatus, track);
  }
  catch (Py_BAD_PARAM& bp) {
    bp.add(omniPy::formatString("Value box %r", "O",
				PyTuple_GET_ITEM(d_o, 3)));
    throw;
  }
}



static void
marshalIndirection(cdrStream& stream, omni::s_size_t pos)
{
  stream.declareArrayLength(omni::ALIGN_4, 8);
  CORBA::ULong indirect = 0xffffffff;
  indirect >>= stream;

  omni::s_size_t offset = pos - stream.currentOutputPtr();

  OMNIORB_ASSERT(offset < -4 || stream.currentOutputPtr() == 0);
  // In a counting stream, the currentOutputPtr is always zero.

#if (OMNI_SIZEOF_PTR == 8)
  if (offset < -0x7fffffff - 1) {
    // Value is more than 2GB earlier in the stream!
    OMNIORB_THROW(MARSHAL, MARSHAL_InvalidIndirection,
                  (CORBA::CompletionStatus)stream.completion());
  }
#endif

  stream.marshalLong((CORBA::Long)offset);
}


static void
marshalMembers(cdrValueChunkStream& stream, PyObject* d_o, PyObject* a_o);

static void
real_marshalPyObjectValue(cdrValueChunkStream& stream,
			  PyObject* d_o, PyObject* a_o)
{ // class, repoid, value name, valuemodifier, truncatable base repoids,
  // concrete base descr, [ member name, desc, visibility ] ...

  CORBA::Long tag = 0x7fffff00 | CHUNKED;

  pyOutputValueTracker* tracker = (pyOutputValueTracker*)stream.valueTracker();

  PyObject* idlRepoId    = PyTuple_GET_ITEM(d_o, 2);
  PyObject* actualRepoId = PyObject_GetAttr(a_o, omniPy::pyNP_RepositoryId);

  OMNIORB_ASSERT(actualRepoId);
  Py_DECREF(actualRepoId); // Obj still holds a ref

  // Find descriptor of actual value being marshalled
  CORBA::Boolean derived = 0;
  if (!omni::ptrStrMatch(String_AS_STRING(idlRepoId),
			 String_AS_STRING(actualRepoId))) {

    d_o = PyDict_GetItem(omniPy::pyomniORBtypeMap, actualRepoId);
    derived = 1;
  }

  // Determine whether to send repoId(s)
  PyObject* baseIds = 0;

  if (derived) {
    baseIds = PyTuple_GET_ITEM(d_o, 5);

    if (baseIds == Py_None) {
      tag |= REPOID_SINGLE;
    }
    else {
      tag |= REPOID_LIST;
    }
  }
  else {
    // Not a derived value.

    if (tracker->inTruncatable()) {
      // Nested inside a truncatable value, we always send the repoId(s)
      // in case the receiver truncates, then has a later indirection.
      baseIds = PyTuple_GET_ITEM(d_o, 5);

      if (baseIds == Py_None) {
	tag |= REPOID_SINGLE;
      }
      else {
	tag |= REPOID_LIST;
      }
    }
    else {
      // RMI: repository ids must always be sent
      const char* id = String_AS_STRING(actualRepoId);
      if (id[0] == 'R' && id[1] == 'M' && id[2] == 'I' && id[3] == ':') {
	tag |= REPOID_SINGLE;
      }
    }
  }

  // Start the value header
  stream.startOutputValueHeader(tag);

  // Marshal repoId(s) if necessary
  if ((tag & REPOID_MASK) == REPOID_LIST) {

    OMNIORB_ASSERT(baseIds && baseIds != Py_None);

    omni::s_size_t pos = tracker->addRepoIds(baseIds,
                                             stream.currentOutputPtr());

    if (pos != -1) {
      marshalIndirection(stream, pos);
    }
    else {
      CORBA::Long bases = PyTuple_GET_SIZE(baseIds);
      bases >>= stream;
      for (CORBA::Long i=0; i<bases; i++) {
	stream.alignOutput(omni::ALIGN_4);
	PyObject* id = PyTuple_GET_ITEM(baseIds, i);
	pos = tracker->addRepoIds(id, stream.currentOutputPtr());
	if (pos != -1)
	  marshalIndirection(stream, pos);
	else
	  omniPy::marshalRawPyString(stream, id);
      }
    }
  }
  else if ((tag & REPOID_MASK) == REPOID_SINGLE) {
    omni::s_size_t pos = tracker->addRepoIds(actualRepoId,
                                             stream.currentOutputPtr());
    if (pos != -1)
      marshalIndirection(stream, pos);
    else
      omniPy::marshalRawPyString(stream, actualRepoId);
  }

  // Finally, we marshal the members
  stream.startOutputValueBody();

  if ((tag & REPOID_MASK) == REPOID_LIST)
    tracker->startTruncatable();

  marshalMembers(stream, d_o, a_o);

  if ((tag & REPOID_MASK) == REPOID_LIST)
    tracker->endTruncatable();

  stream.endOutputValue();
}

static void
marshalMembers(cdrValueChunkStream& stream, PyObject* d_o, PyObject* a_o)
{
  PyObject* t_o = PyTuple_GET_ITEM(d_o, 6);
  if (PyTuple_Check(t_o))
    marshalMembers(stream, t_o, a_o);

  int members = (PyTuple_GET_SIZE(d_o) - 7) / 3;

  PyObject* name;
  PyObject* value;

  int i, j;

  for (i=0,j=7; i < members; i++, j+=3) {
    name    = PyTuple_GET_ITEM(d_o, j);
    value   = PyObject_GetAttr(a_o, name);

    omniPy::PyRefHolder h(value);
    omniPy::marshalPyObject(stream, PyTuple_GET_ITEM(d_o, j+1), value);
  }
}  


void
omniPy::
marshalPyObjectValue(cdrStream& stream, PyObject* d_o, PyObject* a_o)
{ // class, repoid, value name, valuemodifier, truncatable base repoids,
  // concrete base descr, [ member name, desc, visibility ] ...

  if (a_o == Py_None) { // Nil
    CORBA::Long tag = 0;
    tag >>= stream;
    return;
  }

  if (!stream.valueTracker()) {
    stream.valueTracker(new pyOutputValueTracker());
  }

  pyOutputValueTracker* tracker = (pyOutputValueTracker*)stream.valueTracker();
  
  OMNIORB_ASSERT(tracker->valid());

  stream.alignOutput(omni::ALIGN_4);

  omni::s_size_t pos = tracker->addValue(a_o, stream.currentOutputPtr());

  if (pos != -1) {
    marshalIndirection(stream, pos);
    return;
  }

  // We choose to always send values chunked. That's much easier than
  // figuring out when we have to chunk.

  cdrValueChunkStream* cstreamp = cdrValueChunkStream::downcast(&stream);
  if (cstreamp) {
    real_marshalPyObjectValue(*cstreamp, d_o, a_o);
  }
  else {
    cdrValueChunkStream cstream(stream);
    try {
      real_marshalPyObjectValue(cstream, d_o, a_o);
    }
    catch (...) {
      cstream.exceptionOccurred();
      throw;
    }
  }
}

void
omniPy::
marshalPyObjectValueBox(cdrStream& stream, PyObject* d_o, PyObject* a_o)
{ // class, repoid, valuebox name, boxed type desc

  if (a_o == Py_None) { // Nil
    CORBA::Long tag = 0;
    tag >>= stream;
    return;
  }

  if (!stream.valueTracker()) {
    stream.valueTracker(new pyOutputValueTracker());
  }

  pyOutputValueTracker* tracker = (pyOutputValueTracker*)stream.valueTracker();
  
  OMNIORB_ASSERT(tracker->valid());

  stream.alignOutput(omni::ALIGN_4);

  PyObject*      repoId    = PyTuple_GET_ITEM(d_o, 2);
  PyObject*      boxed_d_o = PyTuple_GET_ITEM(d_o, 4);
  omni::s_size_t pos       = tracker->addValueBox(boxed_d_o, a_o, repoId,
                                                  stream.currentOutputPtr());

  if (pos != -1) {
    marshalIndirection(stream, pos);
    return;
  }

  cdrValueChunkStream* cstreamp = cdrValueChunkStream::downcast(&stream);

  CORBA::Long tag = 0x7fffff00;


  // ValueBoxes are only sent chunked if they're nested inside chunked values.
  if (cstreamp)
    tag |= CHUNKED;

  if (tracker->inTruncatable()) {
    tag |= REPOID_SINGLE;
  }
  else {
    const char* id = String_AS_STRING(repoId);
    if (id[0] == 'R' && id[1] == 'M' && id[2] == 'I' && id[3] == ':') {
      tag |= REPOID_SINGLE;
    }
  }

  if (cstreamp)
    cstreamp->startOutputValueHeader(tag);
  else
    tag >>= stream;

  if (tag & REPOID_SINGLE) {
    omni::s_size_t pos = tracker->addRepoIds(repoId, stream.currentOutputPtr());
    if (pos != -1)
      marshalIndirection(stream, pos);
    else
      omniPy::marshalRawPyString(stream, repoId);
  }

  // Marshal the boxed value
  if (cstreamp)
    cstreamp->startOutputValueBody();

  omniPy::marshalPyObject(stream, boxed_d_o, a_o);

  if (cstreamp)
    cstreamp->endOutputValue();
}



//
// Unmarshalling functions
//

static PyObject*
unmarshalValueRepoId(cdrStream& stream, pyInputValueTracker* tracker)
{
  // Unmarshal a raw string, or an indirection to one

  CORBA::ULong len; len <<= stream;
  omni::s_size_t  pos = stream.currentInputPtr();

  if (len == 0xffffffff) {
    CORBA::Long offset;
    offset <<= stream;
    if (offset >= -4) {
      OMNIORB_THROW(MARSHAL, MARSHAL_InvalidIndirection,
		    (CORBA::CompletionStatus)stream.completion());
    }
    return tracker->lookup(pos + offset,
			   (CORBA::CompletionStatus)stream.completion());
  }
  if (!stream.checkInputOverrun(1, len))
    OMNIORB_THROW(MARSHAL, MARSHAL_PassEndOfMessage,
		  (CORBA::CompletionStatus)stream.completion());

  PyObject* pystring = omniPy::unmarshalRawPyString(stream, len);

  tracker->add(pystring, pos-4);
  return pystring;
}

static void
unmarshalMembers(cdrStream& stream, PyObject* desc,
		 PyObject* instance, PyObject* member_list);

static PyObject*
real_unmarshalPyObjectValue(cdrStream& stream, cdrValueChunkStream* cstreamp,
			    PyObject* d_o, CORBA::ULong tag, omni::s_size_t pos)
{ // class, repoid, value name, valuemodifier, truncatable base repoids,
  // concrete base descr, [ member name, desc, visibility ] ...
  //or:
  // class, repoid, valuebox name, boxed type desc

  pyInputValueTracker* tracker = (pyInputValueTracker*)stream.valueTracker();

  // Ignore any codebase URLs

  if (tag & CODEBASE_URL) {
    CORBA::ULong length;
    length <<= stream;
    if (length == 0xffffffff) { // Indirection. Skip it.
      length <<= stream;
    }
    else {
      stream.skipInput(length);
    }
  }

  CORBA::Boolean truncating = 0;
  int i;

  PyObject* instance  = 0;
  PyObject* factory   = 0;
  PyObject* desc      = 0;
  PyObject* idlrepoId = PyTuple_GET_ITEM(d_o, 2);
  PyObject* repoId    = 0;

  // Read any repoIds

  if ((tag & REPOID_MASK) == REPOID_LIST) {
    PyObject* repoIds;

    CORBA::ULong count;
    count <<= stream;

    omni::s_size_t idpos = stream.currentInputPtr();

    if (count == 0xffffffff) { // Indirection
      CORBA::Long offset;
      offset <<= stream;

      if (offset >= -4) {
	OMNIORB_THROW(MARSHAL, MARSHAL_InvalidIndirection,
		      (CORBA::CompletionStatus)stream.completion());
      }
      repoIds = tracker->lookup(idpos + offset,
				(CORBA::CompletionStatus)stream.completion());
    }
    else {
      if (!stream.checkInputOverrun(4, count, omni::ALIGN_4)) {
	OMNIORB_THROW(MARSHAL, MARSHAL_SequenceIsTooLong,
		      (CORBA::CompletionStatus)stream.completion());
      }
      repoIds = PyTuple_New(count);
      if (!repoIds)
	OMNIORB_THROW(NO_MEMORY, 0,
		      (CORBA::CompletionStatus)stream.completion());

      for (CORBA::ULong i=0; i<count; i++) {
	PyObject* repoId = unmarshalValueRepoId(stream, tracker);
	PyTuple_SET_ITEM(repoIds, i, repoId);
      }
    }
    tracker->add(repoIds, idpos - 4);

    for (i=0; i < PyTuple_GET_SIZE(repoIds); i++) {
      repoId  = PyTuple_GET_ITEM(repoIds, i);
      factory = PyDict_GetItem(omniPy::pyomniORBvalueMap, repoId);
      if (factory) {
	// Make sure we have a type descriptor
	if (omni::ptrStrMatch(String_AS_STRING(repoId),
			      String_AS_STRING(idlrepoId))) {
	  // Most derived id is target
	  desc = d_o;
	  break;
	}
	else {
	  desc = PyDict_GetItem(omniPy::pyomniORBtypeMap, repoId);
	  if (desc)
	    break;
	}
      }
      else {
	if (omni::ptrStrMatch(String_AS_STRING(repoId),
			      String_AS_STRING(idlrepoId))) {
	  // RepoId matches the target, but we don't have a factory
	  // for it. We can't truncate any further. Break out here,
	  // and throw MARSHAL below.
	  desc = d_o;
	  break;
	}
      }
    }
    Py_INCREF(repoId);
    Py_DECREF(repoIds);
    if (i > 0) {
      // If the first repoId didn't match, we need to truncate.
      truncating = 1;
    }      
  }
  else if ((tag & REPOID_MASK) == REPOID_SINGLE) {
    repoId  = unmarshalValueRepoId(stream, tracker);
    factory = PyDict_GetItem(omniPy::pyomniORBvalueMap, repoId);

    if (omni::ptrStrMatch(String_AS_STRING(repoId),
			  String_AS_STRING(idlrepoId)))
      desc = d_o;
    else
      desc = PyDict_GetItem(omniPy::pyomniORBtypeMap, repoId);
  }
  else {
    // No repoId marshalled -- it's the target type
    if (d_o == omniPy::pyCORBAValueBaseDesc) {
      // If we're expecting ValueBase, the sender must specify a repoId.
      OMNIORB_THROW(MARSHAL, MARSHAL_NoRepoIdInValueType,
		    (CORBA::CompletionStatus)stream.completion());
    }
    Py_INCREF(idlrepoId);
    repoId  = idlrepoId;
    factory = PyDict_GetItem(omniPy::pyomniORBvalueMap, repoId);
    desc    = d_o;
  }

  // If the value is chunked, tell the chunk stream we're about to
  // unmarshal the value body.
  if (cstreamp)
    cstreamp->startInputValueBody();

  // Check there's a factory and a type descriptor for the chosen repoId.

  try {
    PyObject* member_list = 0;

    if (!factory || factory == Py_None) {
      if (desc) {
	PyObject* vclass = PyTuple_GET_ITEM(desc, 1);
	if (PyObject_IsSubclass(vclass, omniPy::pyomniORBUnknownValueBase)) {
	  // Value is inside an Any, and has a TypeCode for which we
	  // have no static knowledge. We create an instance of the
	  // class created as the TypeCode was unmarshalled.
	  omniORB::logs(25, "Unmarshal unknown valuetype inside Any.");
	  factory = vclass;
	  member_list = PyList_New(0);
	}
      }
      if (!factory || factory == Py_None) {
	OMNIORB_THROW(MARSHAL, MARSHAL_NoValueFactory,
		      (CORBA::CompletionStatus)stream.completion());
      }
    }

    if (!desc)
      OMNIORB_THROW(NO_IMPLEMENT, NO_IMPLEMENT_NoValueImpl,
		    (CORBA::CompletionStatus)stream.completion());

    // Are we unmarshalling a valuetype or valuebox?
    CORBA::ULong dtype = Int_AS_LONG(PyTuple_GET_ITEM(desc, 0));

    if (dtype == CORBA::tk_value) {
      PyObject* target = PyTuple_GET_ITEM(d_o, 1);

      // Call the factory
      instance = PyObject_CallObject(factory, 0);

      if (!instance)
	omniPy::handlePythonException();

      if (!PyObject_IsInstance(instance, target))
	OMNIORB_THROW(BAD_PARAM, BAD_PARAM_WrongPythonType,
		      (CORBA::CompletionStatus)stream.completion());

      tracker->add(instance, pos);

      // Finally, we have a blank value which we can unmarshal the members to.
      unmarshalMembers(stream, desc, instance, member_list);
      if (member_list) {
	PyObject_SetAttrString(instance, (char*)"_values", member_list);
	Py_DECREF(member_list);
      }

      // Allow the object to finish initialising itself after unmarshalling.
      PyObject* postHook = PyObject_GetAttrString(instance,
                                                  (char*)"_NP_postUnmarshal");
      if (postHook) {
        PyObject* hookResult = PyObject_CallObject(postHook, 0);
        if (!hookResult) {
          Py_DECREF(postHook);
          omniPy::handlePythonException();
        }
        Py_DECREF(postHook);
        Py_DECREF(instance);
        instance = hookResult;
      }
      else {
        PyErr_Clear();
      }
    }
    else if (dtype == CORBA::tk_value_box) {
      PyObject* boxedtype = PyTuple_GET_ITEM(desc, 4);
      instance = omniPy::unmarshalPyObject(stream, boxedtype);
      tracker->add(instance, pos);
    }
    else {
      OMNIORB_THROW(MARSHAL, MARSHAL_TypeIsNotAValueType,
		    (CORBA::CompletionStatus)stream.completion());
    }

    // Skip remaining parts if we're truncating.

    if (truncating) {
      if (omniORB::trace(25)) {
	omniORB::logger l;
	l << "Truncating input value to "
	  << String_AS_STRING(repoId) << "\n";
      }
      cdrValueChunkStream* cstreamp = cdrValueChunkStream::downcast(&stream);

      if (!cstreamp) {
	// Can't truncate if it's not a chunked stream.
	OMNIORB_THROW(MARSHAL, MARSHAL_InvalidChunkedEncoding,
		      (CORBA::CompletionStatus)stream.completion());
      }
      cdrValueChunkStream& cstream = *cstreamp;
      CORBA::Long level = cstream.nestLevel();

      PyObject* nested;

      while (cstream.skipToNestedValue(level)) {
	// Unmarshal a nested value, in case there's a later indirection to it.

	try {
	  nested = omniPy::unmarshalPyObjectValue(cstream,
					       omniPy::pyCORBAValueBaseDesc);
	  Py_DECREF(nested);
	}
	catch (CORBA::MARSHAL& ex) {
	  omniORB::logs(25,"Ignore MARSHAL exception while truncating value.");
	}
      }
    }
  }
  catch (...) {
    Py_DECREF(repoId);
    Py_XDECREF(instance);
    throw;
  }
  Py_DECREF(repoId);
  return instance;
}

static void
unmarshalMembers(cdrStream& stream, PyObject* desc,
		 PyObject* instance, PyObject* member_list)
{
  PyObject* t_o = PyTuple_GET_ITEM(desc, 6);
  if (PyTuple_Check(t_o))
    unmarshalMembers(stream, t_o, instance, member_list);

  int members = (PyTuple_GET_SIZE(desc) - 7) / 3;

  PyObject* name;
  PyObject* value;

  int i, j;

  for (i=0,j=7; i < members; i++, j+=3) {
    name  = PyTuple_GET_ITEM(desc, j);
    value = omniPy::unmarshalPyObject(stream, PyTuple_GET_ITEM(desc, j+1));

    if (member_list)
      PyList_Append(member_list, value);

    if (PyObject_SetAttr(instance, name, value) == -1) {
      // Error setting attribute, probably because the object has a
      // __setattr__ or __slots__.
      Py_DECREF(value);
      omniPy::handlePythonException();
    }
    Py_DECREF(value);
  }
}


PyObject*
omniPy::
unmarshalPyObjectValue(cdrStream& stream, PyObject* d_o)
{
  CORBA::ULong tag; // Spec says Long but it's easier to deal with ULong
  tag <<= stream;

  if (tag == 0) {
    // nil
    Py_INCREF(Py_None);
    return Py_None;
  }

  if (!stream.valueTracker()) {
    stream.valueTracker(new pyInputValueTracker());
  }

  pyInputValueTracker* tracker = (pyInputValueTracker*)stream.valueTracker();

  OMNIORB_ASSERT(tracker->valid());

  PyObject* result;
  omni::s_size_t pos = stream.currentInputPtr();

  if (tag == 0xffffffff) {
    // indirection
    CORBA::Long offset;
    offset <<= stream;

    if (offset >= -4) {
      OMNIORB_THROW(MARSHAL, MARSHAL_InvalidIndirection,
		    (CORBA::CompletionStatus)stream.completion());
    }
    result = tracker->lookup(pos + offset,
			     (CORBA::CompletionStatus)stream.completion());
    tracker->add(result, pos - 4);
    return result;
  }
  if (tag < 0x7fffff00 || tag > 0x7fffffff) {
    OMNIORB_THROW(MARSHAL, MARSHAL_InvalidValueTag,
		  (CORBA::CompletionStatus)stream.completion());
  }

  cdrValueChunkStream* cstreamp = cdrValueChunkStream::downcast(&stream);
  if (tag & CHUNKED) {
    if (cstreamp) {
      result = real_unmarshalPyObjectValue(stream, cstreamp, d_o, tag, pos-4);
    }
    else {
      cdrValueChunkStream cstream(stream);
      try {
	cstream.initialiseInput();
	result = real_unmarshalPyObjectValue(cstream, &cstream,
					     d_o, tag, pos-4);
      }
      catch (...) {
	cstream.exceptionOccurred();
	throw;
      }
    }
  }
  else {
    // Not chunked. Make sure we're not in a chunk stream
    if (cstreamp) {
      OMNIORB_THROW(MARSHAL, MARSHAL_InvalidChunkedEncoding,
		    (CORBA::CompletionStatus)stream.completion());
    }
    else {
      result = real_unmarshalPyObjectValue(stream, 0, d_o, tag, pos-4);
    }
  }
  return result;
}



PyObject*
omniPy::
copyArgumentValue(PyObject* d_o, PyObject* a_o,
		  CORBA::CompletionStatus compstatus)
{
  // *** This code does not actually copy the value. Normally
  // valuetype arguments are not "copied" using this code. Instead,
  // all the arguments are marshalled together to properly handle
  // cross-references between arguments. This code is only used if the
  // copyValuesInLocalCalls parameter is set false, meaning we have
  // chosen to use the wrong copy semantics, so we might as well do
  // the fastest thing.

  Py_INCREF(a_o);
  return a_o;
}

PyObject*
omniPy::
copyArgumentValueBox(PyObject* d_o, PyObject* a_o,
		     CORBA::CompletionStatus compstatus)
{
  // Same comment as copyArgumentValue above.

  Py_INCREF(a_o);
  return a_o;
}

