/**
 * \file Persistence.cpp
 *
 * \brief See how entities or attributes on entities are maintained
 *        across modeling operations such as webcut.
 *
 * This cuts the 62_shaver1.brep OpenCascade model into two pieces
 * with the z=40 plane. Both pieces are written to the same
 * output polydata file.
 *
 * Three faces (0, 26, and 149) are observed and all generate
 * notification events on the "StupidObserver" instance we create,
 * even though only face 26 is intersected by the cutting plane.
 *
 * Also, it's unclear how memory management of observers/observed
 * geometry is handled; do the observers remove destroyed
 * geometry from their list automagically or must we take
 * care to unregister notifications on each observed entity?
 */


#undef NDEBUG
#include <cassert>

#include "stdio.h"

#include "GMem.hpp"
#include "GeometryQueryTool.hpp"
#include "GeometryModifyTool.hpp"
#include "RefEdge.hpp"
#include "RefFace.hpp"
#include "Body.hpp"
#include "CubitDefines.h"
#include "CubitBox.hpp"
#include "InitCGMA.hpp"
#include "TestUtilities.hpp"

#include "vtkNew.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkXMLPolyDataWriter.h"

#ifndef SRCDIR
# define SRCDIR .
#endif

#define TEST_OCC
#ifdef TEST_ACIS
#  define ENGINE "ACIS"
#elif defined (TEST_OCC)
#  define ENGINE "OCC"
#else
#  error "Which engine to test?"
#endif

class StupidObserver : public CubitObserver
{
public:
  static StupidObserver* ofEntity(RefEntity* ent)
    {
    DLIList<CubitObserver*> list;
    StupidObserver* eo = NULL;

    ent->get_observer_list(list);
    for (int i = list.size(); !eo && i--; )
      {
      eo = dynamic_cast<StupidObserver*>(list.step_and_get());
      }
    return eo;
    }

  virtual bool observingEntity(RefEntity* ent)
    {
    DLIList<CubitObserver*> list;
    StupidObserver* eo = NULL;

    ent->get_observer_list(list);
    for (int i = list.size(); !eo && i--; )
      {
      eo = dynamic_cast<StupidObserver*>(list.step_and_get());
      if (eo == this)
        {
        return true;
        }
      }
    return false;
    }

  void observeEntity(RefEntity* ent)
    {
    if (!this->observingEntity(ent))
      {
      this->register_observable(ent);
      }
    }

  ///
  virtual CubitStatus notify_observer(
    CubitObservable* watched,
    const CubitEvent& event,
    CubitBoolean fromObservable)
    {
    cout
      << "Notification: watched " << watched
      << " event " << event.get_event_type() << "," << event.get_event_sub_type()
      << " from " << (fromObservable ? "observable" : "something else")
      << "\n";
    RefFace* face = dynamic_cast<RefFace*>(watched);
    if (face)
      {
      cout << "    " << face << " is a face\n";
      }
    RefEdge* edge = dynamic_cast<RefEdge*>(watched);
    if (edge)
      {
      cout << "    " << edge << " is an edge\n";
      }
    if (event.get_event_type() == GEOMETRY_TOPOLOGY_MODIFIED)
      {
      cout << "    Event: geom+topology modified (e.g., partitioned).\n";
      }
    return CUBIT_SUCCESS;
    }
};

int Persistence()
{
  GeometryQueryEngine* engine = GeometryQueryTool::instance()->get_gqe();

  if(!engine)
    {
    printf("no engine\n");
    return 1;
    }


  DLIList<RefFace*> faces;
  GeometryQueryTool::instance()->ref_faces(faces);
  GMem gmem;

  StupidObserver* observer = new StupidObserver;
  if (faces.size() > 0)
    {
    // Observe the first face.
    // It is firmly to one side of z=40
    observer->observeEntity(faces[0]);
    }
  if (faces.size() > 26)
    {
    // Observe the 27th face.
    observer->observeEntity(faces[26]);
    }
  if (faces.size() > 149)
    {
    // Observe the 149th face.
    observer->observeEntity(faces[149]);
    }
  faces.clean_out();

  // Cut the thing in twain:
  GeometryModifyTool* gmod = GeometryModifyTool::instance();
  DLIList<Body*> thingsToCut;
  DLIList<Body*> thingsThatWereCut;
  DLIList<Body*> cutNeighbors;
  GeometryQueryTool::instance()->bodies(thingsToCut);
  CubitVector basePoint(10., 0., 40.);
  CubitVector xyAxes[2];
  xyAxes[0].set(1., 0., 0.);
  xyAxes[1].set(0., 1., 0.);
  double width = 100., height = 100.;
  cout << thingsToCut.size() << " things to cut\n";
  gmod->webcut_with_planar_sheet(
    thingsToCut,
    basePoint, xyAxes, width, height,
    thingsThatWereCut, cutNeighbors,
    NO_IMPRINT, /*merge:*/ CUBIT_FALSE, /*preview:*/ CUBIT_FALSE);

  cout
    << thingsThatWereCut.size() << " things that were cut, "
    << cutNeighbors.size() << " cut neighbors"
    << "\n";


  // get graphics data for the surfaces
  vtkNew<vtkPolyData> pdata;
  vtkNew<vtkPoints> ppts;
  vtkNew<vtkIdTypeArray> surfs;
  vtkNew<vtkCellArray> polys;
  surfs->SetName("SourceSurface");
  pdata->SetPoints(ppts.GetPointer());
  pdata->SetPolys(polys.GetPointer());
  pdata->GetCellData()->SetPedigreeIds(surfs.GetPointer());
  vtkIdType poff = 0;
  if (thingsThatWereCut.size() > 0)
    {
    for (int v = 0; v < thingsThatWereCut.size(); ++v, thingsThatWereCut.step())
      {
      cout << "Volume " << (v + 1) << "/" << thingsThatWereCut.size() << "\n";
      thingsThatWereCut.get()->ref_faces(faces);

      std::vector<vtkIdType> conn;
      for(int i=0; i<faces.size(); i++)
        {
        cout << "  Face " << (i + 1) << "/" << faces.size() << "\r";
        RefFace* face = faces.get_and_step();
        Surface* surf = face->get_surface_ptr();
        if(!surf)
          {
          cout << "Skipping surfaceless face " << i << " of " << faces.size() << "\n";
          continue;
          }
        if(engine->get_graphics(surf, &gmem) != CUBIT_SUCCESS ||
          gmem.pointListCount == 0 || gmem.fListCount == 0)
          {
          printf("got no face facet data for face %i\n", face->id());
          }
        else
          {
          const GPoint* pts = gmem.point_list();
          const int* facets = gmem.facet_list();
          int facet_size = gmem.fListCount;
          int plist_size = gmem.pointListCount;
          for (int j = 0; j < plist_size; ++j)
            {
            const GPoint& pt = pts[j];
            ppts->InsertNextPoint(pt.x, pt.y, pt.z);
            }
          for(int j=0; j<facet_size;)
            {
            vtkIdType num = facets[j++];
            conn.resize(num);
            for(int k=0; k<num; k++, ++j)
              {
              conn[k] = facets[j] + poff;
              //const GPoint& pt = pts[facets[j]];
              }
            vtkIdType coff = polys->InsertNextCell(num, &conn[0]);
            surfs->InsertNextValue(i);
            }
          }
        poff = ppts->GetNumberOfPoints();
        }
      cout << "\n";
      }
    }

  vtkNew<vtkXMLPolyDataWriter> wri;
  wri->SetFileName("brep.vtp");
  wri->SetInputDataObject(pdata.GetPointer());
  wri->Write();

  return 0;
}

// main program - initialize, then send to proper function
int main (int argc, char **argv)
{
  CubitStatus s = InitCGMA::initialize_cgma( ENGINE );
  if (CUBIT_SUCCESS != s) return 1;

  //Do tests.
  ModelImportOptions opts;
  GeometryQueryTool::instance()->import_solid_model(
    argc > 1 ? argv[1] : "62_shaver1.brep",
    argc > 2 ? Model_File_Type(atoi(argv[2])) : OCC_TYPE,
    opts);
  int rsl = Persistence();
  if (rsl == 1)
    {
    PRINT_INFO("Operation Failed");
    }

  int ret_val = ( CubitMessage::instance()->error_count() );
  if ( ret_val != 0 )
    {
    PRINT_ERROR("Errors found during session.\n");
    }
  else
    {
    ret_val = 0;
    }

  return ret_val;
}
