//=========================================================================
//  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 "smtk/mesh/Manager.h"
#include "smtk/mesh/Collection.h"
#include "smtk/io/ModelToMesh.h"

#include "smtk/model/Manager.h"
#include "smtk/model/Volume.h"
#include "smtk/model/EntityIterator.h"

#include "smtk/model/testing/cxx/helpers.h"
#include "smtk/mesh/testing/cxx/helpers.h"

#include <algorithm>

namespace {

const std::size_t num_volumes_in_model = 1;
const std::size_t num_models = 1;

//----------------------------------------------------------------------------
void create_simple_model( smtk::model::ManagerPtr modelManager )
{
  using namespace smtk::model::testing;

  smtk::model::SessionRef sess = modelManager->createSession("native");
  smtk::model::Model model = modelManager->addModel();
  smtk::common::UUIDArray uids = createTet(modelManager);
  model.addCell( smtk::model::Volume(modelManager, uids[21]));

  model.setSession(sess);
  modelManager->assignDefaultNames();

}

//----------------------------------------------------------------------------
void verify_constructors()
{
  smtk::mesh::ManagerPtr mgrPtr;
  test( !mgrPtr, "empty shared_ptr to Manager test failed");

  smtk::mesh::ManagerPtr mgrPtr2 = smtk::mesh::Manager::create();
  test( !!mgrPtr2, "failed create method on Manager");

  mgrPtr = mgrPtr2;
  test( !!mgrPtr, "failed copy assignment of manager shared_ptr");
  test( !!mgrPtr2, "failed copy assignment of manager shared_ptr");

  smtk::mesh::ManagerPtr mgrPtr3(mgrPtr);
  test( !!mgrPtr3, "failed copy assignment of manager shared_ptr");
}

//----------------------------------------------------------------------------
void verify_collection_queries()
{
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();

  smtk::mesh::CollectionPtr collection = mgr->makeCollection();
  test( mgr->numberOfCollections() == 1, "Incorrect results from numberOfCollections");

  const bool has = mgr->hasCollection(collection);
  test( has, "Incorrect results from hasCollection");

  smtk::mesh::CollectionPtr collection_bad = smtk::mesh::Collection::create();
  bool doesnt_have = !mgr->hasCollection(collection_bad);
  test( doesnt_have, "Incorrect results from hasCollection when giving a bad collection");

  //test search capabilities
  smtk::mesh::CollectionPtr findBad = mgr->collection( smtk::common::UUID() );
  smtk::mesh::CollectionPtr findBad2 = mgr->collection( smtk::common::UUID() );
  smtk::mesh::CollectionPtr findRes = mgr->collection( collection->entity() );
  smtk::mesh::CollectionPtr findRes2 = mgr->findCollection( collection->entity() )->second;

  test( !findBad, "Failed to return an invalid Collection from a bad find call");
  test( !findBad2, "Failed to return an invalid Collection from a bad find call");
  test( findRes && findRes->isValid(), "Failed to return an valid Collection from a find call");
  test( findRes2 && findRes2->isValid(), "Failed to return an valid Collection from a find call");
}

//----------------------------------------------------------------------------
void verify_collection_iterators()
{
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();

  //first test iterators given no items in the manager
  test( mgr->numberOfCollections() == 0, "Incorrect results from numberOfCollections");

  smtk::mesh::Manager::const_iterator begin = mgr->collectionBegin();
  smtk::mesh::Manager::const_iterator end = mgr->collectionEnd();
  std::size_t distance = std::distance(begin,end);
  test( distance == 0, "Incorrect iterators given from an empty manager");

  smtk::mesh::CollectionPtr collection= mgr->makeCollection();
  test( mgr->numberOfCollections() == 1, "Incorrect results from numberOfCollections");

  //now that we have an item verify the iterators work
  begin = mgr->collectionBegin();
  end = mgr->collectionEnd();
  distance = std::distance(begin,end);
  test( distance == 1, "Incorrect iterators given from a manager with items");
}

//----------------------------------------------------------------------------
void verify_add_remove_collection()
{
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();

  const int size=1024;
  for( int i=0 ; i < size; ++i)
    {
    mgr->makeCollection(); //make a collection and add it to manager
    }
  test( mgr->numberOfCollections() == size, "Incorrect results from numberOfCollections");


  std::vector< smtk::common::UUID > uids_to_remove;
  for (int i=0; i < 10; ++i)
    { //remove the first item in the collection each time through
    smtk::mesh::Manager::const_iterator begin = mgr->collectionBegin();
    uids_to_remove.push_back( begin->first );
    const bool result = mgr->removeCollection( mgr->collection( begin->first ) );
    test( result == true, "failed to remove a collection from a manager");
    }

  test( mgr->numberOfCollections() == size-10, "Incorrect results from numberOfCollections");

  smtk::mesh::Manager::const_iterator end = mgr->collectionEnd();
  for(std::vector< smtk::common::UUID >::const_iterator i=uids_to_remove.begin();
      i != uids_to_remove.end();
      ++i)
    {
    smtk::mesh::CollectionPtr badCollection = mgr->collection( *i );
    smtk::mesh::Manager::const_iterator badIterator = mgr->findCollection( *i );
    test( !badCollection, "remove failed to actually remove a collection item");
    test( badIterator == end, "remove failed to actually remove a collection item");
    }

}

//----------------------------------------------------------------------------
void verify_name_generation()
{
  //verify that we can generate 1024 collections with unique names
  {
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  const int size=1024;
  for( int i=0 ; i < size; ++i)
    {
    //make a collection and add it to manager
    smtk::mesh::CollectionPtr c = mgr->makeCollection();
    test(mgr->assignUniqueName( c ), "failed to generate a unique name");
    }

  //We need to verify that every collection has a unique name
  std::set< std::string > names;
  for(smtk::mesh::Manager::const_iterator i=mgr->collectionBegin();
      i != mgr->collectionEnd();
      ++i)
    {
    names.insert( i->second->name() );
    }
  test( names.size() == size, "Found multiple collections with the same name");
  }

  //verify that assignUniqueName doesn't override an existing name
  {
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::mesh::CollectionPtr c = mgr->makeCollection();
  c->name( std::string("a") );
  mgr->assignUniqueName(c);
  test( c->name() == std::string("a"), "assignUniqueName overwrote existing unique name" );
  }

  //verify that assignUniqueName overrides an existing name that isn't unique
  {
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::mesh::CollectionPtr c1 = mgr->makeCollection();
  smtk::mesh::CollectionPtr c2 = mgr->makeCollection();
  c1->name( std::string("a") );
  c2->name( std::string("a") );
  mgr->assignUniqueName(c1); //will change, as name is not unique
  mgr->assignUniqueName(c2); //will not change as name is now unique

  test( c1->name() != std::string("a"),
        "assignUniqueName didn't overwrite existing non-unique name" );
  test( c2->name() == std::string("a"),
       "assignUniqueName overwrote existing unique name" );
  }


  //verify that assignUniqueName handles not generating a clashing unique
  //name, when the existing name matches the pattern of the auto generator
  {
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::mesh::CollectionPtr c1 = mgr->makeCollection();
  smtk::mesh::CollectionPtr c2 = mgr->makeCollection();
  c1->name( std::string("Mesh_1") );
  mgr->assignUniqueName(c1);
  mgr->assignUniqueName(c2); //shoud be Mesh_2 not Mesh_1

  test( c1->name() == std::string("Mesh_1"),
        "assignUniqueName overwrote existing unique name" );
  test( c2->name() == std::string("Mesh_2"),
       "assignUniqueName generated a non-unique name" );
  }

  //verify that the only the owning manager can assign unique names
  {
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::mesh::ManagerPtr mgr2 = smtk::mesh::Manager::create();
  smtk::mesh::CollectionPtr c = mgr->makeCollection();
  test( mgr2->assignUniqueName( c ) == false,
       "only the owning manager can generate unique names for a collection");

  c->reparent(mgr2);
  test( mgr2->assignUniqueName( c ) == true);
  }


}

//----------------------------------------------------------------------------
void verify_has_association()
{
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::model::EntityRef invalidModelRef;

  //verify that the null uuid isn't part of a collection
  test( mgr->isAssociatedToACollection( invalidModelRef ) == false,
        "invalid model ref associated to collection" );

  //add some empty collection to the manager
  const int size=256;
  for( int i=0 ; i < size; ++i) { mgr->makeCollection(); }

  //verify that at this point no collections have associations
  test(mgr->collectionsWithAssociations().size() == 0,
       "new empty collections reporting they have associations");

  //next we are going to create a simple smtk model
  smtk::model::ManagerPtr modelManager = smtk::model::Manager::create();
  create_simple_model(modelManager);

  //now that we have a model, convert it to a mesh
  smtk::io::ModelToMesh convert;
  smtk::mesh::CollectionPtr collectionWithAssoc = convert(mgr,modelManager);
  test( mgr->numberOfCollections() == (size+1) );

  //now verify that the manager can see the association
  test(mgr->collectionsWithAssociations().size() == 1,
       "only one collection should have an association");

  //verify that the null uuid isn't part of a collection even after we
  //have an association
  test( mgr->isAssociatedToACollection( invalidModelRef ) == false,
        "invalid model ref associated to collection");

  //walk the model and verify that we can query the Manager to get the
  //collection that is associated to it.
  smtk::model::EntityIterator it;
  smtk::model::EntityRefs models =
    modelManager->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(smtk::model::MODEL_ENTITY);
  it.traverse(models.begin(), models.end(), smtk::model::ITERATE_MODELS);

  std::size_t count = 0;
  for (it.begin(); !it.isAtEnd(); ++it)
    {
    const bool isAssociated = mgr->isAssociatedToACollection(*it);
    count += isAssociated ? 1 : 0;

    if( isAssociated )
      {
      std::vector< smtk::mesh::CollectionPtr > assocCollections =
                                            mgr->associatedCollections(*it);
      test( assocCollections.size() == 1);
      test( assocCollections[0] == collectionWithAssoc);
      }
    }

  test( (num_models + num_volumes_in_model) == count, "incorrect number of associations");

  //walk all the collections and get the model uuids that is aware of
  smtk::mesh::MeshSet meshes = collectionWithAssoc->meshes();
  smtk::common::UUIDArray knownAssociations = meshes.modelEntityIds();
  test( knownAssociations.size() == num_volumes_in_model);

  typedef smtk::common::UUIDArray::const_iterator cit;
  for(cit i=knownAssociations.begin(); i != knownAssociations.end(); ++i)
    {
    smtk::model::EntityRef eref(modelManager, *i);
    test( mgr->isAssociatedToACollection( eref ) == true);
    }
}

//----------------------------------------------------------------------------
void verify_has_multiple_association()
{

  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::model::ManagerPtr modelManager = smtk::model::Manager::create();

  //add some collections to the manager that are associated to the same model
  create_simple_model(modelManager); //single model
  smtk::io::ModelToMesh convert;
  const int size=256;
  for( int i=0 ; i < size; ++i)
    { //convert the model into 256 different mesh collections
    convert(mgr,modelManager);
    }

  //verify that at this point no collections have associations
  typedef std::vector< smtk::mesh::CollectionPtr > AssocCollections;
  AssocCollections assocCollections = mgr->collectionsWithAssociations();
  test(assocCollections.size() == size);

  //next verify that we get a multiple collections back when we request
  //an association
  smtk::model::EntityIterator it;
  smtk::model::EntityRefs models =
    modelManager->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(smtk::model::MODEL_ENTITY);
  it.traverse(models.begin(), models.end(), smtk::model::ITERATE_MODELS);

  //nothing should be associated
  for (it.begin(); !it.isAtEnd(); ++it)
    {
    if( mgr->isAssociatedToACollection(*it)  )
      {
      test(  (mgr->associatedCollections(*it).size() == size) );
      }
    }
}

//----------------------------------------------------------------------------
void verify_add_remove_association()
{
  //first add a collection that is associated.
  //collected the uuids that are associated
  //remove the collection
  //verify none of the uuids are associated
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::model::ManagerPtr modelManager = smtk::model::Manager::create();

  //add some collections to the manager that are associated to the same model
  create_simple_model(modelManager); //single model
  smtk::io::ModelToMesh convert;
  smtk::mesh::CollectionPtr collectionWithAssoc = convert(mgr,modelManager);

  typedef std::vector< smtk::mesh::CollectionPtr > AssocCollections;
  AssocCollections assocCollections = mgr->collectionsWithAssociations();
  test(assocCollections.size() == 1);

  smtk::mesh::MeshSet meshes = collectionWithAssoc->meshes();
  smtk::common::UUIDArray knownAssociations = meshes.modelEntityIds();

  typedef smtk::common::UUIDArray::const_iterator cit;
  for(cit i=knownAssociations.begin(); i != knownAssociations.end(); ++i)
    {
    smtk::model::EntityRef eref(modelManager, *i);
    test( mgr->isAssociatedToACollection( eref ) == true);
    }


  //now remove the collection.
  mgr->removeCollection( collectionWithAssoc );
  for(cit i=knownAssociations.begin(); i != knownAssociations.end(); ++i)
    {
    smtk::model::EntityRef eref(modelManager, *i);
    test( mgr->isAssociatedToACollection( eref ) == false);
    }

  assocCollections = mgr->collectionsWithAssociations();
  test(assocCollections.size() == 0);
}

//----------------------------------------------------------------------------
void verify_no_association()
{
  smtk::mesh::ManagerPtr mgr = smtk::mesh::Manager::create();
  smtk::model::EntityRef invalidModelRef;

  //verify that the null uuid isn't part of any collection
  test( mgr->isAssociatedToACollection( invalidModelRef ) == false );

  //next we are going to create a simple smtk model
  smtk::model::ManagerPtr modelManager = smtk::model::Manager::create();
  create_simple_model(modelManager);

  //now verify that none of the valid model uuids are part of the association
  smtk::model::EntityIterator it;
  smtk::model::EntityRefs models =
    modelManager->entitiesMatchingFlagsAs<smtk::model::EntityRefs>(smtk::model::MODEL_ENTITY);
  it.traverse(models.begin(), models.end(), smtk::model::ITERATE_MODELS);

  //nothing should be associated
  for (it.begin(); !it.isAtEnd(); ++it)
    {
    test( mgr->isAssociatedToACollection(*it) == false );
    }
}

} // anonymous namespace

//----------------------------------------------------------------------------
int UnitTestManager(int, char** const)
{
  verify_constructors();

  verify_collection_queries();
  verify_collection_iterators();

  verify_add_remove_collection();

  verify_name_generation();

  verify_has_association();
  verify_has_multiple_association();
  verify_add_remove_association();
  verify_no_association();

  return 0;
}
