// Filename: geomVertexArrayData.cxx
// Created by:  drose (17Mar05)
//
////////////////////////////////////////////////////////////////////
//
// PANDA 3D SOFTWARE
// Copyright (c) 2001 - 2004, Disney Enterprises, Inc.  All rights reserved
//
// All use of this software is subject to the terms of the Panda 3d
// Software license.  You should have received a copy of this license
// along with this source code; you will also find a current copy of
// the license at http://etc.cmu.edu/panda3d/docs/license/ .
//
// To contact the maintainers of this program write to
// panda3d-general@lists.sourceforge.net .
//
////////////////////////////////////////////////////////////////////

#include "geomVertexArrayData.h"
#include "geom.h"
#include "preparedGraphicsObjects.h"
#include "reversedNumericData.h"
#include "bamReader.h"
#include "bamWriter.h"
#include "pset.h"

TypeHandle GeomVertexArrayData::_type_handle;

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Default Constructor
//       Access: Private
//  Description: Constructs an invalid object.  This is only used when
//               reading from the bam file.
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
GeomVertexArrayData() {
  _endian_reversed = false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
GeomVertexArrayData(const GeomVertexArrayFormat *array_format,
                    GeomVertexArrayData::UsageHint usage_hint) :
  _array_format(array_format)
{
  OPEN_ITERATE_ALL_STAGES(_cycler) {
    CDStageWriter cdata(_cycler, pipeline_stage);
    cdata->_usage_hint = usage_hint;
  }
  CLOSE_ITERATE_ALL_STAGES(_cycler);

  _endian_reversed = false;
  nassertv(_array_format->is_registered());
}
  
////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Copy Constructor
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
GeomVertexArrayData(const GeomVertexArrayData &copy) :
  TypedWritableReferenceCount(copy),
  _array_format(copy._array_format),
  _cycler(copy._cycler)
{
  _endian_reversed = false;
  nassertv(_array_format->is_registered());
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Copy Assignment Operator
//       Access: Published
//  Description: The copy assignment operator is not pipeline-safe.
//               This will completely obliterate all stages of the
//               pipeline, so don't do it for a GeomVertexArrayData
//               that is actively being used for rendering.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
operator = (const GeomVertexArrayData &copy) {
  TypedWritableReferenceCount::operator = (copy);
  _array_format = copy._array_format;
  _cycler = copy._cycler;

  OPEN_ITERATE_ALL_STAGES(_cycler) {
    CDStageWriter cdata(_cycler, pipeline_stage);
    cdata->_modified = Geom::get_next_modified();
  }
  CLOSE_ITERATE_ALL_STAGES(_cycler);

  nassertv(_array_format->is_registered());
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::Destructor
//       Access: Published, Virtual
//  Description: 
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::
~GeomVertexArrayData() {
  release_all();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::set_usage_hint
//       Access: Published
//  Description: Changes the UsageHint hint for this array.  See
//               get_usage_hint().
//
//               Don't call this in a downstream thread unless you
//               don't mind it blowing away other changes you might
//               have recently made in an upstream thread.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
set_usage_hint(GeomVertexArrayData::UsageHint usage_hint) {
  CDWriter cdata(_cycler, true);
  cdata->_usage_hint = usage_hint;
  cdata->_modified = Geom::get_next_modified();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::output
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
output(ostream &out) const {
  out << get_num_rows() << " rows: " << *get_array_format();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::write
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
write(ostream &out, int indent_level) const {
  _array_format->write_with_data(out, indent_level, this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::prepare
//       Access: Public
//  Description: Indicates that the data should be enqueued to be
//               prepared in the indicated prepared_objects at the
//               beginning of the next frame.  This will ensure the
//               data is already loaded into the GSG if it is expected
//               to be rendered soon.
//
//               Use this function instead of prepare_now() to preload
//               datas from a user interface standpoint.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
prepare(PreparedGraphicsObjects *prepared_objects) {
  prepared_objects->enqueue_vertex_buffer(this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::prepare_now
//       Access: Public
//  Description: Creates a context for the data on the particular
//               GSG, if it does not already exist.  Returns the new
//               (or old) VertexBufferContext.  This assumes that the
//               GraphicsStateGuardian is the currently active
//               rendering context and that it is ready to accept new
//               datas.  If this is not necessarily the case, you
//               should use prepare() instead.
//
//               Normally, this is not called directly except by the
//               GraphicsStateGuardian; a data does not need to be
//               explicitly prepared by the user before it may be
//               rendered.
////////////////////////////////////////////////////////////////////
VertexBufferContext *GeomVertexArrayData::
prepare_now(PreparedGraphicsObjects *prepared_objects, 
            GraphicsStateGuardianBase *gsg) {
  Contexts::const_iterator ci;
  ci = _contexts.find(prepared_objects);
  if (ci != _contexts.end()) {
    return (*ci).second;
  }

  VertexBufferContext *vbc = prepared_objects->prepare_vertex_buffer_now(this, gsg);
  if (vbc != (VertexBufferContext *)NULL) {
    _contexts[prepared_objects] = vbc;
  }
  return vbc;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::release
//       Access: Public
//  Description: Frees the data context only on the indicated object,
//               if it exists there.  Returns true if it was released,
//               false if it had not been prepared.
////////////////////////////////////////////////////////////////////
bool GeomVertexArrayData::
release(PreparedGraphicsObjects *prepared_objects) {
  Contexts::iterator ci;
  ci = _contexts.find(prepared_objects);
  if (ci != _contexts.end()) {
    VertexBufferContext *vbc = (*ci).second;
    prepared_objects->release_vertex_buffer(vbc);
    return true;
  }

  // Maybe it wasn't prepared yet, but it's about to be.
  return prepared_objects->dequeue_vertex_buffer(this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::release_all
//       Access: Public
//  Description: Frees the context allocated on all objects for which
//               the data has been declared.  Returns the number of
//               contexts which have been freed.
////////////////////////////////////////////////////////////////////
int GeomVertexArrayData::
release_all() {
  // We have to traverse a copy of the _contexts list, because the
  // PreparedGraphicsObjects object will call clear_prepared() in response
  // to each release_vertex_buffer(), and we don't want to be modifying the
  // _contexts list while we're traversing it.
  Contexts temp = _contexts;
  int num_freed = (int)_contexts.size();

  Contexts::const_iterator ci;
  for (ci = temp.begin(); ci != temp.end(); ++ci) {
    PreparedGraphicsObjects *prepared_objects = (*ci).first;
    VertexBufferContext *vbc = (*ci).second;
    prepared_objects->release_vertex_buffer(vbc);
  }

  // Now that we've called release_vertex_buffer() on every known context,
  // the _contexts list should have completely emptied itself.
  nassertr(_contexts.empty(), num_freed);

  return num_freed;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::clear_prepared
//       Access: Private
//  Description: Removes the indicated PreparedGraphicsObjects table
//               from the data array's table, without actually
//               releasing the data array.  This is intended to be
//               called only from
//               PreparedGraphicsObjects::release_vertex_buffer(); it should
//               never be called by user code.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
clear_prepared(PreparedGraphicsObjects *prepared_objects) {
  Contexts::iterator ci;
  ci = _contexts.find(prepared_objects);
  if (ci != _contexts.end()) {
    _contexts.erase(ci);
  } else {
    // If this assertion fails, clear_prepared() was given a
    // prepared_objects which the data array didn't know about.
    nassertv(false);
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::reverse_data_endianness
//       Access: Private
//  Description: Returns a new data array with all numeric values
//               expressed in the indicated array reversed,
//               byte-for-byte, to convert littleendian to bigendian
//               and vice-versa.
////////////////////////////////////////////////////////////////////
PTA_uchar GeomVertexArrayData::
reverse_data_endianness(const PTA_uchar &data) {
  // First, make a complete copy of the data.
  PTA_uchar new_data;
  new_data.v() = data.v();

  int num_columns = _array_format->get_num_columns();

  // Now, walk through each row of the data.
  unsigned char *begin = new_data;
  unsigned char *end = new_data + new_data.size();
  for (unsigned char *row_data = begin; 
       row_data < end; 
       row_data += _array_format->get_stride()) {
    nassertr(row_data + _array_format->get_stride() <= end, new_data);

    // For each row, visit all of the columns; and for each column,
    // visit all of the components of that column.
    for (int ci = 0; ci < num_columns; ++ci) {
      const GeomVertexColumn *col = _array_format->get_column(ci);
      int component_bytes = col->get_component_bytes();
      if (component_bytes > 1) {
        unsigned char *col_data = row_data + col->get_start();
        int num_components = col->get_num_components();
        for (int cj = 0; cj < num_components; ++cj) {
          // Reverse the bytes of each component.
          ReversedNumericData nd(col_data, component_bytes);
          nd.store_value(col_data, component_bytes);
          col_data += component_bytes;
        }
      }
    }
  }
  
  return new_data;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::register_with_read_factory
//       Access: Public, Static
//  Description: Tells the BamReader how to create objects of type
//               GeomVertexArrayData.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
register_with_read_factory() {
  BamReader::get_factory()->register_factory(get_class_type(), make_from_bam);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::write_datagram
//       Access: Public, Virtual
//  Description: Writes the contents of this object to the datagram
//               for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
write_datagram(BamWriter *manager, Datagram &dg) {
  TypedWritableReferenceCount::write_datagram(manager, dg);

  manager->write_pointer(dg, _array_format);
  manager->write_cdata(dg, _cycler, this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::write_raw_data
//       Access: Public
//  Description: Called by CData::write_datagram to write the raw data
//               of the array to the indicated datagram.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
write_raw_data(BamWriter *manager, Datagram &dg, const PTA_uchar &data) {
  dg.add_uint32(data.size());

  if (manager->get_file_endian() == BE_native) {
    // For native endianness, we only have to write the data directly.
    dg.append_data(data, data.size());

  } else {
    // Otherwise, we have to convert it.
    PTA_uchar new_data = reverse_data_endianness(data);
    dg.append_data(new_data, new_data.size());
  }
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::read_raw_data
//       Access: Public
//  Description: Called by CData::fillin to read the raw data
//               of the array from the indicated datagram.
////////////////////////////////////////////////////////////////////
PTA_uchar GeomVertexArrayData::
read_raw_data(BamReader *manager, DatagramIterator &scan) {
  size_t size = scan.get_uint32();
  PTA_uchar data = PTA_uchar::empty_array(size);
  const unsigned char *source_data = 
    (const unsigned char *)scan.get_datagram().get_data();
  memcpy(data, source_data + scan.get_current_index(), size);
  scan.skip_bytes(size);

  if (manager->get_file_endian() != BE_native) {
    // For non-native endian files, we have to convert the data.  
    if (_array_format == (GeomVertexArrayFormat *)NULL) {
      // But we can't do that until we've completed the _array_format
      // pointer, which tells us how to convert it.
      _endian_reversed = true;
    } else {
      // Since we have the _array_format pointer now, we can reverse
      // it immediately (and we should, to support threaded CData
      // updates).
      data = reverse_data_endianness(data);
    }
  }

  return data;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::complete_pointers
//       Access: Public, Virtual
//  Description: Receives an array of pointers, one for each time
//               manager->read_pointer() was called in fillin().
//               Returns the number of pointers processed.
////////////////////////////////////////////////////////////////////
int GeomVertexArrayData::
complete_pointers(TypedWritable **p_list, BamReader *manager) {
  int pi = TypedWritableReferenceCount::complete_pointers(p_list, manager);

  _array_format = DCAST(GeomVertexArrayFormat, p_list[pi++]);

  return pi;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::finalize
//       Access: Public, Virtual
//  Description: Called by the BamReader to perform any final actions
//               needed for setting up the object after all objects
//               have been read and all pointers have been completed.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
finalize(BamReader *manager) {
  // Now we need to register the format that we have read from the bam
  // file (since it doesn't come out of the bam file automatically
  // registered).  This may change the format's pointer, which we
  // should then update our own data to reflect.  But since this may
  // cause the unregistered object to destruct, we have to also tell
  // the BamReader to return the new object from now on.

  CDWriter cdata(_cycler, true);

  CPT(GeomVertexArrayFormat) new_array_format = 
    GeomVertexArrayFormat::register_format(_array_format);

  manager->change_pointer(_array_format, new_array_format);
  _array_format = new_array_format;

  if (_endian_reversed) {
    // Now is the time to endian-reverse the data.
    cdata->_data = reverse_data_endianness(cdata->_data);
  }

  // Now is also the time to node_ref the data.
  cdata->_data.node_ref();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::make_from_bam
//       Access: Protected, Static
//  Description: This function is called by the BamReader's factory
//               when a new object of type GeomVertexArrayData is encountered
//               in the Bam file.  It should create the GeomVertexArrayData
//               and extract its information from the file.
////////////////////////////////////////////////////////////////////
TypedWritable *GeomVertexArrayData::
make_from_bam(const FactoryParams &params) {
  GeomVertexArrayData *object = new GeomVertexArrayData;
  DatagramIterator scan;
  BamReader *manager;

  parse_params(params, scan, manager);
  object->fillin(scan, manager);
  manager->register_finalize(object);

  return object;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::fillin
//       Access: Protected
//  Description: This internal function is called by make_from_bam to
//               read in all of the relevant data from the BamFile for
//               the new GeomVertexArrayData.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::
fillin(DatagramIterator &scan, BamReader *manager) {
  TypedWritableReferenceCount::fillin(scan, manager);

  manager->read_pointer(scan);
  manager->read_cdata(scan, _cycler, this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::Destructor
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
GeomVertexArrayData::CData::
~CData() {
  _data.node_unref();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::make_copy
//       Access: Public, Virtual
//  Description:
////////////////////////////////////////////////////////////////////
CycleData *GeomVertexArrayData::CData::
make_copy() const {
  return new CData(*this);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::write_datagram
//       Access: Public, Virtual
//  Description: Writes the contents of this object to the datagram
//               for shipping out to a Bam file.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::CData::
write_datagram(BamWriter *manager, Datagram &dg, void *extra_data) const {
  GeomVertexArrayData *array_data = (GeomVertexArrayData *)extra_data;
  dg.add_uint8(_usage_hint);
  WRITE_PTA(manager, dg, array_data->write_raw_data, _data);
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayData::CData::fillin
//       Access: Public, Virtual
//  Description: This internal function is called by make_from_bam to
//               read in all of the relevant data from the BamFile for
//               the new GeomVertexArrayData.
////////////////////////////////////////////////////////////////////
void GeomVertexArrayData::CData::
fillin(DatagramIterator &scan, BamReader *manager, void *extra_data) {
  GeomVertexArrayData *array_data = (GeomVertexArrayData *)extra_data;
  _usage_hint = (UsageHint)scan.get_uint8();
  READ_PTA(manager, scan, array_data->read_raw_data, _data);

  _modified = Geom::get_next_modified();
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataPipelineWriter::set_num_rows
//       Access: Public
//  Description: 
////////////////////////////////////////////////////////////////////
bool GeomVertexArrayDataPipelineWriter::
set_num_rows(int n) {
  int stride = _object->_array_format->get_stride();
  int delta = n - (_cdata->_data.size() / stride);
  
  if (delta != 0) {
    if (_cdata->_data.get_node_ref_count() > 1) {
      // Copy-on-write: the data is already reffed somewhere else,
      // so we're just going to make a copy.
      PTA_uchar new_data;
      new_data.reserve(n * stride);
      new_data.insert(new_data.end(), n * stride, 0);
      memcpy(new_data, _cdata->_data, 
             min((size_t)(n * stride), _cdata->_data.size()));
      _cdata->_data.node_unref();
      _cdata->_data = new_data;
      _cdata->_data.node_ref();
      
    } else {
      // We've got the only reference to the data, so we can change
      // it directly.
      if (delta > 0) {
        _cdata->_data.insert(_cdata->_data.end(), delta * stride, 0);
        
      } else {
        _cdata->_data.erase(_cdata->_data.begin() + n * stride, 
                           _cdata->_data.end());
      }
    }

    _cdata->_modified = Geom::get_next_modified();

    return true;
  }
  
  return false;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataPipelineWriter::modify_data
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
PTA_uchar GeomVertexArrayDataPipelineWriter::
modify_data() {
  // Perform copy-on-write: if the *node* reference count on the
  // vertex data is greater than 1, assume some other
  // GeomVertexArrayData has the same pointer, so make a copy of it
  // first.
  if (_cdata->_data.get_node_ref_count() > 1) {
    PTA_uchar orig_data = _cdata->_data;
    _cdata->_data.node_unref();
    _cdata->_data = PTA_uchar();
    _cdata->_data.v() = orig_data.v();
    _cdata->_data.node_ref();
  }
  _cdata->_modified = Geom::get_next_modified();

  return _cdata->_data;
}

////////////////////////////////////////////////////////////////////
//     Function: GeomVertexArrayDataPipelineWriter::set_data
//       Access: Published
//  Description: 
////////////////////////////////////////////////////////////////////
void GeomVertexArrayDataPipelineWriter::
set_data(CPTA_uchar array) {
  _cdata->_data.node_unref();
  _cdata->_data = (PTA_uchar &)array;
  _cdata->_data.node_ref();
  _cdata->_modified = Geom::get_next_modified();
}
