/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#include "itkMRCHeaderObject.h"
#include "itkMRCImageIOPrivate.h"
#include "itkByteSwapper.h"


namespace itk
{
void
MRCHeaderObject::DeepCopy(ConstPointer h)
{
  memcpy(&this->m_Header, &h->m_Header, sizeof(Header));

  this->m_ExtendedHeaderSize = h->m_ExtendedHeaderSize;
  if (this->m_ExtendedHeaderSize)
  {
    delete[] static_cast<char *>(this->m_ExtendedHeader);

    this->m_ExtendedHeader = new char[this->m_ExtendedHeaderSize];
    memcpy(this->m_ExtendedHeader, h->m_ExtendedHeader, this->m_ExtendedHeaderSize);
  }

  this->m_BigEndianHeader = h->m_BigEndianHeader;

  if (h->m_ExtendedFeiHeader)
  {
    this->m_ExtendedFeiHeader = static_cast<FeiExtendedHeader *>(this->m_ExtendedHeader);
  }
  else
  {
    this->m_ExtendedFeiHeader = nullptr;
  }
}

const MRCHeaderObject::Header &
MRCHeaderObject::GetHeader() const
{
  return this->m_Header;
}

bool
MRCHeaderObject::SetHeader(const Header * buffer)
{
  if (!buffer)
  {
    return false;
  }

  const auto * _header = static_cast<const Header *>(buffer);

  memcpy(&this->m_Header, buffer, sizeof(Header));

  // the cmap field must should either be the magic field or 0
  if (strncmp(_header->cmap, magicMAP, 4) != 0 && memcmp(_header->cmap, "\0\0\0\0", 4) != 0)
  {
    itkWarningMacro("The header's cmap field does not have expected values");
    return false;
  }

  // the stamp may help up tell the endian of the header
  // or it's an older header
  if (_header->stamp[0] == 17)
  {
    this->m_BigEndianHeader = true;
    this->swapHeader(this->m_BigEndianHeader);
  }
  else if (_header->stamp[0] == 68)
  {
    this->m_BigEndianHeader = false;
    this->swapHeader(this->m_BigEndianHeader);
  }
  else if (_header->stamp[0] == 0 && _header->stamp[1] == 0 && _header->stamp[2] == 0 && _header->stamp[3] == 0)
  {
    this->m_BigEndianHeader = ByteSwapper<void *>::SystemIsBigEndian();

    // do a check to see which byte order make sense
    if (this->m_Header.mapc < 1 || this->m_Header.mapc > 3 || this->m_Header.mapr < 1 || this->m_Header.mapr > 3 ||
        this->m_Header.maps < 1 || this->m_Header.maps > 3)
    {
      this->m_BigEndianHeader = !this->m_BigEndianHeader;
      this->swapHeader(this->m_BigEndianHeader);
    }
  }
  else
  {
    // the stamp is not expected
    itkWarningMacro("The header's stamp field does not have expected values");
    return false;
  }

  // clean up
  delete[] static_cast<char *>(this->m_ExtendedHeader);

  this->m_ExtendedHeader = nullptr;
  this->m_ExtendedFeiHeader = nullptr;

  this->m_ExtendedHeaderSize = this->m_Header.next;

  // check to make sure the data makes sense
  if (this->m_Header.nx <= 0 || this->m_Header.ny <= 0 || this->m_Header.nz <= 0 ||
      (this->m_Header.nx > 65535 || this->m_Header.ny > 65535 || this->m_Header.nz > 65535) ||
      this->m_Header.mapc < MRCHEADER_MAP_X || this->m_Header.mapc > MRCHEADER_MAP_Z ||
      this->m_Header.mapr < MRCHEADER_MAP_X || this->m_Header.mapr > MRCHEADER_MAP_Z ||
      this->m_Header.maps < MRCHEADER_MAP_X || this->m_Header.maps > MRCHEADER_MAP_Z ||
      this->m_Header.nxstart >= this->m_Header.nx || this->m_Header.nystart >= this->m_Header.ny ||
      this->m_Header.nzstart >= this->m_Header.nz)
  {
    itkWarningMacro("Some header data does not have sensable values");
    return false;
  }

  if (this->m_Header.nxstart != 0 || this->m_Header.nystart != 0 || this->m_Header.nzstart != 0)
  {
    itkWarningMacro("The header's nxstart, nystart and nzstart fields are not supported correctly");
  }

  return true;
}

SizeValueType
MRCHeaderObject::GetExtendedHeaderSize() const
{
  return this->m_ExtendedHeaderSize;
}

bool
MRCHeaderObject::SetExtendedHeader(const void * buffer)
{
  if (!this->m_ExtendedHeaderSize)
  {
    return false;
  }

  delete[] static_cast<char *>(this->m_ExtendedHeader);

  this->m_ExtendedHeader = new char[this->m_ExtendedHeaderSize];
  memcpy(this->m_ExtendedHeader, buffer, this->m_ExtendedHeaderSize);

  this->m_ExtendedFeiHeader = nullptr;
  if (this->m_ExtendedHeaderSize == 128 * 1024 && this->m_Header.nint == 0 && this->m_Header.nreal == 32)
  {
    this->m_ExtendedFeiHeader = static_cast<FeiExtendedHeader *>(this->m_ExtendedHeader);

    if (this->m_BigEndianHeader)
    {
      ByteSwapper<float>::SwapRangeFromSystemToBigEndian((float *)this->m_ExtendedHeader, this->m_ExtendedHeaderSize);
    }
    else
    {
      ByteSwapper<float>::SwapRangeFromSystemToLittleEndian((float *)this->m_ExtendedHeader,
                                                            this->m_ExtendedHeaderSize);
    }
  }
  return true;
}

bool
MRCHeaderObject::IsOriginalHeaderBigEndian() const
{
  return this->m_BigEndianHeader;
}

MRCHeaderObject::MRCHeaderObject()


{
  memset(&this->m_Header, 0, sizeof(Header));
  this->m_BigEndianHeader = ByteSwapper<void *>::SystemIsBE();
}

MRCHeaderObject::~MRCHeaderObject()
{
  delete[] static_cast<char *>(this->m_ExtendedHeader);
}

void
MRCHeaderObject::swapHeader(bool bigEndian)
{
  using FloatSwapper = itk::ByteSwapper<float>;
  using Int32Swapper = itk::ByteSwapper<int32_t>;
  using Int16Swapper = itk::ByteSwapper<int16_t>;

  if (FloatSwapper::SystemIsBigEndian())
  {
    this->m_Header.stamp[0] = 17;
  }
  else
  {
    this->m_Header.stamp[0] = 68;
  }

  if (bigEndian)
  {
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.nx);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.ny);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.nz);

    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.mode);

    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.nxstart);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.nystart);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.nzstart);

    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.mx);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.my);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.mz);

    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.xlen);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.ylen);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.zlen);

    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.alpha);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.beta);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.gamma);

    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.mapc);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.mapr);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.maps);

    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.amin);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.amax);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.amean);

    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.ispg);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.nsymbt);
    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.next);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.creatid);

    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.nint);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.nreal);

    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.idtype);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.lens);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.nd1);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.nd2);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.vd1);
    Int16Swapper::SwapFromSystemToBigEndian(&this->m_Header.vd2);

    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.tiltangles[0]);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.tiltangles[1]);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.tiltangles[2]);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.tiltangles[3]);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.tiltangles[4]);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.tiltangles[5]);

    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.xorg);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.yorg);
    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.zorg);

    FloatSwapper::SwapFromSystemToBigEndian(&this->m_Header.rms);

    Int32Swapper::SwapFromSystemToBigEndian(&this->m_Header.nlabl);
  }
  else
  {
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nx);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.ny);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nz);

    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.mode);

    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nxstart);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nystart);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nzstart);

    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.mx);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.my);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.mz);

    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.xlen);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.ylen);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.zlen);

    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.alpha);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.beta);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.gamma);

    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.mapc);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.mapr);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.maps);

    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.amin);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.amax);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.amean);

    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.ispg);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nsymbt);
    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.next);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.creatid);

    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nint);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nreal);

    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.idtype);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.lens);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nd1);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nd2);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.vd1);
    Int16Swapper::SwapFromSystemToLittleEndian(&this->m_Header.vd2);

    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.tiltangles[0]);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.tiltangles[1]);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.tiltangles[2]);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.tiltangles[3]);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.tiltangles[4]);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.tiltangles[5]);

    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.xorg);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.yorg);
    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.zorg);

    FloatSwapper::SwapFromSystemToLittleEndian(&this->m_Header.rms);

    Int32Swapper::SwapFromSystemToLittleEndian(&this->m_Header.nlabl);
  }
}

void
MRCHeaderObject::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "number: " << this->m_Header.nx << ' ' << this->m_Header.ny << ' ' << this->m_Header.nz << std::endl;
  os << indent << "mode: " << this->m_Header.mode << std::endl;
  os << indent << "start: " << this->m_Header.nxstart << ' ' << this->m_Header.nystart << ' ' << this->m_Header.nzstart
     << std::endl;
  os << indent << "grid: " << this->m_Header.mx << ' ' << this->m_Header.my << ' ' << this->m_Header.mz << std::endl;
  os << indent << "len: " << this->m_Header.xlen << ' ' << this->m_Header.ylen << ' ' << this->m_Header.zlen
     << std::endl;
  os << indent << "abg angles: " << this->m_Header.alpha << ' ' << this->m_Header.beta << ' ' << this->m_Header.gamma
     << std::endl;
  os << indent << "map: " << this->m_Header.mapc << ' ' << this->m_Header.mapr << ' ' << this->m_Header.maps
     << std::endl;
  os << indent << "mmm: " << this->m_Header.amin << ' ' << this->m_Header.amax << ' ' << this->m_Header.amean
     << std::endl;
  os << indent << "ispg: " << this->m_Header.ispg << std::endl;
  os << indent << "nsymbt: " << this->m_Header.nsymbt << std::endl;
  os << indent << "next: " << this->m_Header.next << std::endl;
  os << indent << "creatid: " << this->m_Header.creatid << std::endl;
  os << indent << "nint: " << this->m_Header.nint << std::endl;
  os << indent << "nreal: " << this->m_Header.nreal << std::endl;
  os << indent << "idtype: " << this->m_Header.idtype << std::endl;
  os << indent << "lens: " << this->m_Header.lens << std::endl;
  os << indent << "nd: " << this->m_Header.nd1 << ' ' << this->m_Header.nd2 << std::endl;
  os << indent << "vd: " << this->m_Header.vd1 << ' ' << this->m_Header.vd2 << std::endl;
  os << indent << "tiltangles: (" << this->m_Header.tiltangles[0] << ", " << this->m_Header.tiltangles[1] << ", "
     << this->m_Header.tiltangles[2] << ") (" << this->m_Header.tiltangles[3] << ", " << this->m_Header.tiltangles[4]
     << ", " << this->m_Header.tiltangles[5] << ')' << std::endl;
  os << indent << "org: " << this->m_Header.xorg << ' ' << this->m_Header.yorg << ' ' << this->m_Header.zorg
     << std::endl;
  os << indent << "cmap: \"" << this->m_Header.cmap[0] << this->m_Header.cmap[1] << this->m_Header.cmap[2]
     << this->m_Header.cmap[3] << '"' << std::endl;
  os << indent << "stamp: " << static_cast<int>(this->m_Header.stamp[0]) << ' '
     << static_cast<int>(this->m_Header.stamp[1]) << ' ' << static_cast<int>(this->m_Header.stamp[2]) << ' '
     << static_cast<int>(this->m_Header.stamp[3]) << std::endl;
  os << indent << "rms: " << this->m_Header.rms << std::endl;
  os << indent << "nlabl: " << this->m_Header.nlabl << std::endl;

  for (int32_t i = 0; i < this->m_Header.nlabl && i < 10; ++i)
  {
    os.write(this->m_Header.label[i], 80);
    os << indent << std::endl;
  }

  if (this->m_ExtendedFeiHeader)
  {
    os << indent << "Extended Header: " << std::endl;
    os << indent
       << "( atilt, btilt, xstage, ystage, zstage, xshift, yshift, defocus, exptime, meanint, tiltaxis, pixelsize, "
          "magnification)"
       << std::endl;
    for (int32_t z = 0; z < this->m_Header.nz && z < 1024; ++z)
    {
      os << indent << '(' << this->m_ExtendedFeiHeader[z].atilt << ", " << this->m_ExtendedFeiHeader[z].btilt << ", "
         << this->m_ExtendedFeiHeader[z].xstage << ", " << this->m_ExtendedFeiHeader[z].ystage << ", "
         << this->m_ExtendedFeiHeader[z].zstage << ", " << this->m_ExtendedFeiHeader[z].xshift << ", "
         << this->m_ExtendedFeiHeader[z].yshift << ", " << this->m_ExtendedFeiHeader[z].defocus << ", "
         << this->m_ExtendedFeiHeader[z].exptime << ", " << this->m_ExtendedFeiHeader[z].meanint << ", "
         << this->m_ExtendedFeiHeader[z].tiltaxis << ", " << this->m_ExtendedFeiHeader[z].pixelsize << ", "
         << this->m_ExtendedFeiHeader[z].magnification << ')' << std::endl;
    }
  }
}

} // namespace itk
