/*=========================================================================
 *
 *  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 "itkGDCMSeriesFileNames.h"
#include "itksys/SystemTools.hxx"
#include "itkProgressReporter.h"
#include "itkPrintHelper.h"
#include "gdcmSerieHelper.h"

namespace itk
{


GDCMSeriesFileNames::GDCMSeriesFileNames()
  : m_SerieHelper{ new gdcm::SerieHelper() }
{}

GDCMSeriesFileNames::~GDCMSeriesFileNames() = default;


void
GDCMSeriesFileNames::SetInputDirectory(const char * name)
{
  if (!name)
  {
    itkExceptionMacro("SetInputDirectory() received a nullptr string");
  }
  std::string fname = name;
  this->SetInputDirectory(fname);
}

void
GDCMSeriesFileNames::AddSeriesRestriction(const std::string & tag)
{
  m_SerieHelper->AddRestriction(tag);
}

void
GDCMSeriesFileNames::SetInputDirectory(std::string const & name)
{
  if (name.empty())
  {
    itkWarningMacro("You need to specify a directory where "
                    "the DICOM files are located");
    return;
  }
  if (m_InputDirectory == name)
  {
    return;
  }
  if (!itksys::SystemTools::FileIsDirectory(name.c_str()))
  {
    itkWarningMacro(<< name << " is not a directory");
    return;
  }
  m_InputDirectory = name;
  m_SerieHelper->Clear();
  m_SerieHelper->SetUseSeriesDetails(m_UseSeriesDetails);
  m_SerieHelper->SetLoadMode((m_LoadSequences ? 0 : gdcm::LD_NOSEQ) | (m_LoadPrivateTags ? 0 : gdcm::LD_NOSHADOW));
  m_SerieHelper->SetDirectory(name, m_Recursive);
  // as a side effect it also execute
  this->Modified();
}

const GDCMSeriesFileNames::SeriesUIDContainerType &
GDCMSeriesFileNames::GetSeriesUIDs()
{
  m_SeriesUIDs.clear();
  // Accessing the first serie found (assume there is at least one)
  gdcm::FileList * flist = m_SerieHelper->GetFirstSingleSerieUIDFileSet();
  while (flist)
  {
    if (!flist->empty()) // make sure we have at leat one serie
    {
      gdcm::File * file = (*flist)[0]; // for example take the first one

      // Create its unique series ID
      std::string id = m_SerieHelper->CreateUniqueSeriesIdentifier(file).c_str();

      m_SeriesUIDs.push_back(id.c_str());
    }
    flist = m_SerieHelper->GetNextSingleSerieUIDFileSet();
  }
  if (m_SeriesUIDs.empty())
  {
    itkWarningMacro("No Series were found");
  }
  return m_SeriesUIDs;
}

const GDCMSeriesFileNames::FileNamesContainerType &
GDCMSeriesFileNames::GetFileNames(const std::string serie)
{
  m_InputFileNames.clear();
  // Accessing the first serie found (assume there is at least one)
  gdcm::FileList * flist = m_SerieHelper->GetFirstSingleSerieUIDFileSet();
  if (!flist)
  {
    itkWarningMacro("No Series can be found, make sure your restrictions are not too strong");
    return m_InputFileNames;
  }
  if (!serie.empty()) // user did not specify any sub selection based on UID
  {
    bool found = false;
    while (flist && !found)
    {
      if (!flist->empty()) // make sure we have at leat one serie
      {
        gdcm::File * file = (*flist)[0]; // for example take the first one
        std::string  id = m_SerieHelper->CreateUniqueSeriesIdentifier(file).c_str();

        if (id == serie)
        {
          found = true; // we found a match
          break;
        }
      }
      flist = m_SerieHelper->GetNextSingleSerieUIDFileSet();
    }
    if (!found)
    {
      itkWarningMacro("No Series were found");
      return m_InputFileNames;
    }
  }
  m_SerieHelper->OrderFileList(flist);

  gdcm::FileList::iterator it;
  if (!flist->empty())
  {
    ProgressReporter progress(this, 0, static_cast<itk::SizeValueType>(flist->size()), 10);
    for (it = flist->begin(); it != flist->end(); ++it)
    {
#if GDCM_MAJOR_VERSION < 2
      gdcm::File * header = *it;
      if (!header)
      {
        itkWarningMacro("GDCMSeriesFileNames got nullptr header, "
                        "this is a serious bug");
        continue;
      }
      if (!header->IsReadable())
      {
        itkWarningMacro("GDCMSeriesFileNames got a non DICOM file:" << header->GetFileName());
        continue;
      }
      m_InputFileNames.push_back(header->GetFileName());
      progress.CompletedPixel();
#else
      gdcm::FileWithName * header = *it;
      m_InputFileNames.push_back(header->filename);
      progress.CompletedPixel();
#endif
    }
  }
  else
  {
    itkDebugMacro("No files were found");
  }

  return m_InputFileNames;
}

const GDCMSeriesFileNames::FileNamesContainerType &
GDCMSeriesFileNames::GetInputFileNames()
{
  // Do not specify any UID
  return this->GetFileNames("");
}

const GDCMSeriesFileNames::FileNamesContainerType &
GDCMSeriesFileNames::GetOutputFileNames()
{
  // We are trying to extract the original filename and compose it with a path:

  // There are two different approaches if directory does not exist:
  // 1. Exit
  // 2. Mkdir
  // bool SystemTools::FileExists(const char* filename)
  // bool SystemTools::FileIsDirectory(const char* name)
  m_OutputFileNames.clear();

  if (m_OutputDirectory.empty())
  {
    itkDebugMacro("No output directory was specified");
    return m_OutputFileNames;
  }

  itksys::SystemTools::ConvertToUnixSlashes(m_OutputDirectory);
  if (m_OutputDirectory.back() != '/')
  {
    m_OutputDirectory += '/';
  }

  if (!m_InputFileNames.empty())
  {
    bool hasExtension = false;
    for (const auto & inputFileName : m_InputFileNames)
    {
      // look for extension ".dcm" and ".DCM"
      std::string::size_type dcmPos = inputFileName.rfind(".dcm");
      if ((dcmPos != std::string::npos) && (dcmPos == inputFileName.length() - 4))
      {
        hasExtension = true;
      }
      else
      {
        dcmPos = inputFileName.rfind(".DCM");
        if ((dcmPos != std::string::npos) && (dcmPos == inputFileName.length() - 4))
        {
          hasExtension = true;
        }
      }

      // look for extension ".dicom" and ".DICOM"
      std::string::size_type dicomPos = inputFileName.rfind(".dicom");
      if ((dicomPos != std::string::npos) && (dicomPos == inputFileName.length() - 6))
      {
        hasExtension = true;
      }
      else
      {
        dicomPos = inputFileName.rfind(".DICOM");
        if ((dicomPos != std::string::npos) && (dicomPos == inputFileName.length() - 6))
        {
          hasExtension = true;
        }
      }

      // construct a filename, adding an extension if necessary
      std::string filename = m_OutputDirectory + itksys::SystemTools::GetFilenameName(inputFileName);
      if (!hasExtension)
      {
        // input filename has no extension, add a ".dcm"
        filename += ".dcm";
      }

      // Add the file name to the output list
      m_OutputFileNames.push_back(filename);
    }
  }
  else
  {
    itkDebugMacro("No files were found.");
  }

  return m_OutputFileNames;
}

void
GDCMSeriesFileNames::PrintSelf(std::ostream & os, Indent indent) const
{
  using namespace print_helper;

  Superclass::PrintSelf(os, indent);

  os << indent << "InputDirectory: " << m_InputDirectory << std::endl;
  os << indent << "OutputDirectory: " << m_OutputDirectory << std::endl;

  os << indent << "InputFileNames: " << m_InputFileNames << std::endl;
  os << indent << "OutputFileNames: " << m_OutputFileNames << std::endl;

  os << indent << "SerieHelper: ";
  if (m_SerieHelper.get() != nullptr)
  {
    os << m_SerieHelper.get() << std::endl;
  }
  else
  {
    os << "(null)" << std::endl;
  }

  os << indent << "SeriesUIDs: " << m_SeriesUIDs << std::endl;

  os << indent << "UseSeriesDetails: " << (m_UseSeriesDetails ? "On" : "Off") << std::endl;
  os << indent << "Recursive: " << (m_Recursive ? "On" : "Off") << std::endl;
  os << indent << "LoadSequences: " << (m_LoadSequences ? "On" : "Off") << std::endl;
  os << indent << "LoadPrivateTags: " << (m_LoadPrivateTags ? "On" : "Off") << std::endl;
}

void
GDCMSeriesFileNames::SetUseSeriesDetails(bool useSeriesDetails)
{
  m_UseSeriesDetails = useSeriesDetails;
  m_SerieHelper->SetUseSeriesDetails(m_UseSeriesDetails);
  m_SerieHelper->CreateDefaultUniqueSeriesIdentifier();
}
} // namespace itk
