/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2012 Robert McNeel & Associates. All rights reserved.
// OpenNURBS, Rhinoceros, and Rhino3D are registered trademarks of Robert
// McNeel & Associates.
//
// THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
// ALL IMPLIED WARRANTIES OF FITNESS FOR ANY PARTICULAR PURPOSE AND OF
// MERCHANTABILITY ARE HEREBY DISCLAIMED.
//				
// For complete openNURBS copyright information see <http://www.opennurbs.org>.
//
////////////////////////////////////////////////////////////////
*/


#if !defined(OPENNURBS_EXTENSIONS_INC_)
#define OPENNURBS_EXTENSIONS_INC_

/*
Description:
  The ONX_ErrorCounter is useful for counting errors that occur in a section of code.
*/
class ON_CLASS ONX_ErrorCounter
{
public:
  ONX_ErrorCounter() = default;
  ~ONX_ErrorCounter() = default;
  ONX_ErrorCounter(const ONX_ErrorCounter&) = default;
  ONX_ErrorCounter& operator=(const ONX_ErrorCounter&) = default;

  const ONX_ErrorCounter operator += (const ONX_ErrorCounter& rhs);
  const ONX_ErrorCounter operator + (const ONX_ErrorCounter& rhs);

  static const ONX_ErrorCounter Zero;

  /*
  Returns:
    Number of failures.
  */
  unsigned int FailureCount() const;

  /*
  Returns:
    Number of errors.
  */
  unsigned int ErrorCount() const;

  /*
  Returns:
    Number of warnings.
  */
  unsigned int WarningCount() const;

  /*
  Returns:
    Number of failures, erros, and warnings.
  */
  unsigned int TotalCount() const;

  /*
  Description:
    Adds one to the failure count.
  Returns:
    Number of failures including this one.
  */
  unsigned int IncrementFailureCount();

  /*
  Description:
    Adds one to the error count.
  Returns:
    Number of errors including this one.
  */
  unsigned int IncrementErrorCount();

  /*
  Description:
    Adds one to the warning count.
  Returns:
    Number of warnings including this one.
  */
  unsigned int IncrementWarningCount();

  /*
  Description:
    Saves the current value of ON_GetErrorCount() 
    so future calls to ON_ERROR can be counted.
  */
  void ClearLibraryErrors();

  /*
  Description:
    Adds the number of calls to ON_ERROR since the last 
    call to ClearLibraryErrors(), AddLibraryErrors(),
    ClearLibraryErrorsAndWarnings, or AddLibraryErrorsAndWarnings().
  Returns:
    The number of errors added.
  */
  unsigned int AddLibraryErrors();

  /*
  Description:
    Saves the current value of ON_GetWarningCount() 
    so future calls to ON_WARNING can be counted.
  */
  void ClearLibraryWarnings();

  /*
  Description:
    Adds the number of calls to ON_WARNING since the last 
    call to ClearLibraryWarnings(), AddLibraryWarnings(),
    ClearLibraryErrorsAndWarnings(), or AddLibraryErrorsAndWarnings().
  Returns:
    The number of warnings added.
  */
  unsigned int AddLibraryWarnings();

  /*
  Description:
    Calls ClearLibraryErrors() and ClearLibraryWarnings().
  */
  void ClearLibraryErrorsAndWarnings();

  /*
  Description:
    Calls AddLibraryErrors() and AddLibraryWarnings().
  Returns:
    The number of errors and warnings added.
  */
  unsigned int AddLibraryErrorsAndWarnings();

  void Dump(ON_TextLog& text_log) const;

private:
  unsigned int m_failure_count = 0;
  unsigned int m_error_count = 0;
  unsigned int m_warning_count = 0;

  unsigned int m_state_bit_field = 0;
  unsigned int m_opennurbs_library_error_count = 0;
  unsigned int m_opennurbs_library_warning_count = 0;
};


/*
Description:
  Used to store user data information in an ONX_Model.
*/
class ON_CLASS ONX_Model_UserData
{
public:
#if defined(OPENNURBS_EXPORTS) || defined(OPENNURBS_IMPORTS)
  // See comments at the top of opennurbs_extensions.cpp for details.

  // new/delete
  void* operator new(size_t);
  void  operator delete(void*);

  // array new/delete
  void* operator new[] (size_t);
  void  operator delete[] (void*);

  // in place new/delete
  void* operator new(size_t,void*);
  void  operator delete(void*,void*);
#endif

  ONX_Model_UserData() = default;
  ~ONX_Model_UserData() = default;
  ONX_Model_UserData(const ONX_Model_UserData&) = default;
  ONX_Model_UserData& operator=(const ONX_Model_UserData&) = default;

  void Dump( ON_TextLog& ) const;

  ON_UUID m_uuid = ON_nil_uuid;
  ON_3dmGoo m_goo;

public:
  // 3dm version = 1,2,3,4,5,50,60,...
  unsigned int m_usertable_3dm_version = 0;

  // opennurbs_version = old yyyymmddn value or
  // a value from ON_VersionNumberConstruct().
  unsigned int m_usertable_opennurbs_version = 0;
};

#if defined(ON_DLL_TEMPLATE)
ON_DLL_TEMPLATE template class ON_CLASS ON_SimpleArray<ONX_Model_UserData*>;
#endif

/*
Description:
  Pedegodgical example of all the things in an OpenNURBS 3dm archive.
  The openNURBS examples use ONX_Model to store the information
  read from 3dm archives.  Please study example_read.cpp for
  details.
*/
class ON_CLASS ONX_Model
{
#if defined(OPENNURBS_EXPORTS) || defined(OPENNURBS_IMPORTS)
  // See comments at the top of opennurbs_extensions.cpp for details.

public:

  // new/delete
  void* operator new(size_t);
  void  operator delete(void*);

  // array new/delete
  void* operator new[] (size_t);
  void  operator delete[] (void*);

  // in place new/delete
  void* operator new(size_t,void*);
  void  operator delete(void*,void*);
#endif

public:
  ONX_Model();
  virtual ~ONX_Model();

  void Reset();

private:
  // prohibit use of copy construction and operator=
  ONX_Model(const ONX_Model&) = delete;
  ONX_Model& operator=(const ONX_Model&) = delete;

public:
  /*
  Description:
    Reads an openNURBS archive and saves the information in this model
  Parameters:
    archive - [in]
      archive to read from
    table_filter - [in]
      If table_filter is zero, then everything in the archive is read.
      Otherwise the bits in table_filter identify what tables should 
      be read.  The bits are defined by the 
      ON_BInaryArchive::table_type enum.
    model_object_type_filter - [in]
      If model_object_type_filter is not zero, then it is a bitfield filter 
      made by bitwise oring ON::object_type values to select which types of 
      objects will be read from the model object table.
    error_log - [out] 
      any archive reading errors are logged here.
  Returns:
    true if archive is read with no error.  False if errors occur.
    Error details are logged in error_log.  If crc errors are in
    the archive, then ONX_Model::m_crc_error_count is set to the 
    number of crc errors.
  Example:

            // for ASCII file names
            const char* sFileName = ....;
            FILE* fp = ON::OpenFile( sFileName, "rb");

            // for UNICODE file names
            const wchar_t* wsFileName = ....;
            FILE* fp = ON::OpenFile( wsFileName, L"rb");

            bool bModelRead = false;
            bool bModelIsValid = false;

            ON_TextLog error_log;
            ONX_Model model;

            if ( 0 != fp )
            {
              ON_BinaryFile archive( ON::archive_mode::read3dm, fp );
              bModelRead = model.Read( archive, error_log );
              ON::CloseFile( fp );
            }

            if ( bModelRead )
            {
              bModelIsValid = model.Validate(error_log);
            }

  See Also:
    ONX_Model::IsValid
    ONX_Model::Write
    ONX_Model::m_crc_error_count
  */
  bool Read( 
    ON_BinaryArchive& archive,
    unsigned int table_filter,
    unsigned int model_object_type_filter,
    ON_TextLog* error_log
    );

  bool Read( 
    const char* filename,
    unsigned int table_filter,
    unsigned int model_object_type_filter,
    ON_TextLog* error_log
    );

  bool Read( 
    const wchar_t* filename,
    unsigned int table_filter,
    unsigned int model_object_type_filter,
    ON_TextLog* error_log
    );

  bool Read( 
    ON_BinaryArchive& archive,
    ON_TextLog* error_log = nullptr
    );

  bool Read( 
    const char* filename,
    ON_TextLog* error_log = nullptr
    );

  bool Read( 
    const wchar_t* filename,
    ON_TextLog* error_log = nullptr
    );

  /*
  Description:
    Reads everything up to the object table.

  Parameters:
    archive - [in] 
      archive to read from
    bManageComponents - [in]
      true: 
        The ONX_Model destructor will delete the model components
        created by this function.
      false: 
        The caller must delete the ON_ModelComponent components after
        the ONX_Model is destroyed.
    table_filter - [in]
      If table_filter is zero, then everything in the archive before
      the model object table is read.  Otherwise the bits in 
      table_filter identify what tables should be read.  The bits
      are defined by the ON_BInaryArchive::table_type enum.
    error_log - [out] any archive reading errors are logged here.
     pass nullptr if you don't want to log errors

  Returns:
    If the input is valid and everything before the model object
    table is successfully read, then true is returned.  Otherwise
    false is returned.

  Example:

            // for ASCII file names
            const char* sFileName = ....;
            FILE* fp = ON::OpenFile( sFileName, "rb");

            // for UNICODE file names
            const wchar_t* wsFileName = ....;
            FILE* fp = ON::OpenFile( wsFileName, L"rb");

            bool bModelRead = false;
            bool bModelIsValid = false;

            ON_TextLog error_log;

            if ( 0 != fp )
            {
              ON_BinaryFile archive( ON::archive_mode::read3dm, fp );
              ONX_Model model;
              
              // Read settings, layer information, and other tables
              // with information that is referenced by model object
              // attributes.
              bModelRead = model.IncrementalReadBegin( archive, error_log );

              if ( bModelRead )
              {
                object_filter = ON::mesh_object   // read meshes
                              | ON::curve_object  //  and curves
                              ;
                for(;;)
                {
                  // read the next model object
                  ON_ModelGeometryComponent* pModelObject = model.IncrementalReadModelObject(object_filter,0);
                  if ( 0 == pModelObject )
                    break;
                  
                  ... // work with this model object

                  // done with this object.
                  pModelObject = 0;
                  model.m_object_table.Remove();
                }                
              }
              ON::CloseFile( fp );
            }
  See Also:
    ONX_Model::IsValid
    ONX_Model::Write
    ONX_Model::m_crc_error_count
  */
  bool IncrementalReadBegin(
    ON_BinaryArchive& archive,
    bool bManageComponents,
    unsigned int table_filter,
    ON_TextLog* error_log
    );

  /*
  Description:
    Reads the next item in the model geometry table.

  Parameters:
    archive - [in]
    bManageModelGeometryComponent - [in]
      true: 
        The ONX_Model destructor will delete the ON_ModelGeometryComponent components
        created by this function.
      false: 
        The caller must delete the ON_ModelGeometryComponent components after
        the ONX_Model is destroyed.
    bManageGeometry - [in]
      true: 
        The ON_ModelGeometryComponent destructor will delete the ON_Geometry 
        classes created by this function.
      false: 
        The caller must delete the ON_Geometry classes after
        the ONX_Model and ON_ModelGeometryComponent components are destroyed.
    bManageAttributes - [in]
      true: 
        The ON_ModelGeometryComponent destructor will delete the ON_3dmObjectAttributes
        classes created by this function.
      false: 
        The caller must delete the ON_3dmObjectAttributes classes after
        the ONX_Model and ON_ModelGeometryComponent components are destroyed.
    model_object_type_filter - [in]
      If model_object_type_filter is not zero, then it is a bitfield filter 
      made by bitwise oring ON::object_type values to select which types of
      objects will be read from the model object table.
    model_geometry_reference - [out]
      A reference to an ON_ModelGeometryComponent.  This referenced ON_ModelGeometryComponent
      component is also added to the ONX_Model. 
      Call ONX_Model.RemoveComponent() if you want to discard it before
      continuing.  
  Returns:
    True
      Succesful.  If model_geometry_reference.IsEmpty() is true, 
      then no more geometry objects are available and you should call
      IncrementalReadFinish().
    False
      An error occured and reading should terminate.
  Remarks:
    You must call IncrementalReadBegin() before making any calls to
    IncrementalReadModelObject().
  */
  bool IncrementalReadModelGeometry(
    ON_BinaryArchive& archive,
    bool bManageModelGeometryComponent,
    bool bManageGeometry,
    bool bManageAttributes,
    unsigned int model_object_type_filter,
    ON_ModelComponentReference& model_geometry_reference
    );

  /*
  Description:
    Reads everything up to the object table.

  Parameters:
    archive - [in] 
      archive to read from
    bManageComponents - [in]
      true: 
        The ONX_Model destructor will delete the model components
        created by this function.
      false: 
        The caller must delete the ON_ModelComponent components after
        the ONX_Model is destroyed.
    table_filter - [in]
      If table_filter is zero, then everything in the archive before
      the model object table is read.  Otherwise the bits in 
      table_filter identify what tables should be read.  The bits
      are defined by the ON_BInaryArchive::table_type enum.
    error_log - [out] any archive reading errors are logged here.
     pass nullptr if you don't want to log errors

  Returns:
    If the input is valid and everything before the model object
    table is successfully read, then true is returned.  Otherwise
    false is returned.

  See Also:
    ONX_Model::IsValid
    ONX_Model::Write
    ONX_Model::m_crc_error_count
  */
  bool IncrementalReadFinish(
    ON_BinaryArchive& archive,
    bool bManageComponents,
    unsigned int table_filter,
    ON_TextLog* error_log
    );

  /*
  Description:
    Writes contents of this model to an openNURBS archive.

  Parameters:
    filename - [in]

    version - [in] 
      Version of the openNURBS archive to write.
        0 default value and suggested.
           When 0 is passed in, the value of ON_BinaryArchive::CurrentArchiveVersion()
           is used.
        2, 3, 4, 50, 60, ...
          If you pass in a value < ON_BinaryArchive::CurrentArchiveVersion(), then some
          information in current data structures will not be saved in the 3dm archive.
          Rhino 2.x can read version 2 files.
          Rhino 3.x can read version 2 and 3 files.
          Rhino 4.x can read version 2, 3, and 4 files.
          Rhino 5.x can read version 2, 3, 4, 5, and 50 files.
          Rhino 6.x can read version 2, 3, 4, 5, 50, and 60 files.

    error_log - [out]
      any archive writing errors are logged here.

  Returns:
    True if archive is written with no error. 
    False if errors occur.
    Error details are logged in error_log.
  */
  bool Write( 
    const char* filename,
    int version = 0,
    ON_TextLog* error_log = nullptr
    ) const;

  /*
  Description:
    Writes contents of this model to an openNURBS archive.

  Parameters:
    filename - [in]

    version - [in] 
      Version of the openNURBS archive to write.
        0 default value and suggested.
           When 0 is passed in, the value of ON_BinaryArchive::CurrentArchiveVersion()
           is used.
        2, 3, 4, 50, 60, ...
          If you pass in a value < ON_BinaryArchive::CurrentArchiveVersion(), then some
          information in current data structures will not be saved in the 3dm archive.
          Rhino 2.x can read version 2 files.
          Rhino 3.x can read version 2 and 3 files.
          Rhino 4.x can read version 2, 3, and 4 files.
          Rhino 5.x can read version 2, 3, 4, 5, and 50 files.
          Rhino 6.x can read version 2, 3, 4, 5, 50, and 60 files.

    error_log - [out]
      any archive writing errors are logged here.

  Returns:
    True if archive is written with no error. 
    False if errors occur.
    Error details are logged in error_log.
  */
  bool Write( 
    const wchar_t* filename,
    int version = 0,
    ON_TextLog* error_log = nullptr
    ) const;

  /*
  Description:
    Writes contents of this model to an openNURBS archive.

  Parameters:
    archive - [in]
      archive to write to
      You must call archive.SetArchiveFullPath(...) i order for file references to work correctly.

    version - [in] 
      Version of the openNURBS archive to write.
        0 default value and suggested.
           When 0 is passed in, the value of ON_BinaryArchive::CurrentArchiveVersion()
           is used.
        2, 3, 4, 50, 60, ...
          If you pass in a value < ON_BinaryArchive::CurrentArchiveVersion(), then some
          information in current data structures will not be saved in the 3dm archive.
          Rhino 2.x can read version 2 files.
          Rhino 3.x can read version 2 and 3 files.
          Rhino 4.x can read version 2, 3, and 4 files.
          Rhino 5.x can read version 2, 3, 4, 5, and 50 files.
          Rhino 6.x can read version 2, 3, 4, 5, 50, and 60 files.

    error_log - [out]
      any archive writing errors are logged here.

  Returns:
    True if archive is written with no error. 
    False if errors occur.
    Error details are logged in error_log.

  Example:

            model = ...;
            if ( model.IsValid( error_log ) )
            {
              const wchar_t* wsFileName = ....;
              FILE* fp = ON::OpenFile( wsFileName, L"wb");

              bool ok = false;
              if ( 0 != fp )
              {
                const char* sStartSectionComment = "...";
                int version = 5; // 2, 3, 4 or 5 are valid
                ON_BinaryFile archive( ON::archive_mode::write3dm, fp );
                archive.SetArchiveFullPath(wsFileName);
                ok = model.write( archive, 
                                  version, 
                                  sStartSectionComment, 
                                  error_log );
                ON::CloseFile( fp );
              }
           }

  */
  bool Write( 
    ON_BinaryArchive& archive,
    int version = 0,
    ON_TextLog* error_log = nullptr
    ) const;

  /////////////////////////////////////////////////////////////////////
  //
  // BEGIN model definitions
  //

  // 3dm archive start section information
  int m_3dm_file_version = 0;
  unsigned int m_3dm_opennurbs_version = 0;
  ON__UINT64 m_3dm_file_byte_count = 0;

  ON_String m_sStartSectionComments;

  // Properties include revision history, notes, information about
  // the applicaton that created the file, and an optional preview image.
  ON_3dmProperties m_properties;

  // Settings include tolerance, and unit system, and defaults used
  // for creating views and objects.
  ON_3dmSettings m_settings;

  /*
  Description:
    A manifest of every model component in this ONX_Model.
  Remarks:
    Use the manifest to find model objects from a name, id or index.

    The manifest Id, Name, and Index values are values used in
    the model. These are assigned when a component is added to the ONX_Model.
    When possible the id and name are not changed.

    The manifest=model and original component values are different when:
    - The original component Id or Name was not set and a value was automatically
      assigned.
    - The original component Id or Name was not unique and was modified when the component
      was added to the model.
    - Generally the original component index differs from the manifest=model component
      index.
    
    The OriginalToModelMap() can be used to convert original component index 
    and id to the manifest=model index and id.
    
    The ModelToOriginalMap() can be used to manifest=model index and id to
    the original component index and id.
  */
  const ON_ComponentManifest& Manifest() const;

  /*
  Returns:
    A map from original component index and id to manifest=model index and id.
  Remarks:
    ON_ManifestMapItem Source = original component index and id.
    ON_ManifestMapItem Destination = model-manifest index and id.
  */
  const ON_ManifestMap& OriginalToModelMap() const;

  /*
  Returns:
    A map from manifest=model index and id to original component index and id.
  Remarks:
    ON_ManifestMapItem Source = model-manifest index and id.
    ON_ManifestMapItem Destination = original component index and id.
  */
  const ON_ManifestMap& ModelToOriginalMap() const;

  /*
  Description:
    This number changes every time the content of the ONX_Model is modified.
  */
  ON__UINT64 ModelContentVersionNumber() const;

  /*
  Description:
    Add an copy of a model_compoent to this model.
  model_component - [in]
    A copy of model_component is added to this model.
    The index, id, and name of the copied component are
    set the the model values (Manifest() "Manifest" index, name, and id).
  
  bResolveIdAndNameConflicts - [in]
    If bResolveIdAndNameConflicts is false, then model_component.Id() must be non-nil 
    and not used in this model and model_component.Name() must be correctly set.
    If bResolveIdAndNameConflicts is true, then id and name will be modified
    as needed in the model and manifest.

  Returns:
    A reference to the added model component.
    If the reference is empty (ON_ModelComponent::IsEmpty() is true)
    then the input was not valid.
  */
  ON_ModelComponentReference AddModelComponent(
    const class ON_ModelComponent& model_component,
    bool bResolveIdAndNameConflicts
    );

  ON_ModelComponentReference AddModelComponent(
    const class ON_ModelComponent& model_component
    );

  ON_ModelComponentReference RemoveModelComponent(
    ON_ModelComponent::Type component_type,
    ON_UUID component_id
    );
  
  /*
  Description:
    Easy way to add a layer to the model.
  Returns:
    If layer_name is valid, the layer's index (>=0) is returned. Otherwise,
    ON_UNSET_INT_INDEX is returned.
  */
  int AddLayer(
    const wchar_t* layer_name,
    ON_Color layer_color
  );

  /*
  Description:
    Easy way to add a default layer to the model.
  Properties:
    layer_name - [in]
      can be nullptr or empty.
    layer_color - [in]
      can be ON_Color::UnsetColor
  Returns:
    The default layer's index (>=0) is returned.
  */
  int AddDefaultLayer(
    const wchar_t* layer_name,
    ON_Color layer_color
  );

  /*
  Description:
    Easy way to add a default dimension style to the model.
  Parameters:
    dimension_style_name - [in]
      can be nullptr or empty
    length_unit_system - [in]
      If ON::LengthUnitSystem::Unset, then settings length unit system is used.
    tolerance - [in]
      If not > 0, then settings tolerance is used.
  Returns:
    The default dimension style's index (>=0) is returned.
  */
  int AddDefaultDimensionStyle(
    const wchar_t* dimension_style_name,
    ON::LengthUnitSystem length_unit_system,
    double model_tolerance
  );



  /*
  Description:
    Add a managed model component (ON_Layer, ON_DimStyle, ...) to this model.

  managed_model_component - [in]
    managed_model_component must be created by operator new and on the heap.
    It will be deleted when the model and last ON_ModelComponentReference are 
    destroyed.
  
  bResolveIdAndNameConflicts - [in]
    If bResolveIdAndNameConflicts is false, then model_component.Id() must be non-nil 
    and not used in this model and model_component.Name() must be correctly set.
    If bResolveIdAndNameConflicts is true, then id and name will be modified
    as needed in managed_model_component, the model, and the manifest.

  Returns:
    A reference to the added model component.
    If the reference is empty (ON_ModelComponent::IsEmpty() is true)
    then the input was not valid.
  */
  ON_ModelComponentReference AddManagedModelComponent(
    class ON_ModelComponent* managed_model_component,
    bool bResolveIdAndNameConflicts
    );

  ON_ModelComponentReference AddManagedModelComponent(
    class ON_ModelComponent* managed_model_component
    );

  /*
  Description:
    Add a model component to this model and control how the model_component instance
    is managed.
  
  model_component - [in]
    An ON_ModelComponent created on the heap by calling new X where X is
    derived from ON_ModelComponent.

  bManagedComponent  - [in]
    If bManagedComponent is true, then ~ONX_Model will delete the component.
    If bManagedComponent is false, then you are responsible for insuring
    the component exists past the desctruction of this ONX_Model.

  bResolveIdAndNameConflicts  - [in]
    If bResolveIdAndNameConflicts is false, then model_component.Id() must be non-nil 
    and not used in this model and model_component.Name() must be correctly set.
    If bResolveIdAndNameConflicts is true, then id and name will be modified
    as needed.
    
  bUpdateComponentIdentification - [in]
    The model_component Index(), Id(), and Name() values are set to match
    the ones used in the model (Manifest() "Manifest" values.)
    
  Returns:
    A reference to the added model component.
    If the reference is empty (ON_ModelComponentReference::IsEmpty() is true),
    then the input was not valid and the model component was not added.
  */
  ON_ModelComponentReference AddModelComponentForExperts(
    class ON_ModelComponent* model_component,
    bool bManagedComponent,
    bool bResolveIdAndNameConflicts,
    bool bUpdateComponentIdentification
    );

  /*
  Description:
    Add an copy of the model_geometry and attrbutes to this model.

  Parameters:
    geometry_object - [in]
      geometry_object must point to a geometric object (curve, surface, brep, mesh, points, ...),
      a render light, an annotation object, or a detail object.
      A copy of geometry_object is added to and managed by this model.
    attributes - [in]
      nullptr if not available.
      A copy of attributes is added to and managed by this model.
  
  bResolveIdAndNameConflicts - [in]
    If bResolveIdAndNameConflicts is false, then attributes must be nullptr 
    or attributes->m_uid must be non-nil and not used in this model.
    If bResolveIdAndNameConflicts is true, then id will be modified
    as needed.

  Returns:
    A reference to the added model component.
    If the reference is empty (ON_ModelComponent::IsEmpty() is true)
    then the input was not valid.
  */
  ON_ModelComponentReference AddModelGeometryComponent(
    const class ON_Object* geometry_object,
    const class ON_3dmObjectAttributes* attributes,
    bool bResolveIdAndNameConflicts
    );

  ON_ModelComponentReference AddModelGeometryComponent(
    const class ON_Object* geometry_object,
    const class ON_3dmObjectAttributes* attributes
    );

  /*
  Description:
    Add an copy of the model_geometry and attrbutes to this model.

  Parameters:
    managed_geometry_object - [in]
      managed_geometry_object must point to an instance geometric object (curve, surface, brep, mesh, points, ...),
      a render light, an annotation object, or a detail object created by operator new and on the heap.
      It will be deleted when the this ONX_Model and the last ON_ModelComponentReference are destroyed.

    managed_attributes - [in]
      managed_attributes should be nullptr or point to an instance created by operator new and on the heap.
      It will be deleted when the this ONX_Model and the last ON_ModelComponentReference are destroyed.  

  bResolveIdAndNameConflicts - [in]
    If bResolveIdAndNameConflicts is false, then managed_attributes must be nullptr 
    or managed_attributes->m_uuid must be non-nil and not used in this model.
    If bResolveIdAndNameConflicts is true, then id will be modified
    as needed.

  Returns:
    A reference to the added model component.
    If the reference is empty (ON_ModelComponent::IsEmpty() is true)
    then the input was not valid.
  */
  ON_ModelComponentReference AddManagedModelGeometryComponent(
    class ON_Object* managed_geometry_object,
    class ON_3dmObjectAttributes* managed_attributes,
    bool bResolveIdAndNameConflicts
    );

  ON_ModelComponentReference AddManagedModelGeometryComponent(
    class ON_Object* managed_geometry_object,
    class ON_3dmObjectAttributes* managed_attributes
    );

  /*
  Description:
    Add geometry and attibutes to this model and control how the instances are managed.

  Parameters:
    bManageGeometry - [in]
      If true, geometry_object should point to an instance created by operator new and on the heap.
      It will be deleted when the this ONX_Model and the last ON_ModelComponentReference are destroyed.
      If false, the expert caller is carefully managing the instance and memory to insure
      model_geometry is a valid instance while this ONX_Model and any ON_ModelComponentReference 
      are active.

    geometry_object - [in]
      geometry_object should point to a geometric object (curve, surface, brep, mesh, points, ...),
      a render light, an annotation object, or a detail object.

    bManageAttributes - [in]
      If true, attributes should be nullptr or point to an instance created by operator new and on the heap.
      It will be deleted when the this ONX_Model and the last ON_ModelComponentReference are destroyed.
      If false, the expert caller is carefully managing the instance and memory to insure
      attributes is a valid instance while this ONX_Model and and ON_ModelComponentReference 
      are active.

    attributes - [in]
      nullptr if not avaiable.
  
  bResolveIdAndNameConflicts - [in]
    If bResolveIdAndNameConflicts is false, then attributes must be nullptr 
    or attributes->m_uid must be non-nil and not used in this model.
    If bResolveIdAndNameConflicts is true, then id will be modified
    as needed.

  Returns:
    A reference to the added model component.
    If the reference is empty (ON_ModelComponent::IsEmpty() is true)
    then the input was not valid.
  */
  ON_ModelComponentReference AddModelGeometryComponentForExperts(
    bool bManageGeometry,
    class ON_Object* geometry_object,
    bool bManageAttributes,
    class ON_3dmObjectAttributes* attributes,
    bool bResolveIdAndNameConflicts
    );

  unsigned int ComponentIndexLimit(
    ON_ModelComponent::Type component_type
    ) const;

  /*
  Returns:
    Number of active and deleted components.
    Count does not include system components.
  */
  unsigned int ActiveAndDeletedComponentCount(
    ON_ModelComponent::Type component_type
    ) const;

  /*
  Returns:
    Number of active components.
    Count does not include system components.
  */
  unsigned int ActiveComponentCount(
    ON_ModelComponent::Type component_type
    ) const;

  /*
  Returns:
    Number of deleted components.
  */
  unsigned int DeletedComponentCount(
    ON_ModelComponent::Type component_type
    ) const;

  ON_ModelComponentReference ComponentFromIndex(
    ON_ModelComponent::Type component_type,
    int component_model_index
    ) const;    

  ON_ModelComponentReference ComponentFromUnsignedIndex(
    ON_ModelComponent::Type component_type,
    unsigned int component_model_index
    ) const;    

  ON_ModelComponentReference ComponentFromId(
    ON_ModelComponent::Type component_type,
    ON_UUID component_model_id
    ) const;  

  ON_ModelComponentReference ComponentFromName(
    ON_ModelComponent::Type component_type,
    ON_UUID component_parent_id,
    const wchar_t* component_model_name
    ) const;

  ON_ModelComponentReference ComponentFromNameHash(
    ON_ModelComponent::Type component_type,
    const ON_NameHash& component_model_name_hash
    ) const;

  /*
  Parameters:
    runtime_serial_number - [in]
      Value of ON_ModelComponent::RuntimeSerialNumber() to search for.
  Returns:
    If there is a model component with the specified runtime serial number,
    then a reference to that component is returned.
    Otherwise, ON_ModelComponentReference::Empty is returned.
  Remarks:
    ONX_Model::ComponentFromRuntimeSerialNumber() used to get a reference rather than a copy of the model's 
    primary ON_ModelComponentReference. This is the function that must be used if a caller is going to 
    use exclusive access funcitons like

      ON_ModelComponent* ON_ModelComponentReference::ExclusiveModelComponent()
      ON_3dmObjectAttributes* ON_ModelGeometryComponent::ExclusiveAttributes()
      ON_Geometry* ON_ModelGeometryComponent::ExclusiveGeometry()

    to modify content that is in the ONX_Model. The exclusive access functions 
    will only return non-nullptr values when there are no external references to
    the model component.
  */
  const ON_ModelComponentReference& ComponentFromRuntimeSerialNumber(
    ON__UINT64 runtime_serial_number
  ) const;

  /*
  Description:
    Get an image from its model index.
  Parameters:
    image_model_index - [in]
  Returns:
    An ON_ModelComponentReference to the image.
  Remarks:
    Model index and Manifest() manifest item index are the same.
  */
  ON_ModelComponentReference ImageFromIndex(
    int image_model_index
    ) const;

  ON_ModelComponentReference ImageFromId(
    ON_UUID image_id
    ) const;

  ON_ModelComponentReference ImageFromFileFullPath(
    const wchar_t* image_file_full_path_name
    ) const;

  ON_ModelComponentReference ImageFromFileContent(
    const ON_ContentHash& image_file_content_hash
    ) const;

  ON_ModelComponentReference ImageFromFileReference(
    const ON_FileReference& file_reference
    ) const;

  /*
  Description:
    Get a line pattern from its model index.
  Parameters:
    line_pattern_model_index - [in]
  Returns:
    An ON_ModelComponentReference to the line pattern.
  Remarks:
    Model index and Manifest() manifest item index are the same.
  */
  ON_ModelComponentReference LinePatternFromIndex(
    int line_pattern_model_index
    ) const;

  ON_ModelComponentReference LinePatternFromId(
    ON_UUID line_pattern_model_id
    ) const;

  ON_ModelComponentReference LinePatternFromName(
    const wchar_t* line_pattern_name
    ) const;

  ON_ModelComponentReference LinePatternFromNameHash(
    ON_NameHash line_pattern_model_name_hash
    ) const;

  /*
  Description:
    Get linetype from object attributes.
  Parameters:
    attributes - [in] object attributes.
    line_pattern - [out] linetype
  */
  ON_ModelComponentReference LinePatternFromAttributes( 
    const ON_3dmObjectAttributes& attributes
    ) const;

  ON_ModelComponentReference LinePatternFromLayerIndex( 
    int layer_index
    ) const;

  /*
  Description:
    Get render material from object attributes.
  Parameters:
    attributes - [in] object attributes.
    material - [out] render material
  */
  ON_ModelComponentReference RenderMaterialFromLayerIndex( 
    int layer_index
    ) const;

  ON_ModelComponentReference RenderMaterialFromAttributes( 
    const ON_3dmObjectAttributes& attributes
    ) const;

  ON_ModelComponentReference RenderMaterialFromIndex( 
    int render_material_index
    ) const;

  ON_ModelComponentReference RenderMaterialFromId( 
    ON_UUID render_material_id
    ) const;
    
  /*
  Description:
    Get a layer from its model index.
  Parameters:
    layer_model_index - [in]
  Returns:
    An ON_ModelComponentReference to the layer.
  Remarks:
    Model index and Manifest() manifest item index are the same.
  */
  ON_ModelComponentReference LayerFromIndex(
    int layer_model_index
    ) const;

  ON_ModelComponentReference LayerFromId(
    ON_UUID layer_model_id
    ) const;

  ON_ModelComponentReference LayerFromName(
    ON_UUID layer_parent_id,
    const wchar_t* layer_name
    ) const;

  ON_ModelComponentReference LayerFromNameHash(
    const ON_NameHash& layer_model_name_hash
    ) const;

  ON_ModelComponentReference LayerFromAttributes( 
    const ON_3dmObjectAttributes& attributes
    ) const;

  /*
  Description:
    Get a dimension style from its model index.
  Parameters:
    dimension_style_model_index - [in]
  Returns:
    An ON_ModelComponentReference to the dimension style.
  Remarks:
    Model index and Manifest() manifest item index are the same.
  */
  ON_ModelComponentReference DimensionStyleFromIndex(
    int dimension_style_index
    ) const;
  ON_ModelComponentReference DimensionStyleFromId(
    ON_UUID dimension_styleid
    ) const;
  ON_ModelComponentReference DimensionStyleFromName(
    const wchar_t* dimension_style_name
    ) const;
  ON_ModelComponentReference DimensionStyleFromNameHash(
    ON_NameHash dimension_style_name_hash
    ) const;

  /*
  Returns:
    Id of the current dimension style or nil if the current style is 
    not set or not in this model.
  */
  ON_UUID CurrentDimensionStyleId() const;

  /*
  Parameters:
    dimension_style_id - [in]
      Id of a dimension style in this model, a system dimension style, or ON_nil_uuid.
  Returns:
    true if dimension_style_id is valid and is set.
  */
  bool SetCurrentDimensionStyleId(
    ON_UUID dimension_style_id
  );

  /*
  Returns:
    Current dimension style
    = DimensionStyleFromId(CurrentDimensionStyleId())
  */
  ON_ModelComponentReference CurrentDimensionStyle() const;


  /*
  Returns:
    A system dimension style that is the default for this model
    and is used when a referenced dimension style is missing from
    this model.
  */
  ON_ModelComponentReference DefaultDimensionStyle() const;

  /*
  Parameters:
    font - [in]
    model_space_text_scale - [in]
      If model_space_text_scale > 0, then the DimScale() must be equal to model_space_text_scale.
    bIgnoreSystemDimStyles - [in]
  Returns:
    The first dimension style with the specified font.
  Remarks:
    dimension styles with a non-nil parent id are ignored.
  */
  ON_ModelComponentReference FirstDimensionStyleFromFont(
    const ON_Font* font,
    double model_space_text_scale,
    bool bIgnoreSystemDimStyles
    ) const;

  /*
  Parameters:
    managed_font_serial_number - [in]
    model_space_text_scale - [in]
      If model_space_text_scale > 0, then the DimScale() must be equal to model_space_text_scale.
    bIgnoreSystemDimStyles - [in]
  Returns:
    The first dimension style with the specified font.
  Remarks:
    dimension styles with a non-nil parent id are ignored.
  */
  ON_ModelComponentReference FirstDimensionStyleFromManagedFontSerialNumber(
    unsigned int managed_font_serial_number,
    double model_space_text_scale,
    bool bIgnoreSystemDimStyles
    ) const;

  /*
  Description:
    Find or create a dimension style with the specified font characteristics.
  */
  ON_ModelComponentReference DimensionStyleWithFontCharacteristics(
    const ON_Font& font_characteristics,
    double model_space_text_scale
    );

  /*
  Description:
    Find a model geometry component from Id
  Parameters:
    model_geometry_component_id - [in]
  Returns:
    If there is a model geometry component with the id, it is returned.
    Otherwise, ON_ModelComponentReference::Empty is returned.
  */
  ON_ModelComponentReference ModelGeometryFromId(
    ON_UUID model_geometry_component_id
    ) const;

  /*
  Description:
    Find a model geometry component from Id
  Parameters:
    model_geometry_component_id - [in]
  Returns:
    If there is a model geometry component with the id, it is returned.
    Otherwise, ON_ModelGeometryComponent::Unset is returned.
  */
  const ON_ModelGeometryComponent& ModelGeometryComponentFromId(
    ON_UUID model_geometry_component_id
    ) const;

public:
  ON_SimpleArray<ONX_Model_UserData*> m_userdata_table;

private:
  ON_ModelComponentReference m_default_render_material = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_Material::Default);
  ON_ModelComponentReference m_default_line_pattern = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_Linetype::Continuous);
  ON_ModelComponentReference m_default_layer = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_Layer::Default);
  ON_ModelComponentReference m_default_text_style = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_TextStyle::Default);
  ON_ModelComponentReference m_default_dimension_style = ON_ModelComponentReference::CreateConstantSystemComponentReference(ON_DimStyle::Default);

private:
  ON_ModelComponentReference Internal_AddModelComponent(
    ON_ModelComponent* model_component,
    ON_UUID id,
    ON_UUID name_parent_id,
    const ON_wString& name,
    bool bManagedComponent,
    bool bUpdateComponentIdentification
    );
  
private:
  // Content version is incremented every time the 
  // contents of the ONX_Model are modified.
  ON__UINT64 m_model_content_version_number = 0;

private:
  void Internal_IncrementModelContentVersionNumber();


private:
  // A manifest of everything in the model. Use the manifest to find
  // objects from a name, id or index.
  ON_ComponentManifest m_manifest;
  ON_ManifestMap m_original_to_manifest_map;
  ON_ManifestMap m_manifest_to_original_map;

private:
  friend class ONX_ModelComponentIterator;
  class ONX_ModelComponentReferenceLink* Internal_ModelComponentLinkFromSerialNumber( 
    ON__UINT64 model_component_runtime_serial_number 
    ) const;
  class ONX_ModelComponentReferenceLink* Internal_AddModelComponentReference(
    ON_ModelComponentReference mcr
    );
  void Internal_RemoveModelComponentReferenceLink(
    class ONX_ModelComponentReferenceLink* mcr_link
    );
  // A map used to lookup by serial number.
  ON_SerialNumberMap m_mcr_sn_map;
  ON_FixedSizePool m_mcr_link_fsp;
#pragma ON_PRAGMA_WARNING_PUSH
#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) 
  // C4251: ... needs to have dll-interface to be used by clients of class ...
  // This warning is not correct. 
  // m_mcr_lists is private and all code that manages m_mcr_lists is explicitly implemented in the DLL.
  class ONX_ModelComponentList
  {
  public:
    ON_ModelComponent::Type m_component_type = ON_ModelComponent::Type::Unset;
    unsigned int m_count = 0;
    class ONX_ModelComponentReferenceLink* m_first_mcr_link = nullptr;
    class ONX_ModelComponentReferenceLink* m_last_mcr_link = nullptr;
  };
  enum : unsigned int
  {
    ONX_MCR_LIST_COUNT = 16
  };
  ONX_ModelComponentList m_mcr_lists[ONX_MCR_LIST_COUNT];
  const ONX_ModelComponentList& Internal_ComponentListConst(ON_ModelComponent::Type component_type) const;
  ONX_ModelComponentList& Internal_ComponentList(ON_ModelComponent::Type component_type);
#pragma ON_PRAGMA_WARNING_POP

public:
  bool ValdateComponentIdAndName(
    ON_ModelComponent::Type component_type,
    const ON_UUID& candidate_id,
    const ON_UUID& component_parent_id,
    const wchar_t* candidate_name,
    bool bResolveIdConflict,
    bool bResolveNameConflict,
    ON_UUID& model_id,
    ON_wString& model_name
    ) const;

  //
  // END model definitions
  //
  /////////////////////////////////////////////////////////////////////

public:
  /*
  Returns:
    Bounding box of every object in m_object_table[].
  */
  ON_BoundingBox ModelGeometryBoundingBox() const;

  /*
  Returns:
    Bounding box of every render light in m_light_table[].
  */
  ON_BoundingBox RenderLightBoundingBox() const;

private:
  void Internal_ComponentTypeBoundingBox(
    const ON_ModelComponent::Type component_type,
    ON_BoundingBox& bbox
    ) const;

public:


  /*
  Description:
    Get wireframe drawing color from object attributes.
  Parameters:
    attributes - [in] object attributes.
  Returns:
    Wireframe drawing color.
  */
  ON_Color WireframeColorFromAttributes(
    const ON_3dmObjectAttributes& attributes
    ) const;

  /* 
  Description:
    See if the instance reference iref refers to an instance
    definition.
  Parameters:
    iref - [in]
    idef_uuid - [in] id of idef we are looking for
  Returns:
    @untitled table
     0         iref does not use idef
     1         iref directly references idef
    >1         iref has a nested reference to idef (nesting depth returned)
    -1         iref.m_instance_definition_uuid is not valid
    -2         invalid idef found
  */
  int UsesIDef( 
    const ON_InstanceRef& iref,
    ON_UUID idef_uuid
    ) const;

  /////////////////////////////////////////////////////////////////////
  //
  // BEGIN model document level user string tools
  //

  /*
  Description:
    Attach a user string to the document.
  Parameters:
    key - [in] id used to retrieve this string.
    string_value - [in] 
      If nullptr, the string with this id will be removed.
  Returns:
    True if successful.
  */
  bool SetDocumentUserString( 
    const wchar_t* key, 
    const wchar_t* string_value 
    );

  /*
  Description:
    Get user string from the document.
  Parameters:
    key - [in] id used to retrieve the string.
    string_value - [out]
  Returns:
    True if a string with id was found.
  */
  bool GetDocumentUserString( 
    const wchar_t* key, 
    ON_wString& string_value 
    ) const;

  /*
  Description:
    Get a list of all user strings in the document.
  Parameters:
    user_strings - [out]
      user strings are appended to this list.
  Returns:
    Number of elements appended to the user_strings list.
  */
  int GetDocumentUserStrings( ON_ClassArray<ON_UserString>& user_strings ) const;

  //
  // END model document level user string tools
  //
  /////////////////////////////////////////////////////////////////////


  /////////////////////////////////////////////////////////////////////
  //
  // BEGIN model text dump tools
  //

  // text dump of entire model
  void Dump( ON_TextLog& ) const;
  
  // text dump of model properties and settings
  void DumpSummary( ON_TextLog& ) const;

  // text dump of user data table
  void DumpUserDataTable( ON_TextLog& ) const;

  void DumpComponentList( 
    ON_ModelComponent::Type component_type,
    ON_TextLog& text_log
    ) const;

  /*
  Returns:
    A text dump of all component lists.
  */
  void DumpComponentLists(
    ON_TextLog& text_log
  ) const;

  /*
  Returns:
    A SHA-1 hash of the model's content. If two models have identical content,
    then the have equal ContentHash() values.
  */
  ON_SHA1_Hash ContentHash() const;

public:


private:
  void Internal_DumpSummary(
    ON_TextLog& dump, 
    bool bInvariantContentOnly
  ) const;

public:

  //
  // END model text dump tools
  //
  /////////////////////////////////////////////////////////////////////


  /////////////////////////////////////////////////////////////////////
  //
  // BEGIN Render Development Toolkit (RDK) information
  //
  // The following functions allow the developer access to the information saved per document or per-object in the 3dm file by the 
  // RDK plug-in, built into Rhino.  There are two parts to this information - the XML data that constitutes the information
  // about materials, textures and environments in addition to some of the document settings such as sun data, skylighting
  // ground plane and so on - and the embedded support files which are saved as byte-per-byte copies of the actual file data
  // for the original files.  Typically, these embedded files will be textured used by materials, environments or decals.

  // Call this function to determine if RDK document information has been saved in this model and can be read using the GetRDKDocumentInfomation function.
  // Returns true if RDK document information is available.
  static bool IsRDKDocumentInformation(const ONX_Model_UserData& docud);

  // This function returns the entire XML associated with the RDK document data for this file.  The XML will include details about
  // materials, textures and environments as well as sun, skylighting, ground plane and so on.
  // Returns true if RDK document information is available.
  static bool GetRDKDocumentInformation(const ONX_Model_UserData& docud,ON_wString& rdk_xml_document_data);

  // This function returns the embedded support files written with this document.  The returned arrays will be empty if no support filers were saved.
  // Typically, these files will be used by materials and environments.  Rhino unpacks these files into a folder with the suffix "embedded_files" next to the
  // 3dm file on disk.
  // This is only supported for Version 6 files onwards.
  // Returns true if embedded files were found.
  ON_DEPRECATED_MSG("This function is deprecated as it did not return the buffer sizes, making it useless")
  static bool GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray<ON_wString>& paths, ON_SimpleArray<unsigned char*>& embedded_files_as_buffers);

  // This function returns the embedded support files written with this document.  The returned arrays will be empty if no support filers were saved.
  // Typically, these files will be used by materials and environments.  Rhino unpacks these files into a folder with the suffix "embedded_files" next to the
  // 3dm file on disk.
  // This is only supported for Version 6 files onwards.
  // Returns true if embedded files were found.
  static bool GetRDKEmbeddedFiles(const ONX_Model_UserData& docud, ON_ClassArray<ON_wString>& paths, ON_SimpleArray<unsigned char*>& embedded_files_as_buffers, ON_SimpleArray<size_t>& buffer_sizes);

  // This function returns the paths of the embedded support files written with this document.  The returned arrays will be empty if no support filers were saved.
  // This function is similar to GetRDKEmbeddedFiles, but is faster and uses less memory to return only the paths.  Use the paths (exactly the strings returned from this function) to 
  // extract the embedded files using GetRDKEmbeddedFile
  static bool GetRDKEmbeddedFilePaths(const ONX_Model_UserData& docud, ON_ClassArray<ON_wString>& paths);

  // This function extracts one embedded file from the support files written with this document.  Use the exact path as returned from GetRDKEmbeddedFilePaths
  static bool GetRDKEmbeddedFile(const ONX_Model_UserData& docud, const wchar_t* path, ON_SimpleArray<unsigned char>& bytes);

  // Call this function to determine if RDK object information has saved in this model and can be read using the GetRDKObjectInformation function.
  // Returns true if RDK object information is available.
  static bool IsRDKObjectInformation(const ON_UserData& objectud);

  // This function returns the entire XML associated with the RDK object.  The XML includes details about decals.
  // Returns true if RDK object information is available.
  static bool GetRDKObjectInformation(const ON_Object& object,ON_wString& rdk_xml_object_data);
  //
  // END Render Development Toolkit (RDK) information
  //
  /////////////////////////////////////////////////////////////////////

private:
  mutable ON_BoundingBox m_model_geometry_bbox = ON_BoundingBox::UnsetBoundingBox;
  mutable ON_BoundingBox m_render_light_bbox = ON_BoundingBox::UnsetBoundingBox;
  class ON_DocumentUserStringList* m_model_user_string_list = nullptr;
};

class ON_CLASS ONX_ModelComponentIterator
{
public:
  ONX_ModelComponentIterator() = default;
  ~ONX_ModelComponentIterator() = default;
  ONX_ModelComponentIterator(const ONX_ModelComponentIterator&) = default;
  ONX_ModelComponentIterator& operator=(const ONX_ModelComponentIterator&) = default;

  ONX_ModelComponentIterator(
    const ONX_Model& model,
    ON_ModelComponent::Type component_type
    );

  const ONX_Model* Model() const;

  ON_ModelComponentReference FirstComponentReference();
  ON_ModelComponentReference LastComponentReference();
  ON_ModelComponentReference CurrentComponentReference() const;
  ON_ModelComponentReference NextComponentReference();
  ON_ModelComponentReference PreviousComponentReference();

  ON_ModelComponentWeakReference FirstComponentWeakReference();
  ON_ModelComponentWeakReference LastComponentWeakReference();
  ON_ModelComponentWeakReference NextComponentWeakReference();
  ON_ModelComponentWeakReference PreviousComponentWeakReference();
  ON_ModelComponentWeakReference CurrentComponentWeakReference() const;

  // Use these with caution unless it is clear you are the only thread
  // with references to the model and the iterator.
  const ON_ModelComponent* FirstComponent();
  const ON_ModelComponent* LastComponent();
  const ON_ModelComponent* CurrentComponent() const;
  const ON_ModelComponent* NextComponent();
  const ON_ModelComponent* PreviousComponent();

  /*
  Returns:
    Number of active components in the current model.
  Remarks:
    If the model is modified during iteration, this value will changes.
  */
  unsigned int ActiveComponentCount() const;
  
private:
  const class ONX_Model::ONX_ModelComponentList* Internal_List() const;
  void Internal_SetLink(const class ONX_ModelComponentReferenceLink* link) const;
  void Internal_SetLink(ON__UINT64 model_component_sn) const;

  ON_ModelComponent::Type m_component_type = ON_ModelComponent::Type::Unset;
  const class ONX_Model* m_model = nullptr;
  mutable ON__UINT64 m_model_content_version = 0;
  mutable const class ONX_Model::ONX_ModelComponentList* m_list = nullptr;
  mutable const class ONX_ModelComponentReferenceLink* m_link = nullptr;
  mutable ON__UINT64 m_current_component_sn = 0;
  mutable ON__UINT64 m_next_component_sn = 0;
  mutable ON__UINT64 m_prev_component_sn = 0;

  // The current component is a weak ref so that a stand alone iterator cannot
  // keep the current element alive since iterations often involve deletion.
  // The iterators next/prev will still work as expected when the current element
  // is deleted. In particular, an iterator can be used to efficiently delete
  // portions of a model and have the deletion occur when many people
  // expect it to occur and not at a later time. This makes debugging 
  // invalid deletions much easier.
  mutable ON_ModelComponentWeakReference m_current_component_weak_ref;
};

class ON_CLASS ONX_ModelTest
{
public:
  ONX_ModelTest() = default;
  ~ONX_ModelTest() = default;
  ONX_ModelTest(const ONX_ModelTest&) = default;
  ONX_ModelTest& operator=(const ONX_ModelTest&) = default;

  static const ONX_ModelTest Unset;

public:

#pragma region // XXRH_C_SHARED_ENUM // [ONX_ModelTest::Type] [Rhino.Geometry.Something.Type] [nested:byte]
  /// <summary>
  /// ONX_ModelTest::Type identifies the type of file reading test to perform.
  /// </summary>
  enum class Type : unsigned char
  {
    Unset = 0,

    ///<summary>
    /// Read the source 3dm file.
    ///</summary>
    Read = 1,

    ///<summary>
    /// Read the source 3dm file and write one or two temporary 3dm files. The original
    /// source file is not modified. If the 3dm version of the source file
    /// is < ON_BinaryArchive::CurrentArchiveVersion(), then two temporary 3dm
    /// files are written, the first with 3dm version = ON_BinaryArchive::CurrentArchiveVersion()-10
    /// and the second with 3dm version = ON_BinaryArchive::CurrentArchiveVersion().
    /// For example, if Rhino 6 is the current version of Rhino and a file written 
    /// by Rhino 5 is read, then both a temporary Rhino 5 and a temporary Rhino 6 3dm
    /// file are written.
    ///</summary>
    ReadWrite = 2,

    ///<summary>
    /// Perform the ReadWrite test and read the temporary files.
    ///</summary>
    ReadWriteRead = 3,

    ///<summary>
    /// Perform the ReadWriteRead test. If one of the temporary files has the same 3dm version
    /// as the original source file, verify that the ONX_Models created by reading the original
    /// 3dm file and the temporary 3dm file with the same version have identical values
    /// of ONX_Model::ContentHash().
    ///</summary>
    ReadWriteReadCompare = 4
  };
#pragma endregion

  static const char* TestTypeToString(ONX_ModelTest::Type test_type);
  static const wchar_t* TestTypeToWideString(ONX_ModelTest::Type test_type);

#pragma region // XXRH_C_SHARED_ENUM // [ONX_ModelTest::Result] [Rhino.Geometry.Something.Result] [nested:byte]
  /// <summary>
  /// ONX_ModelTest::Result reports the result of a test.
  /// </summary>
  enum class Result : unsigned char
  {
    ///<summary>
    /// Test result is not set.
    ///</summary>
    Unset = 0,

    ///<summary>
    /// Test failed to complete.
    ///</summary>
    Fail = 1,

    ///<summary>
    /// Test was performed and completed, but at least one ON_ERROR occured.
    ///</summary>
    Errors = 2,

    ///<summary>
    /// Test was performed and completed, but at least one ON_WARNING occured.
    ///</summary>
    Warnings = 3,
 

    ///<summary>
    /// Test was performed and passed.
    ///</summary>
    Pass = 4,

    ///<summary>
    /// Test was not perfomed because the input did not satisfy prerequisites or an
    /// earlier test failed.
    /// For example, if a ONX_ModelReadTest::TestType::ReadWriteReadCompare 
    /// test is requested and the source file is a Rhino 1 file, the compare
    /// test is skipped.
    /// For example, if a ONX_ModelReadTest::TestType::ReadWriteRead 
    /// test is requested and the Write test failes, the second Read test is skipped.
    ///</summary>
    Skip = 5,
  };
#pragma endregion

  static const char* ResultToString(ONX_ModelTest::Result result);
  static const wchar_t* ResultToWideString(ONX_ModelTest::Result result);

  static ONX_ModelTest::Result WorstResult(
    ONX_ModelTest::Result a,
    ONX_ModelTest::Result b
  );

  /*
  Parameters:
    error_count - [in]
    no_errors_result - [in]
      result to return when 0 = error_count.TotalCount().
  */
  static ONX_ModelTest::Result ResultFromErrorCounter(
    ONX_ErrorCounter error_count,
    ONX_ModelTest::Result no_errors_result
    );

  /*
  Description:
    Test ONX_Model::Read() and ONX_Model::Write().
  Parameters:
    file_path - [in]
      file path
    test_type - [in]
      test to perform.
    bKeepModels - [in]
      If true, then the ONX_Models created by reading 3dm archives are saved
      so the can be examined after the tests complete.
    text_log_file_path - [in]
      If not empty, the string to use for file_path in the output text_log.
      This is used to create logs on different computers that can be compared.
    text_log - [in]
      If text_log is not nullptr, then a summary of the test is sent to text_log.
  Returns:
    True if every test passed with no warnings or errors.
    False if a test failed or warnings or errors occured.
  */
  bool ReadTest(
    const char* file_path,
    ONX_ModelTest::Type test_type,
    bool bKeepModels,
    const char* text_log_file_path,
    ON_TextLog* text_log
  );

  /*
  Description:
    ONX_Model::ReadTest() can be used to test reading a specific file.
  Parameters:
    file_path - [in]
      file path
    test_type - [in]
      test to perform.
    bKeepModels - [in]
      If true, then the ONX_Models created by reading 3dm archives are saved
      so the can be examined after the tests complete.
    text_log_file_path - [in]
      If not empty, the string to use for file_path in the output text_log.
      This is used to create logs on different computers that can be compared.
    text_log - [in]
      If text_log is not nullptr, then a summary of the test is sent to text_log.
  Returns:
    True if every test passed with no warnings or errors.
    False if a test failed or warnings or errors occured.
  */
  bool ReadTest(
    const wchar_t* file_path,
    ONX_ModelTest::Type test_type,
    bool bKeepModels,
    const wchar_t* text_log_file_path,
    ON_TextLog* text_log
  );

  /*
  Description:
    ONX_Model::ReadTest() can be used to test reading a specific file.
  Parameters:
    fp - [in]
      fp pointer to a file opened with ON_FileStream::Opent(...,"rb");
    test_type - [in]
      test to perform.
    bKeepModels - [in]
      If true, then the ONX_Models created by reading 3dm archives are saved
      so the can be examined after the tests complete.
    text_log_file_path - [in]
      If not empty, the string to use for file_path in the output text_log.
      This is used to create logs on different computers that can be compared.
    text_log - [in]
      If text_log is not nullptr, then a summary of the test is sent to text_log.
  Returns:
    True if every test passed with no warnings or errors.
    False if a test failed or warnings or errors occured.
  */
  bool ReadTest(
    FILE* fp,
    ONX_ModelTest::Type test_type,
    bool bKeepModels,
    const wchar_t* text_log_file_path,
    ON_TextLog* text_log
  );


  /*
  Description:
    ONX_Model::Test() can be used to test reading a specific file.
  Parameters:
    archive - [in]      
    test_type - [in]
      test to perform.
    bKeepModels - [in]
      If true, then the ONX_Models created by reading 3dm archives are saved
      so the can be examined after the tests complete.
    text_log_file_path - [in]
      If not empty, the string to use for file_path in the output text_log.
      This is used to create logs on different computers that can be compared.
    text_log - [in]
      If text_log is not nullptr, then a summary of the test is sent to text_log.
  Returns:
    True if every test passed with no warnings or errors.
    False if a test failed or warnings or errors occured.
  */
  bool ReadTest(
    ON_BinaryArchive& archive,
    ONX_ModelTest::Type test_type,
    bool bKeepModels,
    const wchar_t* text_log_file_path,
    ON_TextLog* text_log
  );

  /*
  Description:
    Prints test results.
  */
  void Dump(ON_TextLog& text_log) const;


  /*
  Description:
    Prints the model context to text_log.
  */
  static bool DumpModel(const ONX_Model* model, ON_TextLog& text_log);

  /*
  Description:
    Prints the source model context to text file next to the source file
    with the file _ONX_ModelText_original_<PLATFORM>.txt appended to the 
    source file name.
  Remark:
    Call after test is completed.
  */
  bool DumpSourceModel() const;

  /*
  Description:
    Prints the source model context to text_log.
  Remark:
    Call after test is completed.
  */
  bool DumpSourceModel(const wchar_t* text_file_full_path) const;

  /*
  Description:
    Prints the source model context to text_log.
  Remark:
    Call after test is completed.
  */
  bool DumpSourceModel(ON_TextLog& text_log) const;

  /*
  Description:
    Prints the model obtained from the last read in the read-write-read test to
    with the file _ONX_ModelText_copy_<PLATFORM>.txt appended to the 
    original source file name.
  Remark:
    Call after test is completed.
  */
  bool DumpReadWriteReadModel() const;

  /*
  Description:
    Prints the model obtained from the last read in the read-write-read test to
    with the file _ONX_ModelText_copy_<PLATFORM>.txt appended to a text file
    with the specified name.
  Remark:
    Call after test is completed.
  */
  bool DumpReadWriteReadModel(const wchar_t* text_file_full_path) const;

  /*
  Description:
    Prints the model obtained from the last read in the read-write-read test to
    with the file _ONX_ModelText_copy_<PLATFORM>.txt appended to the text_log.
  Remark:
    Call after test is completed.
  */
  bool DumpReadWriteReadModel(ON_TextLog& text_log) const;

private:
  void Internal_BeginTest();

  void Internal_EndCurrentTest();

  void Internal_BeginNextTest(
    ONX_ModelTest::Type test_type
  );


  void Internal_ReadTest(
    ON_BinaryArchive& archive,
    ONX_ModelTest::Type test_type,
    bool bKeepModels,
    const wchar_t* text_log_file_path,
    ON_TextLog* text_log
  );

  bool Internal_TallyTestResults();

public:

  // Test that was performed.
  ONX_ModelTest::Type TestType() const;

  /*
  Returns:
    The name of the source 3dm file.
  */
  const ON_wString Source3dmFilePath() const;

  /*
  Returns:
    The string used in the output log to identify the source 3dm file.
  */
  const ON_wString TextLogSource3dmFilePath() const;

  /*
  Returns:
    Version of the 3dm fie, 1,2,3,4,5,50,60,...
  */
  unsigned int Source3dmFileVersion() const;
  
  /*
  Returns:
    Worst result for any test that was attempted.
  */
  ONX_ModelTest::Result TestResult() const;
 
  /*
  Parameters:
    test_type - [in]
  Returns:
    Result of the test identified by the test_type parameter.
  */
  ONX_ModelTest::Result TestResult(
    ONX_ModelTest::Type test_type
  );

  static bool SkipCompare(
  unsigned int source_3dm_file_version
  );

  /*
  Returns:
    Total number of failures, errors, and warnings for all tests that
    were performed.
  */
  ONX_ErrorCounter ErrorCounter() const;
 
  /*
  Returns:
    Total number of failures, errors, and warnings for all tests that
    were performed.
  */
  ONX_ErrorCounter ErrorCounter(
   ONX_ModelTest::Type test_type
   ) const;

  const ON_SHA1_Hash SourceModelHash();
  const ON_SHA1_Hash ReadWriteReadModelHash();

  /*
  Returns:
    nullptr if the test was run with bKeepModels=false or the 
    source archive could not be read.
    Otherwise, a pointer to the source model.
  */
  std::shared_ptr<ONX_Model> SourceModel() const;

  /*
  Returns:
    nullptr if the read write read test was not performed or was run with bKeepModels=false.
    Otherwise, a pointer to the result of the read write read test.
  */
  std::shared_ptr<ONX_Model> ReadWriteReadModel() const;


 private:
   ONX_ModelTest::Type m_test_type = ONX_ModelTest::Type::Unset;

   ON_wString m_source_3dm_file_path;

   // if set, used when printing the name of m_source_3dm_file_path in the text
   // log so results from different computers can be compared.
   ON_wString m_text_log_3dm_file_path;

   unsigned int m_model_3dm_file_version[3];

   unsigned int m_current_test_index = 0;

  ONX_ModelTest::Result m_test_result = ONX_ModelTest::Result::Unset;
  ONX_ModelTest::Result m_test_results[7] = {};

  ONX_ErrorCounter m_error_count;
  ONX_ErrorCounter m_error_counts[7];

#pragma ON_PRAGMA_WARNING_PUSH
#pragma ON_PRAGMA_WARNING_DISABLE_MSC( 4251 ) 
  // C4251: ... : class 'std::shared_ptr<ON_MeshThicknessAnalysisImpl>' 
  //        needs to have dll-interface to be used by clients ...
  // m_model[] is private and all code that manages m_sp is explicitly implemented in the DLL.

  // m_model[0] = model from source file
  // m_model[1] = model[0] -> write to current 3dm version -> read into model[1]
  // m_model[2] = model[0] -> write to prev 3dm version -> read into model[2]
  std::shared_ptr<ONX_Model> m_model[3];
#pragma ON_PRAGMA_WARNING_POP

  // m_model_hash[i] = m_model[0].Hash()
  ON_SHA1_Hash m_model_hash[3];
};


#endif
