//=============================================================================
//
//  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/bridge/mesh/Topology.h"

#include "smtk/mesh/CellSet.h"
#include "smtk/mesh/Collection.h"
#include "smtk/mesh/ForEachTypes.h"
#include "smtk/mesh/MeshSet.h"

#include <numeric>

namespace smtk {
namespace bridge {
namespace mesh {

namespace
{
typedef std::vector<std::pair<smtk::mesh::MeshSet,
                      Topology::Element*> > ElementShells;

class AddFreeElements : public smtk::mesh::MeshForEach
{
public:
  AddFreeElements(Topology* topology,
                  Topology::Element* root) : m_topology(topology),
                                             m_root(root),
                                             m_shells(nullptr),
                                             m_dimension(-1) {}

  void setElementShells(ElementShells* shells) { this->m_shells = shells; }
  void setDimension(int dimension) { this->m_dimension = dimension; }

  void forMesh(smtk::mesh::MeshSet& singleMesh)
  {
    // Each free mesh is an element. It gets a unique id and has the model as
    // its parent.
    smtk::common::UUID id = (this->m_topology->m_collection->modelManager()->
                             unusedUUID());
    // Assign the unique id to the mesh
    singleMesh.setModelEntityId(id);
    // add the unique id as a child of the model
    this->m_root->m_children.push_back(id);
    // construct an element for the mesh, insert it into the topology's map
    // with its id as the key, and store its shell as a pair along with a
    // pointer to its associated Element (for use in extracting bound elements).
    Topology::Element* element = &this->m_topology->m_elements.insert(
      std::make_pair(id, Topology::Element(this->m_dimension))).first->second;
    if (this->m_shells)
      {
      smtk::mesh::MeshSet shell = singleMesh.extractShell();
      if (!shell.is_empty())
        {
        for(std::size_t i = 0; i < shell.size(); i++)
          {
          if (!shell.subset(i).is_empty())
            {
            this->m_shells->push_back(std::make_pair(shell.subset(i), element));
            }
          }
        }
      }
  }

protected:
  Topology* m_topology;
  Topology::Element* m_root;
  ElementShells* m_shells;
  int m_dimension;
};

struct AddBoundElements
{
  AddBoundElements(Topology* topology) : m_topology(topology) {}

  void setElementShells(ElementShells* shells) { this->m_shells = shells; }
  void setDimension(int dimension) { this->m_dimension = dimension; }

  void operator()(ElementShells::iterator start, ElementShells::iterator end)
  {
    std::vector<ElementShells::iterator> activeShells;

    ElementShells::iterator seed = start;

    while (seed != end)
    {
      activeShells.push_back(seed++);

      while (!activeShells.empty())
        {
        smtk::mesh::MeshSet m = activeShells.back()->first;
        for (ElementShells::iterator j = seed; j != end; ++j)
          {
          smtk::mesh::CellSet cs = smtk::mesh::set_intersect(m.cells(),
                                                             j->first.cells());
          if (!cs.is_empty())
            {
            m = this->m_topology->m_collection->createMesh(cs);
            activeShells.push_back(j);
            }

          if (this->m_dimension == 2 && activeShells.size() == 2)
            {
            break;
            }
          }

        if (!m.is_empty())
          {
          // We have an intersection, so we must now process it. We start by
          // creating a new id for it.
          smtk::common::UUID id = (this->m_topology->m_collection->
                                   modelManager()->unusedUUID());

          // Next, we remove the intersection from the contributing mesh sets
          // and add the new id as a child of these sets.
          for (auto&& shell : activeShells)
            {
            if (activeShells.size() > 1)
              {
              smtk::mesh::MeshSet tmp =
                this->m_topology->m_collection->createMesh(
                  smtk::mesh::set_difference(shell->first.cells(), m.cells()));
              this->m_topology->m_collection->removeMeshes(shell->first);
              shell->first = tmp;
              }
           shell->second->m_children.push_back(id);
            }

          // We then register the intersection witih the new id
          m.setModelEntityId(id);
          // finally, we insert it as an element into the topology. If
          // necessary, we store its shell for the bound element calculation of
          // lower dimension
          Topology::Element* element = &this->m_topology->m_elements.insert(
            std::make_pair(id, Topology::Element(this->m_dimension))).
            first->second;
          if (this->m_shells)
            {
            this->m_shells->push_back(std::make_pair(m.extractShell(),
                                                     element));
            }
          }
        if (activeShells.size() == 1)
          {
          activeShells.pop_back();
          }
        else
          {
          activeShells.erase(std::next(activeShells.begin()),
                             activeShells.end());
          }
        }
      }
  }

  Topology* m_topology;
  ElementShells* m_shells;
  int m_dimension;
};

}

Topology::Topology(smtk::mesh::CollectionPtr collection) :
  m_collection(collection)
{
  // Insert the collection as the top-level element representing the model
  Element* model =
    &(this->m_elements.insert(std::make_pair(collection->entity(),
                                             Element())).first->second);

  // Extract single meshes as volumes and grab their shells
  ElementShells volumeShells;
  ElementShells faceShells;
  ElementShells edgeShells;

  ElementShells* elementShells[4] = { nullptr, &edgeShells, &faceShells,
                                      &volumeShells };

  AddFreeElements addFreeElements(this, model);

  // all meshes of the highest dimension are considered to be free elements with
  // the model as their parent
    int dimension = 3;
    smtk::mesh::TypeSet types = collection->types();
    while (dimension >=0 && !types.hasDimension(
             static_cast<smtk::mesh::DimensionType>(dimension)))
      {
        --dimension;
      }

    if (dimension < 0)
      {
      // We have been passed an empty collection.
      return;
      }

    {
    addFreeElements.setElementShells(elementShells[dimension]);
    addFreeElements.setDimension(dimension);
    smtk::mesh::for_each(collection->meshes(
                           static_cast<smtk::mesh::DimensionType>(dimension)),
                         addFreeElements);
    }

  AddBoundElements addBoundElements(this);
  --dimension;
  for (; dimension >= 0; dimension--)
    {
    addFreeElements.setElementShells(elementShells[dimension]);
    addFreeElements.setDimension(dimension);

    smtk::mesh::MeshSet allMeshes = collection->meshes(
      static_cast<smtk::mesh::DimensionType>(dimension));
    smtk::mesh::MeshSet boundMeshes;

    for (auto&& shell : *elementShells[dimension+1])
      {
      boundMeshes.append(shell.first);
      }
    if (!boundMeshes.is_empty() && !boundMeshes.cells().is_empty())
      {
      smtk::mesh::MeshSet freeMeshes = m_collection->createMesh(
        smtk::mesh::set_difference(allMeshes.cells(), boundMeshes.cells()));
      smtk::mesh::for_each(freeMeshes, addFreeElements);
      }

    addBoundElements.setElementShells(elementShells[dimension]);
    addBoundElements.setDimension(dimension);
    addBoundElements(elementShells[dimension + 1]->begin(),
                     elementShells[dimension + 1]->end());
    }

  if (false)
    {
    std::size_t count[4] = {0,0,0,0};
    for (auto&& i : this->m_elements)
      {
      if (i.second.m_dimension >= 0 && i.second.m_dimension <= 3)
        count[i.second.m_dimension]++;
      }

    std::cout<<count[3]<<" volumes"<<std::endl;
    std::cout<<count[2]<<" faces"<<std::endl;
    std::cout<<count[1]<<" edges"<<std::endl;
    std::cout<<count[0]<<" vertices"<<std::endl;
    }
}

}
}
}
