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

#include "smtk/bridge/discrete/Session.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/FileItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/io/ModelToMesh.h"
#include "smtk/mesh/Collection.h"
#include "smtk/model/Manager.h"
#include "smtk/model/Model.h"
#include "smtk/model/Operator.h"
#include "smtk/model/SessionRef.h"

#include "smtk/extension/vtk/reader/vtkCMBGeometryReader.h"
#include "vtkDataSetRegionSurfaceFilter.h"
#include "vtkDiscreteModelWrapper.h"
#include "vtkMasterPolyDataNormals.h"
#include "vtkMergeDuplicateCells.h"
#include "vtkModel.h"
#include "vtkModelItem.h"
#include "vtkPDataSetReader.h"

#ifdef SMTK_ENABLE_REMUS_SUPPORT
#include "smtk/extension/vtk/meshing/vtkCMBTriangleMesher.h"
#include "smtk/extension/vtk/reader/vtkCMBGeometry2DReader.h"
#include "smtk/extension/vtk/reader/vtkCMBMapReader.h"
#endif

#ifdef SMTK_ENABLE_MOAB_DISCRETE_READER
#include "smtk/bridge/discrete/moabreader/vtkCmbMoabReader.h"
#endif

#include "ImportOperator_xml.h"
#include "ModelParserHelper.h"
#include <vtksys/SystemTools.hxx>

#include "cJSON.h"
#include "smtk/io/SaveJSON.h"

using namespace smtk::model;

namespace smtk
{
namespace bridge
{

namespace discrete
{

ImportOperator::ImportOperator()
{
}

bool ImportOperator::ableToOperate()
{
  if (!this->specification()->isValid())
    return false;

  std::string filename = this->specification()->findFile("filename")->value();
  if (filename.empty())
    return false;
  std::string ext = vtksys::SystemTools::GetFilenameLastExtension(filename);
  std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
  bool able = (ext == ".vtk" || ext == ".2dm" || ext == ".3dm" ||
#ifdef SMTK_ENABLE_MOAB_DISCRETE_READER
    ext == ".h5m" || ext == ".sat" || ext == ".brep" || ext == ".stp" || ext == ".cub" ||
    ext == ".exo" ||
#endif
#ifdef SMTK_ENABLE_REMUS_SUPPORT
    ext == ".poly" || ext == ".smesh" || ext == ".map" ||
#endif
    /*  ext == ".tin" ||
      ext == ".fac" ||
      ext == ".obj" ||
      ext == ".sol" ||*/
    ext == ".stl");

// for shape files, the reader needs user inputs, so
// "ShapeBoundaryStyle" item needs to be checked first.
#ifdef SMTK_ENABLE_REMUS_SUPPORT
  if (ext == ".shp")
  {
    smtk::attribute::StringItem::Ptr boundaryItem =
      this->specification()->findString("ShapeBoundaryStyle");
    able = boundaryItem->isEnabled();
  }
#endif

  return able;
}

OperatorResult ImportOperator::operateInternal()
{
  /*
  smtk::attribute::FileItem::Ptr filenameItem =
    this->specification()->findFile("filename");
  smtk::attribute::StringItem::Ptr filetypeItem =
    this->specification()->findString("filetype");

  std::string filename = filenameItem ?
    filenameItem->value() : "";
//  std::string filetype = filetypeItem ?
//    filetypeItem->value() : "";
*/
  std::string filename = this->specification()->findFile("filename")->value();

  /*
  cJSON* json = cJSON_CreateObject();
  smtk::io::SaveJSON::forOperator(this->specification(), json);
  std::cout << "Import Operator: " << cJSON_Print(json) << "\n";
  cJSON_Delete(json);

*/
  if (filename.empty())
  {
    std::cerr << "File name is empty!\n";
    return this->createResult(OPERATION_FAILED);
  }

  // Create a new model to hold the result.
  vtkNew<vtkDiscreteModelWrapper> mod;

  // ******************************************************************************
  // This is where we should have the logic to import files other than .cmb formats
  // ******************************************************************************
  std::string ext = vtksys::SystemTools::GetFilenameLastExtension(filename);
  if (ext == ".h5m" || ext == ".sat" || ext == ".brep" || ext == ".stp" || ext == ".cub" ||
    ext == ".exo")
  {
#ifdef SMTK_ENABLE_MOAB_DISCRETE_READER

    vtkNew<vtkCmbMoabReader> reader;
    reader->SetFileName(filename.c_str());
    reader->Update();

    this->m_op->Operate(mod.GetPointer(), reader.GetPointer());
#endif
  }
  else if (ext == ".2dm" || ext == ".3dm" ||
#ifdef SMTK_ENABLE_REMUS_SUPPORT
    ext == ".poly" || ext == ".smesh" ||
#endif
    /*  ext == ".tin" ||
      ext == ".fac" ||
      ext == ".obj" ||
      ext == ".sol" || */
    ext == ".stl")
  {
    vtkNew<vtkCMBGeometryReader> reader;
    reader->SetFileName(filename.c_str());
    reader->SetPrepNonClosedSurfaceForModelCreation(true);
    reader->Update();

    bool hasBoundaryEdges = reader->GetHasBoundaryEdges();

    if (ext == ".poly" || ext == ".smesh" || hasBoundaryEdges)
    {
      this->m_op->Operate(mod.GetPointer(), reader.GetPointer());
    }
    else
    {
      vtkNew<vtkMasterPolyDataNormals> normals;
      normals->SetInputData(0, reader->GetOutputDataObject(0));
      normals->Update();

      vtkNew<vtkMergeDuplicateCells> merge;
      merge->SetModelRegionArrayName(ModelParserHelper::GetShellTagName());
      merge->SetModelFaceArrayName(ModelParserHelper::GetModelFaceTagName());
      merge->SetInputData(0, normals->GetOutputDataObject(0));
      merge->Update();

      this->m_op->Operate(mod.GetPointer(), merge.GetPointer());
    }
  }
#ifdef SMTK_ENABLE_REMUS_SUPPORT
  else if (ext == ".map")
  {
    vtkNew<vtkCMBMapReader> reader;
    reader->SetFileName(filename.c_str());
    reader->Update();

    vtkNew<vtkCMBTriangleMesher> trimesher;
    trimesher->SetPreserveEdgesAndNodes(true);
    trimesher->SetInputData(0, reader->GetOutputDataObject(0));
    trimesher->Update();

    this->m_mapOp->Operate(mod.GetPointer(), trimesher.GetPointer());
  }
  else if (ext == ".shp")
  {
    smtk::attribute::StringItem::Ptr boundaryItem =
      this->specification()->findString("ShapeBoundaryStyle");
    if (boundaryItem->isEnabled())
    {
      vtkNew<vtkCMBGeometry2DReader> reader;
      reader->SetFileName(filename.c_str());
      std::string boundaryStyle = boundaryItem->value();
      if (boundaryStyle == "None") // default
      {
        reader->SetBoundaryStyle(vtkCMBGeometry2DReader::NONE);
      }
      else if (boundaryStyle == "Relative Margin")
      {
        reader->SetBoundaryStyle(vtkCMBGeometry2DReader::RELATIVE_MARGIN);
        smtk::attribute::StringItem::Ptr relMarginItem =
          this->specification()->findString("relative margin");
        reader->SetRelativeMarginString(relMarginItem->value().c_str());
      }
      else if (boundaryStyle == "Absolute Margin")
      {
        reader->SetBoundaryStyle(vtkCMBGeometry2DReader::ABSOLUTE_MARGIN);
        smtk::attribute::StringItem::Ptr absMarginItem =
          this->specification()->findString("absolute margin");
        reader->SetAbsoluteMarginString(absMarginItem->value().c_str());
      }
      else if (boundaryStyle == "Bounding Box")
      {
        reader->SetBoundaryStyle(vtkCMBGeometry2DReader::ABSOLUTE_BOUNDS);
        smtk::attribute::StringItem::Ptr absBoundsItem =
          this->specification()->findString("absolute bounds");
        reader->SetAbsoluteBoundsString(absBoundsItem->value().c_str());
      }
      else if (boundaryStyle == "Bounding File")
      {
        reader->SetBoundaryStyle(vtkCMBGeometry2DReader::IMPORTED_POLYGON);
        smtk::attribute::StringItem::Ptr boundsFileItem =
          this->specification()->findString("imported polygon");
        reader->SetBoundaryFile(boundsFileItem->value().c_str());
      }
      else
      {
        std::cerr << "Invalid Shape file boundary. No boundary will be set.\n";
        reader->SetBoundaryStyle(vtkCMBGeometry2DReader::NONE);
      }
      reader->Update();
      this->m_shpOp->Operate(mod.GetPointer(), reader.GetPointer(),
        /*cleanVerts:*/ 0);
    }
    else
      std::cerr << "Shape file boundary has to be set.\n";
  }
#endif
  else if (ext == ".vtk")
  {
    vtkNew<vtkPDataSetReader> reader;
    reader->SetFileName(filename.c_str());
    reader->Update();

    vtkNew<vtkDataSetRegionSurfaceFilter> surface;
    surface->SetRegionArrayName(ModelParserHelper::GetShellTagName());
    surface->SetInputData(0, reader->GetOutputDataObject(0));
    surface->Update();

    vtkNew<vtkMasterPolyDataNormals> normals;
    normals->SetInputData(0, surface->GetOutputDataObject(0));
    normals->Update();

    vtkNew<vtkMergeDuplicateCells> merge;
    merge->SetModelRegionArrayName(ModelParserHelper::GetShellTagName());
    merge->SetModelFaceArrayName(ModelParserHelper::GetModelFaceTagName());
    merge->SetInputData(0, normals->GetOutputDataObject(0));
    merge->Update();

    this->m_op->Operate(mod.GetPointer(), merge.GetPointer());
  }

  // Now assign a UUID to the model and associate its filename with
  // a URL property (if things went OK).
  if (!this->m_op->GetOperateSucceeded()
#ifdef SMTK_ENABLE_REMUS_SUPPORT
    && !this->m_mapOp->GetOperateSucceeded() && !this->m_shpOp->GetOperateSucceeded()
#endif
        )
  {
    std::cerr << "Failed to import file \"" << filename << "\".\n";
    return this->createResult(OPERATION_FAILED);
  }

  // Now that the model is imported, we need to set the associated file name as
  // the .cmb file, rather than the original file from which the import
  // occurred. Otherwise, the imported file extension will be incorrectly used
  // when subsequently serializing to disk.
  filename = (vtksys::SystemTools::GetFilenameWithoutLastExtension(filename) + ".cmb");
  smtk::common::UUID modelId =
    this->discreteSession()->trackModel(mod.GetPointer(), filename, this->manager());
  smtk::model::EntityRef modelEntity(this->manager(), modelId);

  OperatorResult result = this->createResult(OPERATION_SUCCEEDED);
  this->addEntityToResult(result, modelEntity, CREATED);
  // for 2dm files model and mesh have same geometry,
  // so create meshes for faces and edges
  if (ext == ".2dm" || ext == ".3dm")
  {
    smtk::io::ModelToMesh convert;
    smtk::mesh::CollectionPtr c = convert(modelEntity.as<smtk::model::Model>());
    if (c->isValid() && c->numberOfMeshes() > 0)
    {
      if (c->name().empty())
      {
        c->name(vtksys::SystemTools::GetFilenameWithoutExtension(filename));
      }
      result->findModelEntity("mesh_created")->setValue(modelEntity);
    }
  }

  modelEntity.as<smtk::model::Model>().setSession(
    smtk::model::SessionRef(modelEntity.manager(), this->session()->sessionId()));

  return result;
}

} // namespace discrete
} // namespace bridge
} // namespace smtk

smtkImplementsModelOperator(SMTKDISCRETESESSION_EXPORT, smtk::bridge::discrete::ImportOperator,
  discrete_import, "import", ImportOperator_xml, smtk::bridge::discrete::Session);
