MStatus AlembicWriteJob::PreProcess()
{
  ESS_PROFILE_SCOPE("AlembicWriteJob::PreProcess");
  // check filenames
  if (mFileName.length() == 0) {
    MGlobal::displayError("[ExocortexAlembic] No filename specified.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no filename specified");
    return MStatus::kInvalidParameter;
  }

  // check objects
  if (mSelection.length() == 0) {
    MGlobal::displayError("[ExocortexAlembic] No objects specified.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no objects specified");
    return MStatus::kInvalidParameter;
  }

  // check frames
  if (mFrames.size() == 0) {
    MGlobal::displayError("[ExocortexAlembic] No frames specified.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no frame specified");
    return MStatus::kInvalidParameter;
  }

  // check if the file is currently in use
  if (getRefArchive(mFileName) > 0) {
    MGlobal::displayError("[ExocortexAlembic] Error writing to file '" +
                          mFileName + "'. File currently in use.");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: no filename already in "
        "use");
    return MStatus::kInvalidParameter;
  }

  // init archive (use a locally scoped archive)
  // TODO: determine how to access the current maya scene path
  // MString sceneFileName = "Exported from:
  // "+Application().GetActiveProject().GetActiveScene().GetParameterValue("FileName").GetAsText();
  try {
    createArchive("Exported from Maya.");

    mTop = mArchive.getTop();

    // get the frame rate
    mFrameRate = MTime(1.0, MTime::kSeconds).as(MTime::uiUnit());
    const double timePerSample = 1.0 / mFrameRate;
    std::vector<AbcA::chrono_t> frames;
    for (LONG i = 0; i < mFrames.size(); i++) {
      frames.push_back(mFrames[i] * timePerSample);
    }

    // create the sampling
    if (frames.size() > 1) {
      const double timePerCycle = frames[frames.size() - 1] - frames[0];
      AbcA::TimeSamplingType samplingType((Abc::uint32_t)frames.size(),
                                          timePerCycle);
      AbcA::TimeSampling sampling(samplingType, frames);
      mTs = mArchive.addTimeSampling(sampling);
    }
    else {
      AbcA::TimeSampling sampling(1.0, frames[0]);
      mTs = mArchive.addTimeSampling(sampling);
    }
    Abc::OBox3dProperty boxProp = AbcG::CreateOArchiveBounds(mArchive, mTs);

    MDagPath dagPath;
    {
      MItDag().getPath(dagPath);
    }
    SceneNodePtr exoSceneRoot = buildMayaSceneGraph(dagPath, this->replacer);
    const bool bFlattenHierarchy = GetOption("flattenHierarchy") == "1";
    const bool bTransformCache = GetOption("transformCache") == "1";
    const bool bSelectChildren = false;
    {
      std::map<std::string, bool> selectionMap;
      for (int i = 0; i < (int)mSelection.length(); ++i) {
        MFnDagNode dagNode(mSelection[i]);
        selectionMap[dagNode.fullPathName().asChar()] = true;
      }
      selectNodes(exoSceneRoot, selectionMap,
                  !bFlattenHierarchy || bTransformCache, bSelectChildren,
                  !bTransformCache, true);
    }

    // create object for each
    MProgressWindow::reserve();
    MProgressWindow::setTitle("Alembic Export: Listing objects");
    MProgressWindow::setInterruptable(true);
    MProgressWindow::setProgressRange(0, mSelection.length());
    MProgressWindow::setProgress(0);

    MProgressWindow::startProgress();
    int interrupt = 20;
    bool processStopped = false;
    std::deque<PreProcessStackElement> sceneStack;

    sceneStack.push_back(PreProcessStackElement(exoSceneRoot, mTop));

    while (!sceneStack.empty()) {
      if (--interrupt == 0) {
        interrupt = 20;
        if (MProgressWindow::isCancelled()) {
          processStopped = true;
          break;
        }
      }

      PreProcessStackElement &sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();

      Abc::OObject oParent = sElement.oParent;
      Abc::OObject oNewParent;

      AlembicObjectPtr pNewObject;
      if (eNode->selected) {
        switch (eNode->type) {
          case SceneNode::SCENE_ROOT:
            break;
          case SceneNode::ITRANSFORM:
          case SceneNode::ETRANSFORM:
            pNewObject.reset(new AlembicXform(eNode, this, oParent));
            break;
          case SceneNode::CAMERA:
            pNewObject.reset(new AlembicCamera(eNode, this, oParent));
            break;
          case SceneNode::POLYMESH:
            pNewObject.reset(new AlembicPolyMesh(eNode, this, oParent));
            break;
          case SceneNode::SUBD:
            pNewObject.reset(new AlembicSubD(eNode, this, oParent));
            break;
          case SceneNode::CURVES:
            pNewObject.reset(new AlembicCurves(eNode, this, oParent));
            break;
          case SceneNode::PARTICLES:
            pNewObject.reset(new AlembicPoints(eNode, this, oParent));
            break;
          case SceneNode::HAIR:
            pNewObject.reset(new AlembicHair(eNode, this, oParent));
            break;
          default:
            ESS_LOG_WARNING("Unknown type: not exporting " << eNode->name);
        }
      }

      if (pNewObject) {
        AddObject(pNewObject);
        oNewParent = oParent.getChild(eNode->name);
      }
      else {
        oNewParent = oParent;
      }

      if (oNewParent.valid()) {
        for (std::list<SceneNodePtr>::iterator it = eNode->children.begin();
             it != eNode->children.end(); ++it) {
          if (!bFlattenHierarchy ||
              (bFlattenHierarchy && eNode->type == SceneNode::ETRANSFORM &&
               isShapeNode((*it)->type))) {
            // If flattening the hierarchy, we want to attach each external
            // transform to its corresponding geometry node.
            // All internal transforms should be skipped. Geometry nodes will
            // never have children (If and XSI geonode is parented
            // to another geonode, each will be parented to its extracted
            // transform node, and one node will be parented to the
            // transform of the other.
            sceneStack.push_back(PreProcessStackElement(*it, oNewParent));
          }
          else {
            // if we skip node A, we parent node A's children to the parent of A
            sceneStack.push_back(PreProcessStackElement(*it, oParent));
          }
        }
      }
      //*
      else {
        ESS_LOG_ERROR("Do not have reference to parent.");
        MPxCommand::setResult(
            "Error caught in AlembicWriteJob::PreProcess: do not have "
            "reference to parent");
        return MS::kFailure;
      }
      //*/
    }

    MProgressWindow::endProgress();
    return processStopped ? MStatus::kEndOfFile : MStatus::kSuccess;
  }
  catch (AbcU::Exception &e) {
    this->forceCloseArchive();
    MString exc(e.what());
    MGlobal::displayError("[ExocortexAlembic] Error writing to file '" +
                          mFileName + "' (" + exc +
                          "). Do you still have it opened?");
    MPxCommand::setResult(
        "Error caught in AlembicWriteJob::PreProcess: error writing file");
  }

  return MS::kFailure;
}
bool AlembicWriteJob::PreProcess()
{
    // check filenames
    if(mFileName.empty())
    {
        ESS_LOG_WARNING("[alembic] No filename specified.");
        return false;
    }

    //// check objects
    //if(mSelection.Count() == 0)
    //{
    //    ESS_LOG_WARNING("[alembic] No objects specified.");
    //    return false;
    //}

    // check frames
    if(mFrames.size() == 0)
    {
        ESS_LOG_WARNING("[alembic] No frames specified.");
        return false;
    }


	const bool bParticleMesh = GetOption("exportParticlesAsMesh");
   bool bMergePolyMeshSubtree = GetOption("mergePolyMeshSubtree");

   bool bSelectParents = GetOption("includeParentNodes");/*|| !bFlattenHierarchy || bTransformCache*/
   const bool bSelectChildren = false;
   bool bTransformCache = GetOption("transformCache");
   const bool bFlattenHierarchy = GetOption("flattenHierarchy");

   if(bMergePolyMeshSubtree){
      bTransformCache = false;
      //bSelectParents = true;
   }

   bcsgSelection::types buildSelection = bcsgSelection::ALL;

   const bool bExportSelected = GetOption("exportSelected");
   const bool bObjectsParameterExists = GetOption("objectsParameterExists");
   if(bExportSelected){
      //copy max selection
      buildSelection = bcsgSelection::APP;
   }
   else if(bObjectsParameterExists){
      //select nothing when building, fill in later from parameter data
      buildSelection = bcsgSelection::NONE;
   }
   else{
      //select everything
   }

   int nNumNodes = 0;
   exoSceneRoot = buildCommonSceneGraph(nNumNodes, true, buildSelection);
   //WARNING ILM robot right crashes when printing
   //printSceneGraph(exoSceneRoot, false);


   if(bObjectsParameterExists){
      //Might be better to use refineSelection here, but call a function that sets up dccSelected flag first, then delete this function from codebase
      selectNodes(exoSceneRoot, mObjectsMap,  bSelectParents, bSelectChildren, !bTransformCache);

      bool bAllResolved = true;

      if(bObjectsParameterExists){
         for(SceneNode::SelectionT::iterator it = mObjectsMap.begin(); it != mObjectsMap.end(); it++){
            if(it->second == false){
               bAllResolved = false;
               ESS_LOG_ERROR("Could not resolve objects identifier: "<<it->first);
            }
         }
      }

      if(bAllResolved){
         removeUnselectedNodes(exoSceneRoot);
      }
      else{
         return false;
      }
   }
   else if(bExportSelected){
      refineSelection(exoSceneRoot, bSelectParents, bSelectChildren, !bTransformCache);
      removeUnselectedNodes(exoSceneRoot);
   }
   

   if(bMergePolyMeshSubtree){
      replacePolyMeshSubtree<SceneNodeMaxPtr, SceneNodeMax>(exoSceneRoot);
   }

   if(bFlattenHierarchy){
      nNumNodes = 0;
      flattenSceneGraph(exoSceneRoot, nNumNodes);
   }

   
  
   if(GetOption("renameConflictingNodes")){
      renameConflictingNodes(exoSceneRoot, false);
   }
   else{
      int nRenameCount = renameConflictingNodes(exoSceneRoot, true);
      if(nRenameCount){
         ESS_LOG_ERROR("Can not export due sibling node naming conflict. Consider exporting with renameConflictingNodes=true");
         return false;
      }
   }

    const bool bUseOgawa = (bool)GetOption("useOgawa");

    // init archive (use a locally scoped archive)
    std::string sceneFileName = "";
    sceneFileName.append( EC_MSTR_to_UTF8( mApplication->GetCurFilePath() ) );
    try
    {
       if(bUseOgawa){
        mArchive = CreateArchiveWithInfo(
            Alembic::AbcCoreOgawa::WriteArchive(),
			mFileName.c_str(),
			getExporterName( "3DS Max " EC_QUOTE( crate_Max_Version ) ).c_str(),
			getExporterFileName( sceneFileName ).c_str(),
			Abc::ErrorHandler::kThrowPolicy);
       }
       else{
        mArchive = CreateArchiveWithInfo(
			Alembic::AbcCoreHDF5::WriteArchive( true ),
			mFileName.c_str(),
			getExporterName( "3DS Max " EC_QUOTE( crate_Max_Version ) ).c_str(),
			getExporterFileName( sceneFileName ).c_str(),
			Abc::ErrorHandler::kThrowPolicy);
       }

    }
    catch(Alembic::Util::Exception& e)
    {
        std::string exc(e.what());
		    ESS_LOG_ERROR("[alembic] Error writing to file: "<<e.what());
        return false;
    }

    // get the frame rate
    mFrameRate = static_cast<float>(GetFrameRate());
    if(mFrameRate == 0.0f)
    {
        mFrameRate = 25.0f;
    }

    std::vector<AbcA::chrono_t> frames;
    for(LONG i=0;i<mFrames.size();i++)
    {
        frames.push_back(mFrames[i] / mFrameRate);
    }

    // create the sampling
    double timePerSample = 1.0 / mFrameRate;
    if(frames.size() > 1)
    {
		 if( ! HasAlembicWriterLicense() )
		 {
       if( HasAlembicInvalidLicense() ) {
          ESS_LOG_ERROR("[alembic] No license available and EXOCORTEX_ALEMBIC_NO_DEMO defined, aborting." );
          return false;
       }
			 if(frames.size() > 75)
			 {
				frames.resize(75);
				ESS_LOG_WARNING("[ExocortexAlembic] Writer license not found: Maximum exportable samplecount is 75!");
			 }
		 }
		 
	    double timePerCycle = frames[frames.size()-1] - frames[0];
        AbcA::TimeSamplingType samplingType((boost::uint32_t)frames.size(),timePerCycle);
        AbcA::TimeSampling sampling(samplingType,frames);
        mTs = mArchive.addTimeSampling(sampling);
    }
    else
    {
        AbcA::TimeSampling sampling(1.0,frames[0]);
        mTs = mArchive.addTimeSampling(sampling);
    }

    m_ArchiveBoxProp = AbcG::CreateOArchiveBounds(mArchive,mTs);



   std::list<PreProcessStackElement> sceneStack;
   
   sceneStack.push_back(PreProcessStackElement(exoSceneRoot, mArchive.getTop()));

   try{

   while( !sceneStack.empty() )
   {

      PreProcessStackElement sElement = sceneStack.back();
      SceneNodePtr eNode = sElement.eNode;
      sceneStack.pop_back();
      
      Abc::OObject oParent = sElement.oParent;
      Abc::OObject oNewParent;

      AlembicObjectPtr pNewObject;

      if(eNode->type == SceneNode::SCENE_ROOT){
         //we do not want to export the Scene_Root (the alembic archive has one already)
      }
      else if(eNode->type == SceneNode::ITRANSFORM || eNode->type == SceneNode::ETRANSFORM){
         pNewObject.reset(new AlembicXForm(eNode, this, oParent));
      }
      else if(eNode->type == SceneNode::CAMERA){
         pNewObject.reset(new AlembicCamera(eNode, this, oParent));
      }
      else if(eNode->type == SceneNode::POLYMESH || eNode->type == SceneNode::POLYMESH_SUBTREE){
         pNewObject.reset(new AlembicPolyMesh(eNode, this, oParent));
      }
      //TODO: as far I recall we dont support SUBD. verify...
      //else if(eNode->type == SceneNode::SUBD){
      //   pNewObject.reset(new AlembicSubD(eNode, this, oParent));
      //}
      else if(eNode->type == SceneNode::CURVES){
         pNewObject.reset(new AlembicCurves(eNode, this, oParent));
      }
      else if(eNode->type == SceneNode::PARTICLES || eNode->type == SceneNode::PARTICLES_TP){
         if(bParticleMesh){
            pNewObject.reset(new AlembicPolyMesh(eNode, this, oParent));
         }
         else{
            pNewObject.reset(new AlembicPoints(eNode, this, oParent));
         }
      }
      //else{
      //   ESS_LOG_WARNING("Unknown type: not exporting "<<eNode->name);//Export as transform, and give warning?
      //}

      if(pNewObject){
         //add the AlembicObject to export list if it is not being skipped
         AddObject(pNewObject);
      }

      if(pNewObject){
         oNewParent = oParent.getChild(eNode->name);
      }
      else{ //this case should be unecessary
         //if we skip node A, we parent node A's children to the parent of A
         oNewParent = oParent;
      }

      if(oNewParent.valid()){
         for( std::list<SceneNodePtr>::iterator it = eNode->children.begin(); it != eNode->children.end(); it++){
            sceneStack.push_back(PreProcessStackElement(*it, oNewParent));
         }
      }
      else{
         ESS_LOG_ERROR("Do not have refernce to parent.");
         return false;
      }
   }

   }catch( std::exception& exp ){
      ESS_LOG_ERROR("An std::exception occured: "<<exp.what());
      return false;
   }catch(...){
      ESS_LOG_ERROR("Exception ecountered when exporting.");
   }

   if(mObjects.empty()){
      ESS_LOG_ERROR("No objects specified.");
      return false;
   }

   return true;
}