//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include "vtkCMBMeshClient.h"

#include "vtkCMBModelEdgeMeshClient.h"
#include "vtkCMBModelFaceMeshClient.h"
#include "vtkCMBMeshGridRepresentationClient.h"
#include "vtkCMBModelVertexMesh.h"

#include <vtkCallbackCommand.h>
#include <vtkDiscreteModel.h>
#include <vtkDiscreteModelGeometricEntity.h>
#include <vtkIdList.h>
#include <vtkMergeEventData.h>
#include <vtkModel.h>
#include <vtkModelEdge.h>
#include <vtkModelEntity.h>
#include <vtkModelFace.h>
#include <vtkModelGeometricEntity.h>
#include <vtkModelItemIterator.h>
#include <vtkModelVertex.h>
#include <vtkObjectFactory.h>
#include <vtkSmartPointer.h>
#include <vtkSplitEventData.h>

#include <vtkSMDoubleVectorProperty.h>
#include <vtkSMIntVectorProperty.h>
#include <vtkSMProxy.h>
#include <vtkSMProxyManager.h>
#include "vtkSMProxyProperty.h"
#include "vtkClientServerStream.h"
#include "vtkProcessModule.h"

#include <map>

vtkStandardNewMacro(vtkCMBMeshClient);
vtkCxxSetObjectMacro(vtkCMBMeshClient, ServerModelProxy, vtkSMProxy);

class vtkCMBMeshClientInternals
{
public:
  std::map<vtkModelEdge*, vtkSmartPointer<vtkCMBModelEdgeMeshClient> > ModelEdges;
  std::map<vtkModelFace*, vtkSmartPointer<vtkCMBModelFaceMeshClient> > ModelFaces;
};

//----------------------------------------------------------------------------
vtkCMBMeshClient::vtkCMBMeshClient()
{
  this->ServerModelProxy = NULL;
  this->ServerMeshProxy = NULL;
  this->Internal = new vtkCMBMeshClientInternals;
}

//----------------------------------------------------------------------------
vtkCMBMeshClient::~vtkCMBMeshClient()
{
  if(this->CallbackCommand)
    {
    if(this->Model)
      {
      this->Model->RemoveObserver(this->CallbackCommand);
      }
    this->CallbackCommand = NULL;
    }
  if(this->Internal)
    {
    delete this->Internal;
    this->Internal = NULL;
    }
  if(this->ServerMeshProxy)
    {
    this->ServerMeshProxy->Delete();
    this->ServerMeshProxy = NULL;
    }
  this->SetServerModelProxy(NULL);
}

//----------------------------------------------------------------------------
void vtkCMBMeshClient::Initialize(vtkModel* model, vtkSMProxy* smModelProxy)
{
  if(model == NULL)
    {
    vtkErrorMacro("Passed in NULL model.");
    return;
    }
  if(smModelProxy == NULL)
    {
    vtkErrorMacro("Passed in NULL server side proxy.");
    return;
    }
  if(model->GetModelDimension() != 2)
    {  // do nothing if it's not a 2d model
    return;
    }
  if(this->Model != model)
    {
    this->Reset();
    this->Model = model;
    }
  if(this->ServerModelProxy != smModelProxy)
    {
    this->SetServerModelProxy(smModelProxy);
    }
  if(this->ServerMeshProxy)
    {
    this->ServerMeshProxy->Delete();
    this->ServerMeshProxy = NULL;
    }
  // register model modification events that we want
  // this may not be correct yet
  this->CallbackCommand =
    vtkSmartPointer<vtkCallbackCommand>::New();
  this->CallbackCommand->SetCallback(vtkCMBMeshClient::ModelGeometricEntityChanged);
  this->CallbackCommand->SetClientData(static_cast<void*>(this));
  model->AddObserver(ModelGeometricEntityBoundaryModified, this->CallbackCommand);
  model->AddObserver(ModelGeometricEntitiesAboutToMerge, this->CallbackCommand);
  model->AddObserver(ModelGeometricEntitySplit, this->CallbackCommand);

  // edges
  this->Internal->ModelEdges.clear();
  vtkModelItemIterator* iter = model->NewIterator(vtkModelEdgeType);
  for(iter->Begin();!iter->IsAtEnd();iter->Next())
    {
    vtkModelEdge* edge =
      vtkModelEdge::SafeDownCast(iter->GetCurrentItem());
    vtkSmartPointer<vtkCMBModelEdgeMeshClient> meshRepresentation =
      vtkSmartPointer<vtkCMBModelEdgeMeshClient>::New();
    meshRepresentation->Initialize(this, edge);
    this->Internal->ModelEdges[edge] = meshRepresentation;
    }
  iter->Delete();
  // faces
  this->Internal->ModelFaces.clear();
  iter = model->NewIterator(vtkModelFaceType);
  for(iter->Begin();!iter->IsAtEnd();iter->Next())
    {
    vtkModelFace* face =
      vtkModelFace::SafeDownCast(iter->GetCurrentItem());
    vtkSmartPointer<vtkCMBModelFaceMeshClient> meshRepresentation =
      vtkSmartPointer<vtkCMBModelFaceMeshClient>::New();
    meshRepresentation->Initialize(this, face);
    this->Internal->ModelFaces[face] = meshRepresentation;
    }
  iter->Delete();

  // now initiate the initialization of the server side mesh
  vtkSMProxyManager* manager = vtkSMProxyManager::GetProxyManager();
  this->ServerMeshProxy = manager->NewProxy("CMBSimBuilderMeshGroup", "CMBMeshWrapper");
  if(!this->ServerMeshProxy)
    {
    vtkErrorMacro("Unable to create operator proxy.");
    return;
    }
  this->ServerMeshProxy->SetLocation(this->ServerModelProxy->GetLocation());

  vtkSMProxyProperty* proxyproperty =
    vtkSMProxyProperty::SafeDownCast(
    this->ServerMeshProxy->GetProperty("ModelWrapper"));
  proxyproperty->AddProxy(this->ServerModelProxy);
  this->ServerMeshProxy->UpdateVTKObjects();
}

//----------------------------------------------------------------------------
bool vtkCMBMeshClient::SetGlobalLength(double globalLength)
{
  if(this->GlobalLength == globalLength || !this->ServerMeshProxy)
    {
    return false;
    }
  this->GlobalLength = globalLength > 0. ? globalLength : 0.;

  vtkSMDoubleVectorProperty* lengthProperty =
    vtkSMDoubleVectorProperty::SafeDownCast(
      this->ServerMeshProxy->GetProperty("GlobalLength"));
  lengthProperty->SetElement(0, this->GetGlobalLength());
  this->ServerMeshProxy->UpdateVTKObjects();
  return true;
}

//----------------------------------------------------------------------------
bool vtkCMBMeshClient::SetGlobalMinimumAngle(double minAngle)
{
  if(this->GlobalMinimumAngle == minAngle)
    {
    return false;
    }
  if(minAngle < 0.)
    {
    this->GlobalMinimumAngle = 0;
    }
  else if(minAngle > 33.)
    {
    this->GlobalMinimumAngle = 33.;
    }
  else
    {
    this->GlobalMinimumAngle = minAngle;
    }
  vtkSMDoubleVectorProperty* angleProperty =
    vtkSMDoubleVectorProperty::SafeDownCast(
      this->ServerMeshProxy->GetProperty("GlobalMinimumAngle"));
  angleProperty->SetElement(0, this->GetGlobalMinimumAngle());
  this->ServerMeshProxy->UpdateVTKObjects();
  return true;
}

//----------------------------------------------------------------------------
void vtkCMBMeshClient::Reset()
{
  this->Internal->ModelEdges.clear();
  this->Internal->ModelFaces.clear();
  if(this->ServerMeshProxy)
    {
    this->ServerMeshProxy->Delete();
    this->ServerMeshProxy = NULL;
    }
  this->SetServerModelProxy(NULL);
  if(this->CallbackCommand)
    {
    if(this->Model)
      {
      this->Model->RemoveObserver(this->CallbackCommand);
      }
    this->CallbackCommand = NULL;
    }
  this->Superclass::Reset();
}

//----------------------------------------------------------------------------
bool vtkCMBMeshClient::BuildModelEntityMeshes()
{
  vtkSmartPointer<vtkModelItemIterator> edges;
  edges.TakeReference(this->Model->NewIterator(vtkModelEdgeType));
  for(edges->Begin();!edges->IsAtEnd();edges->Next())
    {
    vtkModelEdge* edge = vtkModelEdge::SafeDownCast(edges->GetCurrentItem());
    vtkCMBModelEdgeMeshClient* edgeMesh =
      vtkCMBModelEdgeMeshClient::SafeDownCast(this->GetModelEntityMesh(edge));
    if(edgeMesh->BuildModelEntityMesh(false) == false)
      {
      vtkErrorMacro("Unable to build model edge mesh.");
      return false;
      }
    }
  vtkSmartPointer<vtkModelItemIterator> faces;
  faces.TakeReference(this->Model->NewIterator(vtkModelFaceType));
  for(faces->Begin();!faces->IsAtEnd();faces->Next())
    {
    vtkModelFace* face = vtkModelFace::SafeDownCast(faces->GetCurrentItem());
    vtkCMBModelFaceMeshClient* faceMesh =
      vtkCMBModelFaceMeshClient::SafeDownCast(this->GetModelEntityMesh(face));
    if(faceMesh->BuildModelEntityMesh(false) == false)
      {
      return false;
      }
    }
  return true;
}

//----------------------------------------------------------------------------
bool vtkCMBMeshClient::BuildModelMeshRepresentation(
  const char* fileName, const bool &isAnalysisMesh,
  vtkSMProxy* meshRepresentionInput)
{
  vtkCMBMeshGridRepresentationClient *meshRep =
    vtkCMBMeshGridRepresentationClient::New();
  vtkDiscreteModel *mod = vtkDiscreteModel::SafeDownCast(this->Model);
  if ( mod )
    {
    meshRep->SetMeshIsAnalysisGrid(isAnalysisMesh);
    if (fileName)
      {
      meshRep->SetGridFileName(fileName);
      }
    meshRep->SetMeshRepresentationSource(meshRepresentionInput);
    bool result =  meshRep->Operate(mod,this->ServerMeshProxy);
    meshRep->Delete();
    return result;
    }
  else
    {
    return false;
    }
}

//----------------------------------------------------------------------------
vtkCMBModelEntityMesh* vtkCMBMeshClient::GetModelEntityMesh(
  vtkModelGeometricEntity* entity)
{
  if(vtkModelEdge* modelEdge = vtkModelEdge::SafeDownCast(entity))
    {
    std::map<vtkModelEdge*,vtkSmartPointer<vtkCMBModelEdgeMeshClient> >::iterator it=
      this->Internal->ModelEdges.find(modelEdge);
    if(it == this->Internal->ModelEdges.end())
      {
      return NULL;
      }
    return it->second;
    }
  if(vtkModelFace* modelFace = vtkModelFace::SafeDownCast(entity))
    {
    std::map<vtkModelFace*,
      vtkSmartPointer<vtkCMBModelFaceMeshClient> >::iterator it=
      this->Internal->ModelFaces.find(modelFace);
    if(it == this->Internal->ModelFaces.end())
      {
      return NULL;
      }
    return it->second;
    }
  vtkErrorMacro("Incorrect type.");
  return NULL;
}

//----------------------------------------------------------------------------
void vtkCMBMeshClient::ModelEdgeSplit(vtkSplitEventData* splitEventData)
{
  vtkModelEdge* sourceEdge = vtkModelEdge::SafeDownCast(
    splitEventData->GetSourceEntity()->GetThisModelEntity());
  if(splitEventData->GetCreatedModelEntityIds()->GetNumberOfIds() != 2)
    {
    vtkGenericWarningMacro("Problem with split event.");
    return;
    }
  vtkModelEdge* createdEdge = vtkModelEdge::SafeDownCast(
    this->Model->GetModelEntity(
      vtkModelEdgeType, splitEventData->GetCreatedModelEntityIds()->GetId(0)));
  if(createdEdge == NULL)
    {
    createdEdge = vtkModelEdge::SafeDownCast(
      this->Model->GetModelEntity(
        vtkModelEdgeType, splitEventData->GetCreatedModelEntityIds()->GetId(1)));
    }
  vtkCMBModelEdgeMesh* sourceMesh = vtkCMBModelEdgeMesh::SafeDownCast(
    this->GetModelEntityMesh(sourceEdge));
  sourceMesh->BuildModelEntityMesh(false);

  vtkSmartPointer<vtkCMBModelEdgeMeshClient> createdMesh =
    vtkSmartPointer<vtkCMBModelEdgeMeshClient>::New();
  createdMesh->Initialize(this, createdEdge);
  createdMesh->SetLength(sourceMesh->GetLength());
  createdMesh->SetMeshedLength(sourceMesh->GetMeshedLength());
  // don't need to trigger the mesh since it's already been done on the server
  this->Internal->ModelEdges[createdEdge] = createdMesh;

  this->Modified();
}

//----------------------------------------------------------------------------
void vtkCMBMeshClient::ModelEdgeMerge(vtkMergeEventData* mergeEventData)
{
  vtkModelEdge* sourceEdge = vtkModelEdge::SafeDownCast(
    mergeEventData->GetSourceEntity()->GetThisModelEntity());
  vtkModelEdge* targetEdge = vtkModelEdge::SafeDownCast(
    mergeEventData->GetTargetEntity()->GetThisModelEntity());
  vtkCMBModelEdgeMesh* sourceMesh = vtkCMBModelEdgeMesh::SafeDownCast(
    this->GetModelEntityMesh(sourceEdge));
  double sourceLength = sourceMesh->GetLength();
  this->Internal->ModelEdges.erase(sourceEdge);
  vtkCMBModelEdgeMesh* targetEdgeMesh = vtkCMBModelEdgeMesh::SafeDownCast(
    this->GetModelEntityMesh(targetEdge));
  if( (targetEdgeMesh->GetLength() > sourceLength && sourceLength > 0.) ||
      targetEdgeMesh->GetLength() <= 0.)
    {
    targetEdgeMesh->SetLength(sourceLength);
    }

  // we can't remesh the target edge yet since the topology hasn't changed
  // yet.  we mark it as modified so that when we see the boundary modified
  // event we will trigger the remeshing then.
  targetEdgeMesh->Modified();

  this->Modified();
}

//----------------------------------------------------------------------------
void vtkCMBMeshClient::ModelEntityBoundaryModified(vtkModelGeometricEntity* /*entity*/)
{
  // no op on the client
}
//----------------------------------------------------------------------------
void vtkCMBMeshClient::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  if(this->ServerModelProxy)
    {
    os << "ServerModelProxy: " << this->ServerModelProxy << "\n";
    }
  else
    {
    os << "ServerModelProxy: (NULL)\n";
    }
  if(this->ServerMeshProxy)
    {
    os << "ServerMeshProxy: " << this->ServerMeshProxy << "\n";
    }
  else
    {
    os << "ServerMeshProxy: (NULL)\n";
    }
}
//----------------------------------------------------------------------------
