//
// Copyright (c) 1993-2022 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>.
//
////////////////////////////////////////////////////////////////

#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

ON_OBJECT_IMPLEMENT(ON_SurfaceProxy,ON_Surface,"4ED7D4E2-E947-11d3-BFE5-0010830122F0");

ON_SurfaceProxy::ON_SurfaceProxy() : m_surface(0), m_bTransposed(0)
{}

ON_SurfaceProxy::ON_SurfaceProxy( const ON_Surface* s ) : m_surface(s), m_bTransposed(0)
{}

ON_SurfaceProxy::ON_SurfaceProxy( const ON_SurfaceProxy& src ) : ON_Surface(src), m_surface(0), m_bTransposed(0)
{
  *this = src;
}

unsigned int ON_SurfaceProxy::SizeOf() const
{
  unsigned int sz = ON_Surface::SizeOf();
  sz += (sizeof(*this) - sizeof(ON_Surface));
  // Do not add in size of m_surface - its memory is not
  // managed by this class.
  return sz;
}

ON__UINT32 ON_SurfaceProxy::DataCRC(ON__UINT32 current_remainder) const
{
  if ( m_surface )
    current_remainder = m_surface->DataCRC(current_remainder);
  current_remainder = ON_CRC32(current_remainder,sizeof(m_bTransposed),&m_bTransposed);
  return current_remainder;
}

ON_SurfaceProxy& ON_SurfaceProxy::operator=( const ON_SurfaceProxy& src )
{
  if ( this != &src ) {
    ON_Surface::operator=(src);
    m_surface = src.m_surface;
    m_bTransposed = src.m_bTransposed;
  }
  return *this;
}

ON_SurfaceProxy::~ON_SurfaceProxy()
{
  m_surface = 0;
}

void ON_SurfaceProxy::SetProxySurface( const ON_Surface* proxy_surface )
{
  // setting m_surface=0 prevents crashes if user has deleted
  // "real" surface before calling SetProxySurface().
  m_surface = 0;

  DestroySurfaceTree();
  if ( proxy_surface == this )
    proxy_surface = 0;
  m_surface = proxy_surface;
  m_bTransposed = false;
}

const ON_Surface* ON_SurfaceProxy::ProxySurface() const
{
  return m_surface;
}

bool ON_SurfaceProxy::ProxySurfaceIsTransposed() const
{
  return m_bTransposed;
}


ON_Surface* ON_SurfaceProxy::DuplicateSurface() const
{
  ON_Surface* dup_srf = 0;
  if ( m_surface )
  {
    dup_srf = m_surface->Duplicate();
    if ( m_bTransposed && dup_srf )
      dup_srf->Transpose();
  }
  return dup_srf;
}


bool ON_SurfaceProxy::IsValid( ON_TextLog* text_log ) const
{
  return ( m_surface ) ? m_surface->IsValid(text_log) : false;
}

void
ON_SurfaceProxy::Dump( ON_TextLog& dump ) const
{
  dump.Print("ON_SurfaceProxy uses %x\n",m_surface);
  if (m_surface ) 
    m_surface->Dump(dump);
}

bool 
ON_SurfaceProxy::Write(
       ON_BinaryArchive&  // open binary file
     ) const
{
  return false;
}

bool 
ON_SurfaceProxy::Read(
       ON_BinaryArchive&  // open binary file
     )
{
  return false;
}

int 
ON_SurfaceProxy::Dimension() const
{
  return ( m_surface ) ? m_surface->Dimension() : 0;
}

bool ON_SurfaceProxy::GetBBox( // returns true if successful
         double* boxmin,    // minimum
         double* boxmax,    // maximum
         bool bGrowBox
         ) const
{
  return ( m_surface ) ? m_surface->GetBBox(boxmin,boxmax,bGrowBox) : false;
}

bool
ON_SurfaceProxy::Transform( 
    const ON_Xform& // xform - formal parameter intentionally ignored in this virtual function
    )
{
  return false; // cannot modify m_surface
}


ON_Interval
ON_SurfaceProxy::Domain( int dir ) const
{
  ON_Interval d;
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  if ( m_surface ) 
    d = m_surface->Domain(dir);
  return d;
}

bool ON_SurfaceProxy::GetSurfaceSize( 
    double* width, 
    double* height 
    ) const
{
  bool rc = false;
  if ( m_surface )
  {
    if ( m_bTransposed )
    {
      double* ptr = width;
      width = height;
      height = ptr;
    }
    rc = m_surface->GetSurfaceSize(width,height);
  }
  else
  {
    if ( width )
      *width = 0.0;
    if ( height )
      *height = 0.0;
  }
  return rc;
}

int
ON_SurfaceProxy::SpanCount( int dir ) const
{
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  return ( m_surface ) ? m_surface->SpanCount(dir) : false;
}

bool
ON_SurfaceProxy::GetSpanVector( int dir, double* s ) const
{
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  return ( m_surface ) ? m_surface->GetSpanVector(dir,s) : false;
}

int
ON_SurfaceProxy::Degree( int dir ) const
{
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  return ( m_surface ) ? m_surface->Degree(dir) : false;
}


bool 
ON_SurfaceProxy::GetParameterTolerance(
         int dir,
         double t,  // t = parameter in domain
         double* tminus, // tminus
         double* tplus   // tplus
         ) const
{
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  return ( m_surface ) ? m_surface->GetParameterTolerance(dir,t,tminus,tplus) : false;
}

bool 
ON_SurfaceProxy::IsClosed( int dir ) const
{
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  return ( m_surface ) ? m_surface->IsClosed( dir ) : false;
}

ON_Surface::ISO 
ON_SurfaceProxy::IsIsoparametric( // returns isoparametric status of 2d curve
        const ON_Curve& crv,
        const ON_Interval* subdomain
        ) const
{
  // this is a virtual override of an ON_Surface::IsIsoparametric

  const ON_Curve* pC = &crv;
	ON_Curve* pTranC = nullptr;
	if(m_bTransposed)
  {
		pTranC = crv.DuplicateCurve();
		pTranC->SwapCoordinates(0,1);
		pC = pTranC;
	}	

	ON_Surface::ISO iso = m_surface->IsIsoparametric( *pC, subdomain);

	if (pTranC)
  {
		switch(iso)
    {
		case x_iso:
			iso = y_iso;
      break;
		case y_iso:
			iso = x_iso;
      break;
		case W_iso:
			iso = S_iso;
      break;
		case S_iso:
			iso = W_iso;
      break;
		case N_iso:
			iso = E_iso;
      break;
		case E_iso:
			iso = N_iso;
      break;
    default:
      // intentionally ignoring other ON_Surface::ISO enum values
      break;
		}
    delete pTranC;
	}

	return iso;
}

ON_Surface::ISO 
ON_SurfaceProxy::IsIsoparametric( // returns isoparametric status based on bounding box
        const ON_BoundingBox& box
        ) const
{	
  // this is a virtual override of an ON_Surface::IsIsoparametric
	const ON_BoundingBox* pbox = &box;
	ON_BoundingBox Tbox(	ON_3dPoint( box.m_min[1],box.m_min[0],0.0),
												ON_3dPoint( box.m_max[1],box.m_max[0],0.0) );
	if(m_bTransposed)
		pbox = &Tbox;

	ON_Surface::ISO iso = m_surface->IsIsoparametric( *pbox);

	if( m_bTransposed){
		switch(iso)
    {
		case x_iso:
			iso = y_iso;
      break;
		case y_iso:
			iso = x_iso;
      break;
		case W_iso:
			iso = S_iso;
      break;
		case S_iso:
			iso = W_iso;
      break;
		case N_iso:
			iso = E_iso;
      break;
		case E_iso:
			iso = N_iso;
      break;
    default:
      // intentionally ignoring other ON_Surface::ISO enum values
      break;
		}
	}
	return iso;
}




bool ON_SurfaceProxy::IsPlanar(
      ON_Plane* plane,
      double tolerance
      ) const
{
  bool rc = false;
  if ( m_surface )
  {
    rc = m_surface->IsPlanar( plane, tolerance );
    if (rc && m_bTransposed && plane )
      plane->Flip();
  }
  return rc;
}

bool 
ON_SurfaceProxy::IsPeriodic( int dir ) const
{
  if ( m_bTransposed ) {
    dir = (dir) ? 0 : 1;
  }
  return ( m_surface ) ? m_surface->IsPeriodic( dir ) : false;
}

bool ON_SurfaceProxy::GetNextDiscontinuity( 
                int dir,
                ON::continuity c,
                double t0,
                double t1,
                double* t,
                int* hint,
                int* dtype,
                double cos_angle_tolerance,
                double curvature_tolerance
                ) const
{
  // untested code
  bool rc = false;

  if ( 0 != m_surface && dir >= 0 && dir <= 1 )
  {
    rc = m_surface->GetNextDiscontinuity(m_bTransposed?1-dir:dir,c,t0,t1,t,hint,dtype,cos_angle_tolerance,curvature_tolerance);
  }

  return rc;
}

bool 
ON_SurfaceProxy::IsSingular( int side ) const
{
  if ( m_bTransposed ) {
    switch(side) {
    case 0:
      side = 3;
      break;
    case 1:
      side = 2;
      break;
    case 2:
      side = 1;
      break;
    case 3:
      side = 0;
      break;
    }
  }
  return ( m_surface ) ? m_surface->IsSingular( side ) : false;
}

bool
ON_SurfaceProxy::Reverse( 
    int // dir - formal parameter intentionally ignored in this virtual function
    )
{
  return false; // cannot modify m_surface
}

bool
ON_SurfaceProxy::Transpose()
{
  DestroySurfaceTree();
  m_bTransposed = (m_bTransposed) ? false : true;
  return true;
}

bool ON_SurfaceProxy::IsContinuous(
    ON::continuity desired_continuity,
    double s, 
    double t, 
    int* hint, // default = nullptr,
    double point_tolerance, // default=ON_ZERO_TOLERANCE
    double d1_tolerance, // default==ON_ZERO_TOLERANCE
    double d2_tolerance, // default==ON_ZERO_TOLERANCE
    double cos_angle_tolerance, // default==ON_DEFAULT_ANGLE_TOLERANCE_COSINE
    double curvature_tolerance  // default==ON_SQRT_EPSILON
    ) const
{
  bool rc = true;
  if ( m_surface )
  {
    if ( m_bTransposed )
    {
      double x = s;
      s = t;
      t = x;
    }
    rc = m_surface->IsContinuous( desired_continuity, s, t, hint,
                                      point_tolerance, d1_tolerance, d2_tolerance,
                                      cos_angle_tolerance, curvature_tolerance );
  }
  return rc;
}



bool 
ON_SurfaceProxy::Evaluate( // returns false if unable to evaluate
       double s, double t, // evaluation parameters
       int der_count,  // number of derivatives (>=0)
       int v_stride,   // v[] array stride (>=Dimension())
       double* v,      // v[] array of length stride*(ndir+1)
       int side,       // optional - determines which side to evaluate from
                       //         0 = default
                       //      <  0 to evaluate from below, 
                       //      >  0 to evaluate from above
       int* hint       // optional - evaluation hint (int) used to speed
                       //            repeated evaluations
       ) const
{
  if ( m_bTransposed ) {
    double x = s; s = t; t = x;
  }
  return ( m_surface ) ? m_surface->Evaluate(s,t,der_count,v_stride,v,side,hint) : false;
}


ON_Curve* ON_SurfaceProxy::IsoCurve(
       int dir,
       double c
       ) const
{
  ON_Curve* isocurve = 0;

  if ( m_bTransposed )
  {
    dir = 1-dir;
  }

  if ( 0 != m_surface && dir >= 0 && dir <= 1 )
  {
    isocurve = m_surface->IsoCurve( dir, c );
  }

  return isocurve;
}


int ON_SurfaceProxy::GetNurbForm( // returns 0: unable to create NURBS representation
                   //            with desired accuracy.
                   //         1: success - returned NURBS parameterization
                   //            matches the surface's to the desired accuracy
                   //         2: success - returned NURBS point locus matches
                   //            the surfaces's to the desired accuracy but, on
                   //            the interior of the surface's domain, the 
                   //            surface's parameterization and the NURBS
                   //            parameterization may not match to the 
                   //            desired accuracy.
        ON_NurbsSurface& nurbs,
        double tolerance
        ) const
{
  int rc = ( m_surface ) ? m_surface->GetNurbForm(nurbs,tolerance) : 0;
  if ( rc && m_bTransposed ) {
		if (!nurbs.Transpose())
			rc = 0;
  }
  return rc;
}

int 
ON_SurfaceProxy::HasNurbForm( // returns 0: unable to create NURBS representation
                   //            with desired accuracy.
                   //         1: success - returned NURBS parameterization
                   //            matches the surface's to the desired accuracy
                   //         2: success - returned NURBS point locus matches
                   //            the surfaces's to the desired accuracy but, on
                   //            the interior of the surface's domain, the 
                   //            surface's parameterization and the NURBS
                   //            parameterization may not match to the 
                   //            desired accuracy.
        ) const

{
  if (!m_surface)
    return 0;
  return m_surface->HasNurbForm();
}

bool ON_SurfaceProxy::GetSurfaceParameterFromNurbFormParameter(
      double nurbs_s, double nurbs_t,
      double* surface_s, double* surface_t
      ) const
{
  bool rc = false;
  if ( m_surface )
  {
    rc = m_bTransposed 
       ? m_surface->GetSurfaceParameterFromNurbFormParameter(nurbs_t,nurbs_s,surface_t,surface_s)
       : m_surface->GetSurfaceParameterFromNurbFormParameter(nurbs_s,nurbs_t,surface_s,surface_t);
  }
  return rc;
}

bool ON_SurfaceProxy::GetNurbFormParameterFromSurfaceParameter(
      double surface_s, double surface_t,
      double* nurbs_s,  double* nurbs_t
      ) const
{
  bool rc = false;
  if ( m_surface )
  {
    rc = m_bTransposed 
       ? m_surface->GetNurbFormParameterFromSurfaceParameter(surface_t,surface_s,nurbs_t,nurbs_s)
       : m_surface->GetNurbFormParameterFromSurfaceParameter(surface_s,surface_t,nurbs_s,nurbs_t);
  }
  return rc;
}
