/** \file   ReorderTool.cpp
 *  \author Jason Kraftcheck 
 *  \date   2011-05-23
 */

#include "moab/ReorderTool.hpp"
#include "moab/Core.hpp"
#include "moab/Range.hpp"

#include "SequenceManager.hpp"
#include "TypeSequenceManager.hpp"
#include "EntitySequence.hpp"

#include <algorithm>
#include <numeric>
#include <set>
#include <iostream>

namespace moab {

// no-op function as a convenient spot to set a breakpoint
static inline ErrorCode error(ErrorCode val)
  { return val; }

#define CHKERR \
  if (MB_SUCCESS != rval) \
    return error(rval)

#define UNRECOVERABLE(ERRCODE) do { if (MB_SUCCESS != (ERRCODE)) { \
  error((ERRCODE)); \
  std::cerr << "Unreconverable error during mesh reorder." << std::endl \
            << "Error Code " << (ERRCODE) << " at " \
            << __FILE__ << ":" << __LINE__ << std::endl; \
  std::cerr.flush(); \
  abort(); \
} } while(false)

static ErrorCode check_tag_type( Interface* moab,
                                 Tag tag,
                                 DataType exp_type,
                                 int exp_size )
{
  ErrorCode rval;
  DataType act_type;
  int act_size;

  rval = moab->tag_get_data_type( tag, act_type );
  CHKERR;
  
  rval = moab->tag_get_bytes( tag, act_size );
  CHKERR;
  
  if (act_type != exp_type || act_size != exp_size)
    return MB_TYPE_OUT_OF_RANGE;

  return MB_SUCCESS;
}

ErrorCode ReorderTool::handle_order_from_int_tag( Tag tag,
                                                  int skip_value,
                                                  Tag& new_handles )
{
  ErrorCode rval;
 
    // check that input tag handles are what we expect
  rval = check_tag_type( mMB, tag, MB_TYPE_INTEGER, sizeof(int) );
  CHKERR;
  EntityHandle zero = 0;
  rval = mMB->tag_get_handle( 0, 1, MB_TYPE_HANDLE, new_handles,
                              MB_TAG_DENSE|MB_TAG_CREAT|MB_TAG_EXCL, &zero );
  CHKERR;

    // Can only reorder within same type/connectivity (or vertex/num_coords)
    // groupings.  Call helper function for each such grouping.  
  for (EntityType t = MBVERTEX; t < MBENTITYSET; ++t) {
      // Get list of all connectivity lengths (or vertex dimensions)
      // that exist for type t.
    TypeSequenceManager& seqs = mMB->sequence_manager()->entity_map(t);
    TypeSequenceManager::iterator i;
    std::set<int> values;
    for (i = seqs.begin(); i != seqs.end(); ++i) {
      EntitySequence* seq = *i;
        // 0 values per entity implies structured data, which
        // we cannot reorder.  Skip those.
      if (t == MBVERTEX || 0 < seq->values_per_entity())
        values.insert( seq->values_per_entity() );
    }
  
      // Call helper function for each (type,size) tuple.
    std::set<int>::iterator j;
    for (j = values.begin(); j != values.end(); ++j) {
      rval = handle_order_from_int_tag( t, *j, tag, skip_value, new_handles );
      if (MB_SUCCESS != rval) {
        mMB->tag_delete( new_handles );
        return error(rval);
      }
    }
  }
  
  return MB_SUCCESS;
}

void ReorderTool::get_entities( EntityType t, int vals_per_ent, Range& entities )
{
  Range::iterator hint = entities.begin();;
  TypeSequenceManager& seqs = mMB->sequence_manager()->entity_map(t);
  TypeSequenceManager::iterator s;
  for (s = seqs.begin(); s != seqs.end(); ++s) {
    EntitySequence* seq = *s;
    if (seq->values_per_entity() == vals_per_ent)
      hint = entities.insert( hint, seq->start_handle(), seq->end_handle() );
  }
}

ErrorCode ReorderTool::handle_order_from_int_tag( EntityType t,
                                                  int vals_per_ent,
                                                  Tag tag,
                                                  int skip_value,
                                                  Tag new_handles )
{
  ErrorCode rval;
 
    // check that input tag handles are what we expect
  rval = check_tag_type( mMB, tag, MB_TYPE_INTEGER, sizeof(int) );
  CHKERR;
  rval = check_tag_type( mMB, new_handles, MB_TYPE_HANDLE, sizeof(EntityHandle) );
  CHKERR;

    // get entities to re-order
  Range entities;
  get_entities( t, vals_per_ent, entities );

    // get entity order values
  std::vector<int> sortvals(entities.size());
  rval = mMB->tag_get_data( tag, entities, &sortvals[0] );
  CHKERR;
  
    // remove any entities for which value is skip_value
  size_t r = 0, w = 0;
  for (Range::iterator i = entities.begin(); i != entities.end(); ++r) {
    if (sortvals[r] == skip_value)
      i = entities.erase(i);
    else {
      sortvals[w++] = sortvals[r];
      ++i;
    }
  }
  sortvals.resize(w);
  
    // sort 
  std::sort( sortvals.begin(), sortvals.end() );
    // Convert to unique list and offsets for each value
    // When done, sortvals will contain unique, sortvals and
    // offsets will contain, for each unique value in sortvals,
    // the number of values that occured in the non-unique list
    // before the first instance of that value.
  std::vector<size_t> offsets;
  offsets.push_back( 0 );
  offsets.push_back( 1 );
  for (w = 0, r = 1; r < sortvals.size(); ++r) {
    if (sortvals[r] == sortvals[w]) {
      ++offsets.back();
    }
    else {
      ++w;
      sortvals[w] = sortvals[r];
      offsets.push_back(offsets.back()+1);
    }
  }
  ++w;
  assert(w+1 == offsets.size());
  sortvals.resize(w);
  
    // Tag each entity with its new handle
  for (Range::iterator i = entities.begin(); i != entities.end(); ++i) {
    int val;
    rval = mMB->tag_get_data( tag, &*i, 1, &val );
    CHKERR;
    w = std::lower_bound( sortvals.begin(), sortvals.end(), val ) - sortvals.begin();
    assert( w < sortvals.size() );
    size_t offset = offsets[w];
    ++offsets[w];
      // should maybe copy range into array to avoid possibly n^2 behavior here
    EntityHandle h = *(entities.begin() + offset);
    rval = mMB->tag_set_data( new_handles, &*i, 1, &h );
    CHKERR;
  }

  return MB_SUCCESS;  
}
    
    
    

ErrorCode
ReorderTool::handle_order_from_sets_and_adj( const Range& sets,
                                             Tag& handle_tag )
{
  ErrorCode rval;
  
  if (!sets.all_of_type(MBENTITYSET))
    return MB_TYPE_OUT_OF_RANGE;
  
  Tag order_tag;
  const int negone = -1;
  rval = mMB->tag_get_handle( 0, 1, MB_TYPE_INTEGER, order_tag,
                              MB_TAG_DENSE|MB_TAG_CREAT|MB_TAG_EXCL, &negone );
  if (MB_SUCCESS != rval) {
    mMB->tag_delete( handle_tag );
    handle_tag = 0;
    return error(rval);
  }
  
  std::vector<std::vector<EntityHandle>*> data;
  rval = int_order_from_sets_and_adj( sets, order_tag, negone, data );
  for (size_t i = 0; i < data.size(); ++i)
    delete data[i];
  if (MB_SUCCESS != rval) {
    mMB->tag_delete( order_tag );
    return error(rval);
  }
  
  rval = handle_order_from_int_tag( order_tag, negone, handle_tag );
  if (MB_SUCCESS != rval) {
    mMB->tag_delete( order_tag );
    return error(rval);
  }
  
  rval = mMB->tag_delete( order_tag );
  if (MB_SUCCESS != rval)
    return error(rval);
  
  return MB_SUCCESS;
}
  
    // Compare function to use for a map keyed on pointers to sorted vectors
struct CompSortedVect {
  bool operator()( const std::vector<EntityHandle>* v1,
                   const std::vector<EntityHandle>* v2 ) const
  {
    std::vector<EntityHandle>::const_iterator i1, i2;
    for (i1 = v1->begin(), i2 = v2->begin(); i1 != v1->end(); ++i1, ++i2) {
      if (i2 == v2->end() || *i1 > *i2)
        return false;
      else if (*i1 < *i2)
        return true;
    }
    return i2 != v2->end();
  }
};

ErrorCode ReorderTool::int_order_from_sets_and_adj( const Range& sets,
                                                    Tag order_tag,
                                                    int skip_val,
                  std::vector<std::vector<EntityHandle>*>& revMap )
{
  ErrorCode rval;

  if (!sets.all_of_type(MBENTITYSET))
    return MB_TYPE_OUT_OF_RANGE;
  
  rval = check_tag_type( mMB, order_tag, MB_TYPE_INTEGER, sizeof(int) );
  CHKERR;
  
    // Compare function to use for a map keyed on pointers to sorted vectors
  CompSortedVect lessthan;
    // Map from sorted list of handles to index
  std::map< std::vector<EntityHandle>*, int, CompSortedVect > forMap;
  std::map< std::vector<EntityHandle>*, int, CompSortedVect >::iterator fiter, fiter2;
  std::vector<EntityHandle> sharing; // tmp storage for entity
  
    // for each set
  for (Range::iterator s = sets.begin(); s != sets.end(); ++s) {

      // gather up all entities and adjacencies
    Range tmp, ents, adj[4]; // indexed by dimension
    for (int dim = 0; dim < 4; ++dim) {
      rval = mMB->get_entities_by_dimension( *s, dim, tmp );
      CHKERR;
      for (int ldim = 0; ldim < dim; ++ldim) {
        rval = mMB->get_adjacencies( tmp, ldim, false, adj[ldim], Interface::UNION );
        CHKERR;
      }
      for (int udim = dim+1; udim <= 3; ++udim) {
        rval = mMB->get_adjacencies( tmp, udim, false, adj[udim], Interface::UNION );
        CHKERR;
      }
      ents.merge(tmp);
      tmp.clear();
    }
    for (int dim = 0; dim < 4; ++dim) {
      ents.merge( adj[dim] );
      adj[dim].clear();
    }

      // process each entity
    for (Range::iterator e = ents.begin(); e != ents.end(); ++e) {
      int val;
      rval = mMB->tag_get_data( order_tag, &*e, 1, &val );
      CHKERR;
      
        // If this entity is already in one or more of the sets (either
        // directly or through adjacency) then get the existing list of
        // sets and append this set handle (we are iterating over sets
        // in sorted order, so appending should aways maintain a sorted
        // list.)
      sharing.clear();
      if (val != skip_val) {
        sharing = *revMap[val];
        assert( std::lower_bound(sharing.begin(), sharing.end(), *s) == sharing.end() );
      }
      sharing.push_back( *s );
      
        // Check if new sharing list already exists in forward map
      fiter = forMap.lower_bound( &sharing );
      if (fiter == forMap.end() || lessthan(fiter->first,&sharing)) {
          // Add new sharing list to forward and reverse maps.
        std::vector<EntityHandle>* newvec = new std::vector<EntityHandle>;
        newvec->swap(sharing);
        if ((int)revMap.size() == skip_val)
          revMap.push_back(0);
        fiter2 = forMap.insert( fiter, std::pair<std::vector<EntityHandle>*, int>(newvec,revMap.size()) );
        assert(fiter2 != fiter);
        fiter = fiter2;
        revMap.push_back( newvec );
      }
      
        // Update index on entity
      val = fiter->second;
      rval = mMB->tag_set_data( order_tag, &*e, 1, &val );
      CHKERR;
    }
  }
  
  return MB_SUCCESS;
}

ErrorCode 
ReorderTool::get_reordered_handles( Tag tag, 
                                    const Range& old_handles, 
                                    std::vector<EntityHandle>& new_handles )
{
  new_handles.resize( old_handles.size() );
  ErrorCode rval = mMB->tag_get_data( tag, old_handles, (new_handles.empty())?NULL:&new_handles[0] );
  CHKERR;
  
  Range::const_iterator it1 = old_handles.begin();
  std::vector<EntityHandle>::iterator it2 = new_handles.begin();
  for (;it1 != old_handles.end(); ++it1, ++it2) 
    if (0 == *it2)
      *it2 = *it1;
  
  return MB_SUCCESS;
}

ErrorCode ReorderTool::get_reordered_handles( Tag tag, 
                        const std::vector<EntityHandle>& old_handles,
                        std::vector<EntityHandle>& new_handles )
{
  new_handles.resize( old_handles.size() );
  return get_reordered_handles( tag, &old_handles[0], &new_handles[0], old_handles.size() );
}

ErrorCode ReorderTool::get_reordered_handles( Tag tag, 
                                        const EntityHandle* old_handles,
                                        EntityHandle* new_handles,
                                        size_t num_handles )
{
  ErrorCode rval = mMB->tag_get_data( tag, old_handles, num_handles, new_handles );
  CHKERR;
  
  for (size_t i = 0; i < num_handles; ++i)
    if (0 == new_handles[i])
      new_handles[i] = old_handles[i];
  
  return MB_SUCCESS;
}
  
ErrorCode ReorderTool::get_new_handles( Tag tag,
                                        Range& old_handles,
                                        std::vector<EntityHandle>& newhandles )
{
    // get new handles for tagged entities
  newhandles.resize(old_handles.size());
  ErrorCode rval = mMB->tag_get_data( tag, old_handles, (newhandles.empty())?NULL:&newhandles[0] );
  CHKERR;

    // remove entities that were not reordered
  Range::iterator i = old_handles.begin();
  size_t w = 0;
  for (size_t r = 0; r < newhandles.size(); ++r) {
    if (0 != newhandles[r]) {
      newhandles[w] = newhandles[r];
      ++w;
      ++i;
    }
    else {
      i = old_handles.erase(i);
    }
  }
  newhandles.resize(w);
  assert(newhandles.size() == old_handles.size());
  return MB_SUCCESS;
}

ErrorCode ReorderTool::reorder_entities( Tag new_handles )
{
  ErrorCode rval;

  rval = check_tag_type( mMB, new_handles, MB_TYPE_HANDLE, sizeof(EntityHandle) );
  CHKERR;
  EntityHandle defval;
  rval = mMB->tag_get_default_value( new_handles, &defval );
  CHKERR;
  if (0 != defval)
    return error(MB_INDEX_OUT_OF_RANGE);

    // Can only reorder within same type/connectivity (or vertex/num_coords)
    // groupings.  
  for (EntityType t = MBVERTEX; t < MBENTITYSET; ++t) {
      // Get list of all connectivity lengths (or vertex dimensions)
      // that exist for type t.
    TypeSequenceManager& seqs = mMB->sequence_manager()->entity_map(t);
    TypeSequenceManager::iterator i;
    std::set<int> values;
    for (i = seqs.begin(); i != seqs.end(); ++i) {
      EntitySequence* seq = *i;
        // 0 values per entity implies structured data, which
        // we cannot reorder.  Skip those.
      if (t == MBVERTEX || 0 < seq->values_per_entity())
        values.insert( seq->values_per_entity() );
    }
  
      // reorder primary data for each (type,size) tuple.
    std::set<int>::iterator j;
    for (j = values.begin(); j != values.end(); ++j) {
      Range entities;
      get_entities( t, *j, entities );
      std::vector<EntityHandle> handles;
      rval = get_reordered_handles( new_handles, entities, handles );
      UNRECOVERABLE(rval);
      
      if (t == MBVERTEX) {
        std::vector<double> coords( entities.size() * 3 );
        rval = mMB->get_coords( entities, &coords[0] );
        UNRECOVERABLE(rval);
        rval = mMB->set_coords( &handles[0], handles.size(), &coords[0] );
        UNRECOVERABLE(rval);
      }
      else {
        std::vector<EntityHandle> conn;
        conn.reserve( entities.size() * *j );
        std::vector<EntityHandle> old_handles;
        old_handles.resize( entities.size() );
        std::copy( entities.begin(), entities.end(), old_handles.begin() );
        rval = mMB->get_connectivity( &old_handles[0], old_handles.size(),
                                      conn, false );
        UNRECOVERABLE(rval);
        old_handles.clear();
        old_handles = conn;
        rval = get_reordered_handles( new_handles, old_handles, conn );
        UNRECOVERABLE(rval);
        for (unsigned int h = 0; h < handles.size(); ++h) {
          rval = mMB->set_connectivity( handles[h], &conn[h * *j], *j );
          UNRECOVERABLE(rval);
        }
      }
    }
  }
  
    // now update tag data
  std::vector<Tag> tag_handles;
  mMB->tag_get_tags( tag_handles );
  for (size_t i = 0; i < tag_handles.size(); ++i) {
    Tag tag = tag_handles[i];
    if (tag == new_handles) // don't mess up mapping from old to new handles
      continue; 

    for (EntityType t = MBVERTEX; t <= MBENTITYSET; ++t) {
      rval = reorder_tag_data( t, new_handles, tag );
      UNRECOVERABLE(rval);
    }
  }
  
  rval = update_set_contents( new_handles );
  UNRECOVERABLE(rval);
  
  return MB_SUCCESS;
}

ErrorCode ReorderTool::reorder_tag_data( EntityType etype, Tag new_handles, Tag tag )
{
  ErrorCode rval;
  
  int tagsize;
  DataType tagtype;
  rval = mMB->tag_get_data_type( tag, tagtype );
  if (MB_SUCCESS != rval) 
    return error(rval);
  if (MB_TYPE_BIT == tagtype)
    tagsize = 1;
  else {
    rval = mMB->tag_get_bytes( tag, tagsize );
    if (MB_VARIABLE_DATA_LENGTH == rval)
      tagsize = -1;
    else if (MB_SUCCESS != rval) 
      return error(rval);
  }

    // we don't re-order set handles, so we don't care about sets
    // unless the tag contains handles that need to be updated
  if (MBENTITYSET == etype && MB_TYPE_HANDLE != tagtype)
    return MB_SUCCESS;

    // get tagged entities
  Range old_tagged;
  rval = mMB->get_entities_by_type_and_tag( 0, etype, &tag, 0, 1, old_tagged );
  if (MB_SUCCESS != rval && old_tagged.empty()) 
    return error(rval);
  
    // remove entities that were not reordered, unless the tag
    // is handle type in which case we need to update the data
    // for all entities, regardless of reordering.
  std::vector<EntityHandle> newhandles;
  if (MB_TYPE_HANDLE == tagtype) 
    rval = get_reordered_handles( new_handles, old_tagged, newhandles );
  else
    rval = get_new_handles( new_handles, old_tagged, newhandles );
  CHKERR;

  if (old_tagged.empty())
    return MB_SUCCESS;

    // get copy of all tag data   
  std::vector<unsigned char> buffer;
  std::vector<const void*> pointers;
  std::vector<int> sizes;
    // if variable-length tag
  if (-1 == tagsize) { 
    pointers.resize(old_tagged.size());
    sizes.resize(pointers.size());
    rval = mMB->tag_get_by_ptr( tag, old_tagged, &pointers[0], &sizes[0] );
    CHKERR;
    int total = std::accumulate( sizes.begin(), sizes.end(), 0 );
    DataType dtype;
    mMB->tag_get_data_type( tag, dtype );
    int type_size;
    switch (dtype) {
      case MB_TYPE_INTEGER: type_size = sizeof(int);  break;
      case MB_TYPE_DOUBLE:  type_size = sizeof(double); break;
      case MB_TYPE_HANDLE:  type_size = sizeof(EntityHandle); break;
      case MB_TYPE_BIT:     type_size = 1; break;
      case MB_TYPE_OPAQUE:  type_size = 1; break;
      default:              return MB_TYPE_OUT_OF_RANGE;
    }
    buffer.resize( total*type_size );
    size_t off = 0;
    for (size_t j = 0; j < pointers.size(); ++j) {
      memcpy( &buffer[off], pointers[j], type_size*sizes[j] );
      pointers[j] = &buffer[off];
      off += sizes[j] * type_size;
    }
  }
    // if fixed-length tag
  else {
    buffer.resize( old_tagged.size() * tagsize );
    rval = mMB->tag_get_data( tag, old_tagged, &buffer[0] );
    CHKERR;
  }
  
    // if handle tag, update tag values for reordered handles
  if (MB_TYPE_HANDLE == tagtype) {
    assert(!(buffer.size() % sizeof(EntityHandle)));
    std::vector<unsigned char> buffer2(buffer.size());
    rval = get_reordered_handles( new_handles,
                 reinterpret_cast<const EntityHandle*>(&buffer[0]), 
                 reinterpret_cast<EntityHandle*>(&buffer2[0]), 
                 buffer.size() / sizeof(EntityHandle) );
    CHKERR;
      // if var-length tag then do not do swap because pointers[] contains pointers
      // into old buffer
    if (-1 == tagsize)
      memcpy( &buffer[0], &buffer2[0], buffer.size() );
    else
      buffer.swap(buffer2);
  }
  
    // store re-orederd tag data
  if (-1 == tagsize) {
    rval = mMB->tag_set_by_ptr( tag, &newhandles[0], newhandles.size(), &pointers[0], &sizes[0] );
  }
  else {
    rval = mMB->tag_set_data( tag, &newhandles[0], newhandles.size(), &buffer[0] );
  }
  CHKERR;
  
    // all permutations should be cyclical, but not all permuted
    // entities necessarily had tag values, so we might need to delete
    // tags for some entities
  std::sort( newhandles.begin(), newhandles.end() );
  std::vector<EntityHandle>::iterator k = newhandles.begin();
  Range::iterator i = old_tagged.begin();
  while (i != old_tagged.end()) {
    while (k != newhandles.end() && *k < *i)
      ++k;
    if (k == newhandles.end())
      break;
    
    if (*i == *k) // what old_tagged -= newhandles
      i = old_tagged.erase( i );
    else
      ++i;
  }
  
  if (!old_tagged.empty()) {
    rval = mMB->tag_delete_data( tag, old_tagged );
    CHKERR;
  }
  
  return MB_SUCCESS;
}

ErrorCode ReorderTool::update_set_contents( Tag nh_tag )
{
  Range sets;
  ErrorCode rval = mMB->get_entities_by_type( 0, MBENTITYSET, sets );
  CHKERR;
  
  std::vector<EntityHandle> old_handles, new_handles;
  for (Range::iterator i = sets.begin(); i != sets.end(); ++i) {
      // If set is un-ordered...
    unsigned opts = 0;
    mMB->get_meshset_options( *i, opts );
    if (!(opts & MESHSET_ORDERED)) {
      Range contents;
      rval = mMB->get_entities_by_handle( *i, contents );
      CHKERR;
    
      rval = get_new_handles( nh_tag, contents, new_handles );
      CHKERR;
      
      Range replace;
      std::sort( new_handles.begin(), new_handles.end() );
      Range::iterator hint = replace.begin();
      for (size_t j = 0; j < new_handles.size(); ++j)
        hint = replace.insert( hint, new_handles[j] );
      Range common = intersect(contents,replace);
      contents -= common;
      replace -= common;
      assert(contents.size() == replace.size());
      if (!contents.empty()) {
        rval = mMB->remove_entities( *i, contents );
        CHKERR;
        rval = mMB->add_entities( *i, replace );
      }
    }
    
      // If set is ordered...
    else {
        // get set contents
      old_handles.clear();
      rval = mMB->get_entities_by_handle( *i, old_handles );
      CHKERR;

        // get new handles from old contained handles
      rval = get_reordered_handles( nh_tag, old_handles, new_handles );
      CHKERR;

      rval = mMB->clear_meshset( &*i, 1 );
      CHKERR;
        
      rval = mMB->add_entities( *i, &new_handles[0], new_handles.size() );
      CHKERR;
    }
  } // for each set
  
  return MB_SUCCESS;
}

} // namespace moab
