//=========================================================================
//  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 "vtkCMBXMLBCSWriter.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCharArray.h"
#include "vtkDiscreteModel.h"
#include "vtkDiscreteModelEdge.h"
#include "vtkDiscreteModelEntityGroup.h"
#include "vtkDiscreteModelFace.h"
#include "vtkDiscreteModelRegion.h"
#include "vtkDiscreteModelWrapper.h"
#include "vtkErrorCode.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkModelItemIterator.h"
#include "vtkModelMaterial.h"
#include "vtkModelUserName.h"
#include "vtkModelVertex.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#
#include "vtkStringArray.h"
#include "vtkXMLDataElement.h"
#include "vtkXMLUtilities.h"

#include <iomanip>
#include <sstream>
#include <vtksys/SystemTools.hxx>

vtkStandardNewMacro(vtkCMBXMLBCSWriter);
vtkCxxSetObjectMacro(vtkCMBXMLBCSWriter, ModelWrapper, vtkDiscreteModelWrapper);

vtkCMBXMLBCSWriter::vtkCMBXMLBCSWriter()
{
  this->SetNumberOfInputPorts(0);
  this->SetNumberOfOutputPorts(0);
  this->ModelWrapper = NULL;
  this->Stream = NULL;
  this->WritingToFile = true;
}

vtkCMBXMLBCSWriter::~vtkCMBXMLBCSWriter()
{
  this->SetModelWrapper(0);
}

const char* vtkCMBXMLBCSWriter::GetDefaultFileExtension()
{
  return "bcs";
}

int vtkCMBXMLBCSWriter::GetDataSetMajorVersion()
{
  return 2;
}

int vtkCMBXMLBCSWriter::GetDataSetMinorVersion()
{
  return 0;
}

void vtkCMBXMLBCSWriter::SetFileName(const char* fileName)
{
  this->Superclass::SetFileName(fileName);
  this->WritingToFile = true;
}

void vtkCMBXMLBCSWriter::SetStream(std::ostream& stream)
{
  //we have to pass a fake file name so that vtkXMLWriter thinks we have
  //a file name we are saving too, yes this is fairly hackish. But this
  //is the only way while we wait for vtk 6.2 which xml writer support
  //streams natively.
  this->Superclass::SetFileName("fake file name");
  this->Stream = &stream;
  this->WritingToFile = false;
}

bool vtkCMBXMLBCSWriter::IsWritingToStream() const
{
  return !this->WritingToFile;
}

bool vtkCMBXMLBCSWriter::IsWrittingToFile() const
{
  return this->WritingToFile;
}

int vtkCMBXMLBCSWriter::WriteData()
{
  vtkDiscreteModel* Model = this->ModelWrapper->GetModel();
  const DiscreteMesh& mesh = Model->GetMesh();
  vtkDiscreteModel::ClassificationType& meshClassif = Model->GetMeshClassification();

  vtkIndent indent;
  vtkIndent indent2 = indent.GetNextIndent();
  vtkIndent indent3 = indent2.GetNextIndent();
  vtkIndent indent4 = indent3.GetNextIndent();

  vtkSmartPointer<vtkXMLDataElement> bcsfile = vtkSmartPointer<vtkXMLDataElement>::New();
  bcsfile->SetName("BCSFile");
  std::stringstream version;
  version << this->GetDataSetMajorVersion() << "." << this->GetDataSetMinorVersion();
  bcsfile->SetAttribute("Version", version.str().c_str());
  // current objects are Points, MeshFacets, ModelFaces, Materials,
  // ModelRegions, and BoundaryConditionSets
  bcsfile->SetIntAttribute("NumberOfObjects", 6);

  //
  // write out the point data
  //

  vtkSmartPointer<vtkXMLDataElement> points = vtkSmartPointer<vtkXMLDataElement>::New();
  points->SetName("Points");
  points->SetAttribute("Description",
    "Implicitly ordered array of point locations (x,y,z tuples) starting at 0 and numbered "
    "consecutively. Optionally may contain information for mapping from point Ids in this grid to "
    "another grid (e.g. mapping from point Ids in the surface mesh to point Ids in the volumetric "
    "mesh that the surface mesh was extracted from.");
  points->SetIntAttribute("NumberOfObjects", 1);
  bcsfile->AddNestedElement(points);
  vtkSmartPointer<vtkXMLDataElement> locations = vtkSmartPointer<vtkXMLDataElement>::New();
  locations->SetName("Data");
  locations->SetIntAttribute("NumberOfValues", mesh.GetNumberOfPoints() * 3);
  locations->SetAttribute("type", "Float64");
  locations->SetAttribute("Description", "The point location tuples.");
  locations->SetIntAttribute("NumberOfObjects", mesh.GetNumberOfPoints());
  vtkIdType i;
  std::stringstream data;
  data << "\n";
  char str[1024];
  double loc[3];
  for (i = 0; i < mesh.GetNumberOfPoints(); i++)
  {
    mesh.GetPoint(i, loc);

    // make sure we write sufficient precision
    sprintf(str, "%.16lg %.16lg %.16lg\n", loc[0], loc[1], loc[2]);
    data << indent4 << str;
    //cout << i << " is the point " << data.str().c_str() << " " << data.gcount() << endl;
  }
  data << indent3;
  locations->AddCharacterData(data.str().c_str(), data.str().length());
  points->AddNestedElement(locations);

  //
  // write out the cell/mesh facets data
  //

  vtkSmartPointer<vtkPolyData> MasterPoly = mesh.ShallowCopyFaceData();
  vtkCellArray* cells = MasterPoly->GetPolys();
  cells->InitTraversal();
  vtkIdType numCells = mesh.GetNumberOfCells();

  vtkSmartPointer<vtkXMLDataElement> meshFacets = vtkSmartPointer<vtkXMLDataElement>::New();
  meshFacets->SetName("MeshFacets");
  meshFacets->SetIntAttribute("NumberOfObjects", 1);
  meshFacets->SetAttribute("Description",
    "Implicitly ordered cell data starting from 0 and numbered consecutively with "
    "each line being an instance of cell data.  The first number is the number of "
    "points of the cell, the second is the unique persistent Id of the model face "
    "the cell belongs to and the subsequent numbers are the point ids of the cell.");
  bcsfile->AddNestedElement(meshFacets);
  vtkSmartPointer<vtkXMLDataElement> connectivity = vtkSmartPointer<vtkXMLDataElement>::New();
  connectivity->SetName("Data");

  meshFacets->AddNestedElement(connectivity);

  vtkIdType npts;
  vtkIdType* pts;
  i = 0;
  std::stringstream conn;
  conn << "\n";
  int NumberOfConnectivityValues = 0;

  while (cells->GetNextCell(npts, pts))
  {
    vtkDiscreteModelGeometricEntity* entity = meshClassif.GetEntity(i);
    const vtkIdType CellClassification = entity->GetThisModelEntity()->GetUniquePersistentId();
    conn << indent4 << npts << " " << CellClassification;
    for (int j = 0; j < npts; j++)
    {
      conn << " " << pts[j];
    }
    conn << "\n";
    NumberOfConnectivityValues += 2 + npts;
    i++;
  }
  conn << indent3;
  connectivity->AddCharacterData(conn.str().c_str(), conn.str().length());
  connectivity->SetIntAttribute("NumberOfValues", NumberOfConnectivityValues);
  connectivity->SetIntAttribute("NumberOfObjects", numCells);
  connectivity->SetAttribute("type", "Int64");

  //
  // write out the domain set data
  //
  vtkSmartPointer<vtkXMLDataElement> materials = vtkSmartPointer<vtkXMLDataElement>::New();
  materials->SetName("DomainSets");
  materials->SetIntAttribute(
    "NumberOfObjects", Model->GetNumberOfModelEntities(vtkModelMaterialType));
  materials->SetAttribute("Description",
    "Implicitly ordered domain sets starting at 0 and numbered consecutively along with a Name of "
    "the domain set that can be set and a unique persistent Id of the domain set.");
  bcsfile->AddNestedElement(materials);
  std::map<int, int> materialIdToOutputId;
  vtkModelItemIterator* Materials = Model->NewIterator(vtkModelMaterialType);
  for (Materials->Begin(); !Materials->IsAtEnd(); Materials->Next())
  {
    vtkModelMaterial* Material = vtkModelMaterial::SafeDownCast(Materials->GetCurrentItem());
    vtkIdType MaterialId = Material->GetUniquePersistentId();
    const char* MaterialUserName = vtkModelUserName::GetUserName(Material);
    vtkSmartPointer<vtkXMLDataElement> material = vtkSmartPointer<vtkXMLDataElement>::New();
    material->SetName("DomainSet");
    material->SetIntAttribute("UniquePersistentId", MaterialId);
    material->SetAttribute("Name", MaterialUserName);
    materials->AddNestedElement(material);
    vtkIdType Count = materialIdToOutputId.size();
    materialIdToOutputId[Material->GetUniquePersistentId()] = Count;
  }
  Materials->Delete();

  //
  // write out the region data (previously referred to as a shell)
  //
  vtkSmartPointer<vtkXMLDataElement> regions = vtkSmartPointer<vtkXMLDataElement>::New();
  regions->SetName("ModelRegions");
  regions->SetIntAttribute("NumberOfObjects", Model->GetNumberOfModelEntities(vtkModelRegionType));
  regions->SetAttribute("Description",
    "Implicitly ordered model regions starting at 0 and numbered consecutively along with a Name "
    "of the model region that can be set in CMB and the domain set Id the model region is "
    "associated with.  The region also has a unique persistent Id.");
  bcsfile->AddNestedElement(regions);
  vtkModelItemIterator* Regions = Model->NewIterator(vtkModelRegionType);
  std::map<vtkIdType, vtkIdType> regionIdToOutputId;
  for (Regions->Begin(); !Regions->IsAtEnd(); Regions->Next())
  {
    vtkDiscreteModelRegion* Region =
      vtkDiscreteModelRegion::SafeDownCast(Regions->GetCurrentItem());
    vtkIdType RegionId =
      vtkModelEntity::SafeDownCast(Regions->GetCurrentItem())->GetUniquePersistentId();
    const char* RegionUserName = vtkModelUserName::GetUserName(Region);
    vtkModelMaterial* Material = Region->GetMaterial();
    vtkSmartPointer<vtkXMLDataElement> region = vtkSmartPointer<vtkXMLDataElement>::New();
    region->SetName("ModelRegion");
    region->SetAttribute("Name", RegionUserName);
    region->SetIntAttribute("DomainSetId", materialIdToOutputId[Material->GetUniquePersistentId()]);
    region->SetIntAttribute("UniquePersistentId", Region->GetUniquePersistentId());
    regions->AddNestedElement(region);
    vtkIdType count = regionIdToOutputId.size();
    regionIdToOutputId[RegionId] = count;
  }
  Regions->Delete();

  //
  // write out the model face data
  //
  vtkSmartPointer<vtkXMLDataElement> modelFaces = vtkSmartPointer<vtkXMLDataElement>::New();
  modelFaces->SetName("ModelFaces");
  modelFaces->SetIntAttribute("NumberOfObjects", Model->GetNumberOfAssociations(vtkModelFaceType));
  modelFaces->SetAttribute("Description",
    "A model face is an aggregation of mesh facets that is used to specify information "
    "collectively for the mesh facets.  The model face has two associated model regions and User "
    "specified Name. ModelRegionId1 corresponds to the side of the model face that the mesh "
    "facets' normals point towards (using a counterclockwise ordering). A -1 indicates that there "
    "is no associated model region for the indicated side of the model face. The face also has a "
    "unique persistent Id.");
  bcsfile->AddNestedElement(modelFaces);
  int FaceCounter = 0;
  vtkModelItemIterator* Faces = Model->NewIterator(vtkModelFaceType);
  for (Faces->Begin(); !Faces->IsAtEnd(); Faces->Next(), FaceCounter++)
  {
    vtkSmartPointer<vtkXMLDataElement> modelFace = vtkSmartPointer<vtkXMLDataElement>::New();
    modelFace->SetName("ModelFace");
    vtkModelFace* Face = vtkModelFace::SafeDownCast(Faces->GetCurrentItem());
    // probably region0 is the one we want
    vtkModelRegion* Region0 = Face->GetModelRegion(0);
    vtkModelRegion* Region1 = Face->GetModelRegion(1);
    vtkIdType Region0Id = -1;
    if (Region0)
    {
      Region0Id = regionIdToOutputId[Region0->GetUniquePersistentId()];
    }
    vtkIdType Region1Id = -1;
    if (Region1)
    {
      Region1Id = regionIdToOutputId[Region1->GetUniquePersistentId()];
    }
    modelFace->SetIntAttribute("ModelRegionId0", Region0Id);
    modelFace->SetIntAttribute("ModelRegionId1", Region1Id);
    modelFace->SetIntAttribute("UniquePersistentId", Face->GetUniquePersistentId());
    const char* MFName = vtkModelUserName::GetUserName(Face);
    modelFace->SetAttribute("Name", MFName);
    modelFaces->AddNestedElement(modelFace);
  }
  Faces->Delete();

  //
  // write out the boundary condition set data
  //
  vtkSmartPointer<vtkXMLDataElement> BCSs = vtkSmartPointer<vtkXMLDataElement>::New();
  BCSs->SetName("BoundaryConditionSets");
  BCSs->SetIntAttribute(
    "NumberOfObjects", Model->GetNumberOfModelEntities(vtkDiscreteModelEntityGroupType));
  BCSs->SetAttribute("Description",
    "Implicitly ordered boundary condition sets (BCSs) starting at 0 and numbered consecutively "
    "along with a Name that can be set and a Unique Persistent Id.  Each BCS element also has a "
    "nested Data element used to store the Ids of the model faces it is applied over.");
  bcsfile->AddNestedElement(BCSs);
  vtkModelItemIterator* Groups = Model->NewIterator(vtkDiscreteModelEntityGroupType);
  for (Groups->Begin(); !Groups->IsAtEnd(); Groups->Next())
  {
    vtkDiscreteModelEntityGroup* Group =
      vtkDiscreteModelEntityGroup::SafeDownCast(Groups->GetCurrentItem());
    if (Group->GetEntityType() != vtkModelFaceType)
    {
      vtkErrorMacro("Only currently support BCSs defined over model faces.");
    }
    vtkIdType BCSId = Group->GetUniquePersistentId();
    const char* BCSUserName = vtkModelUserName::GetUserName(Group);

    vtkSmartPointer<vtkXMLDataElement> BCS = vtkSmartPointer<vtkXMLDataElement>::New();
    BCS->SetName("BoundaryConditionSet");
    BCS->SetIntAttribute("UniquePersistentId", BCSId);
    BCS->SetAttribute("Name", BCSUserName);
    BCS->SetAttribute("Type", "face");
    BCSs->AddNestedElement(BCS);

    vtkSmartPointer<vtkXMLDataElement> BCSModelFaces = vtkSmartPointer<vtkXMLDataElement>::New();
    BCSModelFaces->SetName("Data");
    BCSModelFaces->SetAttribute("type", "Int32");
    BCSModelFaces->SetIntAttribute("NumberOfObjects", Group->GetNumberOfModelEntities());

    std::stringstream BCSMFs;
    BCSMFs << "\n" << indent4 << " ";
    vtkModelItemIterator* GroupFaces = Group->NewModelEntityIterator();
    for (GroupFaces->Begin(); !GroupFaces->IsAtEnd(); GroupFaces->Next())
    {
      BCSMFs << " "
             << vtkModelEntity::SafeDownCast(GroupFaces->GetCurrentItem())->GetUniquePersistentId();
    }
    GroupFaces->Delete();
    BCSMFs << "\n" << indent4;
    BCSModelFaces->SetCharacterData(BCSMFs.str().c_str(), BCSMFs.str().length());
    BCS->AddNestedElement(BCSModelFaces);
  }
  Groups->Delete();

  //
  // write out the hard points data
  //
  if (Model->GetNumberOfModelEntities(vtkModelEdgeType))
  {
    this->AddHardPointsData(bcsfile, Model, indent4);
  }

  if (this->WritingToFile)
  {
    vtkXMLUtilities::WriteElementToFile(bcsfile, this->GetFileName(), &indent);
  }
  else if (this->Stream) //only write to stream if it isn't NULL
  {
    vtkXMLUtilities::FlattenElement(bcsfile, *this->Stream, &indent);
  }

  return 1;
}

void vtkCMBXMLBCSWriter::AddHardPointsData(
  vtkXMLDataElement* ParentElement, vtkDiscreteModel* Model, vtkIndent indent4)
{
  vtkSmartPointer<vtkXMLDataElement> HardPointsSets = vtkSmartPointer<vtkXMLDataElement>::New();
  vtkModelItemIterator* itSets = Model->NewIterator(vtkModelEdgeType);
  int HardPointsCounter = 0;
  for (itSets->Begin(); !itSets->IsAtEnd(); itSets->Next())
  {
    vtkDiscreteModelEdge* HardPoints = vtkDiscreteModelEdge::SafeDownCast(itSets->GetCurrentItem());
    if (HardPoints->GetNumberOfAssociations(vtkModelRegionType) > 0)
    {
      HardPointsCounter++;
      vtkIdType GroupId = HardPoints->GetUniquePersistentId();
      const char* GroupUserName = vtkModelUserName::GetUserName(HardPoints);

      vtkSmartPointer<vtkXMLDataElement> GroupElement = vtkSmartPointer<vtkXMLDataElement>::New();
      GroupElement->SetName("HardPointsGroup");
      GroupElement->SetIntAttribute("UniquePersistentId", GroupId);
      GroupElement->SetAttribute("Name", GroupUserName);
      HardPointsSets->AddNestedElement(GroupElement);

      vtkSmartPointer<vtkXMLDataElement> locations = vtkSmartPointer<vtkXMLDataElement>::New();
      locations->SetName("Data");
      locations->SetIntAttribute("NumberOfValues", (HardPoints->GetLineResolution() + 1) * 3);
      locations->SetAttribute("type", "Float64");
      locations->SetAttribute("Description", "The point location tuples.");
      locations->SetIntAttribute("NumberOfObjects", HardPoints->GetLineResolution() + 1);

      std::stringstream data;
      data << "\n";

      // write points tuples
      char str[1024];
      vtkModelVertex* vertex1 = HardPoints->GetAdjacentModelVertex(0);
      vtkModelVertex* vertex2 = HardPoints->GetAdjacentModelVertex(1);

      double xyz1[3], xyz2[3];
      if (vertex1->GetPoint(xyz1) && vertex2->GetPoint(xyz2))
      {
        double x[3], v[3], tc;
        for (int i = 0; i < 3; i++)
        {
          v[i] = xyz2[i] - xyz1[i];
        }
        for (int i = 0; i < HardPoints->GetLineResolution() + 1; i++)
        {
          tc = (static_cast<double>(i) / HardPoints->GetLineResolution());
          for (int j = 0; j < 3; j++)
          {
            x[j] = xyz1[j] + tc * v[j];
          }
          // make sure we write sufficient precision
          sprintf(str, "%.16lg %.16lg %.16lg\n", x[0], x[1], x[2]);
          data << indent4 << str;
        }
      }
      else
      {
        vtkErrorMacro("Could not get location of a model vertex.");
      }

      data << indent4;
      locations->AddCharacterData(data.str().c_str(), data.str().length());
      GroupElement->AddNestedElement(locations);
    }
  }
  itSets->Delete();
  HardPointsSets->SetName("HardPointsGroups");
  HardPointsSets->SetIntAttribute("NumberOfObjects", HardPointsCounter);
  HardPointsSets->SetAttribute("Description", "Independent HardPoints sets.  Each HardPoints "
                                              "element also has a nested Data element used to "
                                              "store the point locations (x,y,z tuples).");
  ParentElement->AddNestedElement(HardPointsSets);
}

const char* vtkCMBXMLBCSWriter::GetDataSetName()
{
  return NULL;
}

void vtkCMBXMLBCSWriter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "ModelWrapper: " << this->ModelWrapper << "\n";
}
