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

#ifndef __smoab_detail_ReadSparseTag_h
#define __smoab_detail_ReadSparseTag_h

#include "CellSets.h"
#include "SimpleMoab.h"

#include <vtkIntArray.h>

#include <algorithm>
#include <string>
#include <vector>

namespace smoab
{
namespace detail
{

struct ReadSparseTag
{

  ReadSparseTag(
    const smoab::CellSets& mSets, const smoab::Range& cells, const smoab::Interface& interface)
    : Interface(interface)
    , MeshSets(mSets)
    , Cells(cells)
  {
  }

  //the cellTag describes the tag mapping between the cells
  //and meshsets.
  void fill(vtkIntArray* array, const Tag* cellTag) const;
  void fill(vtkIdTypeArray* array, const Tag* cellTag) const;

private:
  template <typename T>
  void fillRawArray(T* sparseTagArray, std::size_t length, const Tag* cellTag) const;

  template <typename T>
  void singleSetRead(T* sparseTagArray, const std::vector<int>& values, std::size_t length) const;

  template <typename T>
  void multiSetRead(T* sparseTagArray, const std::vector<int>& values) const;

  const smoab::Interface& Interface;
  const smoab::CellSets& MeshSets;
  const smoab::Range& Cells;
};

void ReadSparseTag::fill(vtkIntArray* array, const Tag* cellTag) const
{
  const std::size_t length = this->Cells.size();
  array->SetNumberOfValues(length);
  int* raw = static_cast<int*>(array->GetVoidPointer(0));
  this->fillRawArray(raw, length, cellTag);
}

void ReadSparseTag::fill(vtkIdTypeArray* array, const Tag* cellTag) const
{
  const std::size_t length = this->Cells.size();
  array->SetNumberOfValues(length);
  vtkIdType* raw = static_cast<vtkIdType*>(array->GetVoidPointer(0));
  this->fillRawArray(raw, length, cellTag);
}

template <typename T>
void ReadSparseTag::fillRawArray(
  T* sparseTagArray, std::size_t length, const smoab::Tag* sparseTag) const
{

  typedef std::vector<int>::const_iterator IdConstIterator;
  typedef std::vector<int>::iterator IdIterator;
  typedef smoab::CellSets::const_iterator CellSetIterator;

  std::vector<int> sparseTagValues =
    smoab::getTagValues<int>(sparseTag, this->MeshSets, this->Interface);

  if (this->MeshSets.size() == 1)
  {
    this->singleSetRead(sparseTagArray, sparseTagValues, length);
  }
  else
  {
    //fill each value in the output with the default value.
    //before we do the scan, in some use cases the meshsets might
    //not cover all the entities so we want the sparse tags default value
    //to be used.
    const int defaultValue = this->Interface.getDefaultTagVaue<int>(*sparseTag);
    T fillValue = T(defaultValue);
    std::fill(sparseTagArray, sparseTagArray + length, fillValue);

    this->multiSetRead(sparseTagArray, sparseTagValues);
  }
}

template <typename T>
void ReadSparseTag::singleSetRead(
  T* sparseTagArray, const std::vector<int>& values, std::size_t length) const
{

  //now we set all the values as this has a single meshset so we
  //have no complicated logic for mapping each cell to a meshset
  T value = static_cast<T>(values[0]);
  std::fill(sparseTagArray, sparseTagArray + length, value);
}

template <typename T>
void ReadSparseTag::multiSetRead(T* sparseTagArray, const std::vector<int>& sparseTagValues) const
{
  typedef std::vector<smoab::EntityHandle>::const_iterator EntityHandleIterator;
  typedef std::vector<int>::const_iterator IdConstIterator;
  typedef smoab::CellSets::const_iterator CellSetIterator;
  typedef smoab::Range::const_iterator RangeIterator;

  //create the search structure as a range is really slow to search with
  //lower_bounds
  std::vector<smoab::EntityHandle> searchableCells;
  smoab::RangeToVector(this->Cells, searchableCells);

  EntityHandleIterator s_begin = searchableCells.begin();
  EntityHandleIterator s_end = searchableCells.end();

  IdConstIterator currentTagValue = sparseTagValues.begin();
  for (CellSetIterator i = this->MeshSets.begin(); i != this->MeshSets.end();
       ++i, ++currentTagValue)
  {
    T value = static_cast<T>(*currentTagValue);

    const smoab::Range& entitiesCells = i->cells();
    for (RangeIterator j = entitiesCells.begin(); j != entitiesCells.end(); ++j)
    {
      EntityHandleIterator result = std::lower_bound(s_begin, s_end, *j);
      std::size_t newId = std::distance(s_begin, result);
      sparseTagArray[newId] = value;
    }
  }
}
}
}

#endif // __smoab_detail_ReadSparseTag_h
