#include <mystdlib.h>

#include <myadt.hpp>
#include <linalg.hpp>
#include <csg.hpp>

#include <meshing.hpp>



#include <inctcl.hpp>



#include <visual.hpp>

#include <stlgeom.hpp>

#include "vsstl.hpp"

extern "C" int Ng_STL_Init (Tcl_Interp * interp);



namespace netgen
{
  DLL_HEADER extern shared_ptr<NetgenGeometry>  ng_geometry;
  DLL_HEADER extern shared_ptr<Mesh> mesh;
  DLL_HEADER extern MeshingParameters mparam;
  DLL_HEADER extern STLParameters stlparam;

  static VisualSceneSTLGeometry vsstlgeom;
  static VisualSceneSTLMeshing vsstlmeshing;

  char * err_needsstlgeometry = (char*) "This operation needs an STL geometry";





  class STLGeometryVisRegister : public GeometryRegister
  {
  public:
    virtual NetgenGeometry * Load (const filesystem::path & filename) const { return NULL; }
    virtual VisualScene * GetVisualScene (const NetgenGeometry * geom) const;
    virtual void SetParameters (Tcl_Interp * interp) 
    {
      stlparam.yangle =
	atof (Tcl_GetVar (interp, "::stloptions.yangle", 0));
      stlparam.contyangle =
	atof (Tcl_GetVar (interp, "::stloptions.contyangle", 0));
      stlparam.edgecornerangle =
	atof (Tcl_GetVar (interp, "::stloptions.edgecornerangle", 0));
      stlparam.chartangle =
	atof (Tcl_GetVar (interp, "::stloptions.chartangle", 0));
      stlparam.outerchartangle =
	atof (Tcl_GetVar (interp, "::stloptions.outerchartangle", 0));

      stlparam.usesearchtree =
	atoi (Tcl_GetVar (interp, "::stloptions.usesearchtree", 0));


      stlparam.atlasminh =
	atof (Tcl_GetVar (interp, "::stloptions.atlasminh", 0));

      stlparam.resthsurfcurvfac =
	atof (Tcl_GetVar (interp, "::stloptions.resthsurfcurvfac", 0));
      stlparam.resthsurfcurvenable =
	atoi (Tcl_GetVar (interp, "::stloptions.resthsurfcurvenable", 0));

      stlparam.resthatlasfac =
	atof (Tcl_GetVar (interp, "::stloptions.resthatlasfac", 0));
      stlparam.resthatlasenable =
	atoi (Tcl_GetVar (interp, "::stloptions.resthatlasenable", 0));

      stlparam.resthchartdistfac =
	atof (Tcl_GetVar (interp, "::stloptions.resthchartdistfac", 0));
      stlparam.resthchartdistenable =
	atoi (Tcl_GetVar (interp, "::stloptions.resthchartdistenable", 0));

      stlparam.resthlinelengthfac =
	atof (Tcl_GetVar (interp, "::stloptions.resthlinelengthfac", 0));
      stlparam.resthlinelengthenable =
	atoi (Tcl_GetVar (interp, "::stloptions.resthlinelengthenable", 0));

      stlparam.resthedgeanglefac =
	atof (Tcl_GetVar (interp, "::stloptions.resthedgeanglefac", 0));
      stlparam.resthedgeangleenable =
	atoi (Tcl_GetVar (interp, "::stloptions.resthedgeangleenable", 0));

      stlparam.resthsurfmeshcurvfac =
	atof (Tcl_GetVar (interp, "::stloptions.resthsurfmeshcurvfac", 0));
      stlparam.resthsurfmeshcurvenable =
	atoi (Tcl_GetVar (interp, "::stloptions.resthsurfmeshcurvenable", 0));

      stlparam.recalc_h_opt =
	atoi (Tcl_GetVar (interp, "::stloptions.recalchopt", 0));
      //  stlparam.Print (cout);      
    }
  };



  int Ng_SetSTLParameters  (ClientData clientData,
			    Tcl_Interp * interp,
			    int argc, tcl_const char *argv[])
  {
    STLGeometryVisRegister reg;
    reg.SetParameters (interp);

    return TCL_OK;
  }

  






  int Ng_STLDoctor (ClientData clientData,
		    Tcl_Interp * interp,
		    int argc, tcl_const char *argv[])
  {
    //cout << "STL doctor" << endl;
    STLGeometry * stlgeometry = 
          dynamic_cast<STLGeometry*> (ng_geometry.get());
      

    stldoctor.drawmeshededges =
      atoi (Tcl_GetVar (interp, "::stldoctor.drawmeshededges", 0));

    stldoctor.geom_tol_fact =
      atof (Tcl_GetVar (interp, "::stldoctor.geom_tol_fact", 0));


    stldoctor.useexternaledges =
      atoi (Tcl_GetVar (interp, "::stldoctor.useexternaledges", 0));

    stldoctor.showfaces =
      atoi (Tcl_GetVar (interp, "::stldoctor.showfaces", 0));

    stldoctor.conecheck =
      atoi (Tcl_GetVar (interp, "::stldoctor.conecheck", 0));

    stldoctor.spiralcheck =
      atoi (Tcl_GetVar (interp, "::stldoctor.spiralcheck", 0));

    stldoctor.selectwithmouse =
      atoi (Tcl_GetVar (interp, "::stldoctor.selectwithmouse", 0));

    stldoctor.showedgecornerpoints =
      atoi (Tcl_GetVar (interp, "::stldoctor.showedgecornerpoints", 0));

    stldoctor.showmarkedtrigs =
      atoi (Tcl_GetVar (interp, "::stldoctor.showmarkedtrigs", 0));

    stldoctor.showtouchedtrigchart =
      atoi (Tcl_GetVar (interp, "::stldoctor.showtouchedtrigchart", 0));

    //cout << "smt=" << stldoctor.showmarkedtrigs << endl;

    stldoctor.dirtytrigfact =
      atof (Tcl_GetVar (interp, "::stldoctor.dirtytrigfact", 0));

    stldoctor.smoothnormalsweight =
      atof (Tcl_GetVar (interp, "::stldoctor.smoothnormalsweight", 0));

    stldoctor.smoothangle =
      atof (Tcl_GetVar (interp, "::stldoctor.smoothangle", 0));

    stldoctor.selectmode =
      atoi (Tcl_GetVar (interp, "::stldoctor.selectmode", 0));

    stldoctor.edgeselectmode =
      atoi (Tcl_GetVar (interp, "::stldoctor.edgeselectmode", 0));

    stldoctor.longlinefact =
      atoi (Tcl_GetVar (interp, "::stldoctor.longlinefact", 0));

    stldoctor.showexcluded =
      atoi (Tcl_GetVar (interp, "::stldoctor.showexcluded", 0));



    if (!stldoctor.selectwithmouse)
      {
	stldoctor.selecttrig =
	  atoi (Tcl_GetVar (interp, "::stldoctor.selecttrig", 0));

	stldoctor.nodeofseltrig =
	  atoi (Tcl_GetVar (interp, "::stldoctor.nodeofseltrig", 0));
      }

    stldoctor.showvicinity =
      atoi (Tcl_GetVar (interp, "::stldoctor.showvicinity", 0));

    stldoctor.vicinity =
      atoi (Tcl_GetVar (interp, "::stldoctor.vicinity", 0));


    if (argc >= 2)
      {
	if (!stlgeometry)
	  {
	    Tcl_SetResult (interp, err_needsstlgeometry, TCL_STATIC);
	    return TCL_ERROR;
	  }

	if (strcmp (argv[1], "destroy0trigs") == 0)
	  {
	    stlgeometry->DestroyDirtyTrigs();
	  }
	else if (strcmp (argv[1], "movepointtomiddle") == 0)
	  {
	    stlgeometry->MoveSelectedPointToMiddle();
	  }
	else if (strcmp (argv[1], "calcnormals") == 0)
	  {
	    stlgeometry->CalcNormalsFromGeometry();
	  }
	else if (strcmp (argv[1], "showchartnum") == 0)
	  {
	    stlgeometry->ShowSelectedTrigChartnum();
	  }
	else if (strcmp (argv[1], "showcoords") == 0)
	  {
	    stlgeometry->ShowSelectedTrigCoords();
	  }
	else if (strcmp (argv[1], "loadmarkedtrigs") == 0)
	  {
	    stlgeometry->LoadMarkedTrigs();
	  }
	else if (strcmp (argv[1], "savemarkedtrigs") == 0)
	  {
	    stlgeometry->SaveMarkedTrigs();
	  }
	else if (strcmp (argv[1], "neighbourangles") == 0)
	  {
	    stlgeometry->NeighbourAnglesOfSelectedTrig();
	  }
	else if (strcmp (argv[1], "vicinity") == 0)
	  {
	    stlgeometry->CalcVicinity(stldoctor.selecttrig);
	  }
	else if (strcmp (argv[1], "markdirtytrigs") == 0)
	  {
	    stlgeometry->MarkDirtyTrigs(stlparam);
	  }
	else if (strcmp (argv[1], "smoothdirtytrigs") == 0)
	  {
	    stlgeometry->SmoothDirtyTrigs(stlparam);
	  }
	else if (strcmp (argv[1], "smoothrevertedtrigs") == 0)
	  {
	    stlgeometry->GeomSmoothRevertedTrigs(stlparam);
	  }
	else if (strcmp (argv[1], "invertselectedtrig") == 0)
	  {
	    stlgeometry->InvertTrig(stlgeometry->GetSelectTrig());
	  }
	else if (strcmp (argv[1], "deleteselectedtrig") == 0)
	  {
	    stlgeometry->DeleteTrig(stlgeometry->GetSelectTrig());
	  }
	else if (strcmp (argv[1], "smoothgeometry") == 0)
	  {
	    stlgeometry->SmoothGeometry();
	  }
	else if (strcmp (argv[1], "orientafterselectedtrig") == 0)
	  {
	    stlgeometry->OrientAfterTrig(stlgeometry->GetSelectTrig());
	  }
	else if (strcmp (argv[1], "marktoperrortrigs") == 0)
	  {
	    stlgeometry->MarkTopErrorTrigs();
	  }
	else if (strcmp (argv[1], "exportedges") == 0)
	  {
	    stlgeometry->ExportEdges();
	  }
	else if (strcmp (argv[1], "importedges") == 0)
	  {
	    stlgeometry->ImportEdges();
	  }
	else if (strcmp (argv[1], "importexternaledges") == 0)
	  {
	    stlgeometry->ImportExternalEdges(argv[2]);
	  }
	else if (strcmp (argv[1], "loadedgedata") == 0)
	  {
	    if (argc >= 3)
	      {
		stlgeometry->LoadEdgeData(argv[2]);
	      }
	  }
	else if (strcmp (argv[1], "saveedgedata") == 0)
	  {
	    if (argc >= 3)
	      {
		stlgeometry->SaveEdgeData(argv[2]);
	      }
	  }

	else if (strcmp (argv[1], "buildexternaledges") == 0)
	  {
	    stlgeometry->BuildExternalEdgesFromEdges();
	  }
	else if (strcmp (argv[1], "smoothnormals") == 0)
	  {
	    stlgeometry->SmoothNormals(stlparam);
	  }
	else if (strcmp (argv[1], "marknonsmoothnormals") == 0)
	  {
	    stlgeometry->MarkNonSmoothNormals(stlparam);
	  }
	else if (strcmp (argv[1], "addexternaledge") == 0)
	  {
	    stlgeometry->AddExternalEdgeAtSelected();
	  }
	else if (strcmp (argv[1], "addgeomline") == 0)
	  {
	    stlgeometry->AddExternalEdgesFromGeomLine();
	  }
	else if (strcmp (argv[1], "addlonglines") == 0)
	  {
	    stlgeometry->AddLongLinesToExternalEdges();
	  }
	else if (strcmp (argv[1], "addclosedlines") == 0)
	  {
	    stlgeometry->AddClosedLinesToExternalEdges();
	  }
	else if (strcmp (argv[1], "addnotsinglelines") == 0)
	  {
	    stlgeometry->AddAllNotSingleLinesToExternalEdges();
	  }
	else if (strcmp (argv[1], "deletedirtyexternaledges") == 0)
	  {
	    stlgeometry->DeleteDirtyExternalEdges();
	  }
	else if (strcmp (argv[1], "deleteexternaledge") == 0)
	  {
	    stlgeometry->DeleteExternalEdgeAtSelected();
	  }
	else if (strcmp (argv[1], "deletevicexternaledge") == 0)
	  {
	    stlgeometry->DeleteExternalEdgeInVicinity();
	  }

	else if (strcmp (argv[1], "addlonglines") == 0)
	  {
	    stlgeometry->STLDoctorLongLinesToCandidates();
	  }
	else if (strcmp (argv[1], "deletedirtyedges") == 0)
	  {
	    stlgeometry->STLDoctorDirtyEdgesToCandidates();
	  }
	else if (strcmp (argv[1], "undoedgechange") == 0)
	  {
	    stlgeometry->UndoEdgeChange();
	  }
	else if (strcmp (argv[1], "buildedges") == 0)
	  {
	    stlgeometry->STLDoctorBuildEdges(stlparam);
	  }
	else if (strcmp (argv[1], "confirmedge") == 0)
	  {
	    stlgeometry->STLDoctorConfirmEdge();
	  }
	else if (strcmp (argv[1], "candidateedge") == 0)
	  {
	    stlgeometry->STLDoctorCandidateEdge();
	  }
	else if (strcmp (argv[1], "excludeedge") == 0)
	  {
	    stlgeometry->STLDoctorExcludeEdge();
	  }
	else if (strcmp (argv[1], "undefinededge") == 0)
	  {
	    stlgeometry->STLDoctorUndefinedEdge();
	  }
	else if (strcmp (argv[1], "setallundefinededges") == 0)
	  {
	    stlgeometry->STLDoctorSetAllUndefinedEdges();
	  }
	else if (strcmp (argv[1], "erasecandidateedges") == 0)
	  {
	    stlgeometry->STLDoctorEraseCandidateEdges();
	  }
	else if (strcmp (argv[1], "confirmcandidateedges") == 0)
	  {
	    stlgeometry->STLDoctorConfirmCandidateEdges();
	  }
	else if (strcmp (argv[1], "confirmedtocandidateedges") == 0)
	  {
	    stlgeometry->STLDoctorConfirmedToCandidateEdges();
	  }
	else if (strcmp (argv[1], "writechart") == 0)
	  {
            int st = stlgeometry->GetSelectTrig();

            if (st >= 1 && st <= stlgeometry->GetNT() && stlgeometry->AtlasMade())
            {
              auto chartnumber = stlgeometry->GetChartNr(st);
              stlgeometry->WriteChartToFile(chartnumber, "chart.stlb");
            }
	  }
      }

    return TCL_OK;
  }














  int Ng_STLInfo  (ClientData clientData,
		   Tcl_Interp * interp,
		   int argc, tcl_const char *argv[])
  {
    double data[10];
    static char buf[20];

    STLGeometry * stlgeometry = dynamic_cast<STLGeometry*> (ng_geometry.get());

    if (!stlgeometry)
      {
	Tcl_SetResult (interp, err_needsstlgeometry, TCL_STATIC);
	return TCL_ERROR;
      }



    if (stlgeometry)
      {
	stlgeometry->STLInfo(data);
	//      cout << "NT=" << data[0] << endl;

	if (argc == 2)
	  {
	    if (strcmp (argv[1], "status") == 0)
	      {
		switch (stlgeometry->GetStatus())
		  {
		  case STLGeometry::STL_GOOD:
		    strcpy (buf, "GOOD"); break;
		  case STLGeometry::STL_WARNING:
		    strcpy (buf, "WARNING"); break;
		  case STLGeometry::STL_ERROR:
		    strcpy (buf, "ERROR"); break;
		  }
		Tcl_SetResult (interp, buf, TCL_STATIC);
		return TCL_OK;
	      }
	    if (strcmp (argv[1], "statustext") == 0)
	      {
		Tcl_SetResult (interp, (char*)stlgeometry->GetStatusText().c_str(), TCL_STATIC);
		return TCL_OK;
	      }
	    if (strcmp (argv[1], "topology_ok") == 0)
	      {
		snprintf (buf, size(buf), "%d", stlgeometry->Topology_Ok());
		Tcl_SetResult (interp, buf, TCL_STATIC);
	      }
	    if (strcmp (argv[1], "orientation_ok") == 0)
	      {
		snprintf (buf, size(buf), "%d", stlgeometry->Orientation_Ok());
		Tcl_SetResult (interp, buf, TCL_STATIC);
	      }
	  }
      }
    else
      {
	data[0] = 0;
	data[1] = 0;
	data[2] = 0;
	data[3] = 0;
	data[4] = 0;
	data[5] = 0;
	data[6] = 0;
	data[7] = 0;
      }




    snprintf (buf, size(buf), "%i", (int)data[0]);
    Tcl_SetVar (interp, argv[1], buf, 0);

    snprintf (buf, size(buf), "%5.3g", data[1]);
    Tcl_SetVar (interp, argv[2], buf, 0);
    snprintf (buf, size(buf), "%5.3g", data[2]);
    Tcl_SetVar (interp, argv[3], buf, 0);
    snprintf (buf, size(buf), "%5.3g", data[3]);
    Tcl_SetVar (interp, argv[4], buf, 0);

    snprintf (buf, size(buf), "%5.3g", data[4]);
    Tcl_SetVar (interp, argv[5], buf, 0);
    snprintf (buf, size(buf), "%5.3g", data[5]);
    Tcl_SetVar (interp, argv[6], buf, 0);
    snprintf (buf, size(buf), "%5.3g", data[6]);
    Tcl_SetVar (interp, argv[7], buf, 0);

    snprintf (buf, size(buf), "%i", (int)data[7]);
    Tcl_SetVar (interp, argv[8], buf, 0);

    return TCL_OK;
  }



  extern int Ng_SetMeshingParameters  (ClientData clientData,
				       Tcl_Interp * interp,
				       int argc, tcl_const char *argv[]);

  int Ng_STLCalcLocalH  (ClientData clientData,    
			 Tcl_Interp * interp,
			 int argc, tcl_const char *argv[])
  {
    for (int i = 0; i < geometryregister.Size(); i++)
      geometryregister[i] -> SetParameters (interp);


    Ng_SetMeshingParameters (clientData, interp, argc, argv);

    STLGeometry * stlgeometry = dynamic_cast<STLGeometry*> (ng_geometry.get());
    if (mesh && stlgeometry)
      {
	mesh -> SetLocalH (stlgeometry->GetBoundingBox().PMin() - Vec3d(10, 10, 10),
			   stlgeometry->GetBoundingBox().PMax() + Vec3d(10, 10, 10),
			   mparam.grading);
	stlgeometry -> RestrictLocalH(*mesh, mparam.maxh, stlparam, mparam);

	if (stlparam.resthsurfmeshcurvenable)
	  mesh -> CalcLocalHFromSurfaceCurvature (mparam.grading, 
						  stlparam.resthsurfmeshcurvfac);
      }

    return TCL_OK;
  }




  VisualScene * STLGeometryVisRegister :: GetVisualScene (const NetgenGeometry * geom) const
  {
    const STLGeometry * geometry = dynamic_cast<const STLGeometry*> (geom);
    if (geometry)
      {
	vsstlmeshing.SetGeometry (const_cast<STLGeometry*> (geometry));
	return &vsstlmeshing;
      }
    return NULL;
  }
}


using namespace netgen;

extern "C" int Ng_stl_Init (Tcl_Interp * interp);
int Ng_stl_Init (Tcl_Interp * interp)
{
  geometryregister.Append (new STLGeometryVisRegister);

  Tcl_CreateCommand (interp, "Ng_SetSTLParameters", Ng_SetSTLParameters,
		     (ClientData)NULL,
		     (Tcl_CmdDeleteProc*) NULL);
  
  Tcl_CreateCommand (interp, "Ng_STLDoctor", Ng_STLDoctor,
		     (ClientData)NULL,
		     (Tcl_CmdDeleteProc*) NULL);

  Tcl_CreateCommand (interp, "Ng_STLInfo", Ng_STLInfo,
		     (ClientData)NULL,
		     (Tcl_CmdDeleteProc*) NULL);
  
  Tcl_CreateCommand (interp, "Ng_STLCalcLocalH", Ng_STLCalcLocalH,
		     (ClientData)NULL,
		     (Tcl_CmdDeleteProc*) NULL);

  
  return TCL_OK;
}
