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

#include "vtkNIFTIImageHeader.h"
#include "vtkNIFTIImagePrivate.h"

#include "vtkObjectFactory.h"

#include <cctype>
#include <cfloat>
#include <cmath>
#include <cstring>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkNIFTIImageHeader);

//------------------------------------------------------------------------------
namespace
{

// utility function to normalize floats that are close to zero
double vtkNIFTINormalizeFloat(double d)
{
  return (fabs(d) < FLT_MIN ? 0.0 : d);
}

double vtkNIFTINormalizeDouble(double d)
{
  return (fabs(d) < DBL_MIN ? 0.0 : d);
}
} // end anonymous namespace

//------------------------------------------------------------------------------
vtkNIFTIImageHeader::vtkNIFTIImageHeader()
{
  this->Initialize();
}

//------------------------------------------------------------------------------
vtkNIFTIImageHeader::~vtkNIFTIImageHeader() = default;

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::Initialize()
{
  memset(this->Magic, '\0', sizeof(this->Magic));
  this->VoxOffset = 0;
  this->DataType = 0;
  this->BitPix = 0;
  for (int i = 0; i < 8; i++)
  {
    this->Dim[i] = 0;
    this->PixDim[i] = 0.0;
  }
  this->IntentCode = 0;
  memset(this->IntentName, '\0', sizeof(this->IntentName));
  this->IntentP1 = 0.0;
  this->IntentP2 = 0.0;
  this->IntentP3 = 0.0;
  this->SclSlope = 0.0;
  this->SclInter = 0.0;
  this->CalMin = 0.0;
  this->CalMax = 0.0;
  this->SliceDuration = 0.0;
  this->TOffset = 0.0;
  this->SliceStart = 0;
  this->SliceEnd = 0;
  this->SliceCode = 0;
  this->XYZTUnits = 0;
  this->DimInfo = 0;
  memset(this->Descrip, '\0', sizeof(this->Descrip));
  memset(this->AuxFile, '\0', sizeof(this->AuxFile));
  this->QFormCode = 0;
  this->SFormCode = 0;
  this->QuaternB = 0.0;
  this->QuaternC = 0.0;
  this->QuaternD = 0.0;
  this->QOffsetX = 0.0;
  this->QOffsetY = 0.0;
  this->QOffsetZ = 0.0;
  for (int i = 0; i < 4; i++)
  {
    this->SRowX[i] = 0.0;
    this->SRowY[i] = 0.0;
    this->SRowZ[i] = 0.0;
  }
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::SetHeader(const nifti_1_header* hdr)
{
  // clear all fields (in case supplied header is Analyze 7.5)
  this->Initialize();

  // check if header is NIfTI (vs. Analyze 7.5)
  bool isnifti = (hdr->magic[0] == 'n' && (hdr->magic[1] == '+' || hdr->magic[1] == 'i') &&
    hdr->magic[2] == '1' && hdr->magic[3] == '\0');

  if (isnifti)
  {
    memcpy(this->Magic, hdr->magic, sizeof(hdr->magic));
  }
  this->VoxOffset = static_cast<vtkTypeInt64>(hdr->vox_offset);
  this->DataType = hdr->datatype;
  this->BitPix = hdr->bitpix;
  for (int i = 0; i < 8; i++)
  {
    this->Dim[i] = hdr->dim[i];
    this->PixDim[i] = hdr->pixdim[i];
  }
  if (isnifti)
  {
    this->IntentCode = hdr->intent_code;
    strncpy(this->IntentName, hdr->intent_name, sizeof(this->IntentName));
    this->IntentP1 = hdr->intent_p1;
    this->IntentP2 = hdr->intent_p2;
    this->IntentP3 = hdr->intent_p3;
    this->SclSlope = hdr->scl_slope;
    this->SclInter = hdr->scl_inter;
  }
  this->CalMin = hdr->cal_min;
  this->CalMax = hdr->cal_max;
  if (isnifti)
  {
    this->SliceDuration = hdr->slice_duration;
    this->TOffset = hdr->toffset;
    this->SliceStart = hdr->slice_start;
    this->SliceEnd = hdr->slice_end;
    this->SliceCode = hdr->slice_code;
  }
  this->XYZTUnits = hdr->xyzt_units;
  this->DimInfo = hdr->dim_info;
  strncpy(this->Descrip, hdr->descrip, sizeof(this->Descrip));
  strncpy(this->AuxFile, hdr->aux_file, sizeof(this->AuxFile));
  if (isnifti)
  {
    this->QFormCode = hdr->qform_code;
    this->SFormCode = hdr->sform_code;
    this->QuaternB = hdr->quatern_b;
    this->QuaternC = hdr->quatern_c;
    this->QuaternD = hdr->quatern_d;
    this->QOffsetX = hdr->qoffset_x;
    this->QOffsetY = hdr->qoffset_y;
    this->QOffsetZ = hdr->qoffset_z;
    for (int i = 0; i < 4; i++)
    {
      this->SRowX[i] = hdr->srow_x[i];
      this->SRowY[i] = hdr->srow_y[i];
      this->SRowZ[i] = hdr->srow_z[i];
    }
  }
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::GetHeader(nifti_1_header* hdr)
{
  hdr->sizeof_hdr = NIFTI1HeaderSize;
  memcpy(hdr->magic, this->Magic, sizeof(hdr->magic));
  memset(hdr->data_type, '\0', 10);
  memset(hdr->db_name, '\0', 18);
  hdr->extents = 0;
  hdr->session_error = 0;
  hdr->regular = 0;
  hdr->dim_info = this->DimInfo;
  hdr->intent_p1 = vtkNIFTINormalizeFloat(this->IntentP1);
  hdr->intent_p2 = vtkNIFTINormalizeFloat(this->IntentP2);
  hdr->intent_p3 = vtkNIFTINormalizeFloat(this->IntentP3);
  hdr->intent_code = static_cast<short>(this->IntentCode);
  hdr->datatype = static_cast<short>(this->DataType);
  hdr->bitpix = static_cast<short>(this->BitPix);
  hdr->slice_start = this->SliceStart;
  for (int i = 0; i < 8; i++)
  {
    hdr->dim[i] = static_cast<short>(this->Dim[i]);
    hdr->pixdim[i] = vtkNIFTINormalizeFloat(this->PixDim[i]);
  }
  hdr->vox_offset = static_cast<float>(this->VoxOffset);
  strncpy(hdr->intent_name, this->IntentName, sizeof(hdr->intent_name) - 1);
  hdr->intent_name[sizeof(hdr->intent_name) - 1] = '\0';
  hdr->scl_slope = vtkNIFTINormalizeFloat(this->SclSlope);
  hdr->scl_inter = vtkNIFTINormalizeFloat(this->SclInter);
  hdr->cal_min = vtkNIFTINormalizeFloat(this->CalMin);
  hdr->cal_max = vtkNIFTINormalizeFloat(this->CalMax);
  hdr->slice_duration = vtkNIFTINormalizeFloat(this->SliceDuration);
  hdr->toffset = vtkNIFTINormalizeFloat(this->TOffset);
  hdr->glmax = 0;
  hdr->glmin = 0;
  hdr->slice_end = this->SliceEnd;
  hdr->slice_code = this->SliceCode;
  hdr->xyzt_units = this->XYZTUnits;
  strncpy(hdr->descrip, this->Descrip, sizeof(hdr->descrip) - 1);
  hdr->descrip[sizeof(hdr->descrip) - 1] = '\0';
  strncpy(hdr->aux_file, this->AuxFile, sizeof(hdr->aux_file) - 1);
  hdr->aux_file[sizeof(hdr->aux_file) - 1] = '\0';
  hdr->qform_code = static_cast<short>(this->QFormCode);
  hdr->sform_code = static_cast<short>(this->SFormCode);
  hdr->quatern_b = vtkNIFTINormalizeFloat(this->QuaternB);
  hdr->quatern_c = vtkNIFTINormalizeFloat(this->QuaternC);
  hdr->quatern_d = vtkNIFTINormalizeFloat(this->QuaternD);
  hdr->qoffset_x = vtkNIFTINormalizeFloat(this->QOffsetX);
  hdr->qoffset_y = vtkNIFTINormalizeFloat(this->QOffsetY);
  hdr->qoffset_z = vtkNIFTINormalizeFloat(this->QOffsetZ);
  for (int i = 0; i < 4; i++)
  {
    hdr->srow_x[i] = vtkNIFTINormalizeFloat(this->SRowX[i]);
    hdr->srow_y[i] = vtkNIFTINormalizeFloat(this->SRowY[i]);
    hdr->srow_z[i] = vtkNIFTINormalizeFloat(this->SRowZ[i]);
  }
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::SetHeader(const nifti_2_header* hdr)
{
  memcpy(this->Magic, hdr->magic, sizeof(hdr->magic));
  this->VoxOffset = hdr->vox_offset;
  this->DataType = hdr->datatype;
  this->BitPix = hdr->bitpix;
  for (int i = 0; i < 8; i++)
  {
    this->Dim[i] = hdr->dim[i];
    this->PixDim[i] = hdr->pixdim[i];
  }
  this->IntentCode = hdr->intent_code;
  strncpy(this->IntentName, hdr->intent_name, sizeof(this->IntentName));
  this->IntentP1 = hdr->intent_p1;
  this->IntentP2 = hdr->intent_p2;
  this->IntentP3 = hdr->intent_p3;
  this->SclSlope = hdr->scl_slope;
  this->SclInter = hdr->scl_inter;
  this->CalMin = hdr->cal_min;
  this->CalMax = hdr->cal_max;
  this->SliceDuration = hdr->slice_duration;
  this->TOffset = hdr->toffset;
  this->SliceStart = hdr->slice_start;
  this->SliceEnd = hdr->slice_end;
  this->SliceCode = hdr->slice_code;
  this->XYZTUnits = hdr->xyzt_units;
  this->DimInfo = hdr->dim_info;
  strncpy(this->Descrip, hdr->descrip, sizeof(this->Descrip));
  strncpy(this->AuxFile, hdr->aux_file, sizeof(this->AuxFile));
  this->QFormCode = hdr->qform_code;
  this->SFormCode = hdr->sform_code;
  this->QuaternB = hdr->quatern_b;
  this->QuaternC = hdr->quatern_c;
  this->QuaternD = hdr->quatern_d;
  this->QOffsetX = hdr->qoffset_x;
  this->QOffsetY = hdr->qoffset_y;
  this->QOffsetZ = hdr->qoffset_z;
  for (int i = 0; i < 4; i++)
  {
    this->SRowX[i] = hdr->srow_x[i];
    this->SRowY[i] = hdr->srow_y[i];
    this->SRowZ[i] = hdr->srow_z[i];
  }
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::GetHeader(nifti_2_header* hdr)
{
  hdr->sizeof_hdr = NIFTI2HeaderSize;
  memcpy(hdr->magic, this->Magic, sizeof(hdr->magic));
  hdr->datatype = static_cast<short>(this->DataType);
  hdr->bitpix = static_cast<short>(this->BitPix);
  for (int i = 0; i < 8; i++)
  {
    hdr->dim[i] = static_cast<short>(this->Dim[i]);
    hdr->pixdim[i] = vtkNIFTINormalizeDouble(this->PixDim[i]);
  }
  hdr->intent_p1 = vtkNIFTINormalizeDouble(this->IntentP1);
  hdr->intent_p2 = vtkNIFTINormalizeDouble(this->IntentP2);
  hdr->intent_p3 = vtkNIFTINormalizeDouble(this->IntentP3);
  hdr->vox_offset = this->VoxOffset;
  hdr->scl_slope = vtkNIFTINormalizeDouble(this->SclSlope);
  hdr->scl_inter = vtkNIFTINormalizeDouble(this->SclInter);
  hdr->cal_min = vtkNIFTINormalizeDouble(this->CalMin);
  hdr->cal_max = vtkNIFTINormalizeDouble(this->CalMax);
  hdr->slice_duration = vtkNIFTINormalizeDouble(this->SliceDuration);
  hdr->toffset = vtkNIFTINormalizeDouble(this->TOffset);
  hdr->slice_start = this->SliceStart;
  hdr->slice_end = this->SliceEnd;
  strncpy(hdr->descrip, this->Descrip, sizeof(hdr->descrip) - 1);
  hdr->descrip[sizeof(hdr->descrip) - 1] = '\0';
  strncpy(hdr->aux_file, this->AuxFile, sizeof(hdr->aux_file) - 1);
  hdr->aux_file[sizeof(hdr->aux_file) - 1] = '\0';
  hdr->qform_code = static_cast<short>(this->QFormCode);
  hdr->sform_code = static_cast<short>(this->SFormCode);
  hdr->quatern_b = vtkNIFTINormalizeDouble(this->QuaternB);
  hdr->quatern_c = vtkNIFTINormalizeDouble(this->QuaternC);
  hdr->quatern_d = vtkNIFTINormalizeDouble(this->QuaternD);
  hdr->qoffset_x = vtkNIFTINormalizeDouble(this->QOffsetX);
  hdr->qoffset_y = vtkNIFTINormalizeDouble(this->QOffsetY);
  hdr->qoffset_z = vtkNIFTINormalizeDouble(this->QOffsetZ);
  for (int i = 0; i < 4; i++)
  {
    hdr->srow_x[i] = vtkNIFTINormalizeDouble(this->SRowX[i]);
    hdr->srow_y[i] = vtkNIFTINormalizeDouble(this->SRowY[i]);
    hdr->srow_z[i] = vtkNIFTINormalizeDouble(this->SRowZ[i]);
  }
  hdr->slice_code = this->SliceCode;
  hdr->xyzt_units = this->XYZTUnits;
  hdr->intent_code = static_cast<short>(this->IntentCode);
  strncpy(hdr->intent_name, this->IntentName, sizeof(hdr->intent_name) - 1);
  hdr->intent_name[sizeof(hdr->intent_name) - 1] = '\0';
  hdr->dim_info = static_cast<char>(this->DimInfo);
  memset(hdr->unused_str, '\0', 15);
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::DeepCopy(vtkNIFTIImageHeader* o)
{
  if (o)
  {
    nifti_2_header hdr;
    o->GetHeader(&hdr);
    this->SetHeader(&hdr);
  }
  else
  {
    this->Initialize();
  }
}

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

  os.setf(std::ios::hex, std::ios::basefield);
  os << indent << "DimInfo: 0x" << this->DimInfo << "\n";
  os.unsetf(std::ios::hex);
  os << indent << "Dim:";
  for (int i = 0; i < 8; i++)
  {
    os << " " << this->Dim[i];
  }
  os << indent << "\n";
  os << indent << "PixDim:";
  for (int i = 0; i < 8; i++)
  {
    os << " " << this->PixDim[i];
  }
  os << indent << "\n";
  os << indent << "VoxOffset:" << this->VoxOffset << "\n";
  os << indent << "IntentP1: " << this->IntentP1 << "\n";
  os << indent << "IntentP2: " << this->IntentP2 << "\n";
  os << indent << "IntentP3: " << this->IntentP3 << "\n";
  os << indent << "IntentCode: " << this->IntentCode << "\n";
  os << indent << "DataType: " << this->DataType << "\n";
  os << indent << "BitPix: " << this->BitPix << "\n";
  os << indent << "SliceStart: " << this->SliceStart << "\n";
  os << indent << "SclSlope: " << this->SclSlope << "\n";
  os << indent << "SclInter: " << this->SclInter << "\n";
  os << indent << "SliceEnd: " << this->SliceEnd << "\n";
  os << indent << "SliceCode: " << static_cast<int>(this->SliceCode) << "\n";
  os.setf(std::ios::hex, std::ios::basefield);
  os << indent << "XYZTUnits: 0x" << static_cast<int>(this->XYZTUnits) << "\n";
  os.unsetf(std::ios::hex);
  os << indent << "CalMax: " << this->CalMax << "\n";
  os << indent << "CalMin: " << this->CalMin << "\n";
  os << indent << "SliceDuration: " << this->SliceDuration << "\n";
  os << indent << "TOffset: " << this->TOffset << "\n";
  os << indent << "Descrip: \"";
  for (size_t j = 0; j < 80 && this->Descrip[j] != '\0'; j++)
  {
    os << (isprint(this->Descrip[j]) ? this->Descrip[j] : '?');
  }
  os << "\"\n";
  os << indent << "AuxFile: \"";
  for (size_t j = 0; j < 24 && this->AuxFile[j] != '\0'; j++)
  {
    os << (isprint(this->AuxFile[j]) ? this->AuxFile[j] : '?');
  }
  os << "\"\n";
  os << indent << "QFormCode: " << this->QFormCode << "\n";
  os << indent << "SFormCode: " << this->SFormCode << "\n";
  os << indent << "QuaternB: " << this->QuaternB << "\n";
  os << indent << "QuaternC: " << this->QuaternC << "\n";
  os << indent << "QuaternD: " << this->QuaternD << "\n";
  os << indent << "QOffsetX: " << this->QOffsetX << "\n";
  os << indent << "QOffsetY: " << this->QOffsetY << "\n";
  os << indent << "QOffsetZ: " << this->QOffsetZ << "\n";
  os << indent << "SRowX:";
  for (int i = 0; i < 4; i++)
  {
    os << " " << this->SRowX[i];
  }
  os << "\n";
  os << indent << "SRowY:";
  for (int i = 0; i < 4; i++)
  {
    os << " " << this->SRowY[i];
  }
  os << "\n";
  os << indent << "SRowZ:";
  for (int i = 0; i < 4; i++)
  {
    os << " " << this->SRowZ[i];
  }
  os << "\n";
  os << indent << "IntentName: \"";
  for (size_t j = 0; j < 16 && this->IntentName[j] != '\0'; j++)
  {
    os << (isprint(this->IntentName[j]) ? this->IntentName[j] : '?');
  }
  os << "\"\n";
  os << indent << "Magic: \"";
  for (size_t j = 0; j < 4 && this->Magic[j] != '\0'; j++)
  {
    os << (isprint(this->Magic[j]) ? this->Magic[j] : '?');
  }
  os << "\"\n";
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::SetStringValue(char* x, const char* y, size_t n)
{
  if (y == nullptr)
  {
    y = "";
  }
  if (strncmp(x, y, n) != 0)
  {
    strncpy(x, y, n);
    x[n] = '\0';
    this->Modified();
  }
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::SetIntentName(const char* val)
{
  this->SetStringValue(this->IntentName, val, 16);
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::SetDescrip(const char* val)
{
  this->SetStringValue(this->Descrip, val, 80);
}

//------------------------------------------------------------------------------
void vtkNIFTIImageHeader::SetAuxFile(const char* val)
{
  this->SetStringValue(this->AuxFile, val, 24);
}
VTK_ABI_NAMESPACE_END
