/**
 * 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.
 * 
 */

#include <memory.h>
#include <algorithm>

#include "SparseTag.hpp"
#include "moab/Range.hpp"
#include "TagCompare.hpp"
#include "SysUtil.hpp"
#include "SequenceManager.hpp"
#include "moab/Error.hpp"
#include "moab/ErrorHandler.hpp"
#include "moab/CN.hpp"

namespace moab {

static ErrorCode not_found(const std::string& name, EntityHandle h)
{
  // MB_TAG_NOT_FOUND could be a non-error condition, do not call MB_SET_ERR on it
  // Print warning messages for debugging only
  bool mydebug = false;
  if (mydebug) {
    if (h)
      fprintf(stderr, "[Warning]: No sparse tag %s value for %s %lu\n",
                      name.c_str(),
                      CN::EntityTypeName(TYPE_FROM_HANDLE(h)),
                      (unsigned long)ID_FROM_HANDLE(h));
    else
      fprintf(stderr, "[Warning]: No sparse tag %s value for root set\n", name.c_str());
  }

  return MB_TAG_NOT_FOUND;
}

SparseTag::SparseTag(const char* name,
                     int size,
                     DataType type,
                     const void* default_value)
  : TagInfo(name, size, type, default_value, size)
  {}

SparseTag::~SparseTag()
{
  release_all_data(0, 0, true);
}

TagType SparseTag::get_storage_type() const
{
  return MB_TAG_SPARSE;
}

ErrorCode SparseTag::release_all_data(SequenceManager*, Error*, bool)
{
  for(MapType::iterator i = mData.begin(); i != mData.end(); ++i)
    mAllocator.destroy(i->second);
  mData.clear();
  return MB_SUCCESS;
}

ErrorCode SparseTag::set_data(Error*, EntityHandle entity_handle, const void* data)
{
#ifdef MOAB_HAVE_UNORDERED_MAP
  MapType::iterator iter = mData.find(entity_handle);
#else
  MapType::iterator iter = mData.lower_bound(entity_handle);
#endif

  // Data space already exists
  if (iter!= mData.end() && iter->first == entity_handle)
    memcpy(iter->second, data, get_size());
  // We need to make some data space
  else {
    void *new_data = allocate_data(entity_handle, iter, false);
    memcpy(new_data, data, get_size());
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::get_data_ptr(EntityHandle entity_handle, const void*& ptr, bool allocate) const
{
  MapType::const_iterator iter = mData.find(entity_handle);

  if (iter != mData.end())
    ptr = iter->second;
  else if (get_default_value() && allocate)
    ptr = const_cast<SparseTag*>(this)->allocate_data(entity_handle, iter, allocate);
  else 
    return MB_FAILURE;

  return MB_SUCCESS;
}

ErrorCode SparseTag::get_data(Error* /* error */, EntityHandle entity_handle, void* data) const
{
  const void* ptr = 0;
  ErrorCode rval = get_data_ptr(entity_handle, ptr, false);
  if (MB_SUCCESS == rval) {
    memcpy(data, ptr, get_size());
    return rval;
  }
  else if (get_default_value()) {
    memcpy(data, get_default_value(), get_size());
    return MB_SUCCESS;
  }
  else
    return not_found(get_name(), entity_handle);
}

ErrorCode SparseTag::remove_data(Error* /* error */, EntityHandle entity_handle)
{
  MapType::iterator i = mData.find(entity_handle);
  if (i == mData.end()) 
    return not_found(get_name(), entity_handle);

  mAllocator.destroy(i->second);
  mData.erase(i);

  return MB_SUCCESS;
}

ErrorCode SparseTag::get_data(const SequenceManager*,
                              Error* /* error */,
                              const EntityHandle* entities,
                              size_t num_entities,
                              void* data) const
{
  ErrorCode rval;
  unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
  for (size_t i = 0; i < num_entities; ++i, ptr += get_size()) {
    rval = get_data(NULL, entities[i], ptr);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::get_data(const SequenceManager*,
                              Error* /* error */,
                              const Range& entities,
                              void* data) const
{
  ErrorCode rval;
  unsigned char* ptr = reinterpret_cast<unsigned char*>(data);
  Range::const_iterator i;
  for (i = entities.begin(); i != entities.end(); ++i, ptr += get_size()) {
    rval = get_data(NULL, *i, ptr);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::get_data(const SequenceManager*,
                              Error* /* error */,
                              const EntityHandle* entities,
                              size_t num_entities,
                              const void** pointers,
                              int* data_lengths) const
{
  if (data_lengths) {
    int len = get_size();
    SysUtil::setmem(data_lengths, &len, sizeof(int), num_entities);
  }

  ErrorCode rval = MB_SUCCESS, rval_tmp;
  for (size_t i = 0; i < num_entities; ++i, ++pointers) {
    rval_tmp = get_data_ptr(entities[i], *pointers);
    if (MB_SUCCESS != rval_tmp && get_default_value()) {
      *pointers = get_default_value();
    }
    else if (MB_SUCCESS != rval_tmp) {
      rval = not_found(get_name(), entities[i]);
    }
  }

  return rval;
}

ErrorCode SparseTag::get_data(const SequenceManager*,
                              Error* /* error */,
                              const Range& entities,
                              const void** pointers,
                              int* data_lengths) const
{
  if (data_lengths) {
    int len = get_size();
    SysUtil::setmem(data_lengths, &len, sizeof(int), entities.size());
  }

  ErrorCode rval = MB_SUCCESS, rval_tmp;
  Range::const_iterator i;
  for (i = entities.begin(); i != entities.end(); ++i, ++pointers) {
    rval_tmp = get_data_ptr(*i, *pointers);
    if (MB_SUCCESS != rval_tmp && get_default_value()) {
      *pointers = get_default_value();
    }
    else if (MB_SUCCESS != rval_tmp) {
      rval = not_found(get_name(), *i);
    }
  }

  return rval;
}

ErrorCode SparseTag::set_data(SequenceManager* seqman,
                              Error* /* error */,
                              const EntityHandle* entities,
                              size_t num_entities,
                              const void* data)
{
  ErrorCode rval = seqman->check_valid_entities(NULL, entities, num_entities, true);MB_CHK_ERR(rval);

  const unsigned char* ptr = reinterpret_cast<const unsigned char*>(data);
  for (size_t i = 0; i < num_entities; ++i, ptr += get_size()) {
    rval = set_data(NULL, entities[i], ptr);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::set_data(SequenceManager* seqman,
                              Error* /* error */,
                              const Range& entities,
                              const void* data)
{
  ErrorCode rval = seqman->check_valid_entities(NULL, entities);MB_CHK_ERR(rval);

  const unsigned char* ptr = reinterpret_cast<const unsigned char*>(data);
  Range::const_iterator i;
  for (i = entities.begin(); i != entities.end(); ++i, ptr += get_size())
    if (MB_SUCCESS != (rval = set_data(NULL, *i, ptr)))
      return rval;

  return MB_SUCCESS;
}

ErrorCode SparseTag::set_data(SequenceManager* seqman,
                              Error* /* error */,
                              const EntityHandle* entities,
                              size_t num_entities,
                              void const* const* pointers,
                              const int* lengths)
{
  ErrorCode rval = validate_lengths(NULL, lengths, num_entities);MB_CHK_ERR(rval);

  rval = seqman->check_valid_entities(NULL, entities, num_entities, true);MB_CHK_ERR(rval);

  for (size_t i = 0; i < num_entities; ++i, ++pointers) {
    rval = set_data(NULL, entities[i], *pointers);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::set_data(SequenceManager* seqman,
                              Error* /* error */,
                              const Range& entities,
                              void const* const* pointers,
                              const int* lengths)
{
  ErrorCode rval = validate_lengths(NULL, lengths, entities.size());MB_CHK_ERR(rval);

  rval = seqman->check_valid_entities(NULL, entities);MB_CHK_ERR(rval);

  Range::const_iterator i;
  for (i = entities.begin(); i != entities.end(); ++i, ++pointers) {
    rval = set_data(NULL, *i, *pointers);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::clear_data(SequenceManager* seqman,
                                Error* /* error */,
                                const EntityHandle* entities,
                                size_t num_entities,
                                const void* value_ptr,
                                int value_len)
{
  if (value_len && value_len != get_size()) {
    MB_SET_ERR(MB_INVALID_SIZE, "Invalid data size " << get_size() << " specified for sparse tag " << get_name() << " of size " << value_len);
  }

  ErrorCode rval = seqman->check_valid_entities(NULL, entities, num_entities, true);MB_CHK_ERR(rval);

  for (size_t i = 0; i < num_entities; ++i) {
    rval = set_data(NULL, entities[i], value_ptr);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::clear_data(SequenceManager* seqman,
                                Error* /* error */,
                                const Range& entities,
                                const void* value_ptr,
                                int value_len)
{
  if (value_len && value_len != get_size()) {
    MB_SET_ERR(MB_INVALID_SIZE, "Invalid data size " << get_size() << " specified for sparse tag " << get_name() << " of size " << value_len);
  }

  ErrorCode rval = seqman->check_valid_entities(NULL, entities);MB_CHK_ERR(rval);

  Range::const_iterator i;
  for (i = entities.begin(); i != entities.end(); ++i) {
    rval = set_data(NULL, *i, value_ptr);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::remove_data(SequenceManager*,
                                 Error* /* error */,
                                 const EntityHandle* entities,
                                 size_t num_entities)
{
  ErrorCode rval;
  for (size_t i = 0; i < num_entities; ++i) {
    rval = remove_data(NULL, entities[i]);MB_CHK_ERR(rval);
  }

  return MB_SUCCESS;
}

ErrorCode SparseTag::remove_data(SequenceManager*,
                                 Error* /* error */,
                                 const Range& entities)
{
  for (Range::const_iterator i = entities.begin(); i != entities.end(); ++i)
    if (MB_SUCCESS != remove_data(NULL, *i))
      return MB_TAG_NOT_FOUND;

  return MB_SUCCESS;
}

ErrorCode SparseTag::tag_iterate(SequenceManager* seqman,
                                 Error* /* error */,
                                 Range::iterator& iter,
                                 const Range::iterator& end,
                                 void*& data_ptr,
                                 bool allocate)
{
  // Note: We are asked to returning a block of contiguous storage
  //       for some block of contiguous handles for which the tag
  //       storage is also contiguous. As sparse tag storage is
  //       never contiguous, all we can do is return a pointer to the
  //       data for the first entity.

  // If asked for nothing, successfully return nothing.
  if (iter == end)
    return MB_SUCCESS;

  // Note: get_data_ptr will return the default value if the
  //       handle is not found, so test to make sure that the
  //       handle is valid.
  ErrorCode rval = seqman->check_valid_entities(NULL, &*iter, 1);MB_CHK_ERR(rval);

  // Get pointer to tag storage for entity pointed to by iter
  const void* ptr = NULL;
  rval = get_data_ptr(*iter, ptr);
  if (MB_SUCCESS == rval) 
    data_ptr = const_cast<void*>(ptr);
  else if (get_default_value() && allocate)
    ptr = allocate_data(*iter, mData.end());
  else {
    // If allocation was not requested, need to increment the iterator so that
    // the count can be computed properly
    if (get_default_value() && !allocate)
      ++iter;
    return not_found(get_name(), *iter);
  }

  // Increment iterator and return
  ++iter;
  return MB_SUCCESS;
}

template <class Container> static inline
void get_tagged(const SparseTag::MapType& mData,
                EntityType type,
                Container& output_range)
{
  SparseTag::MapType::const_iterator iter;
  typename Container::iterator hint = output_range.begin();
  if (MBMAXTYPE == type) {
    for (iter = mData.begin(); iter != mData.end(); ++iter)
      hint = output_range.insert(hint, iter->first);
  }
  else {
#ifdef MOAB_HAVE_UNORDERED_MAP
    for (iter = mData.begin(); iter != mData.end(); ++iter)
      if (TYPE_FROM_HANDLE(iter->first) == type)
        hint = output_range.insert(hint, iter->first);
#else
    iter = mData.lower_bound(FIRST_HANDLE(type));
    SparseTag::MapType::const_iterator end = mData.lower_bound(LAST_HANDLE(type) + 1);
    for (; iter != end; ++iter)
      hint = output_range.insert(hint, iter->first);
#endif
  }
}

template <class Container> static inline
void get_tagged(const SparseTag::MapType& mData,
                Range::const_iterator begin,
                Range::const_iterator end,
                Container& output_range)
{
  SparseTag::MapType::const_iterator iter;
  typename Container::iterator hint = output_range.begin();
  for (Range::const_iterator i = begin; i != end; ++i)
    if (mData.find(*i) != mData.end())
      hint = output_range.insert(hint, *i);
}

template <class Container> static inline
void get_tagged(const SparseTag::MapType& mData,
                Container& entities,
                EntityType type,
                const Range* intersect)
{
  if (!intersect)
    get_tagged<Container>(mData, type, entities);
  else if (MBMAXTYPE == type)
    get_tagged<Container>(mData, intersect->begin(), intersect->end(), entities);
  else {
    std::pair<Range::iterator,Range::iterator> r = intersect->equal_range(type);
    get_tagged<Container>(mData, r.first, r.second, entities);
  }
}

//! Gets all entity handles that match a type and tag
ErrorCode SparseTag::get_tagged_entities(const SequenceManager*,
                                         Range& output_range,
                                         EntityType type,
                                         const Range* intersect) const
{
  get_tagged(mData, output_range, type, intersect);
  return MB_SUCCESS;
}

//! Gets all entity handles that match a type and tag
ErrorCode SparseTag::num_tagged_entities(const SequenceManager*,
                                         size_t& output_count,
                                         EntityType type,
                                         const Range* intersect) const
{
  InsertCount counter(output_count);
  get_tagged(mData, counter, type, intersect);
  output_count = counter.end();
  return MB_SUCCESS;
}

ErrorCode SparseTag::find_entities_with_value(
#ifdef MOAB_HAVE_UNORDERED_MAP
                                              const SequenceManager* seqman,
#else
                                              const SequenceManager*,
#endif
                                              Error* /* error */,
                                              Range& output_entities,
                                              const void* value,
                                              int value_bytes,
                                              EntityType type,
                                              const Range* intersect_entities) const
{
  if (value_bytes && value_bytes != get_size()) {
    MB_SET_ERR(MB_INVALID_SIZE, "Invalid data size " << get_size() << " specified for sparse tag " << get_name() << " of size " << value_bytes);
  }

  MapType::const_iterator iter, end;
#ifdef MOAB_HAVE_UNORDERED_MAP
  if (intersect_entities) {
    std::pair<Range::iterator,Range::iterator> r;
    if (type == MBMAXTYPE) {
      r.first = intersect_entities->begin();
      r.second = intersect_entities->end();
    }
    else {
      r = intersect_entities->equal_range(type);
    }

    find_map_values_equal(*this, value, get_size(),
                          r.first, r.second,
                          mData, output_entities);
  }
  else if (type == MBMAXTYPE) {
    find_tag_values_equal(*this, value, get_size(),
                          mData.begin(), mData.end(),
                          output_entities);
  }
  else {
    Range tmp;
    seqman->get_entities(type, tmp);
    find_map_values_equal(*this, value, get_size(),
                          tmp.begin(), tmp.end(),
                          mData, output_entities);
  }
#else
  if (intersect_entities) {
    for (Range::const_pair_iterator p = intersect_entities->begin();
         p != intersect_entities->end(); ++p) {
      iter = mData.lower_bound(p->first);
      end = mData.upper_bound(p->second);
      find_tag_values_equal(*this, value, get_size(), iter, end,
                            output_entities);
    }
  }
  else {
    if (type == MBMAXTYPE) {
      iter = mData.begin();
      end = mData.end();
    }
    else {
      iter = mData.lower_bound(CREATE_HANDLE(type, MB_START_ID));
      end = mData.upper_bound(CREATE_HANDLE(type, MB_END_ID));
    }
    find_tag_values_equal(*this, value, get_size(), iter, end,
                          output_entities);
  }
#endif

  return MB_SUCCESS;
}

bool SparseTag::is_tagged(const SequenceManager*, EntityHandle h) const
{
  return mData.find(h) != mData.end();
}

ErrorCode SparseTag::get_memory_use(const SequenceManager*,
                                    unsigned long& total,
                                    unsigned long& per_entity) const

{
  per_entity = get_size() + 4*sizeof(void*);
  total = (mData.size() * per_entity)
        + sizeof(*this) + TagInfo::get_memory_use();

  return MB_SUCCESS;
}

} // namespace moab
