// #include <proteowizard/pwiz/data/msdata/DefaultReaderList.hpp>

#include <QDebug>
#include <QFile>
#include <QFileInfo>


#include "msfileaccessor.h"
#include "pwizmsfilereader.h"
#include "pappsomspp/core/msfile/mzcbormsfilereader.h"
#include "pappsomspp/core/msfile/timsmsfilereader.h"
#include "pappsomspp/core/msfile/bafasciifilereader.h"
#include "pappsomspp/core/msfile/xymsfilereader.h"

#include "pappsomspp/core/exception/exceptionnotfound.h"
#include "pappsomspp/core/exception/exceptionnotpossible.h"
#include "pappsomspp/core/exception/exceptionnotrecognized.h"
#include "pappsomspp/core/msrun/msrunid.h"
#include "pappsomspp/core/msrun/private/mzcbormsrunreader.h"
#include "pappsomspp/core/msrun/private/timsframesmsrunreader.h"

#include "pappsomspp/core/msrun/private/pwizmsrunreader.h"
#include "pappsomspp/core/msrun/private/timsmsrunreader.h"
#include "pappsomspp/core/msrun/private/timsmsrunreaderms2.h"
#include "pappsomspp/core/msrun/private/timsmsrunreaderdia.h"
#include "pappsomspp/core/msrun/bafasciimsrunreader.h"
#include "pappsomspp/core/msrun/xymsrunreader.h"

namespace pappso
{


MsFileAccessor::MsFileAccessor(const QString &file_name,
                               const QString &xml_prefix)
  : m_fileName(file_name), m_xmlPrefix(xml_prefix)
{
  QFile file(file_name);
  if(!file.exists())
    throw(ExceptionNotFound(QObject::tr("File %1 not found.")
                              .arg(QFileInfo(file_name).absoluteFilePath())));


  m_oboPsiModTermNativeIDFormat.setAccession("MS:1000824");
  m_oboPsiModTermNativeIDFormat.m_name = "no nativeID format";
  m_oboPsiModTermNativeIDFormat.m_definition =
    "No nativeID format indicates that the file tagged with this term does not "
    "contain spectra that can have a nativeID format.";
}

MsFileAccessor::MsFileAccessor(const MsFileAccessor &other)
  : m_fileName(other.m_fileName),
    m_xmlPrefix(other.m_xmlPrefix),
    m_fileFormat(other.m_fileFormat),
    m_fileReaderType(other.m_fileReaderType)
{
  m_oboPsiModTermNativeIDFormat = other.m_oboPsiModTermNativeIDFormat;
}

MsFileAccessor::~MsFileAccessor()
{
}

const QString &
MsFileAccessor::getFileName() const
{
  return m_fileName;
}

Enums::MsDataFormat
MsFileAccessor::getFileFormat() const
{
  return m_fileFormat;
}

const OboPsiModTerm
MsFileAccessor::getOboPsiModTermFileFormat() const
{
  OboPsiModTerm term;

  // is_a: MS:1000560 ! mass spectrometer file format
  switch(m_fileFormat)
    {
      case Enums::MsDataFormat::abSciexT2D:
        term.setAccession("MS:1001560");
        term.m_name = "SCIEX TOF/TOF T2D format";
        term.m_definition =
          "Applied Biosystems/MDS Analytical Technologies TOF/TOF instrument "
          "export format.";
        break;
      case Enums::MsDataFormat::abSciexWiff:
        term.setAccession("MS:1000562");
        term.m_name       = "ABI WIFF format";
        term.m_definition = "Applied Biosystems WIFF file format.";
        break;
      case Enums::MsDataFormat::agilentMassHunter:
        term.setAccession("MS:1001509");
        term.m_name = "Agilent MassHunter format";
        term.m_definition =
          "A data file format found in an Agilent MassHunter directory which "
          "contains raw data acquired by an Agilent mass spectrometer.";
        break;
      case Enums::MsDataFormat::brukerBaf:
        break;
      case Enums::MsDataFormat::brukerFid:
        term.setAccession("MS:1000825");
        term.m_name       = "Bruker FID format";
        term.m_definition = "Bruker FID file format.";
        break;
      case Enums::MsDataFormat::brukerTims:
        term.setAccession("MS:1002817");
        term.m_name       = "Bruker TDF format";
        term.m_definition = "Bruker TDF raw file format.";
        break;
      case Enums::MsDataFormat::brukerYep:
        term.setAccession("MS:1000567");
        term.m_name       = "Bruker/Agilent YEP format";
        term.m_definition = "Bruker/Agilent YEP file format.";
        break;
      case Enums::MsDataFormat::MGF:
        term.setAccession("MS:1001062");
        term.m_name       = "Mascot MGF format";
        term.m_definition = "Mascot MGF file format.";
        break;
      case Enums::MsDataFormat::msn:
        break;
      case Enums::MsDataFormat::mz5:
        term.setAccession("MS:1001881");
        term.m_name       = "mz5 format";
        term.m_definition = "mz5 file format, modelled after mzML.";
        break;
      case Enums::MsDataFormat::mzML:
        term.setAccession("MS:1000584");
        term.m_name = "mzML format";
        term.m_definition =
          "Proteomics Standards Inititative mzML file format.";
        break;
      case Enums::MsDataFormat::mzcbor:
        // mzCBOR is a direct translation of mzML
        // waiting for a true OBO term, we choose the mzML definition
        term.setAccession("MS:1000584");
        term.m_name = "mzML format";
        term.m_definition =
          "Proteomics Standards Inititative mzML file format.";
        break;
      case Enums::MsDataFormat::mzXML:
        term.setAccession("MS:1000566");
        term.m_name       = "ISB mzXML format";
        term.m_definition = "Institute of Systems Biology mzXML file format.";
        break;
      case Enums::MsDataFormat::SQLite3:
        break;
      case Enums::MsDataFormat::thermoRaw:

        term.setAccession("MS:1000563");
        term.m_name       = "Thermo RAW format";
        term.m_definition = "Thermo Scientific RAW file format.";
        break;
      case Enums::MsDataFormat::unknown:
        break;
      case Enums::MsDataFormat::watersRaw:
        term.setAccession("MS:1000526");
        term.m_name = "Waters raw format";
        term.m_definition =
          "Waters data file format found in a Waters RAW directory, generated "
          "from an MS acquisition.";
        break;
      case Enums::MsDataFormat::brukerBafAscii:
        term.setAccession("MS:1001369");
        term.m_name = "BafAscii text format";
        term.m_definition =
          "Simple text file format obtained by exporting Bruker Baf to ascii "
          "using Bruker software";
        break;
      case Enums::MsDataFormat::xy:
        term.setAccession("MS:1001369");
        term.m_name = "text format";
        term.m_definition =
          "Simple text file format of \"m/z<separator>intensity\" value pairs "
          "for a single mass spectrum, a PMF (or single MS2) search.";
        break;
      default:
        break;
    }

  return term;
}

const OboPsiModTerm &
MsFileAccessor::getOboPsiModTermNativeIDFormat() const
{


  return m_oboPsiModTermNativeIDFormat;
}

std::vector<MsRunIdCstSPtr>
MsFileAccessor::getMsRunIds()
{
  qDebug();
  // if (mzcbor_ms_file_reader.accept(m_fileName)) {
  // }
  std::vector<MsRunIdCstSPtr> ms_run_ids;


  // try the mzcbor file reader
  MzcborMsFileReader mzcbor_ms_file_reader(m_fileName);
  if(mzcbor_ms_file_reader.getFileFormat() ==
     pappso::Enums::MsDataFormat::mzcbor)
    {
      qDebug() << "sure, this is mzcbor";

      ms_run_ids   = mzcbor_ms_file_reader.getMsRunIds(m_xmlPrefix);
      m_fileFormat = Enums::MsDataFormat::mzcbor;
      return ms_run_ids;
    }

  // Try the PwizMsFileReader

  PwizMsFileReader pwiz_ms_file_reader(m_fileName);

  ms_run_ids = pwiz_ms_file_reader.getMsRunIds(m_xmlPrefix);
  if(ms_run_ids.size())
    {
      qDebug() << "Might well be handled using the Pwiz code.";

      m_fileFormat     = pwiz_ms_file_reader.getFileFormat();
      m_fileReaderType = Enums::FileReaderType::pwiz;

      // But the user might have configured one preferred reader type.

      auto pref = m_preferredFileReaderTypeMap.find(m_fileFormat);
      if(pref != m_preferredFileReaderTypeMap.end())
        {
          m_fileReaderType = pref->second;
        }

      return ms_run_ids;
    }

  // qDebug() << "The Pwiz reader did not work.";

  // Try the TimsData reader

  try
    {
      QString tims_dir = m_fileName;
      if(!QFileInfo(tims_dir).isDir())
        {
          tims_dir = QFileInfo(m_fileName).absolutePath();
        }

      TimsMsFileReader tims_file_reader(tims_dir);

      ms_run_ids = tims_file_reader.getMsRunIds(m_xmlPrefix);

      if(ms_run_ids.size())
        {
          // qDebug() << "Might well be handled using the Bruker code";

          m_fileName       = tims_dir;
          m_fileFormat     = tims_file_reader.getFileFormat();
          m_fileReaderType = Enums::FileReaderType::tims;

          auto pref = m_preferredFileReaderTypeMap.find(m_fileFormat);
          if(pref != m_preferredFileReaderTypeMap.end())
            {
              m_fileReaderType = pref->second;
            }

          // qDebug() << "Returning Bruker::tims ms run(s)."
          //          << "with preferred reader type:"
          //          << Utils::fileReaderTypeAsString(m_fileReaderType);

          return ms_run_ids;
        }
    }
  catch(const pappso::ExceptionNotRecognized &error)
    {
      // qDebug() << "The Tims reader did not work.";
    }


  // Try the Baf->ascii export format from Bruker Compass

  try
    {
      ms_run_ids.clear();
      BafAsciiFileReader baf_ascii_ms_file_reader(m_fileName);

      ms_run_ids = baf_ascii_ms_file_reader.getMsRunIds(m_xmlPrefix);

      if(ms_run_ids.size())
        {
          // qDebug() << "Might well be handled using the BafAscii code";

          m_fileReaderType = Enums::FileReaderType::bafascii;

          m_fileFormat = baf_ascii_ms_file_reader.getFileFormat();

          if(m_fileFormat == Enums::MsDataFormat::unknown)
            {
              ms_run_ids.clear();
            }
          else
            {
              return ms_run_ids;
            }
        }
    }
  catch(const pappso::PappsoException &error)
    {
      // qDebug() << "This is not a BafAscii code file" << error.qwhat();
    }


  // qDebug() << "The BafAscii reader did not work.";

  // At this point try the XyMsFileReader

  XyMsFileReader xy_ms_file_reader(m_fileName);

  ms_run_ids = xy_ms_file_reader.getMsRunIds(m_xmlPrefix);

  if(ms_run_ids.size())
    {
      // qDebug() << "Might well be handled using the XY code";

      m_fileReaderType = Enums::FileReaderType::xy;

      m_fileFormat = xy_ms_file_reader.getFileFormat();

      return ms_run_ids;
    }

  // qDebug() << "The XY reader did not work.";


  return ms_run_ids;
}

void
MsFileAccessor::setPreferredFileReaderType(Enums::MsDataFormat format,
                                           Enums::FileReaderType reader_type)
{
  // qDebug();

  auto ret = m_preferredFileReaderTypeMap.insert(
    std::pair<Enums::MsDataFormat, Enums::FileReaderType>(format, reader_type));

  if(!ret.second)
    {
      // replace
      ret.first->second = reader_type;
    }
}

Enums::FileReaderType
MsFileAccessor::getpreferredFileReaderType(Enums::MsDataFormat format)
{
  // qDebug();

  auto ret = m_preferredFileReaderTypeMap.find(format);

  if(ret != m_preferredFileReaderTypeMap.end())
    {
      return ret->second;
    }

  return m_fileReaderType;
}

Enums::FileReaderType
MsFileAccessor::getFileReaderType() const
{
  return m_fileReaderType;
}

void
MsFileAccessor::setSelectedMsRunId(MsRunIdCstSPtr ms_run_id_csp)
{
  mcsp_selectedMsRunId = ms_run_id_csp;
}

MsRunIdCstSPtr
MsFileAccessor::getSelectedMsRunId() const
{
  return mcsp_selectedMsRunId;
}

TimsMsRunReaderMs2SPtr
MsFileAccessor::buildTimsMsRunReaderMs2SPtr()
{
  // try TimsData reader
  QString tims_dir = m_fileName;
  if(!QFileInfo(tims_dir).isDir())
    {
      tims_dir = QFileInfo(m_fileName).absolutePath();
    }
  TimsMsFileReader tims_file_reader(tims_dir);

  std::vector<MsRunIdCstSPtr> ms_run_ids =
    tims_file_reader.getMsRunIds(m_xmlPrefix);

  if(ms_run_ids.size())
    {
      // qDebug() << "Might well be handled using the Bruker code";
      m_fileReaderType = Enums::FileReaderType::tims_ms2;
      m_fileFormat     = tims_file_reader.getFileFormat();
      m_fileName       = tims_dir;

      return std::make_shared<TimsMsRunReaderMs2>(ms_run_ids.front());
    }
  else
    {
      throw(ExceptionNotPossible(
        QObject::tr("Unable to read mz data directory %1 with TimsTOF reader.")
          .arg(tims_dir)));
    }
}

MsRunReaderSPtr
MsFileAccessor::msRunReaderSPtr(MsRunIdCstSPtr ms_run_id)
{
  qDebug();

  // We want to return a MsRunReader that accounts for the configuration that
  // the user might have set.

  if(m_fileName != ms_run_id->getFileName())
    throw(ExceptionNotPossible(
      QObject::tr("The MsRunId instance must have the name file name as the "
                  "MsFileAccessor. %1 != %2")
        .arg(m_fileName)
        .arg(ms_run_id->getFileName())));

  if(m_fileFormat == Enums::MsDataFormat::mzcbor)
    {
      qDebug() << "Returning a MzcborMsRunReader.";

      return std::make_shared<MzcborMsRunReader>(ms_run_id);
    }
  else if(getpreferredFileReaderType(m_fileFormat) ==
          Enums::FileReaderType::pwiz)
    {
      qDebug() << "Returning a PwizMsRunReader.";
      auto pwiz_reader = std::make_shared<PwizMsRunReader>(ms_run_id);
      m_oboPsiModTermNativeIDFormat =
        pwiz_reader->getOboPsiModTermNativeIDFormat();
      return pwiz_reader;
    }
  else if(getpreferredFileReaderType(m_fileFormat) == Enums::FileReaderType::xy)
    {
      // qDebug() << "Returning a XyMsRunReader.";

      return std::make_shared<XyMsRunReader>(ms_run_id);
    }
  else if(getpreferredFileReaderType(m_fileFormat) ==
          Enums::FileReaderType::tims)
    {
      qDebug() << "Returning a TimsMsRunReader Enums::FileReaderType::tims";
      return std::make_shared<TimsMsRunReader>(ms_run_id);
    }
  else if(getpreferredFileReaderType(m_fileFormat) ==
          Enums::FileReaderType::tims_frames)
    {
      qDebug() << "Returning a TimsFramesMsRunReader "
                  "Enums::FileReaderType::tims_frames.";

      return std::make_shared<TimsFramesMsRunReader>(ms_run_id);
    }
  else if(getpreferredFileReaderType(m_fileFormat) ==
          Enums::FileReaderType::tims_ms2)
    {
      qDebug()
        << "Returning a TimsMsRunReaderMs2 Enums::FileReaderType::tims_ms2";

      return std::make_shared<TimsMsRunReaderMs2>(ms_run_id);
    }
  else if(getpreferredFileReaderType(m_fileFormat) ==
          Enums::FileReaderType::tims_dia)
    {
      qDebug() << "Returning a Enums::FileReaderType::tims_dia";

      // qInfo() << "std::make_shared<TimsMsRunReaderDia>(ms_run_id);";
      return std::make_shared<TimsMsRunReaderDia>(ms_run_id);
    }
  else if(getpreferredFileReaderType(m_fileFormat) ==
          Enums::FileReaderType::bafascii)
    {
      // qDebug() << "Returning a BafAsciiMsRunReader.";

      return std::make_shared<BafAsciiMsRunReader>(ms_run_id);
    }
  if(m_fileFormat == Enums::MsDataFormat::unknown)
    {
      if(ms_run_id.get()->getMsDataFormat() == Enums::MsDataFormat::xy)
        {
          return std::make_shared<XyMsRunReader>(ms_run_id);
        }
      else
        {
          auto pwiz_reader = std::make_shared<PwizMsRunReader>(ms_run_id);
          m_oboPsiModTermNativeIDFormat =
            pwiz_reader->getOboPsiModTermNativeIDFormat();
          return pwiz_reader;
        }
    }
  else
    {
      throw PappsoException(QObject::tr("No file format was found."));
    }

  return nullptr;
}

MsRunReaderSPtr
MsFileAccessor::msRunReaderSPtr(std::size_t ms_run_id_index)
{
  std::vector<MsRunIdCstSPtr> ms_run_ids = getMsRunIds();
  if(ms_run_id_index >= ms_run_ids.size())
    throw PappsoException(QObject::tr("MsRunId request out-of-bound error."));

  return msRunReaderSPtr(ms_run_ids.at(ms_run_id_index));
}

MsRunReaderSPtr
MsFileAccessor::msRunReaderSPtrForSelectedMsRunId()
{
  // qDebug();

  return msRunReaderSPtr(mcsp_selectedMsRunId);
}

MsRunReaderSPtr
MsFileAccessor::buildMsRunReaderSPtr(MsRunIdCstSPtr ms_run_id)
{
  return buildMsRunReaderSPtr(ms_run_id, Enums::FileReaderType::tims);
}

MsRunReaderSPtr
MsFileAccessor::buildMsRunReaderSPtr(
  MsRunIdCstSPtr ms_run_id, Enums::FileReaderType preferred_file_reader_type)
{
  QFile file(ms_run_id.get()->getFileName());
  if(!file.exists())
    throw(ExceptionNotFound(
      QObject::tr("unable to build a reader : file %1 not found.")
        .arg(QFileInfo(ms_run_id.get()->getFileName()).absoluteFilePath())));

  Enums::MsDataFormat file_format = ms_run_id.get()->getMsDataFormat();

  if(file_format == Enums::MsDataFormat::xy)
    {
      // qDebug() << "Returning a XyMsRunReader.";

      return std::make_shared<XyMsRunReader>(ms_run_id);
    }
  else if(file_format == Enums::MsDataFormat::brukerBafAscii)
    {
      // qDebug() << "Returning a XyMsRunReader.";

      return std::make_shared<BafAsciiMsRunReader>(ms_run_id);
    }
  else if(file_format == Enums::MsDataFormat::unknown)
    {
      throw(PappsoException(
        QObject::tr("unable to build a reader for %1 : unknown file format")
          .arg(QFileInfo(ms_run_id.get()->getFileName()).absoluteFilePath())));
    }

  else if(file_format == Enums::MsDataFormat::brukerTims)
    {
      if(preferred_file_reader_type == Enums::FileReaderType::tims)
        {
          return std::make_shared<TimsMsRunReader>(ms_run_id);
        }
      else if(preferred_file_reader_type == Enums::FileReaderType::tims_ms2)
        {
          return std::make_shared<TimsMsRunReaderMs2>(ms_run_id);
        }
      else if(preferred_file_reader_type == Enums::FileReaderType::tims_frames)
        {
          qDebug()
            << "returning std::make_shared<TimsFramesMsRunReader>(ms_run_id).";
          return std::make_shared<TimsFramesMsRunReader>(ms_run_id);
        }
      // qDebug() << "by default, build a TimsMsRunReader.";
      return std::make_shared<TimsMsRunReader>(ms_run_id);
    }
  else if(file_format == Enums::MsDataFormat::mzcbor)
    {
      return std::make_shared<MzcborMsRunReader>(ms_run_id);
    }
  else
    {
      // qDebug() << "Returning a PwizMsRunReader .";
      return std::make_shared<PwizMsRunReader>(ms_run_id);
    }
}

MsRunReaderSPtr
MsFileAccessor::getMsRunReaderSPtrByRunId(const QString &run_id,
                                          const QString &xml_id)
{
  std::vector<MsRunIdCstSPtr> run_list = getMsRunIds();
  MsRunReaderSPtr reader_sp;
  for(MsRunIdCstSPtr &original_run_id : run_list)
    {
      if(original_run_id.get()->getRunId() == run_id)
        {
          MsRunId new_run_id(*original_run_id.get());
          new_run_id.setXmlId(xml_id);

          return msRunReaderSPtr(std::make_shared<MsRunId>(new_run_id));
        }
    }

  if((run_id.isEmpty()) && (run_list.size() == 1))
    {
      MsRunId new_run_id(*run_list[0].get());
      new_run_id.setXmlId(xml_id);

      return msRunReaderSPtr(std::make_shared<MsRunId>(new_run_id));
    }


  if(reader_sp == nullptr)
    {
      throw(
        ExceptionNotFound(QObject::tr("run id %1 not found in file %2")
                            .arg(run_id)
                            .arg(QFileInfo(m_fileName).absoluteFilePath())));
    }
  return reader_sp;
}


} // namespace pappso
