// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-FileCopyrightText: Copyright (c) 2006 Atamai, Inc.
// SPDX-License-Identifier: BSD-3-Clause

#include "vtkMINCImageAttributes.h"

#include "vtkObjectFactory.h"

#include "vtkCharArray.h"
#include "vtkDoubleArray.h"
#include "vtkFloatArray.h"
#include "vtkIdTypeArray.h"
#include "vtkIntArray.h"
#include "vtkShortArray.h"
#include "vtkSignedCharArray.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkUnsignedCharArray.h"

#include "vtkMINC.h"
#include "vtk_netcdf.h"

#include <cctype>
#include <cfloat>
#include <cstdlib>
#include <map>
#include <sstream>
#include <string>

//------------------------------------------------------------------------------
// A container for mapping attribute names to arrays
VTK_ABI_NAMESPACE_BEGIN
class vtkMINCImageAttributeMap
{
public:
  typedef std::map<std::string, vtkSmartPointer<vtkObject>> MapType;

  static vtkMINCImageAttributeMap* New() { return new vtkMINCImageAttributeMap; }

  void Delete() { delete this; }

  void Clear() { this->Map.clear(); }

  void AddArray(vtkDataArray* array) { this->AddObject(array->GetName(), array); }

  void AddArray(vtkStringArray* array) { this->AddObject(array->GetName(), array); }

  vtkDataArray* GetDataArray(const char* name) const
  {
    return vtkDataArray::SafeDownCast(this->GetObject(name));
  }

  vtkCharArray* GetCharArray(const char* name) const
  {
    return vtkCharArray::SafeDownCast(this->GetObject(name));
  }

  vtkDoubleArray* GetDoubleArray(const char* name) const
  {
    return vtkDoubleArray::SafeDownCast(this->GetObject(name));
  }

  vtkStringArray* GetStringArray(const char* name) const
  {
    return vtkStringArray::SafeDownCast(this->GetObject(name));
  }

protected:
  void AddObject(const char* name, vtkObject* object) { this->Map[name] = object; }

  vtkObject* GetObject(const char* name) const
  {
    if (!name)
    {
      return nullptr;
    }
    MapType::const_iterator iter = this->Map.find(name);
    if (iter != this->Map.end())
    {
      return iter->second;
    }
    return nullptr;
  }

private:
  vtkMINCImageAttributeMap() = default;
  ~vtkMINCImageAttributeMap() = default;

  MapType Map;
};

//------------------------------------------------------------------------------
vtkStandardNewMacro(vtkMINCImageAttributes);

vtkCxxSetObjectMacro(vtkMINCImageAttributes, ImageMin, vtkDoubleArray);
vtkCxxSetObjectMacro(vtkMINCImageAttributes, ImageMax, vtkDoubleArray);

//------------------------------------------------------------------------------
vtkMINCImageAttributes::vtkMINCImageAttributes()
{
  this->DimensionNames = vtkStringArray::New();
  this->DimensionLengths = vtkIdTypeArray::New();

  this->VariableNames = vtkStringArray::New();

  this->AttributeNames = vtkMINCImageAttributeMap::New();
  // Add global attribute name array
  vtkStringArray* tmparray = vtkStringArray::New();
  tmparray->SetName("");
  this->AttributeNames->AddArray(tmparray);
  tmparray->Delete();

  this->AttributeValues = vtkMINCImageAttributeMap::New();
  this->StringStore = nullptr;

  this->NumberOfImageMinMaxDimensions = 0;
  this->ImageMin = nullptr;
  this->ImageMax = nullptr;

  this->Name = nullptr;
  this->DataType = VTK_VOID;

  this->ValidateAttributes = 1;
}

//------------------------------------------------------------------------------
vtkMINCImageAttributes::~vtkMINCImageAttributes()
{
  this->SetName(nullptr);

  if (this->DimensionNames)
  {
    this->DimensionNames->Delete();
    this->DimensionNames = nullptr;
  }
  if (this->DimensionLengths)
  {
    this->DimensionLengths->Delete();
    this->DimensionLengths = nullptr;
  }
  if (this->VariableNames)
  {
    this->VariableNames->Delete();
    this->VariableNames = nullptr;
  }
  if (this->AttributeNames)
  {
    this->AttributeNames->Delete();
    this->AttributeNames = nullptr;
  }
  if (this->AttributeValues)
  {
    this->AttributeValues->Delete();
    this->AttributeValues = nullptr;
  }
  if (this->ImageMin)
  {
    this->ImageMin->Delete();
    this->ImageMin = nullptr;
  }
  if (this->ImageMax)
  {
    this->ImageMax->Delete();
    this->ImageMax = nullptr;
  }
  if (this->StringStore)
  {
    this->StringStore->Delete();
    this->StringStore = nullptr;
  }
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Name: " << (this->Name ? this->Name : "(none)") << "\n";
  os << indent << "DataType: " << this->DataType << "\n";
  os << indent << "DimensionNames: " << this->DimensionNames << "\n";
  os << indent << "DimensionLengths: " << this->DimensionLengths << "\n";
  os << indent << "VariableNames: " << this->VariableNames << "\n";
  os << indent << "ImageMin: " << this->ImageMin << "\n";
  os << indent << "ImageMax: " << this->ImageMax << "\n";
  os << indent << "NumberOfImageMinMaxDimensions: " << this->NumberOfImageMinMaxDimensions << "\n";
  os << indent << "ValidateAttributes: " << (this->ValidateAttributes ? "On\n" : "Off\n");
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::Reset()
{
  this->SetName(nullptr);
  this->SetDataType(VTK_VOID);
  this->SetImageMin(nullptr);
  this->SetImageMax(nullptr);
  this->AttributeValues->Clear();
  this->AttributeNames->Clear();
  this->VariableNames->Reset();
  this->DimensionNames->Reset();
  this->DimensionLengths->Reset();
  if (this->StringStore)
  {
    this->StringStore->Reset();
  }
  this->NumberOfImageMinMaxDimensions = 0;

  // Add global attribute name array
  vtkStringArray* tmparray = vtkStringArray::New();
  tmparray->SetName("");
  this->AttributeNames->AddArray(tmparray);
  tmparray->Delete();
}

//------------------------------------------------------------------------------
// Allowed dimension variable names
static const char* vtkMINCDimVarNames[] = { MIxspace, MIyspace, MIzspace, MItime, MIxfrequency,
  MIyfrequency, MIzfrequency, MItfrequency, nullptr };

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::AddDimension(const char* dimension, vtkIdType length)
{
  // Check for duplicates
  vtkIdType n = this->DimensionNames->GetNumberOfValues();
  for (vtkIdType i = 0; i < n; i++)
  {
    if (this->DimensionNames->GetValue(i) == dimension)
    {
      vtkErrorMacro("The dimension " << dimension << " has already been created.");
      return;
    }
  }

  // Ensure the dimension name is valid
  const char** tryname = nullptr;
  for (tryname = vtkMINCDimVarNames; *tryname != nullptr; tryname++)
  {
    if (strcmp(dimension, *tryname) == 0)
    {
      break;
    }
  }
  if (*tryname == nullptr && strcmp(dimension, MIvector_dimension) != 0)
  {
    vtkWarningMacro("The dimension name " << dimension << " is not recognized.");
  }

  this->DimensionNames->InsertNextValue(dimension);
  this->DimensionLengths->InsertNextTuple1(length);
}

//------------------------------------------------------------------------------
// This method also has to store the resulting string internally.
const char* vtkMINCImageAttributes::ConvertDataArrayToString(vtkDataArray* array)
{
  const char* result = "";
  vtkIdType n = array->GetNumberOfTuples();
  if (n == 0)
  {
    return result;
  }

  int dataType = array->GetDataType();
  if (dataType == VTK_CHAR)
  {
    vtkCharArray* charArray = vtkArrayDownCast<vtkCharArray>(array);
    if (charArray)
    {
      result = charArray->GetPointer(0);
      // Check to see if string has a terminal null (the null might be
      // part of the attribute, or stored in the following byte)
      if ((n > 0 && result[n - 1] == '\0') || (charArray->GetSize() > n && result[n] == '\0'))
      {
        return result;
      }
    }
  }

  std::ostringstream os;

  for (vtkIdType i = 0; i < n; i++)
  {
    double val = array->GetComponent(i, 0);
    if (dataType == VTK_DOUBLE || dataType == VTK_FLOAT)
    {
      // Use NetCDF's usual precision for printing the values
      char storage[128];
      if (dataType == VTK_DOUBLE)
      {
        snprintf(storage, 128, "%0.15g", val);
      }
      else
      {
        snprintf(storage, 128, "%0.7g", val);
      }
      // Add a decimal if there isn't one, to distinguish from int
      for (char* cp = storage; *cp != '.'; cp++)
      {
        if (*cp == '\0')
        {
          *cp++ = '.';
          *cp = '\0';
          break;
        }
      }
      os << storage;
    }
    else if (dataType == VTK_CHAR)
    {
      os.put(static_cast<char>(val));
    }
    else
    {
      os << val;
    }
    if (i < n - 1 && dataType != VTK_CHAR)
    {
      os << ", ";
    }
  }

  // Store the string
  std::string str = os.str();

  if (this->StringStore == nullptr)
  {
    this->StringStore = vtkStringArray::New();
  }

  // See if the string is already stored
  vtkIdType m = this->StringStore->GetNumberOfValues();
  vtkIdType j;
  for (j = 0; j < m; j++)
  {
    if (str == this->StringStore->GetValue(j))
    {
      result = this->StringStore->GetValue(j).c_str();
      break;
    }
  }
  // If not, add it to the array.
  if (j == m)
  {
    j = this->StringStore->InsertNextValue(str);
    result = this->StringStore->GetValue(j).c_str();
  }

  return result;
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::PrintFileHeader()
{
  this->PrintFileHeader(cout);
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::PrintFileHeader(ostream& os)
{
  const char* name = "unknown";
  if (this->Name)
  {
    name = this->Name;
  }

  // Get the data type
  const char* imageDataType;
  switch (this->DataType)
  {
    case VTK_CHAR:
    case VTK_SIGNED_CHAR:
    case VTK_UNSIGNED_CHAR:
      imageDataType = "byte";
      break;
    case VTK_SHORT:
    case VTK_UNSIGNED_SHORT:
      imageDataType = "short";
      break;
    case VTK_INT:
    case VTK_UNSIGNED_INT:
      imageDataType = "int";
      break;
    case VTK_FLOAT:
      imageDataType = "float";
      break;
    case VTK_DOUBLE:
      imageDataType = "double";
      break;
    default:
      imageDataType = "void";
  }

  os << "netcdf " << name << " {\n";
  os << "dimensions:\n";

  vtkIdType ndim = 0;
  if (this->DimensionNames)
  {
    ndim = this->DimensionNames->GetNumberOfValues();
  }
  for (vtkIdType idim = 0; idim < ndim; idim++)
  {
    os << "\t" << this->DimensionNames->GetValue(idim) << " = "
       << this->DimensionLengths->GetValue(idim) << " ;\n";
  }

  os << "variables:\n";

  vtkIdType nvar = 0;
  vtkIdType ivar = 0;
  if (this->VariableNames)
  {
    nvar = this->VariableNames->GetNumberOfValues();
  }
  for (ivar = 0; ivar < nvar + 1; ivar++)
  {
    std::string varname;
    if (ivar == nvar)
    {
      os << "\n// global attributes:\n";
    }
    else
    {
      varname = this->VariableNames->GetValue(ivar);
      if (varname == MIimage || varname == MIimagemax || varname == MIimagemin)
      {
        vtkIdType nvardim = this->DimensionNames->GetNumberOfValues();
        // If this is image-min or image-max, only print the
        // dimensions for these variables
        if (varname[5] == '-')
        {
          if (this->NumberOfImageMinMaxDimensions < nvardim)
          {
            nvardim = this->NumberOfImageMinMaxDimensions;
          }
          os << "\tdouble " << varname;
        }
        else
        {
          os << "\t" << imageDataType << " " << varname;
        }

        if (nvardim > 0)
        {
          os << "(";
          for (int ivardim = 0; ivardim < nvardim; ivardim++)
          {
            os << this->DimensionNames->GetValue(ivardim);
            if (ivardim < nvardim - 1)
            {
              os << ", ";
            }
          }
          os << ")";
        }
        os << " ;\n";
      }
      else
      {
        os << "\t"
           << "int " << varname << " ;\n";
      }
    }
    vtkStringArray* attArray = this->AttributeNames->GetStringArray(varname.c_str());
    if (attArray)
    {
      vtkIdType natt = attArray->GetNumberOfValues();
      for (vtkIdType iatt = 0; iatt < natt; iatt++)
      {
        std::string attname = attArray->GetValue(iatt);
        vtkDataArray* array = this->GetAttributeValueAsArray(varname.c_str(), attname.c_str());
        os << "\t\t" << varname << ":" << attname << " = ";
        if (array->GetDataType() == VTK_CHAR)
        {
          os << "\"";
          const char* cp = this->ConvertDataArrayToString(array);
          const char* endcp = cp + strlen(cp);
          char text[512];
          text[0] = '\0';
          while (cp < endcp)
          {
            int c = 0;
            int j;
            for (j = 0; j < 508 && cp < endcp; j++, cp++)
            {
              c = *cp;
              if (c == '\0' && (cp + 1) == endcp)
              {
                // break if at terminal null
                cp++;
                break;
              }
              if (isprint(c) && c != '\\')
              {
                text[j] = c;
              }
              else
              {
                // quote the non-printing characters
                switch (c)
                {
                  case '\\':
                    text[j++] = '\\';
                    text[j] = '\\';
                    break;
                  case '\b':
                    text[j++] = '\\';
                    text[j] = '\b';
                    break;
                  case '\n':
                    text[j++] = '\\';
                    text[j] = 'n';
                    break;
                  case '\r':
                    text[j++] = '\\';
                    text[j] = 'r';
                    break;
                  case '\f':
                    text[j++] = '\\';
                    text[j] = 'f';
                    break;
                  case '\v':
                    text[j++] = '\\';
                    text[j] = 'v';
                    break;
                  case '\'':
                    text[j++] = '\\';
                    text[j] = '\'';
                    break;
                  case '\"':
                    text[j++] = '\\';
                    text[j] = '\"';
                    break;
                  default:
                    text[j++] = '\\';
                    text[j++] = '0' + ((c & 0xc0) >> 6);
                    text[j++] = '0' + ((c & 0x38) >> 3);
                    text[j] = '0' + (c & 0x7);
                    break;
                }
              }
              if (c == '\n')
              {
                j++;
                cp++;
                break;
              }
            }

            text[j] = '\0';
            os << text;
            text[0] = '\0';

            // Start a new string after each newline, unless this
            // newline is the final character.
            if (c == '\n' && cp < endcp)
            {
              os << "\",\n\t\t\t\"";
            }
          }
          os << "\" ;\n";
        }
        else
        {
          // Use handy conversion method
          os << this->ConvertDataArrayToString(array) << " ;\n";
        }
      }
    }
  }

  os << "data:\n";

  if (this->VariableNames)
  {
    nvar = this->VariableNames->GetNumberOfValues();
  }
  for (ivar = 0; ivar < nvar; ivar++)
  {
    std::string varname = this->VariableNames->GetValue(ivar);

    if (varname == MIimage)
    {
      continue;
    }

    os << "\n " << varname << " = ";

    if (varname == MIimagemin)
    {
      if (this->ImageMin)
      {
        os << this->ConvertDataArrayToString(this->ImageMin) << " ;\n";
      }
      else
      {
        os << "0. ;\n";
      }
    }
    else if (varname == MIimagemax)
    {
      if (this->ImageMax)
      {
        os << this->ConvertDataArrayToString(this->ImageMax) << " ;\n";
      }
      else
      {
        os << "1. ;\n";
      }
    }
    else
    {
      os << "_ ;\n";
    }
  }

  os << "}\n";
}

//------------------------------------------------------------------------------
vtkStringArray* vtkMINCImageAttributes::GetAttributeNames(const char* variable)
{
  // If variable is null, use empty string to get global attributes
  if (variable == nullptr)
  {
    variable = MI_EMPTY_STRING;
  }

  return this->AttributeNames->GetStringArray(variable);
}

//------------------------------------------------------------------------------
vtkTypeBool vtkMINCImageAttributes::HasAttribute(const char* variable, const char* attribute)
{
  return (this->GetAttributeValueAsArray(variable, attribute) != nullptr);
}

//------------------------------------------------------------------------------
vtkDataArray* vtkMINCImageAttributes::GetAttributeValueAsArray(
  const char* variable, const char* attribute)
{
  std::string path = MI_GRPNAME;
  if (variable && variable[0] != '\0')
  {
    path += MI_GRP_SEP;
    path += variable;
  }
  path += MI_ATT_SEP;
  path += attribute;

  return this->AttributeValues->GetDataArray(path.c_str());
}

//------------------------------------------------------------------------------
const char* vtkMINCImageAttributes::GetAttributeValueAsString(
  const char* variable, const char* attribute)
{
  vtkDataArray* array = this->GetAttributeValueAsArray(variable, attribute);

  // Return nullptr if not found
  if (array == nullptr)
  {
    return nullptr;
  }

  // Convert any other array to a string.
  return this->ConvertDataArrayToString(array);
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::GetAttributeValueAsInt(const char* variable, const char* attribute)
{
  vtkDataArray* array = this->GetAttributeValueAsArray(variable, attribute);

  if (array == nullptr)
  {
    vtkErrorMacro("The attribute " << variable << ":" << attribute << " was not found.");
    return 0;
  }

  if (array->GetDataType() == VTK_CHAR)
  {
    const char* text = this->ConvertDataArrayToString(array);
    char* endp = const_cast<char*>(text);
    long result = strtol(text, &endp, 10);
    // Check for complete conversion
    if (*endp == '\0' && *text != '\0')
    {
      return static_cast<int>(result);
    }
  }
  else if (array->GetNumberOfTuples() == 1)
  {
    switch (array->GetDataType())
    {
      case VTK_SIGNED_CHAR:
      case VTK_UNSIGNED_CHAR:
      case VTK_SHORT:
      case VTK_INT:
        return static_cast<int>(array->GetComponent(0, 0));
      default:
        break;
    }
  }

  vtkErrorMacro("GetAttributeValueAsInt() used on non-integer attribute " << variable << ":"
                                                                          << attribute << ".");
  return static_cast<int>(array->GetComponent(0, 0));
}

//------------------------------------------------------------------------------
double vtkMINCImageAttributes::GetAttributeValueAsDouble(
  const char* variable, const char* attribute)
{
  if (variable == nullptr)
  {
    variable = MI_EMPTY_STRING;
  }

  vtkDataArray* array = this->GetAttributeValueAsArray(variable, attribute);

  if (array == nullptr)
  {
    vtkErrorMacro("The attribute " << variable << ":" << attribute << " was not found.");
    return 0;
  }

  if (array->GetDataType() == VTK_CHAR)
  {
    const char* text = this->ConvertDataArrayToString(array);
    char* endp = const_cast<char*>(text);
    double result = strtod(text, &endp);
    // Check for complete conversion
    if (*endp == '\0' && *text != '\0')
    {
      return result;
    }
  }
  else if (array->GetNumberOfTuples() == 1)
  {
    switch (array->GetDataType())
    {
      case VTK_SIGNED_CHAR:
      case VTK_UNSIGNED_CHAR:
      case VTK_SHORT:
      case VTK_INT:
      case VTK_FLOAT:
      case VTK_DOUBLE:
        return array->GetComponent(0, 0);
      default:
        break;
    }
  }

  vtkErrorMacro("GetAttributeValueAsDouble() used on non-real attribute " << variable << ":"
                                                                          << attribute << ".");
  return array->GetComponent(0, 0);
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::SetAttributeValueAsArray(
  const char* variable, const char* attribute, vtkDataArray* array)
{
  std::string path = MI_GRPNAME;
  if (variable && variable[0] != '\0')
  {
    path += MI_GRP_SEP;
    path += variable;
  }
  path += MI_ATT_SEP;
  path += attribute;

  array->SetName(path.c_str());
  this->AttributeValues->AddArray(array);

  // Add to variable to VariableNames
  vtkIdType n = this->VariableNames->GetNumberOfValues();
  vtkIdType i = 0;
  for (i = 0; i < n; i++)
  {
    if (this->VariableNames->GetValue(i) == variable)
    {
      break;
    }
  }
  if (i == n && variable[0] != '\0')
  {
    this->VariableNames->InsertNextValue(variable);
  }

  // Add to attribute to AttributeNames
  vtkStringArray* attribs = this->AttributeNames->GetStringArray(variable);
  // Create a new array if necessary
  if (attribs == nullptr)
  {
    attribs = vtkStringArray::New();
    attribs->SetName(variable);
    this->AttributeNames->AddArray(attribs);
    attribs->Delete();
  }

  n = attribs->GetNumberOfValues();
  for (i = 0; i < n; i++)
  {
    if (attribs->GetValue(i) == attribute)
    {
      break;
    }
  }
  if (i == n)
  {
    attribs->InsertNextValue(attribute);
  }

  if (this->ValidateAttributes)
  {
    // Print warning if there is something wrong with the attribute
    int result = this->ValidateAttribute(variable, attribute, array);

    if (result > 1)
    {
      vtkWarningMacro("Attribute " << variable << ":" << attribute << " is not a valid attribute.");
    }
  }
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::SetAttributeValueAsString(
  const char* variable, const char* attribute, const char* value)
{
  size_t length = strlen(value);

  vtkCharArray* array = vtkCharArray::New();
  // Allocate an extra byte to store a null terminator.
  array->Resize(static_cast<vtkIdType>(length + 1));
  char* dest = array->WritePointer(0, static_cast<vtkIdType>(length));
  strcpy(dest, value);
  this->SetAttributeValueAsArray(variable, attribute, array);

  array->Delete();
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::SetAttributeValueAsInt(
  const char* variable, const char* attribute, int value)
{
  vtkIntArray* array = vtkIntArray::New();
  array->SetNumberOfValues(1);
  array->SetValue(0, value);

  this->SetAttributeValueAsArray(variable, attribute, array);

  array->Delete();
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::SetAttributeValueAsDouble(
  const char* variable, const char* attribute, double value)
{
  vtkDoubleArray* array = vtkDoubleArray::New();
  array->SetNumberOfValues(1);
  array->SetValue(0, value);

  this->SetAttributeValueAsArray(variable, attribute, array);

  array->Delete();
}

//------------------------------------------------------------------------------
// These validation methods have three return values:
// 0 means that the attribute should be skipped
// 1 means that the attribute should be set
// 2 means that the attribute wasn't recognized

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateGlobalAttribute(
  const char* attname, vtkDataArray* vtkNotUsed(array))
{
  // Global attributes
  static const char* globalAttributes[] = { MIident, MIhistory, MItitle, nullptr };
  const int autoGlobalAttributes = 2;

  int itry = 0;
  for (itry = 0; globalAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, globalAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (itry < autoGlobalAttributes)
  {
    // Skip to the next attribute
    return 0;
  }
  else if (globalAttributes[itry] == nullptr)
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateGeneralAttribute(
  const char* varname, const char* attname, vtkDataArray* array)
{
  // Attributes that all MINC variables have
  static const char* generalAttributes[] = { MIvartype, // MI_GROUP, MI_DIMENSION, MI_VARATT
    MIvarid,                                            // MI_STDVAR
    MIversion,                                          // MI_VERSION_1_0
    MIparent,                                           // parent variable for this variable
    MIchildren,                                         // newline-separated list of child variables
    MIcomments, // each variable has specific comments to go with it
    nullptr };
  const int autoGeneralAttributes = 5;

  int dataType = array->GetDataType();

  // Check to see if the attribute is one that we automatically generate.
  int itry = 0;
  for (itry = 0; generalAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, generalAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (itry < autoGeneralAttributes)
  {
    // Skip to the next attribute
    return 0;
  }
  else if (generalAttributes[itry] != nullptr)
  {
    if (dataType != VTK_CHAR)
    {
      vtkWarningMacro("The attribute " << varname << ":" << attname << " has the wrong type ("
                                       << dataType << ").");
      return 0;
    }
  }
  else
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateDimensionAttribute(
  const char* varname, const char* attname, vtkDataArray* array)
{
  // Attributes for dimension variables (vartype = MI_DIMENSION)
  static const char* dimensionAttributes[] = { MIstep, MIstart,
    MIspacing,           // MI_REGULAR ("irregular" not supported)
    MIspacetype,         // "native____", "talairach_", "calossal__"
    MIalignment,         // MI_CENTRE ("start_", "end___" not supported)
    MIunits,             // "mm"
    MIdirection_cosines, // three doubles
    nullptr };
  const int autoDimensionAttributes = 3;

  vtkIdType size = (array->GetNumberOfTuples() * array->GetNumberOfComponents());
  int dataType = array->GetDataType();

  int itry = 0;
  for (itry = 0; dimensionAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, dimensionAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (itry < autoDimensionAttributes)
  {
    // Skip to the next attribute
    return 0;
  }
  else if (strcmp(attname, MIdirection_cosines) == 0)
  {
    if (varname[0] == 'x' || varname[0] == 'y' || varname[0] == 'z')
    {
      if (dataType != VTK_DOUBLE || size != 3)
      {
        vtkWarningMacro("The attribute " << varname << ":" << attname << " has the wrong type ("
                                         << dataType << ") or size (" << size << ").");
        return 0;
      }
    }
    else
    {
      vtkWarningMacro("Dimension " << varname
                                   << " cannot have"
                                      " a direction_cosines attribute");
      return 0;
    }
  }
  else if (dimensionAttributes[itry] != nullptr)
  {
    if (dataType != VTK_CHAR)
    {
      vtkWarningMacro("The attribute " << varname << ":" << attname << " has the wrong type ("
                                       << dataType << ").");
      return 0;
    }
  }
  else
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateImageAttribute(
  const char* vtkNotUsed(varname), const char* attname, vtkDataArray* vtkNotUsed(array))
{
  // Attributes for the MIimage variable (vartype = MI_GROUP)
  static const char* imageAttributes[] = {
    MIcomplete,     // MI_TRUE (MI_FALSE means not yet all written)
    MIimagemin      // "--->image-min" variable attribute pointer
      MIimagemax    // "--->image-max" variable attribute pointer
        MIsigntype, // MI_SIGNED or MI_UNSIGNED
    MIvalid_range,  // min and max scalar values as doubles
    nullptr
  };
  const int autoImageAttributes = 5;

  int itry = 0;
  for (itry = 0; imageAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, imageAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (itry < autoImageAttributes)
  {
    // Skip to the next attribute
    return 0;
  }
  else if (imageAttributes[itry] == nullptr)
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateImageMinMaxAttribute(
  const char* varname, const char* attname, vtkDataArray* array)
{
  // Attributes for MIimagemin, MIimagemax (vartype = MI_VARATT)
  static const char* imageMinMaxAttributes[] = {
    MI_FillValue, // 0.0 for image-min, 1.0 for image-max
    MIunits,      // "normalized", "Hounsfields", etc.
    nullptr
  };
  const int autoImageMinMaxAttributes = 1;

  int itry = 0;
  for (itry = 0; imageMinMaxAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, imageMinMaxAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (itry < autoImageMinMaxAttributes)
  {
    // Skip to the next attribute
    return 0;
  }
  else if (imageMinMaxAttributes[itry] != nullptr)
  {
    int dataType = array->GetDataType();
    if (dataType != VTK_CHAR)
    {
      vtkWarningMacro("The attribute " << varname << ":" << attname << " has the wrong type ("
                                       << dataType << ").");
      return 0;
    }
  }
  else
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidatePatientAttribute(
  const char* vtkNotUsed(varname), const char* attname, vtkDataArray* vtkNotUsed(array))
{
  // Attributes for MIpatient variable (vartype = MI_GROUP)
  static const char* patientAttributes[] = { MIfull_name, // "LASTNAME^FIRSTNAME SECONDNAME"
    MIother_names,                                        // newline-separated string
    MIidentification, MIother_ids,
    MIbirthdate, // "YYYYMMDD"
    MIsex,       // "male__", "female", "other_"
    MIage,       // "XXXD", "XXXM", or "XXXY" (days, months, years)
    MIweight,    // "XXkg", "X.Xkg" (assume kg if no units given)
    MIsize,      // "XXXcm" (assume meters if no units given)
    MIaddress,   // newline-separated string
    MIinsurance_id, nullptr };

  int itry = 0;
  for (itry = 0; patientAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, patientAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (patientAttributes[itry] != nullptr)
  {
    // Add checks for correct data type?
  }
  else
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateStudyAttribute(
  const char* vtkNotUsed(varname), const char* attname, vtkDataArray* vtkNotUsed(array))
{
  // Attributes for MIstudy variable (vartype = MI_GROUP)
  static const char* studyAttributes[] = { MIstudy_id,
    MIstart_time,    // "YYYYMMDDHHMMSS.SS"
    MIstart_year,    // as int (use start_time instead)
    MIstart_month,   // as int (use start_time instead)
    MIstart_day,     // as int (use start_time instead)
    MIstart_hour,    // as int (use start_time instead)
    MIstart_minute,  // as int (use start_time instead)
    MIstart_seconds, // as double or int (use start_time instead)
    MImodality,      // "PET__", "SPECT", "GAMMA", "MRI__", "MRS__",
                     // "MRA__", "CT___", "DSA__", "DR___", "label"
    MImanufacturer, MIdevice_model, MIinstitution, MIdepartment, MIstation_id,
    MIreferring_physician, MIattending_physician, MIradiologist, MIoperator, MIadmitting_diagnosis,
    MIprocedure, nullptr };

  int itry = 0;
  for (itry = 0; studyAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, studyAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (studyAttributes[itry] != nullptr)
  {
    // Add checks for correct data type?
  }
  else
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateAcquisitionAttribute(
  const char* vtkNotUsed(varname), const char* attname, vtkDataArray* vtkNotUsed(array))
{
  // Attributes for MIacquisition variable (vartype = MI_GROUP)
  static const char* acquisitionAttributes[] = { MIprotocol,
    MIscanning_sequence, // "GR", "SPGR", etc.
    MIrepetition_time,   // as double, milliseconds
    MIecho_time,         // as double, milliseconds
    MIinversion_time,    // as double, milliseconds
    MInum_averages,      // as int
    MIimaging_frequency, // in Hz, as double
    MIimaged_nucleus,    // "H1", "C13", etc. for MRI
    MIradionuclide,      // for PET and SPECT
    MIradionuclide_halflife, MIcontrast_agent, MItracer, MIinjection_time, MIinjection_year,
    MIinjection_month, MIinjection_day, MIinjection_hour, MIinjection_minute, MIinjection_seconds,
    MIinjection_length, MIinjection_dose, MIdose_units, MIinjection_volume, MIinjection_route,
    nullptr };

  int itry = 0;
  for (itry = 0; acquisitionAttributes[itry] != nullptr; itry++)
  {
    if (strcmp(attname, acquisitionAttributes[itry]) == 0)
    {
      break;
    }
  }
  if (acquisitionAttributes[itry] != nullptr)
  {
    // Add checks for correct data type?
  }
  else
  {
    return 2;
  }

  return 1;
}

//------------------------------------------------------------------------------
int vtkMINCImageAttributes::ValidateAttribute(
  const char* varname, const char* attname, vtkDataArray* array)
{
  // Standard variable names
  static const char* stdVarNames[] = { MIrootvariable, MIimage, MIimagemin, MIimagemax, MIpatient,
    MIstudy, MIacquisition, nullptr };

  int result = 1;
  int vartype = 0;

  const char** tryname = nullptr;
  for (tryname = stdVarNames; *tryname != nullptr; tryname++)
  {
    if (strcmp(varname, *tryname) == 0)
    {
      vartype = 1;
      break;
    }
  }
  for (tryname = vtkMINCDimVarNames; *tryname != nullptr; tryname++)
  {
    if (strcmp(varname, *tryname) == 0)
    {
      vartype = 2;
      break;
    }
  }

  if (strcmp(varname, MI_EMPTY_STRING) == 0)
  {
    // Check global attributes
    result = this->ValidateGlobalAttribute(attname, array);
    // Allow users to create their own global attributes
    if (result == 2)
    {
      result = 1;
    }
  }
  else if (vartype != 0)
  {
    // Check general attributes
    result = this->ValidateGeneralAttribute(varname, attname, array);
  }
  if (result == 2)
  {
    if (vartype == 2)
    {
      result = this->ValidateDimensionAttribute(varname, attname, array);
    }
    else if (strcmp(varname, MIimage) == 0)
    {
      result = this->ValidateImageAttribute(varname, attname, array);
    }
    else if (strcmp(varname, MIimagemin) == 0 || strcmp(varname, MIimagemax) == 0)
    {
      result = this->ValidateImageMinMaxAttribute(varname, attname, array);
    }
    else if (strcmp(varname, MIpatient) == 0)
    {
      result = this->ValidatePatientAttribute(varname, attname, array);
    }
    else if (strcmp(varname, MIstudy) == 0)
    {
      result = this->ValidateStudyAttribute(varname, attname, array);
    }
    else if (strcmp(varname, MIacquisition) == 0)
    {
      result = this->ValidateAcquisitionAttribute(varname, attname, array);
    }
  }

  return result;
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::FindValidRange(double range[2])
{
  // Find the valid range. Start with the default.
  range[0] = 0.0;
  range[1] = 1.0;

  // Look for the valid_range attribute of the data.
  vtkDoubleArray* rangearray =
    vtkDoubleArray::SafeDownCast(this->GetAttributeValueAsArray(MIimage, MIvalid_range));
  if (rangearray)
  {
    range[0] = rangearray->GetValue(0);
    range[1] = rangearray->GetValue(1);
    if (range[0] > range[1])
    {
      double tmpval = range[0];
      range[0] = range[1];
      range[1] = tmpval;
    }

    if (this->DataType == VTK_FLOAT)
    {
      // use float precision if VTK_FLOAT
      range[0] = static_cast<float>(range[0]);
      range[1] = static_cast<float>(range[1]);
    }
  }
  else
  {
    // If there is no valid_range attribute, use maximum range.
    switch (this->DataType)
    {
      case VTK_CHAR:
      case VTK_SIGNED_CHAR:
        range[0] = VTK_SIGNED_CHAR_MIN;
        range[1] = VTK_SIGNED_CHAR_MAX;
        break;
      case VTK_UNSIGNED_CHAR:
        range[0] = VTK_UNSIGNED_CHAR_MIN;
        range[1] = VTK_UNSIGNED_CHAR_MAX;
        break;
      case VTK_SHORT:
        range[0] = VTK_SHORT_MIN;
        range[1] = VTK_SHORT_MAX;
        break;
      case VTK_UNSIGNED_SHORT:
        range[0] = VTK_UNSIGNED_SHORT_MIN;
        range[1] = VTK_UNSIGNED_SHORT_MAX;
        break;
      case VTK_INT:
        range[0] = VTK_INT_MIN;
        range[1] = VTK_INT_MAX;
        break;
      case VTK_UNSIGNED_INT:
        range[0] = VTK_UNSIGNED_INT_MIN;
        range[1] = VTK_UNSIGNED_INT_MAX;
        break;
      case VTK_FLOAT:
        range[0] = -FLT_MAX;
        range[1] = FLT_MAX;
        break;
      case VTK_DOUBLE:
        range[0] = -DBL_MAX;
        range[1] = DBL_MAX;
        break;
    }
  }

  // If the valid_range is set to the full float range, replace
  // with the image range.
  if ((this->DataType == VTK_FLOAT && range[1] == FLT_MAX) ||
    (this->DataType == VTK_DOUBLE && range[1] == DBL_MAX))
  {
    if (this->ImageMin && this->ImageMax && this->ImageMin->GetNumberOfTuples() > 0 &&
      this->ImageMax->GetNumberOfTuples() > 0)
    {
      range[0] = this->ImageMin->GetRange()[0];
      range[1] = this->ImageMax->GetRange()[1];
    }
    else
    {
      range[0] = 0.0;
      range[1] = 1.0;
    }
  }
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::FindImageRange(double range[2])
{
  // Initialize to the default values
  range[0] = 0.0;
  range[1] = 1.0;

  // If image-min and image-max variables exist, use them.
  // Otherwise, use the valid_range of the data instead.
  if (this->ImageMin && this->ImageMax && this->ImageMin->GetNumberOfTuples() > 0 &&
    this->ImageMax->GetNumberOfTuples() > 0)
  {
    range[0] = this->ImageMin->GetRange()[0];
    range[1] = this->ImageMax->GetRange()[1];
  }
  else
  {
    this->FindValidRange(range);
  }
}

//------------------------------------------------------------------------------
void vtkMINCImageAttributes::ShallowCopy(vtkMINCImageAttributes* source)
{
  this->SetName(source->GetName());
  this->SetDataType(source->GetDataType());

  this->SetImageMin(source->GetImageMin());
  this->SetImageMax(source->GetImageMax());
  this->SetNumberOfImageMinMaxDimensions(source->GetNumberOfImageMinMaxDimensions());

  this->DimensionNames->DeepCopy(source->GetDimensionNames());
  this->DimensionLengths->DeepCopy(source->GetDimensionLengths());

  this->VariableNames->Reset();
  this->AttributeValues->Clear();
  this->AttributeNames->Clear();

  vtkStringArray* varnames = source->GetVariableNames();
  vtkIdType nvar = varnames->GetNumberOfValues();
  for (vtkIdType ivar = 0; ivar <= nvar; ivar++)
  {
    // set varname to empty last time around to get global attributes
    std::string varname;
    if (ivar < nvar)
    {
      varname = varnames->GetValue(ivar);
    }
    vtkStringArray* attnames = source->GetAttributeNames(varname.c_str());
    vtkIdType natt = attnames->GetNumberOfValues();
    for (vtkIdType iatt = 0; iatt < natt; iatt++)
    {
      std::string attname = attnames->GetValue(iatt);
      this->SetAttributeValueAsArray(varname.c_str(), attname.c_str(),
        source->GetAttributeValueAsArray(varname.c_str(), attname.c_str()));
    }
  }

  if (this->StringStore)
  {
    this->StringStore->Reset();
  }
}
VTK_ABI_NAMESPACE_END
