// Created on: 1999-01-13
// Created by: Xuan PHAM PHU
// Copyright (c) 1999-1999 Matra Datavision
// Copyright (c) 1999-2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.


#include <Bnd_Array1OfBox2d.hxx>
#include <Bnd_Box2d.hxx>
#include <BndLib_Add2dCurve.hxx>
#include <BRep_Tool.hxx>
#include <BRepClass3d_SolidExplorer.hxx>
#include <BRepClass_FaceClassifier.hxx>
#include <TColStd_Array2OfReal.hxx>
#include <TopExp_Explorer.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <TopoDS_Shape.hxx>
#include <TopOpeBRepTool_2d.hxx>
#include <TopOpeBRepTool_CLASSI.hxx>
#include <TopOpeBRepTool_EXPORT.hxx>
#include <TopOpeBRepTool_face.hxx>
#include <TopOpeBRepTool_TOOL.hxx>

#define SAME     (-1)
#define DIFF     (-2)
#define UNKNOWN  ( 0)
#define oneINtwo ( 1)
#define twoINone ( 2)

#define M_IN(st ) (st == TopAbs_IN )
#define M_OUT(st) (st == TopAbs_OUT)

//=======================================================================
//function : TopOpeBRepTool_CLASSI
//purpose  : 
//=======================================================================

TopOpeBRepTool_CLASSI::TopOpeBRepTool_CLASSI()
{
}

//=======================================================================
//function : Init2d
//purpose  : 
//=======================================================================

void TopOpeBRepTool_CLASSI::Init2d(const TopoDS_Face& Fref)
{
  myFref = Fref;
}

//=======================================================================
//function : HasInit2d
//purpose  : 
//=======================================================================

Standard_Boolean TopOpeBRepTool_CLASSI::HasInit2d() const
{
  return (!myFref.IsNull());
}



//=======================================================================
//function : Add2d
//purpose  : 
//=======================================================================

Standard_Boolean TopOpeBRepTool_CLASSI::Add2d(const TopoDS_Shape& S)
{
  if (!HasInit2d()) return Standard_False;

  Standard_Boolean isb = mymapsbox2d.Contains(S);
  if (isb) return Standard_True;

  Bnd_Box2d B2d;
  TopExp_Explorer exe(S, TopAbs_EDGE);
  for (; exe.More(); exe.Next()){
    const TopoDS_Edge& E = TopoDS::Edge(exe.Current());
    Standard_Real tolE = BRep_Tool::Tolerance(E);

    Standard_Boolean haspc = FC2D_HasCurveOnSurface(E,myFref);
    if (!haspc) return Standard_False;
    BRepAdaptor_Curve2d BC2d(E,myFref);
    Standard_Real tol2d = BC2d.Resolution(tolE);
    BndLib_Add2dCurve::Add(BC2d,tol2d,B2d);
  }
  mymapsbox2d.Add(S,B2d);
  return Standard_True;
}

//=======================================================================
//function : GetBox2d
//purpose  : 
//=======================================================================

Standard_Boolean TopOpeBRepTool_CLASSI::GetBox2d(const TopoDS_Shape& S, Bnd_Box2d& B2d)
{
  Standard_Boolean isb = mymapsbox2d.Contains(S);
  if (!isb) isb = Add2d(S);
  if (!isb) return Standard_False;
  B2d = mymapsbox2d.FindFromKey(S);
  return Standard_True;
}

//=======================================================================
//function : ClassiBnd2d
//purpose  : 
//=======================================================================

Standard_Integer TopOpeBRepTool_CLASSI::ClassiBnd2d(const TopoDS_Shape& S1,const TopoDS_Shape& S2,
				      const Standard_Real tol,const Standard_Boolean chklarge)
{
  Bnd_Array1OfBox2d B(1,2);
  Standard_Boolean isb = mymapsbox2d.Contains(S1);
  if (!isb) isb = Add2d(S1);
  if (!isb) return Standard_False;
  B(1) = mymapsbox2d.FindFromKey(S1);
  isb = mymapsbox2d.Contains(S2);
  if (!isb) isb = Add2d(S2);
  if (!isb) return Standard_False;
  B(2) = mymapsbox2d.FindFromKey(S2);

  TColStd_Array2OfReal UV(1,2, 1,4);
//  for (Standard_Integer i = 1; i <= 2; i++)
  Standard_Integer i ;
  for ( i = 1; i <= 2; i++)
    //      (Umin(i), Vmin(i), Umax(i), Vmax(i))
    B(i).Get(UV(i,1), UV(i,3), UV(i,2), UV(i,4));
  
#ifdef OCCT_DEBUG
  Standard_Boolean trc = Standard_False;
  if (trc) {
    for (Standard_Integer j = 1; j <= 2; j++)
      std::cout<<"B("<<j<<") = ("<<UV(j,1)<<" "<<UV(j,3)<<" "<<UV(j,2)<<" "<<UV(j,4)<<")"<<std::endl;
  }
#endif

  for (Standard_Integer k = 1; k <= 3; k+=2) { 
    for (i = 1; i <= 2; i++) {
      Standard_Integer ii = i, jj = (i == 1) ?  2 : 1;         
      //  diff = Umin<ii> - Umax<jj> : k = 1
      //  diff = Vmin<ii> - Vmax<jj> : k = 3
      Standard_Real diff = UV(ii,k) - UV(jj,k+1);
      // IMPORTANT : for split faces sharing same edge, use
      // chklarge = True.
      Standard_Boolean disjoint = chklarge ? (diff >= -tol) : (diff > 0.);
      if (disjoint) return DIFF;
    }
  }
  
  for (i = 1; i <= 2; i++) {    
    // comparing Bnd2d(ii) with Bnd2d(jj)
    Standard_Integer ii = i, jj = (i == 1) ?  2 : 1;
    Standard_Boolean smaller=Standard_True, same=Standard_True;

//    for (Standard_Integer k = 1; k <= 3; k += 2){         
    Standard_Integer k ;
    for ( k = 1; k <= 3; k += 2){         
      //  diff = Umin<ii> - Umin<jj> : k = 1        
      //  diff = Vmin<ii> - Vmin<jj> : k = 3 
      Standard_Real diff = UV(ii,k) - UV(jj,k);  
      smaller = chklarge ? (smaller && (diff > -tol)) : (smaller && (diff > 0.));
      same = same && (Abs(diff) <= tol);
    }    
    for (k = 2; k <= 4; k +=2){      
      //  diff = Umax<ii> - Umax<jj> : k = 2        
      //  diff = Vmax<ii> - Vmax<jj> : k = 4
      Standard_Real diff = UV(ii,k) - UV(jj,k); 
      smaller = chklarge ? (smaller && (diff < tol)) : (smaller && (diff < 0.));
      same = same && (Abs(diff) <= tol);
    }

    if (same) return SAME;
    if (smaller) {
      Standard_Integer sta = (ii==1) ? oneINtwo : twoINone;
      return sta;
    }
  }
  return UNKNOWN;
}

static Standard_Integer FUN_thegreatest(const TopoDS_Face& F1, BRepClass_FaceClassifier& class2)
// prequesitory : p2d1 IN f2, p2d2 IN f1
//                class2 for f2
// returns oneINtwo || twoINone || UNKNOWN
{
  TopExp_Explorer ex1(F1, TopAbs_EDGE);
  Standard_Real tolf1 = BRep_Tool::Tolerance(F1);
  for (; ex1.More(); ex1.Next()){
    const TopoDS_Edge& e1 = TopoDS::Edge(ex1.Current());
    Standard_Real f1,l1; FUN_tool_bounds(e1,f1,l1);
    Standard_Real x= 0.45678; Standard_Real p1 = (1-x)*f1+x+l1;
    gp_Pnt2d uv1; Standard_Boolean ok1 = FUN_tool_paronEF(e1,p1,F1,uv1,tolf1);
    if (!ok1) continue;
    TopAbs_State sta12 = class2.State();
    if      (M_IN(sta12) ) return oneINtwo;
    else if (M_OUT(sta12)) return twoINone;
  }
  return UNKNOWN;
}

//=======================================================================
//function : Classip2d
//purpose  : 
//=======================================================================

Standard_Integer TopOpeBRepTool_CLASSI::Classip2d(const TopoDS_Shape& S1, const TopoDS_Shape& S2, 
				    const Standard_Integer stabnd2d12)     
{
  if (!HasInit2d()) return UNKNOWN;
  Standard_Boolean bnd2dUNK = (stabnd2d12 == UNKNOWN)||(stabnd2d12 == SAME);

  // fa1,ffi1,finite1 : 
  TopOpeBRepTool_face fa1; 
  Standard_Boolean isb1 = mymapsface.IsBound(S1);
  if (isb1) fa1 = mymapsface.Find(S1);
  else      {
    Standard_Boolean ok1 = fa1.Init(TopoDS::Wire(S1), myFref);
    if (!ok1) return UNKNOWN;
    mymapsface.Bind(S1,fa1);
  }
  const TopoDS_Face& ffi1 = fa1.Ffinite();
  Standard_Boolean finite1 = fa1.Finite();

  // fa2,ffi2,finite2 : 
  TopOpeBRepTool_face fa2; 
  Standard_Boolean isb2 = mymapsface.IsBound(S2);
  if (isb2) fa2 = mymapsface.Find(S2);
  else      {
    Standard_Boolean ok2 = fa2.Init(TopoDS::Wire(S2), myFref);
    if (!ok2) return UNKNOWN;
    mymapsface.Bind(S2,fa2);
  }
  const TopoDS_Face& ffi2 = fa2.Ffinite();
  Standard_Boolean finite2 = fa2.Finite();

  // p2d1 : 
  Standard_Real u1,v1; Standard_Boolean ok1 = BRepClass3d_SolidExplorer::FindAPointInTheFace(ffi1,u1,v1);
  if (!ok1) return UNKNOWN;
  gp_Pnt2d p2d1(u1,v1);
  
  // sta12 = status(p2d1 / ffi2) :
  // recall : ffi1 and ffi2 are built on same face 
  // => ffi1(u1,v1) = ffi2(u1,v1)
  // ----------------------------
  Standard_Real tol2d2 = TopOpeBRepTool_TOOL::TolUV(ffi2,BRep_Tool::Tolerance(ffi2));
  BRepClass_FaceClassifier class2(ffi2,p2d1,tol2d2);
  TopAbs_State sta12 = class2.State();
  
  // staffi12 : only if !bnd2dUNK and stabnd2d12!=DIFF
  // PREQUESITORY : the only possible situations are 
  //  - ffi1 IN ffi2
  //  - ffi2 IN ffi1
  //  - ffi1 and ffi2 are distinct 
  Standard_Integer staffi12 = UNKNOWN; 

  Standard_Boolean try21 = Standard_False;
  if (bnd2dUNK)                {
    try21 = Standard_True;
  }
  else if (stabnd2d12 == DIFF) {
    return DIFF;
  }
  else                         {
    if      (sta12 == TopAbs_IN) {// 2 possible states : oneINtwo || twoINone   
      if      (stabnd2d12 == oneINtwo) staffi12 = oneINtwo; //Bnd2d(S1) IN Bnd2d(S2)
      else if (stabnd2d12 == twoINone) staffi12 = twoINone; //Bnd2d(S2) IN Bnd2d(S1)
      else                             try21 = Standard_True;
    }
    else if (sta12 == TopAbs_OUT) {// 2 possible states : twoINone || DIFF
      if      (stabnd2d12 == twoINone) staffi12 = twoINone;
      //    else if (stabnd2d12 == DIFF)     staffi12 = DIFF;
      else                             try21 = Standard_True;
    }
    else return UNKNOWN; // NYIxpu140199
  }
  
  if (try21) {
    // p2d2 : 
    Standard_Real u2,v2; Standard_Boolean ok2 = BRepClass3d_SolidExplorer::FindAPointInTheFace(ffi2,u2,v2);
    if (!ok2) return UNKNOWN;
    gp_Pnt2d p2d2(u2,v2);

    // sta21 : 
    Standard_Real tol2d1 = TopOpeBRepTool_TOOL::TolUV(ffi1,BRep_Tool::Tolerance(ffi1));
    BRepClass_FaceClassifier class1(ffi1,p2d2,tol2d1);
    TopAbs_State sta21 = class1.State();

    if (bnd2dUNK) {
      if      (M_OUT(sta12)&& M_OUT(sta21)) staffi12 = DIFF;
      else if (M_IN(sta12) && M_OUT(sta21)) staffi12 = oneINtwo;
      else if (M_OUT(sta12)&& M_IN(sta21) ) staffi12 = twoINone;
      else if (M_IN(sta12) && M_IN(sta21) ) staffi12 = ::FUN_thegreatest(ffi1,class2);
    }
    else {    
      if (sta12 == TopAbs_IN) {// 2 possible states : oneINtwo || twoINone   
	if (sta21 == TopAbs_OUT) staffi12 = oneINtwo;
      }
      else if (sta12 == TopAbs_OUT) {// 2 possible states : twoINone || DIFF
	if      (sta21 == TopAbs_OUT) staffi12 = DIFF;
	else if (sta21 == TopAbs_IN)  staffi12 = twoINone;
      }
      else return UNKNOWN; // NYIxpu140199    
    }
  }//try21


  // classif(S1,S2) knowing staffi12, finite, finite2 : 
  if (staffi12 == DIFF)          {
    if (finite1 &&  finite2) return DIFF;
    if (finite1 && !finite2) return twoINone;
    if (!finite1 && finite2) return oneINtwo;
    return oneINtwo; // !!!!!!! or twoINone
  } 
  else if (staffi12 == oneINtwo) {
    if (!finite1 && !finite2) return twoINone;
    return oneINtwo;
  }
  else if (staffi12 == twoINone) {
    if (!finite1 && !finite2) return oneINtwo;
    return twoINone;
  }
  return UNKNOWN;
}

//=======================================================================
//function : Getface
//purpose  : 
//=======================================================================

Standard_Boolean TopOpeBRepTool_CLASSI::Getface(const TopoDS_Shape& S, TopOpeBRepTool_face& fa) const
{
  Standard_Boolean isb = mymapsface.IsBound(S);
  if (!isb) return Standard_False;
  fa = mymapsface.Find(S);
  return Standard_True;
}


//=======================================================================
//function : Classilist
//purpose  : 
//=======================================================================

Standard_EXPORT void FUN_addOwlw(const TopoDS_Shape& Ow, const TopTools_ListOfShape& lw, TopTools_ListOfShape& lresu)
{
  Standard_Integer nw = lw.Extent();
  if (nw == 0) lresu.Append(Ow);
  else {
    TopTools_ListIteratorOfListOfShape it(lw);
    for (; it.More(); it.Next()) lresu.Append(it.Value());
  }
}

Standard_Boolean TopOpeBRepTool_CLASSI::Classilist(const TopTools_ListOfShape& lS, TopTools_DataMapOfShapeListOfShape& mapgreasma)
{
  Standard_Real tolref = BRep_Tool::Tolerance(myFref);
  Standard_Real toluv = TopOpeBRepTool_TOOL::TolUV(myFref,tolref);//nyixpu : cdliser??
  TopTools_ListOfShape null;

  TopTools_ListOfShape lw; lw.Assign(lS);
  mapgreasma.Clear();
  TopTools_ListIteratorOfListOfShape anIterW(lS);
  for (; anIterW.More(); anIterW.Next()) mapgreasma.Bind(anIterW.Value(),null);
  
  Standard_Integer nw = lw.Extent();
  if (nw <= 1) return Standard_True;

  Standard_Integer nite = 0, nitemax = Standard_Integer(nw*(nw-1)/2);
  while (nite <= nitemax){
    nw = lw.Extent();
    if (nw <= 1) break;

    // wi1 : 
    TopoDS_Shape wi1; 
    TopTools_ListIteratorOfListOfShape itw(lw);
    for (; itw.More(); itw.Next()){
      wi1 = itw.Value();
      Standard_Boolean isb1 = mapgreasma.IsBound(wi1);
      if (!isb1) continue; // wi1 stored as smaller shape
      break;
    }

    while (itw.More()) {// compare wi1 with all wi(k) (k>1)
      Standard_Boolean isb1 = mapgreasma.IsBound(wi1);
      if (!isb1) break;

      itw.Next();
      if (!itw.More()) break;

      // wi2, sta12 :       
      Standard_Integer sta12 = UNKNOWN;
      Standard_Boolean OUTall = Standard_False;
      TopoDS_Shape wi2;
      for (; itw.More(); itw.Next()){
	wi2 = itw.Value();
	Standard_Boolean isb2 = mapgreasma.IsBound(wi2);
	if (!isb2) continue;            
	
	Standard_Integer stabnd2d12 = ClassiBnd2d(wi1,wi2,toluv,Standard_True);
	sta12 = Classip2d(wi1,wi2, stabnd2d12);
	if (sta12 == DIFF) {OUTall = Standard_True; continue;}
	break;
      }
      
      // mapgreasma : 
      if      (sta12 == oneINtwo) {//greater = shape two
	TopTools_ListOfShape& lwgre = mapgreasma.ChangeFind(wi2);
	TopTools_ListOfShape lsma; FUN_addOwlw(wi1,mapgreasma.Find(wi1),lsma); 
	mapgreasma.UnBind(wi1);
	lwgre.Append(lsma);
      }
      else if (sta12 == twoINone) {//greater = shape one
	TopTools_ListOfShape& lwgre = mapgreasma.ChangeFind(wi1);
	TopTools_ListOfShape lsma; FUN_addOwlw(wi2,mapgreasma.Find(wi2),lsma); 
	mapgreasma.UnBind(wi2);
	lwgre.Append(lsma);
      }
      else if (OUTall) {
	// nothing's done
      }
      else return Standard_False;
    }//itw.More()

    lw.RemoveFirst();
  }//nite<=nmax
  return Standard_True;
}


 



