/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkMINCImageAttributes.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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.

=========================================================================*/
/*=========================================================================

Copyright (c) 2006 Atamai, Inc.

Use, modification and redistribution of the software, in source or
binary forms, are permitted provided that the following terms and
conditions are met:

1) Redistribution of the source code, in verbatim or modified
   form, must retain the above copyright notice, this license,
   the following disclaimer, and any notices that refer to this
   license and/or the following disclaimer.

2) Redistribution in binary form must include the above copyright
   notice, a copy of this license and the following disclaimer
   in the documentation or with other materials provided with the
   distribution.

3) Modified copies of the source code must be clearly marked as such,
   and must not be misrepresented as verbatim copies of the source code.

THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS"
WITHOUT EXPRESSED OR IMPLIED WARRANTY INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  IN NO EVENT SHALL ANY COPYRIGHT HOLDER OR OTHER PARTY WHO MAY
MODIFY AND/OR REDISTRIBUTE THE SOFTWARE UNDER THE TERMS OF THIS LICENSE
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, LOSS OF DATA OR DATA BECOMING INACCURATE
OR LOSS OF PROFIT OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF
THE USE OR INABILITY TO USE THE SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

=========================================================================*/

#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
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
  {
    MapType::const_iterator iter = this->Map.find(name);
    if (iter != this->Map.end())
    {
      return iter->second;
    }
    return nullptr;
  }

private:
  vtkMINCImageAttributeMap()
    : Map()
  {
  }
  ~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 (strcmp(dimension, this->DimensionNames->GetValue(i)) == 0)
    {
      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++)
  {
    result = this->StringStore->GetValue(j);
    if (strcmp(str.c_str(), result) == 0)
    {
      break;
    }
  }
  // If not, add it to the array.
  if (j == m)
  {
    j = this->StringStore->InsertNextValue(str.c_str());
    result = this->StringStore->GetValue(j);
  }

  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++)
  {
    const char* varname = MI_EMPTY_STRING;
    if (ivar == nvar)
    {
      os << "\n// global attributes:\n";
    }
    else
    {
      varname = this->VariableNames->GetValue(ivar);
      if (strcmp(varname, MIimage) == 0 || strcmp(varname, MIimagemax) == 0 ||
        strcmp(varname, MIimagemin) == 0)
      {
        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);
    if (attArray)
    {
      vtkIdType natt = attArray->GetNumberOfValues();
      for (vtkIdType iatt = 0; iatt < natt; iatt++)
      {
        const char* attname = attArray->GetValue(iatt);
        vtkDataArray* array = this->GetAttributeValueAsArray(varname, attname);
        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++)
  {
    const char* varname = this->VariableNames->GetValue(ivar);

    if (strcmp(varname, MIimage) == 0)
    {
      continue;
    }

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

    if (strcmp(varname, MIimagemin) == 0)
    {
      if (this->ImageMin)
      {
        os << this->ConvertDataArrayToString(this->ImageMin) << " ;\n";
      }
      else
      {
        os << "0. ;\n";
      }
    }
    else if (strcmp(varname, MIimagemax) == 0)
    {
      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);
}

//------------------------------------------------------------------------------
int 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 (strcmp(this->VariableNames->GetValue(i), variable) == 0)
    {
      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 (strcmp(attribs->GetValue(i), attribute) == 0)
    {
      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
    const char* varname = MI_EMPTY_STRING;
    if (ivar < nvar)
    {
      varname = varnames->GetValue(ivar);
    }
    vtkStringArray* attnames = source->GetAttributeNames(varname);
    vtkIdType natt = attnames->GetNumberOfValues();
    for (vtkIdType iatt = 0; iatt < natt; iatt++)
    {
      const char* attname = attnames->GetValue(iatt);
      this->SetAttributeValueAsArray(
        varname, attname, source->GetAttributeValueAsArray(varname, attname));
    }
  }

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