#include "opennurbs.h"

#if !defined(ON_COMPILING_OPENNURBS)
// This check is included in all opennurbs source .c and .cpp files to insure
// ON_COMPILING_OPENNURBS is defined when opennurbs source is compiled.
// When opennurbs source is being compiled, ON_COMPILING_OPENNURBS is defined 
// and the opennurbs .h files alter what is declared and how it is declared.
#error ON_COMPILING_OPENNURBS must be defined when compiling opennurbs
#endif

#include "opennurbs_subd_data.h"

/* $NoKeywords: $ */
/*
//
// Copyright (c) 1993-2014 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_SUBD_WIP)

static bool WriteDouble3(
  const double x[3],
  ON_BinaryArchive& archive
  )
{
  for (;;)
  {
    if (!archive.WriteDouble(3, x))
      break;
    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

static bool ReadDouble3(
  ON_BinaryArchive& archive,
  double x[3]
  )
{
  for (;;)
  {
    if (!archive.ReadDouble(3, x))
      break;
    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

static bool WriteBase(
  const ON_SubDComponentBase* base,
  ON_BinaryArchive& archive
  )
{
  for (;;)
  {
    unsigned int archive_id = base->ArchiveId();
    unsigned int id = base->m_id;
    unsigned short level = base->m_level;
    if (!archive.WriteInt(archive_id))
      break;
    if (!archive.WriteInt(id))
      break;
    if (!archive.WriteShort(level))
      break;

    double P[3], V[3];
    bool bHaveP = false;
    bool bHaveV = false;
    ON_SubD::SubDType subd_P_type = base->SavedSubdivisionPointType();
    ON_SubD::SubDType subd_V_type = base->DisplacementType();
    if (ON_SubD::SubDType::Unset != subd_P_type)
      bHaveP = base->GetSavedSubdivisionPoint(subd_P_type, P);

    if (ON_SubD::SubDType::Unset != subd_V_type)
      bHaveV = base->GetSavedSubdivisionPoint(subd_V_type, V);

    unsigned char cP = bHaveP ? ((unsigned char)subd_P_type) : 0U;
    if (!archive.WriteChar(cP))
      break;
    if (0 != cP)
    {
      if (!WriteDouble3(P,archive))
        break;
    }

    unsigned char cV = bHaveV ? ((unsigned char)subd_V_type) : 0U;
    if (!archive.WriteChar(cV))
      break;
    if (0 != cV)
    {
      if (!WriteDouble3(V,archive))
        break;
    }

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool ReadBase(
  ON_BinaryArchive& archive,
  ON_SubDComponentBase& base
  )
{
  unsigned int archive_id = 0;
  unsigned int id = 0;
  unsigned short level = 0;
  for (;;)
  {
    if (!archive.ReadInt(&archive_id))
      break;
    if (!archive.ReadInt(&id))
      break;
    if (!archive.ReadShort(&level))
      break;

    unsigned char cP = 0U;
    unsigned char cV = 0U;
    double P[3], V[3];

    if (!archive.ReadChar(&cP))
      break;
    if (0 != cP)
    {
      if (!ReadDouble3(archive,P))
        break;
    }

    if (!archive.ReadChar(&cV))
      break;
    if (0 != cV)
    {
      if (!ReadDouble3(archive,V))
        break;
    }

    base.m_id = id;
    base.SetArchiveId(archive_id);
    base.m_level = level;
    if ( 0 != cP )
      base.SetSavedSubdivisionPoint(ON_SubD::SubDTypeFromUnsigned(cP),P);
    if ( 0 != cV )
      base.SetDisplacement(ON_SubD::SubDTypeFromUnsigned(cV),V);


    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool WriteArchiveIdAndFlags(
  unsigned int archive_id,
  ON__UINT_PTR ptr_flags,
  ON_BinaryArchive& archive
  )
{
  if (!archive.WriteInt(archive_id))
    return ON_SUBD_RETURN_ERROR(false);
  unsigned char flags = (unsigned char)ON_SUBD_ELEMENT_FLAGS(ptr_flags);
  if (!archive.WriteChar(flags))
    return ON_SUBD_RETURN_ERROR(false);
  return true;
}



static bool ReadArchiveIdAndFlagsIntoComponentPtr(
  ON_BinaryArchive& archive,
  ON__UINT_PTR& element_ptr
  )
{
  element_ptr = 0;
  unsigned int archive_id = 0;
  if (!archive.ReadInt(&archive_id))
    return ON_SUBD_RETURN_ERROR(false);
  unsigned char flags = 0;
  if (!archive.ReadChar(&flags))
    return ON_SUBD_RETURN_ERROR(false);
  element_ptr = archive_id;
  element_ptr *= (ON_SUBD_ELEMENT_FLAGS_MASK+1);
  element_ptr += (flags & ON_SUBD_ELEMENT_FLAGS_MASK);
  return true;
}

static bool WriteSavedLimitPointList(
  unsigned int vertex_face_count,
  ON_SubD::SubDType subd_type,
  const ON_SubDSectorLimitPoint& limit_point,
  ON_BinaryArchive& archive
  )
{
  unsigned int limit_point_count = 0;
  const ON_SubDSectorLimitPoint* p;

  if (ON_SubD::SubDType::Unset != subd_type)
  {
    for (p = &limit_point; nullptr != p && limit_point_count <= vertex_face_count; p = p->m_next_sector_limit_point)
    {
      if (!ON_IsValid(p->m_limitP[0]))
        break;
      if (limit_point_count > 0 && nullptr == p->m_sector_face)
        break;
      limit_point_count++;
    }
    if (limit_point_count > vertex_face_count || nullptr != p)
      limit_point_count = 0;

    if (limit_point_count > vertex_face_count)
      limit_point_count = 0;
  }
  if ( 0 == limit_point_count )
    subd_type = ON_SubD::SubDType::Unset;

  for (;;)
  {
    unsigned char c = (unsigned char)subd_type;
    if (!archive.WriteChar(c))
      break;

    if (0 == c)
      return true;

    if (!archive.WriteInt(limit_point_count))
      break;
    
    p = &limit_point;
    for (unsigned int i = 0; i < limit_point_count; i++, p = p->m_next_sector_limit_point )
    {
      if (!WriteDouble3(limit_point.m_limitP, archive))
        break;
      if (!WriteDouble3(limit_point.m_limitT1, archive))
        break;
      if (!WriteDouble3(limit_point.m_limitT2, archive))
        break;
      if (!WriteDouble3(limit_point.m_limitN, archive))
        break;
      if (!WriteArchiveIdAndFlags(limit_point.m_sector_face ? limit_point.m_sector_face->ArchiveId() : 0, 0, archive))
        break;
    }
    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

static bool ReadSavedLimitPointList(
  ON_BinaryArchive& archive,
  unsigned int vertex_face_count,
  ON_SubD::SubDType& limitP_type,
  ON_SimpleArray< ON_SubDSectorLimitPoint > limit_points
  )
{
  limit_points.SetCount(0);

  for (;;)
  {
    unsigned char c = 0;
    if (!archive.ReadChar(&c))
      break;

    if ( 0 == c)
      return true;

    unsigned int limit_point_count = 0;
    if (!archive.ReadInt(&limit_point_count))
      break;

    if ( 0 == limit_point_count )
      break;

    if (limit_point_count > vertex_face_count)
      break;

    limit_points.Reserve(limit_point_count);

    for ( unsigned int i = 0; i < limit_point_count; i++ )
    {
      ON_SubDSectorLimitPoint limit_point = ON_SubDSectorLimitPoint::Unset;
      if (!ReadDouble3(archive,limit_point.m_limitP))
        break;
      if (!ReadDouble3(archive,limit_point.m_limitT1))
        break;
      if (!ReadDouble3(archive,limit_point.m_limitT2))
        break;
      if (!ReadDouble3(archive,limit_point.m_limitN))
        break;
      ON_SubDFacePtr fptr = ON_SubDFacePtr::Null;
      if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,fptr.m_ptr))
        break;
      limit_points.Append(limit_point);
    }

    if (limit_point_count != limit_points.UnsignedCount() )
      break;

    limitP_type = ON_SubD::SubDTypeFromUnsigned(c);
    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

static bool WriteVertexList(
  unsigned short vertex_count,
  const ON_SubDVertex*const* vertex,
  ON_BinaryArchive& archive
  )
{
  for (;;)
  {
    ON_SubDArchiveIdMap::ValidateArrayCounts(vertex_count,vertex_count,vertex,0,nullptr);

    if (!archive.WriteShort(vertex_count))
      break;

    if ( 0 == vertex_count )
      return true;
    
    const ON__UINT_PTR ptr_flags = 0; // for future use
    unsigned short i = 0;
    for (i = 0; i < vertex_count; i++)
    {
      const ON_SubDVertex* v = vertex[i];
      if (!WriteArchiveIdAndFlags((nullptr != v) ? v->ArchiveId() : 0, ptr_flags, archive))
        break;
    }
    if ( i < vertex_count )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool ReadVertexList(
  ON_BinaryArchive& archive,
  unsigned short& vertex_count,
  unsigned short vertex_capacity,
  ON_SubDVertex* vertex[]
  )
{
  for (;;)
  {
    unsigned short archive_vertex_count = 0;
    if (!archive.ReadShort(&archive_vertex_count))
      break;

    if (archive_vertex_count != vertex_count)
    {
      ON_ERROR("Archive vertex count != expected vertex count.");
      if ( archive_vertex_count < vertex_count)
        vertex_count = archive_vertex_count;
    }

    ON_SubDArchiveIdMap::ValidateArrayCounts(vertex_count,vertex_capacity,vertex,0,nullptr);

    unsigned short i = 0;
    for (i = 0; i < vertex_count; i++)
    {
      ON__UINT_PTR vptr = 0;
      if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,vptr))
        break;
      vertex[i] = (ON_SubDVertex*)vptr;
    }
    if ( i < vertex_count )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool WriteEdgePtrList(
  unsigned short edge_count,
  unsigned short edgeN_capacity,
  const ON_SubDEdgePtr* edgeN,
  unsigned short edgeX_capacity,
  const ON_SubDEdgePtr* edgeX,
  ON_BinaryArchive& archive
  )
{
  for (;;)
  {
    ON_SubDArchiveIdMap::ValidateArrayCounts(edge_count,edgeN_capacity,edgeN,edgeX_capacity,edgeX);

    if (!archive.WriteShort(edge_count))
      break;

    if ( 0 == edge_count )
      return true;

    const ON_SubDEdgePtr* eptr = edgeN;
    unsigned short i = 0;
    for (i = 0; i < edge_count; i++, eptr++)
    {
      if ( i == edgeN_capacity)
        eptr = edgeX;
      const ON_SubDEdge* edge = ON_SUBD_EDGE_POINTER(eptr->m_ptr);
      if (!WriteArchiveIdAndFlags((nullptr != edge) ? edge->ArchiveId() : 0,eptr->m_ptr,archive))
        break;
    }
    if ( i < edge_count )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool ReadEdgePtrList(
  ON_BinaryArchive& archive,
  unsigned short& edge_count,
  unsigned short edgeN_capacity,
  ON_SubDEdgePtr* edgeN,
  unsigned short edgeX_capacity,
  ON_SubDEdgePtr* edgeX
  )
{
  for (;;)
  {
    unsigned short archive_edge_count = 0;
    if (!archive.ReadShort(&archive_edge_count))
      break;

    if (archive_edge_count != edge_count)
    {
      ON_ERROR("Archive edge count != expected edge count.");
      if ( archive_edge_count < edge_count)
        edge_count = archive_edge_count;
    }

    ON_SubDArchiveIdMap::ValidateArrayCounts(edge_count,edgeN_capacity,edgeN,edgeX_capacity,edgeX);


    ON_SubDEdgePtr* eptr = edgeN;
    unsigned short i = 0;
    for (i = 0; i < edge_count; i++, eptr++)
    {
      if ( i == edgeN_capacity)
        eptr = edgeX;
      if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,eptr->m_ptr))
        break;
    }
    if ( i < edge_count )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool WriteFacePtrList(
  unsigned short face_count,
  size_t faceN_capacity,
  const ON_SubDFacePtr* faceN,
  unsigned short faceX_capacity,
  const ON_SubDFacePtr* faceX,
  ON_BinaryArchive& archive
  )
{
  for (;;)
  {
    ON_SubDArchiveIdMap::ValidateArrayCounts(face_count,faceN_capacity,faceN,faceX_capacity,faceX);

    if (!archive.WriteShort(face_count))
      break;

    if ( 0 == face_count )
      return true;

    const ON_SubDFacePtr* fptr = faceN;
    unsigned short i = 0;
    for (i = 0; i < face_count; i++, fptr++)
    {
      if ( i == faceN_capacity)
        fptr = faceX;
      const ON_SubDFace* face = ON_SUBD_FACE_POINTER(fptr->m_ptr);
      if (!WriteArchiveIdAndFlags((nullptr != face) ? face->ArchiveId() : 0,fptr->m_ptr,archive))
        break;
    }
    if ( i < face_count )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


static bool ReadFacePtrList(
  ON_BinaryArchive& archive,
  unsigned short& face_count,
  unsigned short faceN_capacity,
  ON_SubDFacePtr* faceN,
  unsigned short faceX_capacity,
  ON_SubDFacePtr* faceX
  )
{
  for (;;)
  {
    unsigned short archive_face_count = 0;
    if (!archive.ReadShort(&archive_face_count))
      break;

    if (archive_face_count != face_count)
    {
      ON_ERROR("Archive face count != expected face count.");
      if ( archive_face_count < face_count)
        face_count = archive_face_count;
    }

    ON_SubDArchiveIdMap::ValidateArrayCounts(face_count,faceN_capacity,faceN,faceX_capacity,faceX);

    ON_SubDFacePtr* fptr = faceN;
    unsigned short i = 0;
    for (i = 0; i < face_count; i++, fptr++)
    {
      if ( i == faceN_capacity)
        fptr = faceX;
      if (!ReadArchiveIdAndFlagsIntoComponentPtr(archive,fptr->m_ptr))
        break;
    }
    if ( i < face_count )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

static bool SkipReadingLaterAdditions(
  ON_BinaryArchive& archive,
  unsigned char skip_mark
  )
{
  if ( 0 == skip_mark)
    return true;

  if (1 == skip_mark)
  {
    // TODO ADD THIS // return archive.SkipReadingChunk(); //
    return ON_SUBD_RETURN_ERROR(false);
  }

  // TODO ADD THIS return archive.SkipReadingBytes( skip_mark );
  return ON_SUBD_RETURN_ERROR(false);
}



bool ON_SubDVertex::Write(
  ON_BinaryArchive& archive
  ) const
{
  for (;;)
  {
    if (!WriteBase(this,archive))
      break;
    if (!archive.WriteChar((unsigned char)m_vertex_tag))
      break;
    //if (!archive.WriteChar((unsigned char)m_vertex_edge_order))
    //  break;
    //if (!archive.WriteChar((unsigned char)m_vertex_facet_type))
    //  break;
    if (!WriteDouble3(m_P,archive))
      break;
    if (!archive.WriteShort(m_edge_count))
      break;
    if (!archive.WriteShort(m_face_count))
      break;
    if (!WriteSavedLimitPointList(m_face_count,SavedLimitPointType(),m_limit_point, archive))
      break;
    if (!WriteEdgePtrList(m_edge_count,m_edge_capacity,m_edges,0,nullptr, archive))
      break;
    if (!WriteFacePtrList(m_face_count,m_face_capacity,(const ON_SubDFacePtr*)m_faces,0,nullptr, archive))
      break;

    // mark end with a 0 byte
    // If (when) new stuff is added, the value will be the number of bytes that are added
    // or 1 if a chunk is added.
    if (!archive.WriteChar((unsigned char)0U))
      break;
    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDVertex::Read(
  class ON_BinaryArchive& archive,
  class ON_SubD& subd,
  class ON_SubDVertex*& vertex
  )
{
  vertex = nullptr;

  for (;;)
  {
    ON_SubDimple* subdimple = const_cast<ON_SubDimple*>(subd.SubDimple());
    if ( nullptr == subdimple)
      break;

    ON_SubDComponentBase base = ON_SubDComponentBase::Unset;
    unsigned char vertex_tag = 0;
    //unsigned char vertex_edge_order = 0;
    //unsigned char vertex_facet_type = 0;
    double P[3];
    unsigned short edge_count = 0;
    unsigned short face_count = 0;

    ON_SubD::SubDType limitP_type = ON_SubD::SubDType::Unset;
    ON_SimpleArray<ON_SubDSectorLimitPoint> limit_points;

    if (!ReadBase(archive,base))
      break;
    if (!archive.ReadChar(&vertex_tag))
      break;
    //if (!archive.ReadChar(&vertex_edge_order))
    //  break;
    //if (!archive.ReadChar(&vertex_facet_type))
    //  break;
    if (!ReadDouble3(archive,P))
      break;
    if (!archive.ReadShort(&edge_count))
      break;
    if (!archive.ReadShort(&face_count))
      break;

    if (!ReadSavedLimitPointList(archive, face_count, limitP_type, limit_points))
      break;

    ON_SubDVertex* v = subdimple->AllocateVertex(
      ON_SubD::VertexTagFromUnsigned(vertex_tag),
      base.m_level,
      P,
      edge_count,
      face_count
      );

    if ( nullptr == v )
      break;

    v->ON_SubDComponentBase::operator=(base);

    //v->m_vertex_edge_order = ON_SubD::VertexEdgeOrderFromUnsigned(vertex_edge_order);
    //v->m_vertex_facet_type = ON_SubD::VertexFacetTypeFromUnsigned(vertex_facet_type);

    if (!ReadEdgePtrList(archive,edge_count,v->m_edge_capacity,v->m_edges,0,nullptr))
      break;
    v->m_edge_count = edge_count;

    if (!ReadFacePtrList(archive,face_count,v->m_face_capacity,(ON_SubDFacePtr*)v->m_faces,0,nullptr))
      break;
    v->m_face_count = face_count;


    unsigned char skip_mark = 0;
    if (!archive.ReadChar(&skip_mark))
      break;

    if (!SkipReadingLaterAdditions(archive,skip_mark))
      break;

    if (ON_SubD::SubDType::Unset != limitP_type)
    {
      for (unsigned int i = 0; i < limit_points.UnsignedCount(); i++)
      {
        ON_SubDSectorLimitPoint limit_point = limit_points[i];
        limit_point.m_next_sector_limit_point = (const ON_SubDSectorLimitPoint*)1U; // skips checks
        if (false == v->SetSavedLimitPoint(limitP_type, limit_point))
        {
          v->ClearSavedLimitPoints();
          break;
        }
      }
    }

    vertex = v;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}


bool ON_SubDEdge::Write(
  ON_BinaryArchive& archive
  ) const
{
  for (;;)
  {
    if (!WriteBase(this,archive))
      break;
    if (!archive.WriteChar((unsigned char)m_edge_tag))
      break;
    if (!archive.WriteShort(m_face_count))
      break;
    if (!archive.WriteDouble(2,m_sector_coefficient))
      break;
    if (!archive.WriteDouble(m_sharpness))
      break;
    if (!WriteVertexList(2, m_vertex, archive))
      break;
    if (!WriteFacePtrList(m_face_count,sizeof(m_face2)/sizeof(m_face2[0]),m_face2,m_facex_capacity,m_facex, archive))
      break;

    // mark end with a 0 byte
    // If (when) new stuff is added, the value will be the number of bytes that are added
    // or 1 if a chunk is added.
    if (!archive.WriteChar((unsigned char)0U))
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDEdge::Read(
  class ON_BinaryArchive& archive,
  class ON_SubD& subd,
  class ON_SubDEdge*& edge
  )
{
  edge = nullptr;

  for (;;)
  {
    ON_SubDimple* subdimple = const_cast<ON_SubDimple*>(subd.SubDimple());
    if ( nullptr == subdimple)
      break;

    ON_SubDComponentBase base = ON_SubDComponentBase::Unset;
    unsigned char edge_tag = 0;
    unsigned short face_count = 0;
    double sector_weight[2] = { 0 };
    double sharpness = 0.0;

    if (!ReadBase(archive,base))
      break;
    if (!archive.ReadChar(&edge_tag))
      break;
    if (!archive.ReadShort(&face_count))
      break;
    if (!archive.ReadDouble(2,sector_weight))
      break;
    if (!archive.ReadDouble(&sharpness))
      break;

    ON_SubDVertex* v[2] = { 0 };
    unsigned short vertex_count = 2;
    if (!ReadVertexList(archive, vertex_count, 2, v))
      break;


    ON_SubDEdge* e = subdimple->AllocateEdge(
      ON_SubD::EdgeTagFromUnsigned(edge_tag),
      base.m_level,
      face_count
      );

    if ( nullptr == e )
      break;

    e->ON_SubDComponentBase::operator=(base);

    for ( unsigned short evi = 0; evi < 2 && evi < vertex_count; evi++ )
      e->m_vertex[evi] = v[evi];

    e->m_sector_coefficient[0] = sector_weight[0];
    e->m_sector_coefficient[1] = sector_weight[1];
    e->m_sharpness = sharpness;

    if (!ReadFacePtrList(archive,face_count,sizeof(e->m_face2)/sizeof(e->m_face2[0]),e->m_face2,e->m_facex_capacity,e->m_facex))
      break;
    e->m_face_count = face_count;
   
    unsigned char skip_mark = 0;
    if (!archive.ReadChar(&skip_mark))
      break;

    if (!SkipReadingLaterAdditions(archive,skip_mark))
      break;

    edge = e;

    return true;

  }
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDFace::Write(
  ON_BinaryArchive& archive
  ) const
{
  for (;;)
  {
    if (!WriteBase(this,archive))
      break;
    if (!archive.WriteInt(m_zero_face_id))
      break;
    if (!archive.WriteInt(m_parent_face_id))
      break;
    if (!archive.WriteShort(m_edge_count))
      break;
    if (!WriteEdgePtrList(m_edge_count,sizeof(m_edge4)/sizeof(m_edge4[0]),m_edge4,m_edgex_capacity,m_edgex, archive))
      break;

    // mark end with a 0 byte
    // If (when) new stuff is added, the value will be the number of bytes that are added
    // or 1 if a chunk is added.
    if (!archive.WriteChar((unsigned char)0U))
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDFace::Read(
  class ON_BinaryArchive& archive,
  class ON_SubD& subd,
  class ON_SubDFace*& face
  )
{
  face = nullptr;

  for (;;)
  {
    ON_SubDimple* subdimple = const_cast<ON_SubDimple*>(subd.SubDimple());
    if ( nullptr == subdimple)
      break;

    ON_SubDComponentBase base = ON_SubDComponentBase::Unset;
    unsigned int zero_face_id = 0;
    unsigned int parent_face_id = 0;
    unsigned short edge_count = 0;

    if (!ReadBase(archive,base))
      break;
    if (!archive.ReadInt(&zero_face_id))
      break;
    if (!archive.ReadInt(&parent_face_id))
      break;
    if (!archive.ReadShort(&edge_count))
      break;

    ON_SubDFace* f = subdimple->AllocateFace(
      base.m_level,
      edge_count
      );

    if ( nullptr == f )
      break;

    f->ON_SubDComponentBase::operator=(base);

    f->m_zero_face_id = zero_face_id;
    f->m_parent_face_id = parent_face_id;

    if (!ReadEdgePtrList(archive,edge_count,sizeof(f->m_edge4)/sizeof(f->m_edge4[0]),f->m_edge4,f->m_edgex_capacity,f->m_edgex))
      break;
    f->m_edge_count = edge_count;
    
    unsigned char skip_mark = 0;
    if (!archive.ReadChar(&skip_mark))
      break;

    if (!SkipReadingLaterAdditions(archive,skip_mark))
      break;

    face = f;

    return true;

  }
  return ON_SUBD_RETURN_ERROR(false);
}

unsigned int ON_SubDLevel::SetArchiveId(
  unsigned int archive_id_partition[4]
  ) const
{
  unsigned int archive_id = 1;
  //archive_id_partition[0] = 0;
  //archive_id_partition[1] = 0;
  //archive_id_partition[2] = 0;
  //archive_id_partition[3] = 0;

  archive_id_partition[0] = archive_id;
  for (const ON_SubDVertex* v = m_vertex[0]; nullptr != v; v = v->m_next_vertex)
  {
    v->SetArchiveId(archive_id++);
  }
  archive_id_partition[1] = archive_id;
  for (const ON_SubDEdge* e = m_edge[0]; nullptr != e; e = e->m_next_edge)
  {
    e->SetArchiveId(archive_id++);
  }
  archive_id_partition[2] = archive_id;
  for (const ON_SubDFace* f = m_face[0]; nullptr != f; f = f->m_next_face)
  {
    f->SetArchiveId(archive_id++);
  }
  archive_id_partition[3] = archive_id;

  return archive_id-1;
}


void ON_SubDLevel::ClearArchiveId() const
{
  for (const ON_SubDVertex* v = m_vertex[0]; nullptr != v; v = v->m_next_vertex)
  {
    v->SetArchiveId(0);
  }
  for (const ON_SubDEdge* e = m_edge[0]; nullptr != e; e = e->m_next_edge)
  {
    e->SetArchiveId(0);
  }
  for (const ON_SubDFace* f = m_face[0]; nullptr != f; f = f->m_next_face)
  {
    f->SetArchiveId(0);
  }
}

bool ON_SubDLevel::Write(
  ON_BinaryArchive& archive
  ) const
{
  if (!archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK,1,1))
    return ON_SUBD_RETURN_ERROR(false);

  bool rc = false;
  for (;;)
  {
    if (!archive.WriteShort((unsigned short)m_level_index))
      break;
    if (!archive.WriteChar((unsigned char)m_subdivision_type))
      break;
    if (!archive.WriteChar(m_ordinary_vertex_valence))
      break;
    if (!archive.WriteChar(m_ordinary_face_edge_count))
      break;   
    ON_BoundingBox bbox = m_aggregates.m_bDirtyBoundingBox ? ON_BoundingBox::EmptyBoundingBox : m_aggregates.m_bbox;
    if (!archive.WriteDouble(3,bbox[0]))
      break;
    if (!archive.WriteDouble(3,bbox[1]))
      break;


    unsigned int archive_id_partition[4] = { 0 };
    SetArchiveId(archive_id_partition);

    if (!archive.WriteInt(4,archive_id_partition))
      break;

    const ON_SubDVertex* v = nullptr;
    const ON_SubDEdge* e = nullptr;
    const ON_SubDFace* f = nullptr;
    for (v = m_vertex[0]; nullptr != v; v = v->m_next_vertex)
    {
      if( !v->Write(archive) )
        break;
    }
    if ( nullptr != v )
      break;

    for (e = m_edge[0]; nullptr != e; e = e->m_next_edge)
    {
      if( !e->Write(archive) )
        break;
    }
    if ( nullptr != e )
      break;

    for (f = m_face[0]; nullptr != f; f = f->m_next_face)
    {
      if( !f->Write(archive) )
        break;
    }
    if ( nullptr != f )
      break;

    // chunk 1.1 has meshes
    unsigned char c = 0;
    if (archive.Save3dmRenderMesh(ON::object_type::subd_object) || archive.Save3dmAnalysisMesh(ON::object_type::subd_object))
    {
      if (false == m_limit_mesh.IsEmpty())
      {
        c = 0;
        // c = 1; TODO change to c = 1 when ON_SubDLimitMesh::Write()/Read() actually work
      }
    }
    
    if (!archive.WriteChar(c))
      break;

    if (1 == c)
    {
      //if (!m_limit_mesh.Write(archive))
      //  break;
    }

    rc = true;
    break;
  }

  if (!archive.EndWrite3dmChunk())
    rc = false;
  ClearArchiveId();

  if (rc)
    return rc;
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDLevel::Read(
  ON_BinaryArchive& archive,
  class ON_SubDArchiveIdMap& element_list,
  ON_SubD& subd
  )
{
  if ( false == element_list.Reset())
    return ON_SUBD_RETURN_ERROR(false);

  int major_version = 1;
  int minor_version = 0;
  if (!archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK,&major_version,&minor_version))
    return ON_SUBD_RETURN_ERROR(false);

  bool rc = false;
  for (;;)
  {
    if ( 1 != major_version)
      break;

    unsigned short level_index = 0;
    if (!archive.ReadShort(&level_index))
      break;
    m_level_index = level_index;
    unsigned char c = 0;
    if (!archive.ReadChar(&c))
      break;
    m_subdivision_type = ON_SubD::SubDTypeFromUnsigned(c);
    if (!archive.ReadChar(&m_ordinary_vertex_valence))
      break;
    if (!archive.ReadChar(&m_ordinary_face_edge_count))
      break;   
    ON_BoundingBox bbox;
    if (!archive.ReadDouble(3,bbox[0]))
      break;
    if (!archive.ReadDouble(3,bbox[1]))
      break;
    if (bbox.IsValid())
    {
      m_aggregates.m_bDirtyBoundingBox = false;
      m_aggregates.m_bbox = bbox;
    }
    else
    {
      m_aggregates.m_bDirtyBoundingBox = true;
    }

    if (!archive.ReadInt(4,element_list.m_archive_id_partition))
      break;

    unsigned int archive_id = 0;

    for (archive_id = element_list.m_archive_id_partition[0]; archive_id < element_list.m_archive_id_partition[1]; archive_id++ )
    {
      ON_SubDVertex* v = nullptr;
      if ( false == ON_SubDVertex::Read(archive, subd, v) )
        break;
      if ( nullptr == v )
        break;
      if (archive_id != v->ArchiveId())
        break;
      if ( !element_list.Add(v) )
        break;
      AddVertex(v);      
    }
    if ( archive_id != element_list.m_archive_id_partition[1] )
      break;    

    for (archive_id = element_list.m_archive_id_partition[1]; archive_id < element_list.m_archive_id_partition[2]; archive_id++ )
    {
      ON_SubDEdge* e = nullptr;
      if ( false == ON_SubDEdge::Read(archive, subd, e) )
        break;
      if ( nullptr == e )
        break;
      if (archive_id != e->ArchiveId())
        break;
      if ( !element_list.Add(e) )
        break;
      AddEdge(e);
    }
    if ( archive_id != element_list.m_archive_id_partition[2] )
      break;

    for (archive_id = element_list.m_archive_id_partition[2]; archive_id < element_list.m_archive_id_partition[3]; archive_id++ )
    {
      ON_SubDFace* f = nullptr;
      if ( false == ON_SubDFace::Read(archive, subd, f) )
        break;
      if ( nullptr == f )
        break;
      if (archive_id != f->ArchiveId())
        break;
      if ( !element_list.Add(f) )
        break;
      AddFace(f);
    }
    if ( archive_id != element_list.m_archive_id_partition[3] )
      break;

    if (archive_id != element_list.Count())
      break;

    // Convert archive_id references to runtime pointers.
    archive_id = element_list.ConvertArchiveIdsToRuntimePointers();
    if ( archive_id <= 0 )
      break;    

    if (0 == minor_version )
      break;

    c = 0;
    if (!archive.ReadChar(&c))
      break;

    if (1 == c)
    {
      //if (!m_limit_mesh.Read(archive))
      //  break; 
    }

    rc = true;
    break;
  }

  ClearArchiveId();

  if (!archive.EndRead3dmChunk())
    rc = false;

  if (rc)
    return rc;
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDimple::Write(
  ON_BinaryArchive& archive
  ) const
{
  const_cast< ON_SubDHeap* >(&m_heap)->ClearArchiveId();

  if ( !archive.BeginWrite3dmChunk(TCODE_ANONYMOUS_CHUNK, 1, 0) )
    return ON_SUBD_RETURN_ERROR(false);
  bool rc = false;
  for (;;)
  {
    unsigned int level_count = m_levels.UnsignedCount();
    unsigned int level_index;
    for (level_index = 0; level_index < level_count; level_index++)
    {
      if (nullptr == m_levels[level_index])
      {
        level_count = level_index;
        break;
      }
    }
    if (!archive.WriteInt(level_count))
      break;

    if (!archive.WriteInt(m_max_vertex_id))
      break;
    if (!archive.WriteInt(m_max_edge_id))
      break;
    if (!archive.WriteInt(m_max_face_id))
      break;

    // a global bounding box was saved before May 2015.
    // Something has to be here so file IO is not broken.
    if (!archive.WriteBoundingBox(ON_BoundingBox::EmptyBoundingBox))
      break;

    for (level_index = 0; level_index < level_count; level_index++)
    {
      if ( !m_levels[level_index]->Write(archive) )
        break;
    }
    if (level_index < level_count)
      break;
    rc = true;
    break;
  }
  if (!archive.EndWrite3dmChunk())
    rc = false;
  if (rc)
    return true;
  return ON_SUBD_RETURN_ERROR(false);
}

bool ON_SubDimple::Read(
  ON_BinaryArchive& archive,
  class ON_SubD& subd
  )
{
  m_heap.Clear();
  int major_version = 0;
  int minor_version = 0;
  if ( !archive.BeginRead3dmChunk(TCODE_ANONYMOUS_CHUNK, &major_version, &minor_version) )
    return ON_SUBD_RETURN_ERROR(false);
  bool rc = false;

  unsigned int max_vertex_id = 0;
  unsigned int max_edge_id = 0;
  unsigned int max_face_id = 0;

  for (;;)
  {
    if (1 != major_version)
      break;

    unsigned int i;
    if (!archive.ReadInt(&i))
      break;
    const unsigned int level_count = i;

    if (!archive.ReadInt(&max_vertex_id))
      break;
    if (!archive.ReadInt(&max_edge_id))
      break;
    if (!archive.ReadInt(&max_face_id))
      break;


    ON_BoundingBox bbox_unsued_after_May_2015;
    if (!archive.ReadBoundingBox(bbox_unsued_after_May_2015))
      break;

    ON_SubDArchiveIdMap element_list;

    unsigned int level_index;
    for (level_index = 0; level_index < level_count; level_index++)
    {
      ON_SubDLevel* level = SubDLevel(level_index,true);
      if ( nullptr == level )
        break;
      if (false == level->Read(archive, element_list, subd ) )
        break;
      m_active_level = level;
    }

    if ( level_index != level_count)
      break;

    rc = true;
    break;
  }
  if (!archive.EndRead3dmChunk())
    rc = false;

  m_max_vertex_id = max_vertex_id;
  m_max_edge_id = max_edge_id;
  m_max_face_id = max_face_id;

  if (rc)
    return true;
  return ON_SUBD_RETURN_ERROR(false);
}

//virtual
bool ON_SubD::Write(
  ON_BinaryArchive& archive
  ) const // override
{
  for (;;)
  {
    const ON_SubDimple* subdimple = SubDimple();
    unsigned char c = (nullptr == subdimple) ? 0 : 1;
    if (!archive.WriteChar(c))
      break;
    if (nullptr != subdimple)
    {
      if (!subdimple->Write(archive))
        break;
    }
    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

//virtual
bool ON_SubD::Read(
  ON_BinaryArchive& archive
  ) // override
{
  Destroy();
  for (;;)
  {
    unsigned char c = 0;
    if (!archive.ReadChar(&c))
      break;
    if (1 == c)
    {
      ON_SubDimple* subdimple = SubDimple(true);
      if ( nullptr == subdimple)
        break;
      if (false == subdimple->Read(archive,*this))
      {
        Destroy();
        break;
      }
    }
    else if ( 0 != c )
      break;

    return true;
  }
  return ON_SUBD_RETURN_ERROR(false);
}

/////////////////////////////////////////////////////////////////////////////////////////
//
// ON_SubDMeshProxyUserData
//

ON_OBJECT_IMPLEMENT(ON_SubDMeshProxyUserData,ON_UserData,"2868B9CD-28AE-4EA7-8073-BD390B3E97C8");

const bool ON_SubDMeshProxyUserData::Internal_MeshHasFaces(const ON_Mesh* mesh)
{
  for (;;)
  {
    if (nullptr == mesh)
      break;
    if (mesh->m_F.UnsignedCount() <= 0)
      break;
    if (mesh->m_V.UnsignedCount() <= 2)
      break;
    return true;
  }
  return false;
}

const ON_SHA1_Hash ON_SubDMeshProxyUserData::Internal_FaceSHA1(const ON_Mesh* mesh)
{
  if (false == ON_SubDMeshProxyUserData::Internal_MeshHasFaces(mesh))
    return ON_SHA1_Hash::EmptyContentHash;

  ON_SHA1 sha1;
  const ON_MeshFace* f = mesh->m_F.Array();
  sha1.AccumulateBytes(f, mesh->m_F.UnsignedCount() * sizeof(*f));
  return sha1.Hash();
}

const ON_SHA1_Hash ON_SubDMeshProxyUserData::Internal_VertexSHA1(const ON_Mesh* mesh)
{
  if (false == ON_SubDMeshProxyUserData::Internal_MeshHasFaces(mesh))
    return ON_SHA1_Hash::EmptyContentHash;

  ON_SHA1 sha1;
  const ON_3fPoint* v = mesh->m_V.Array();
  sha1.AccumulateBytes(v, mesh->m_V.UnsignedCount() * sizeof(*v));
  return sha1.Hash();
}


void ON_SubDMeshProxyUserData::Internal_CopyFrom(const ON_SubDMeshProxyUserData& src)
{
  if ( src.IsValid() )
  {
    m_subd = new ON_SubD(*src.m_subd);
    m_mesh_face_count = src.m_mesh_face_count;
    m_mesh_vertex_count = src.m_mesh_vertex_count;
    m_mesh_face_array_sha1 = src.m_mesh_face_array_sha1;
    m_mesh_vertex_array_sha1 = src.m_mesh_vertex_array_sha1;
  }
}

void ON_SubDMeshProxyUserData::Internal_Destroy()
{
  if (nullptr != m_subd)
  {
    delete m_subd;
    m_subd = nullptr;
  }
  m_mesh_face_count = 0;
  m_mesh_vertex_count = 0;
  m_mesh_face_array_sha1 = ON_SHA1_Hash::EmptyContentHash;
  m_mesh_vertex_array_sha1 = ON_SHA1_Hash::EmptyContentHash;
}


const ON_SubDDisplayParameters ON_SubDMeshProxyUserData::MeshProxyDisplayParameters()
{
  return ON_SubDDisplayParameters::CreateFromDisplayDensity(4);

}

ON_Mesh* ON_SubDMeshProxyUserData::MeshProxyFromSubD(
  const ON_SubD* subd
)
{
  ON_Mesh* mesh = nullptr;
  ON_SubD* subd_copy = nullptr;

  for (;;)
  {
    if (nullptr == subd)
      break;
    subd_copy = new ON_SubD(*subd);
    if (nullptr == subd_copy)
      break;
    const ON_SubDDisplayParameters dp = ON_SubDMeshProxyUserData::MeshProxyDisplayParameters();
    mesh = subd_copy->GetLimitSurfaceMesh(dp, nullptr);
    if (false == ON_SubDMeshProxyUserData::Internal_MeshHasFaces(mesh))
      break;
    ON_SubDMeshProxyUserData* ud = new ON_SubDMeshProxyUserData();
    ud->m_subd = subd_copy;
    ud->m_mesh_face_count = mesh->FaceUnsignedCount();
    ud->m_mesh_vertex_count = mesh->VertexUnsignedCount();
    ud->m_mesh_face_array_sha1 = ON_SubDMeshProxyUserData::Internal_FaceSHA1(mesh);
    ud->m_mesh_vertex_array_sha1 = ON_SubDMeshProxyUserData::Internal_VertexSHA1(mesh);
    if (false == mesh->AttachUserData(ud))
    {
      ud->m_subd = nullptr;
      delete ud;
      break;
    }
    return mesh;
  }
  
  if (nullptr != mesh)
    delete mesh;
  
  if (nullptr != subd_copy)
    delete subd_copy;

  return nullptr;
}


ON_SubD* ON_SubDMeshProxyUserData::SubDFromMeshProxy(
  const ON_Mesh* mesh
)
{
  ON_SubD* subd = nullptr;
  ON_SubDMeshProxyUserData* ud = nullptr;
  
  for (;;)
  {
    if (nullptr == mesh)
      break;
    const ON_UUID udid = ON_CLASS_ID(ON_SubDMeshProxyUserData);
    ud = ON_SubDMeshProxyUserData::Cast(mesh->GetUserData(udid));
    if (nullptr == ud)
      break;
    if (false == ud->IsValid())
      break;
    if (false == ud->ParentMeshValid())
      break;
    subd = ud->m_subd;
    ud->m_subd = nullptr;
  }
  
  if (nullptr != ud)
    delete ud;

  return subd;
}


bool ON_SubDMeshProxyUserData::IsSubDMeshProxy(
  const ON_Mesh* mesh
)
{
  return false;
}


ON_SubDMeshProxyUserData::ON_SubDMeshProxyUserData()
{
  m_userdata_uuid = ON_CLASS_ID(ON_SubDMeshProxyUserData);
  m_application_uuid = ON_opennurbs6_id; // opennurbs.dll reads/writes this userdata
                                         // The id must be the version 4 id because
                                         // V5 SaveAs V4 needs to work.
  m_userdata_copycount = 1;
}


ON_SubDMeshProxyUserData::~ON_SubDMeshProxyUserData()
{
  Internal_Destroy();
}


ON_SubDMeshProxyUserData::ON_SubDMeshProxyUserData(const ON_SubDMeshProxyUserData& src)
  : ON_UserData(src)
{
  Internal_CopyFrom(src);
}


ON_SubDMeshProxyUserData& ON_SubDMeshProxyUserData::operator=(const ON_SubDMeshProxyUserData& src)
{
  if (this != &src)
  {
    Internal_Destroy();
    Internal_CopyFrom(src);
  }
  return *this;
}
 
bool ON_SubDMeshProxyUserData::Write(ON_BinaryArchive& archive) const
{
  const int chunk_version = 1;
  if ( false == archive.BeginWrite3dmAnonymousChunk(chunk_version) )
    return false;

  bool rc = false;

  for (;;)
  {
    const bool bIsValid = IsValid();
    if (!archive.WriteBool(bIsValid))
      break;

    if (false == bIsValid)
    {
      rc = true;
      break;
    }

    if (!m_subd->Write(archive))
      break;
    if (!archive.WriteInt(m_mesh_face_count))
      break;
    if (!archive.WriteInt(m_mesh_vertex_count))
      break;
    if (!m_mesh_face_array_sha1.Write(archive))
      break;
    if (!m_mesh_vertex_array_sha1.Write(archive))
      break;

    rc = true;
    break;
  }

  if (!archive.EndWrite3dmChunk())
    rc = false;

  return rc;
}

bool ON_SubDMeshProxyUserData::Read(ON_BinaryArchive& archive)
{
  Internal_Destroy();

  int chunk_version = 0;
  if ( false == archive.BeginRead3dmAnonymousChunk(&chunk_version) )
    return false;

  bool rc = false;

  for (;;)
  {
    if (chunk_version <= 0)
      break;
    bool bIsValid = false;

    if (!archive.ReadBool(&bIsValid))
      break;

    if (false == bIsValid)
    {
      rc = true;
      break;
    }

    m_subd = new ON_SubD();
    if (!m_subd->Read(archive))
      break;
    if (!archive.ReadInt(&m_mesh_face_count))
      break;
    if (!archive.ReadInt(&m_mesh_vertex_count))
      break;
    if (!m_mesh_face_array_sha1.Read(archive))
      break;
    if (!m_mesh_vertex_array_sha1.Read(archive))
      break;

    rc = true;
    break;
  }

  if (!archive.EndRead3dmChunk())
    rc = false;
  
  if (!rc || !IsValid())
    Internal_Destroy();

  return rc;
}


bool ON_SubDMeshProxyUserData::ParentMeshValid() const
{
  for (;;)
  {
    if (!IsValid())
      break;

    const ON_Mesh* mesh = ON_Mesh::Cast(Owner());
    if (false == ON_SubDMeshProxyUserData::Internal_MeshHasFaces(mesh))
      break;

    if (m_mesh_face_count != mesh->m_F.UnsignedCount())
      break;

    if (m_mesh_vertex_count != mesh->m_V.UnsignedCount())
      break;

    const ON_SHA1_Hash f_sha1 = ON_SubDMeshProxyUserData::Internal_FaceSHA1(mesh);
    if (f_sha1 != m_mesh_face_array_sha1)
      break;
    
    const ON_SHA1_Hash v_sha1 = ON_SubDMeshProxyUserData::Internal_VertexSHA1(mesh);
    if (v_sha1 != m_mesh_vertex_array_sha1)
      break;

    return true;
  }

  m_mesh_face_count = 0;
  m_mesh_vertex_count = 0;
  m_mesh_face_array_sha1 = ON_SHA1_Hash::EmptyContentHash;
  m_mesh_vertex_array_sha1 = ON_SHA1_Hash::EmptyContentHash;

  return false;
}

bool ON_SubDMeshProxyUserData::IsValid(
  class ON_TextLog* text_log
) const
{
  for (;;)
  {
    if (nullptr == m_subd)
      break;
    if (m_mesh_face_count <= 0 )
      break;
    if (m_mesh_vertex_count <= 2 )
      break;
    if (ON_SHA1_Hash::EmptyContentHash == m_mesh_face_array_sha1)
      break;
    if (ON_SHA1_Hash::EmptyContentHash == m_mesh_vertex_array_sha1)
      break;
    if (false == m_userdata_xform.IsIdentity())
      break;

    return true;
  }

  return false;
}


bool ON_SubDMeshProxyUserData::GetDescription(ON_wString& description)
{
  if (IsValid())
    description = L"SubD attached to a valid proxy mesh.";
  else
    description = L"SubD attached to an invalid proxy mesh.";
  return true;
}


bool ON_SubDMeshProxyUserData::WriteToArchive(
  const class ON_BinaryArchive& archive,
  const class ON_Object* parent_object
) const
{
  for (;;)
  {
    if (archive.Archive3dmVersion() < 60)
      break;
    if (false == IsValid())
      return false;
    if (false == ParentMeshValid())
      return false;
    return true;
  }

  return false;
}
 

#endif
