/*=========================================================================
 *
 *  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 "itkPolygonGroupSpatialObjectXMLFile.h"
#include "itksys/SystemTools.hxx"
#include "itkMetaDataObject.h"
#include "itkIOCommon.h"
#define RAISE_EXCEPTION(s)                         \
  {                                                \
    ExceptionObject exception(__FILE__, __LINE__); \
    exception.SetDescription(s);                   \
    throw exception;                               \
  }                                                \
  ITK_MACROEND_NOOP_STATEMENT

namespace itk
{
int
PolygonGroupSpatialObjectXMLFileReader::CanReadFile(const char * name)
{
  if (!itksys::SystemTools::FileExists(name) || itksys::SystemTools::FileIsDirectory(name) ||
      itksys::SystemTools::FileLength(name) == 0)
  {
    return 0;
  }
  return 1;
}

void
PolygonGroupSpatialObjectXMLFileReader::StartElement(const char * name, const char ** itkNotUsed(atts))
{
  if (itksys::SystemTools::Strucmp(name, "POLYGONGROUP") == 0)
  {
    m_Group = GroupSpatialObjectType::New();
  }
  else if (itksys::SystemTools::Strucmp(name, "POLYGON") == 0)
  {
    m_CurPoly = PolygonSpatialObjectType::New();
    m_Group->AddChild(m_CurPoly);
    m_CurPointList.clear();
  }
}

void
PolygonGroupSpatialObjectXMLFileReader::EndElement(const char * name)
{
  itk::MetaDataDictionary & thisDic = m_Group->GetMetaDataDictionary();
  if (itksys::SystemTools::Strucmp(name, "POLYGONGROUP") == 0)
  {
    m_OutputObject = &(*m_Group);
  }
  if (itksys::SystemTools::Strucmp(name, "PATIENT-ID") == 0)
  {
    itk::EncapsulateMetaData<std::string>(thisDic, ITK_PatientID, m_CurCharacterData);
  }
  else if (itksys::SystemTools::Strucmp(name, "SCAN-ID") == 0)
  {
    itk::EncapsulateMetaData<std::string>(thisDic, ROI_SCAN_ID, m_CurCharacterData);
  }
  else if (itksys::SystemTools::Strucmp(name, "DATE") == 0)
  {
    itk::EncapsulateMetaData<std::string>(thisDic, ITK_OriginationDate, m_CurCharacterData);
  }
  else if (itksys::SystemTools::Strucmp(name, "X-SIZE") == 0)
  {
    int size = std::stoi(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<int>(thisDic, ROI_X_SIZE, size);
  }
  else if (itksys::SystemTools::Strucmp(name, "Y-SIZE") == 0)
  {
    int size = std::stoi(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<int>(thisDic, ROI_Y_SIZE, size);
  }
  else if (itksys::SystemTools::Strucmp(name, "Z-SIZE") == 0)
  {
    int size = std::stoi(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<int>(thisDic, ROI_Z_SIZE, size);
  }
  else if (itksys::SystemTools::Strucmp(name, "X-RESOLUTION") == 0)
  {
    float res = std::stod(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<float>(thisDic, ROI_X_RESOLUTION, res);
  }
  else if (itksys::SystemTools::Strucmp(name, "Y-RESOLUTION") == 0)
  {
    float res = std::stod(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<float>(thisDic, ROI_Y_RESOLUTION, res);
  }
  else if (itksys::SystemTools::Strucmp(name, "Z-RESOLUTION") == 0)
  {
    float res = std::stod(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<float>(thisDic, ROI_Z_RESOLUTION, res);
  }
  else if (itksys::SystemTools::Strucmp(name, "NUM-SEGMENTS") == 0)
  {
    int size = std::stoi(m_CurCharacterData.c_str());
    itk::EncapsulateMetaData<int>(thisDic, ROI_NUM_SEGMENTS, size);
  }
  else if (itksys::SystemTools::Strucmp(name, "POINT") == 0)
  {
    double       pval[3];
    const char * s = m_CurCharacterData.c_str();
    char *       endptr;
    for (double & i : pval)
    {
      i = strtod(s, &endptr);
      if (s == endptr)
      {
        RAISE_EXCEPTION("Can't convert number");
      }
      else
      {
        s = endptr;
      }
    }
    PolygonPointType p;
    p.SetPositionInObjectSpace(pval);
    m_CurPointList.push_back(p);
  }
  else if (itksys::SystemTools::Strucmp(name, "POLYGON") == 0)
  {
    m_CurPoly->SetPoints(m_CurPointList);
  }
}

void
PolygonGroupSpatialObjectXMLFileReader::CharacterDataHandler(const char * inData, int inLength)
{
  m_CurCharacterData = "";
  for (int i = 0; i < inLength; ++i)
  {
    m_CurCharacterData = m_CurCharacterData + inData[i];
  }
}

int
PolygonGroupSpatialObjectXMLFileWriter::CanWriteFile(const char * itkNotUsed(name))
{
  return true; // not sure what else to say
}

// Define ZeroValue() for std::string so that the value can be
// initialized before calling ExposeMetaData. Since ExposeMetaData can
// return without setting a value, some compilers warn about using an
// initialized variable.
template <>
class NumericTraits<std::string>
{
public:
  static const std::string
  ZeroValue()
  {
    return std::string("");
  }
};

template <typename T>
void
WriteMetaDataAttribute(PolygonGroupSpatialObjectXMLFileWriter * This,
                       itk::MetaDataDictionary &                thisDic,
                       const char * const                       MetaName,
                       const char * const                       attName,
                       std::ofstream &                          output)
{
  T value{};

  if (ExposeMetaData<T>(thisDic, MetaName, value))
  {
    This->WriteStartElement(attName, output);
    output << value;
    This->WriteEndElement(attName, output);
    output << std::endl;
  }
}

int
PolygonGroupSpatialObjectXMLFileWriter::WriteFile()
{
  //
  // sanity checks
  if (m_InputObject == nullptr)
  {
    std::string errmsg("No PolygonGroup to Write");
    RAISE_EXCEPTION(errmsg);
  }
  if (m_Filename.length() == 0)
  {
    std::string errmsg("No filename given");
    RAISE_EXCEPTION(errmsg);
  }
  std::ofstream output(m_Filename.c_str());
  if (output.fail())
  {
    std::string errmsg("Can't Open ");
    errmsg += m_Filename;
    RAISE_EXCEPTION(errmsg);
  }

  WriteStartElement("?xml version=\"1.0\"?", output);
  output << std::endl;
  WriteStartElement("!DOCTYPE POLYGONGROUP", output);
  output << std::endl;
  //
  // Write out metadata

  WriteStartElement("POLYGONGROUP", output);
  output << std::endl;

  itk::MetaDataDictionary & thisDic = m_InputObject->GetMetaDataDictionary();
  WriteMetaDataAttribute<std::string>(this, thisDic, ITK_PatientID, "PATIENT-ID", output);

  WriteMetaDataAttribute<std::string>(this, thisDic, ROI_SCAN_ID, "SCAN-ID", output);
  WriteMetaDataAttribute<std::string>(this, thisDic, ITK_OriginationDate, "DATE", output);
  WriteMetaDataAttribute<int>(this, thisDic, ROI_X_SIZE, "X-SIZE", output);
  WriteMetaDataAttribute<int>(this, thisDic, ROI_Y_SIZE, "Y-SIZE", output);
  WriteMetaDataAttribute<int>(this, thisDic, ROI_Z_SIZE, "Z-SIZE", output);
  WriteMetaDataAttribute<float>(this, thisDic, ROI_X_RESOLUTION, "X-RESOLUTION", output);

  WriteMetaDataAttribute<float>(this, thisDic, ROI_Y_RESOLUTION, "Y-RESOLUTION", output);
  WriteMetaDataAttribute<float>(this, thisDic, ROI_Z_RESOLUTION, "Z-RESOLUTION", output);
  WriteMetaDataAttribute<int>(this, thisDic, ROI_NUM_SEGMENTS, "NUM-SEGMENTS", output);

  //
  // Write out polygondata
  GroupType::ChildrenListType * children = m_InputObject->GetChildren(GroupType::MaximumDepth, "PolygonSpatialObject");
  auto                          it = children->begin();
  auto                          end = children->end();
  while (it != end)
  {
    WriteStartElement("POLYGON", output);
    output << std::endl;
    auto * curstrand = dynamic_cast<PolygonSpatialObjectType *>(it->GetPointer());
    PolygonSpatialObjectType::PolygonPointListType & polygonPoints = curstrand->GetPoints();
    auto                                             pointIt = polygonPoints.begin();
    auto                                             pointItEnd = polygonPoints.end();
    while (pointIt != pointItEnd)
    {
      PolygonSpatialObjectType::PointType curpoint = pointIt->GetPositionInObjectSpace();
      WriteStartElement("POINT", output);
      output << curpoint[0] << ' ' << curpoint[1] << ' ' << curpoint[2];
      WriteEndElement("POINT", output);
      output << std::endl;
      ++pointIt;
    }
    WriteEndElement("POLYGON", output);
    output << std::endl;
    ++it;
  }
  WriteEndElement("POLYGONGROUP", output);
  output << std::endl;
  output.close();

  delete children;
  return 0;
}
} // namespace itk
