//=============================================================================
//
//  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/session/aeva/Session.h"

#include "smtk/session/aeva/Resource.h"

#include "smtk/model/Entity.h"

#include "vtkCellData.h"
#include "vtkDataObject.h"
#include "vtkDataSet.h"
#include "vtkGenerateGlobalIds.h"
#include "vtkIntArray.h"
#include "vtkPointData.h"

namespace smtk
{
namespace session
{
namespace aeva
{

std::string Session::defaultFileExtension(const smtk::model::Model& /*model*/) const
{
  return ".vti";
}

vtkSmartPointer<vtkDataObject> Session::findStorage(const smtk::common::UUID& uid) const
{
  auto it = m_storage.find(uid);
  if (it != m_storage.end())
  {
    return it->second;
  }
  return nullptr;
}

void Session::addStorage(const smtk::common::UUID& uid,
  vtkSmartPointer<vtkDataObject> const& storage)
{
  if (uid.isNull())
  {
    return;
  }

  if (!storage)
  {
    m_storage.erase(uid);
  }
  else
  {
    m_storage[uid] = storage;
  }
}

#if 0
Session::ImageType::Pointer Session::findStorage(const smtk::common::UUID& uid) const
{
  auto it = m_storage.find(uid);
  if (it != m_storage.end())
  {
    return it->second;
  }
  return nullptr;
}

void Session::addStorage(const smtk::common::UUID& uid, ImageType::Pointer storage)
{
  if (uid.isNull())
  {
    return;
  }

  if (!storage)
  {
    m_storage.erase(uid);
  }
  else
  {
    m_storage[uid] = storage;
  }
}
#endif

bool Session::removeStorage(const smtk::common::UUID& uid)
{
  if (uid.isNull())
  {
    return false;
  }

  return m_storage.erase(uid) > 0;
}

bool Session::primaryGeometries(const vtkSmartPointer<vtkDataObject>& data,
  int fieldType, // one of vtkDataObject::AttributeTypes
  std::set<smtk::model::Entity*>& primaries,
  smtk::resource::Resource* resource) const
{
  vtkDataSetAttributes* attr;
  vtkIntArray* gids;
  if (!resource || !data || !(attr = data->GetAttributes(fieldType)) ||
    !(gids = vtkIntArray::SafeDownCast(attr->GetGlobalIds())))
  {
    return false;
  }

  vtkIdType nn = gids->GetNumberOfValues();
  int* tmp = gids->GetValueRange(0);
  std::array<int, 2> dataIdRange{ tmp[0], tmp[1] };

  bool didAdd = false;
  for (const auto& it : m_storage)
  {
    auto comp = std::dynamic_pointer_cast<smtk::model::Entity>(resource->find(it.first));
    if (!comp || !Session::isPrimary(*comp))
    {
      continue;
    }
    // FIXME.
    // Primary objects have contiguous global cell ID ranges
    // but not necessarily point IDs (example: primary surface
    // and volume meshes may share points). Maybe create a
    // "vertex" cell marked primary guaranteed to have contiguous
    // point IDs, which we should always prefer when fieldType is
    // POINTS?
    auto* primaryIds =
      vtkIntArray::SafeDownCast(it.second->GetAttributes(fieldType)->GetGlobalIds());
    if (!primaryIds)
    {
      continue;
    }
    tmp = primaryIds->GetValueRange(0);
    std::array<int, 2> primIdRange{ tmp[0], tmp[1] };
    if (primIdRange[1] < dataIdRange[0] || primIdRange[0] > dataIdRange[1])
    {
      continue;
    }
    // ID ranges overlap. We must handle a special case.
    if (primIdRange[0] < dataIdRange[0] && primIdRange[1] > dataIdRange[1])
    {
      // VTK data may have IDs from primary data "before" and "after"
      // the current primary data, but not the current object.
      // Verify by examining every ID until we find at least one in range.
      bool inRange = false;
      for (vtkIdType ii = 0; ii < nn; ++ii)
      {
        int id = gids->GetValue(ii);
        if (id >= primIdRange[0] && id <= primIdRange[1])
        {
          inRange = true;
          break;
        }
      }
      if (!inRange)
      {
        continue;
      }
    }
    didAdd = true;
    primaries.insert(comp.get());
  }

  return didAdd;
}

bool Session::primaryGeometries(const smtk::model::Entity* in,
  std::set<smtk::model::Entity*>& primaries) const
{
  if (!in)
  {
    return false;
  }
  auto geom = this->findStorage(in->id());
  if (!geom)
  {
    return false;
  }
  int fieldType = in->dimension() == 0 ? vtkDataObject::POINT : vtkDataObject::CELL;
  return this->primaryGeometries(geom, fieldType, primaries, in->resource().get());
}

smtk::model::Entity* Session::primaryGeometry(const smtk::model::Entity* in) const
{
  std::set<smtk::model::Entity*> primaries;
  if (!this->primaryGeometries(in, primaries) || primaries.size() != 1)
  {
    return nullptr;
  }
  return *primaries.begin();
}

smtk::common::UUID Session::primaryGeometryForCell(vtkIdType globalId) const
{
  for (const auto& entry : m_storage)
  {
    auto* attr = entry.second->GetAttributes(vtkDataObject::CELL);
    if (attr)
    {
      auto* gids = vtkIntArray::SafeDownCast(attr->GetGlobalIds());
      if (gids)
      {
        int* range = gids->GetValueRange();
        if (range[0] <= globalId && globalId <= range[1])
        {
          return entry.first;
        }
      }
    }
  }
  return smtk::common::UUID::null();
}

smtk::common::UUID Session::primaryGeometryForPoint(vtkIdType globalId) const
{
  for (const auto& entry : m_storage)
  {
    auto* attr = entry.second->GetAttributes(vtkDataObject::POINT);
    if (attr)
    {
      auto* gids = vtkIntArray::SafeDownCast(attr->GetGlobalIds());
      if (gids)
      {
        int* range = gids->GetValueRange();
        if (range[0] <= globalId && globalId <= range[1])
        {
          return entry.first;
        }
      }
    }
  }
  return smtk::common::UUID::null();
}

smtk::model::SessionInfoBits Session::transcribeInternal(const smtk::model::EntityRef& /*entity*/,
  SessionInfoBits /*requestedInfo*/,
  int /*depth*/)
{
  return smtk::model::SESSION_EVERYTHING;
}

smtk::model::SessionIOPtr Session::createIODelegate(const std::string& /*format*/)
{
  // TODO: If we write any custom JSON to .smtk files, return a handler.
  return nullptr;
}

void Session::offsetGlobalIds(vtkDataSet* data,
  long pointIdOffset,
  long cellIdOffset,
  long& maxPointId,
  long& maxCellId)
{
  if (!data)
  {
    return;
  }
  vtkIntArray* pointDataNums = vtkIntArray::SafeDownCast(data->GetPointData()->GetGlobalIds());
  vtkIntArray* cellDataNums = vtkIntArray::SafeDownCast(data->GetCellData()->GetGlobalIds());

  if (!pointDataNums || !cellDataNums)
  {
    // need to generate globalIds.
    vtkNew<vtkGenerateGlobalIds> generator;
    generator->SetInputDataObject(data);
    generator->Update();
    auto* outdata = vtkDataSet::SafeDownCast(generator->GetOutputDataObject(0));

    // transform point to ints, to matcch .med format
    if (!pointDataNums)
    {
      vtkSmartPointer<vtkIntArray> newPointIds = vtkIntArray::New();
      newPointIds->DeepCopy(outdata->GetPointData()->GetGlobalIds());
      data->GetPointData()->SetGlobalIds(newPointIds);
      pointDataNums = vtkIntArray::SafeDownCast(data->GetPointData()->GetGlobalIds());
    }

    if (!cellDataNums)
    {
      vtkSmartPointer<vtkIntArray> newCellIds = vtkIntArray::New();
      newCellIds->DeepCopy(outdata->GetCellData()->GetGlobalIds());
      data->GetCellData()->SetGlobalIds(newCellIds);
      cellDataNums = vtkIntArray::SafeDownCast(data->GetCellData()->GetGlobalIds());
    }
  }

  if (pointDataNums && pointDataNums->GetNumberOfValues() > 0)
  {
    bool doOffset = pointDataNums->GetValue(0) <= pointIdOffset;
    for (int i = 0; i < pointDataNums->GetNumberOfValues(); ++i)
    {
      if (doOffset)
      {
        pointDataNums->SetValue(i, pointDataNums->GetValue(i) + pointIdOffset);
      }
      maxPointId = std::max(maxPointId, static_cast<long>(pointDataNums->GetValue(i)));
    }
  }

  if (cellDataNums && cellDataNums->GetNumberOfValues() > 0)
  {
    bool doOffset = cellDataNums->GetValue(0) <= cellIdOffset;
    for (int i = 0; i < cellDataNums->GetNumberOfValues(); ++i)
    {
      if (doOffset)
      {
        cellDataNums->SetValue(i, cellDataNums->GetValue(i) + cellIdOffset);
      }
      maxCellId = std::max(maxCellId, static_cast<long>(cellDataNums->GetValue(i)));
    }
  }
}

void Session::setPrimary(smtk::resource::Component& c, bool isPrimary)
{
  if (isPrimary)
  {
    c.properties().get<long>()["primary"] = 1;
    c.properties().erase<long>("side-set");
  }
  else
  {
    c.properties().get<long>()["side-set"] = 1;
    c.properties().erase<long>("primary");
  }
}

bool Session::isPrimary(const smtk::resource::Component& c)
{
  if (c.properties().contains<long>("primary"))
  {
    return !!c.properties().at<long>("primary");
  }
  return false;
}

} // namespace aeva
} // namespace session
} // namespace smtk
