/**
 * MOAB, a Mesh-Oriented datABase, is a software component for creating,
 * storing and accessing finite element mesh data.
 * 
 * Copyright 2004 Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government
 * retains certain rights in this software.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 */

#ifdef _WIN32
#ifdef _DEBUG
// turn off warnings that say they debugging identifier has been truncated
// this warning comes up when using some STL containers
#pragma warning(disable : 4786)
#endif
#endif

#include "ReadUtil.hpp"
#include "moab/Core.hpp"
#include "AEntityFactory.hpp"
#include "moab/Error.hpp"
#include "SequenceManager.hpp"
#include "VertexSequence.hpp"
#include "ElementSequence.hpp"

namespace moab {

#define RR if (MB_SUCCESS != result) return result

ReadUtil::ReadUtil(Core* mdb, Error* /*error_handler*/)
  : ReadUtilIface(), mMB(mdb)
{
}

ErrorCode ReadUtil::get_node_coords(const int /*num_arrays*/,
                                    const int num_nodes,
                                    const int preferred_start_id,
                                    EntityHandle& actual_start_handle,
                                    std::vector<double*>& arrays,
                                    int sequence_size)
{
  ErrorCode error;
  EntitySequence* seq = 0;

  if (num_nodes < 1) {
    actual_start_handle = 0;
    arrays.clear();
    return MB_INDEX_OUT_OF_RANGE;
  }

  // Create an entity sequence for these nodes
  error = mMB->sequence_manager()->create_entity_sequence(
    MBVERTEX, num_nodes, 0, preferred_start_id,
    actual_start_handle,
    seq, sequence_size);

  if (error != MB_SUCCESS)
    return error;

  if (seq->start_handle() > actual_start_handle ||
      seq->end_handle() < actual_start_handle ||
      seq->end_handle() - actual_start_handle + 1 < (unsigned)num_nodes)
    return MB_FAILURE;

  arrays.resize(3);

  error = static_cast<VertexSequence*>(seq)->get_coordinate_arrays(arrays[0], arrays[1], arrays[2]);
  for (unsigned i = 0; i< arrays.size(); ++i)
    if (arrays[i])
      arrays[i] += (actual_start_handle - seq->start_handle());

  return error;
}

ErrorCode ReadUtil::get_element_connect(const int num_elements,
                                        const int verts_per_element,
                                        const EntityType mdb_type,
                                        const int preferred_start_id,
                                        EntityHandle& actual_start_handle,
                                        EntityHandle*& array,
                                        int sequence_size)
{
  ErrorCode error;
  EntitySequence* seq;

  if (num_elements < 1) {
    actual_start_handle = 0;
    array = 0;
    return MB_INDEX_OUT_OF_RANGE;
  }

  //if (mdb_type <= MBVERTEX || mdb_type >= MBPOLYHEDRON || mdb_type == MBPOLYGON)
    //return MB_TYPE_OUT_OF_RANGE;

  // Make an entity sequence to hold these elements
  error = mMB->sequence_manager()->create_entity_sequence(
      mdb_type, num_elements, verts_per_element, preferred_start_id, 
      actual_start_handle, seq, sequence_size);
  if (MB_SUCCESS != error)
    return error;

  if (seq->start_handle() > actual_start_handle ||
      seq->end_handle() < actual_start_handle ||
      seq->end_handle() - actual_start_handle + 1 < (unsigned)num_elements)
    return MB_FAILURE;

  // Get an array for the connectivity
  array = static_cast<ElementSequence*>(seq)->get_connectivity_array();
  if (!array)
    return MB_FAILURE;
  array += (actual_start_handle - seq->start_handle()) 
         * static_cast<ElementSequence*>(seq)->nodes_per_element();

  return error;
}

ErrorCode ReadUtil::create_entity_sets(EntityID num_sets,
                                       const unsigned* flags,
                                       EntityID start_id,
                                       EntityHandle& start_handle)
{
  if (num_sets < 1) {
    start_handle = 0;
    return MB_INDEX_OUT_OF_RANGE;
  }

  ErrorCode error;
  EntitySequence* seq;
  error = mMB->sequence_manager()->create_meshset_sequence(num_sets,
                                                           start_id,
                                                           flags,
                                                           start_handle,
                                                           seq);
  if (MB_SUCCESS != error)
    return error;

  if (seq->start_handle() > start_handle ||
      seq->end_handle() < start_handle ||
      seq->end_handle() - start_handle + 1 < (EntityHandle)num_sets)
    return MB_FAILURE;

  return MB_SUCCESS;
}

ErrorCode ReadUtil::update_adjacencies(const EntityHandle start_handle,
                                       const int number_elements,
                                       const int number_vertices_per_element,
                                       const EntityHandle* conn_array)
{
  EntityHandle tmp_hndl = start_handle;
  AEntityFactory* adj_fact = mMB->a_entity_factory();

  // Iterate over the elements and update adjacency information
  if (adj_fact != NULL && adj_fact->vert_elem_adjacencies()) {
    int j = 0;
    for (int i = 0; i < number_elements; i++) {
      adj_fact->notify_create_entity(tmp_hndl, (conn_array + j), number_vertices_per_element);
      tmp_hndl++;
      j += number_vertices_per_element;
    }
  }

  return MB_SUCCESS;
}

ErrorCode ReadUtil::gather_related_ents(Range &partition,
                                        Range &related_ents,
                                        EntityHandle *file_set)
{
  // Loop over any sets, getting contained ents
  std::pair<Range::const_iterator, Range::const_iterator> pair_it =
    partition.equal_range(MBENTITYSET);

  ErrorCode result = MB_SUCCESS;
  for (Range::const_iterator rit = pair_it.first;
       rit != pair_it.second; ++rit) {
    ErrorCode tmp_result = 
      mMB->get_entities_by_handle(*rit, related_ents,
                                  Interface::UNION);
    if (MB_SUCCESS != tmp_result) result = tmp_result;
  }
  RR;

  // Gather adjacent ents of other dimensions
  Range tmp_ents;
  for (int dim = 3; dim >= 0; dim--) {
    tmp_ents.clear();
    ErrorCode tmp_result = mMB->get_adjacencies(related_ents, dim, false,
                                                tmp_ents,
                                                Interface::UNION);
    if (MB_SUCCESS != tmp_result) result = tmp_result;
    else related_ents.merge(tmp_ents);
  }
  RR;

  // Related ents includes the partition itself
  related_ents.merge(partition);

  // Get contains-related sets
  Range tmp_ents3, last_related;
  if (file_set)
    result = mMB->get_entities_by_type(*file_set, MBENTITYSET, tmp_ents3);
  else
    result = mMB->get_entities_by_type(0, MBENTITYSET, tmp_ents3);
  RR;

  while (related_ents.size() != last_related.size()) {
    last_related = related_ents;
    for (Range::iterator rit = tmp_ents3.begin(); 
         rit != tmp_ents3.end(); ++rit) {
      if (related_ents.find(*rit) != related_ents.end()) continue;

      tmp_ents.clear();
      result = mMB->get_entities_by_handle(*rit, tmp_ents, true); RR;
      Range tmp_ents2 = intersect(tmp_ents, related_ents);

      // If the intersection is not empty, set is related
      if (!tmp_ents2.empty()) related_ents.insert(*rit);
    }
  }

  // Get child-related sets
  last_related.clear();
  while (related_ents.size() != last_related.size()) {
    last_related = related_ents;
    std::pair<Range::const_iterator, Range::const_iterator> it_pair = 
      last_related.equal_range(MBENTITYSET);

    for (Range::const_iterator rit = it_pair.first;
         rit != it_pair.second; ++rit) {
      // Get all children and add to related ents
      tmp_ents.clear();
      result = mMB->get_child_meshsets(*rit, tmp_ents, 0); RR;
      related_ents.merge(tmp_ents);
    }
  }

  // Get parent-related sets
  last_related.clear();
  while (related_ents.size() != last_related.size()) {
    last_related = related_ents;
    std::pair<Range::const_iterator, Range::const_iterator> it_pair =
      last_related.equal_range(MBENTITYSET);

    for (Range::const_iterator rit = it_pair.first;
         rit != it_pair.second; ++rit) {
      // Get all parents and add to related ents
      tmp_ents.clear();
      result = mMB->get_parent_meshsets(*rit, tmp_ents, 0); RR;
      related_ents.merge(tmp_ents);
    }
  }

  return MB_SUCCESS;
}

ErrorCode ReadUtil::get_ordered_vertices(EntityHandle *bound_ents,
                                         int *sense,
                                         int bound_size,
                                         int dim,
                                         EntityHandle *bound_verts,
                                         EntityType &etype)
{
  // Get dimension of bounding entities
  int bound_dim = CN::Dimension(TYPE_FROM_HANDLE(bound_ents[0]));
  int indices[MAX_SUB_ENTITY_VERTICES];
  const EntityHandle *connect = NULL;
  std::vector<EntityHandle> tmp_connect;

  // Find the right entity type based on # bounding ents
  int numv = 0, num_connect = 0;
  ErrorCode result;
  for (EntityType t = MBEDGE; t < MBENTITYSET; t++) {
    int nindex = CN::NumSubEntities(t, bound_dim);
    if (CN::Dimension(t) != dim || nindex != bound_size)
      continue;

    // Fill in vertices from bounding entity vertices
    int nverts = CN::VerticesPerEntity(t);
    std::fill(bound_verts, bound_verts+nverts, 0);
    for (int index = 0; index < nindex; index++) {
      result = mMB->get_connectivity(bound_ents[index], connect, num_connect,
                                     false, &tmp_connect);
      if (MB_SUCCESS != result)
        return result;
      
      CN::SubEntityVertexIndices(t, bound_dim, index, indices);

      for (int c = 0; c < num_connect; c++) {
        if (!bound_verts[indices[c]]) {
          bound_verts[indices[c]] = (sense[index] > 0) ?
              connect[c] : connect[num_connect - c - 1];
          numv++;
        }
      }
      if (numv == nverts) {
        etype = t;
        return MB_SUCCESS;
      }
    }
  }

  // If we get here, we didn't get full connectivity
  etype = MBMAXTYPE;
  return MB_FAILURE;
}

static ErrorCode check_int_tag(Interface* mb, Tag tag)
{
  int size;
  DataType type;
  ErrorCode rval = mb->tag_get_bytes(tag, size);
  if (MB_SUCCESS != rval)
    return rval;
  if (size != sizeof(int))
    return MB_TYPE_OUT_OF_RANGE;
  rval = mb->tag_get_data_type(tag, type);
  if (type != MB_TYPE_OPAQUE && type != MB_TYPE_INTEGER)
    return MB_TYPE_OUT_OF_RANGE;

  return MB_SUCCESS;
}

ErrorCode ReadUtil::assign_ids(Tag id_tag, const Range& ents, int start)
{
  ErrorCode rval = check_int_tag(mMB, id_tag);
  if (MB_SUCCESS != rval)
    return rval;

  Range tmp_range;
  std::vector<int> data;
  for (Range::const_pair_iterator i = ents.pair_begin();
       i != ents.pair_end(); ++i) {
    data.resize(i->second + 1 - i->first);
    for (std::vector<int>::iterator j = data.begin(); j != data.end(); ++j)
      *j = start++;
    tmp_range.clear();
    tmp_range.insert(i->first, i->second);
    rval = mMB->tag_set_data(id_tag, tmp_range, &data[0]);
    if (MB_SUCCESS != rval)
      return rval;
  }

  return MB_SUCCESS;
}

ErrorCode ReadUtil::assign_ids(Tag id_tag,
                               const EntityHandle* ents,
                               size_t num_ents,
                               int start)
{
  ErrorCode rval = check_int_tag(mMB, id_tag);
  if (MB_SUCCESS != rval)
    return rval;

  std::vector<int> data;
  const EntityHandle* const end = ents + num_ents;
  const EntityHandle* i = ents;
  while (i != end) {
    const EntityHandle* next = std::find(i, end, 0u);
    size_t size = next - i;
    if (!size) {
      ++i;
      continue;
    }

    int id = start + (i - ents);
    data.resize(size);
    for (std::vector<int>::iterator j = data.begin(); j != data.end(); ++j)
      *j = id++;

    rval = mMB->tag_set_data(id_tag, i, size, &data[0]);
    if (MB_SUCCESS != rval)
      return rval;
  }

  return MB_SUCCESS;
}

ErrorCode ReadUtil::create_gather_set(EntityHandle& gather_set)
{
  ErrorCode rval = mMB->create_meshset(MESHSET_SET, gather_set);
  if (MB_SUCCESS != rval)
    return rval;

  Tag gather_set_tag;
  rval = mMB->tag_get_handle("GATHER_SET", 1, MB_TYPE_INTEGER, gather_set_tag, MB_TAG_CREAT | MB_TAG_SPARSE);
  if (MB_SUCCESS != rval)
    return rval;

  int gather_val = 1;
  rval = mMB->tag_set_data(gather_set_tag, &gather_set, 1, &gather_val);
  if (MB_SUCCESS != rval)
    return rval;

  return MB_SUCCESS;
}

ErrorCode ReadUtil::get_gather_set(EntityHandle& gather_set)
{
  Tag gather_set_tag;
  ErrorCode rval = mMB->tag_get_handle("GATHER_SET", 1, MB_TYPE_INTEGER, gather_set_tag, MB_TAG_SPARSE);
  if (MB_SUCCESS != rval)
    return rval;

  int gather_val = 1;
  void* vals[] = {&gather_val};
  Range gather_sets;
  rval = mMB->get_entities_by_type_and_tag(0, MBENTITYSET, &gather_set_tag, vals, 1, gather_sets);
  if (MB_SUCCESS != rval)
    return rval;

  if (gather_sets.empty())
    return MB_ENTITY_NOT_FOUND;

  gather_set = gather_sets[0];

  return MB_SUCCESS;
}

} // namespace moab
