/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2026 Filippo Rusconi
 *
 * http://www.msxpertsuite.org
 *
 * This file is part of the msXpertSuite project.
 *
 * The msXpertSuite project is the successor of the massXpert project. This
 * project now includes various independent modules:
 *
 * - massXpert, model polymer chemistries and simulate mass spectrometric data;
 * - mineXpert, a powerful TIC chromatogram/mass spectrum viewer/miner;
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * END software license
 */


/////////////////////// StdLib includes
#include <map>


/////////////////////// Qt includes
#include <QDebug>

/////////////////////// pappsomspp includes
#include <pappsomspp/core/processing/combiners/integrationscope.h>
#include <pappsomspp/core/processing/combiners/integrationscoperect.h>
#include <pappsomspp/core/processing/combiners/integrationscoperhomb.h>

/////////////////////// Local includes
#include "ProcessingStep.hpp"

namespace MsXpS
{
namespace MineXpert
{


ProcessingStep::ProcessingStep(): m_dateAndTime(QDateTime::currentDateTimeUtc())
{
  // So that we know it is not nullptr;
  msp_integrationScope = std::make_shared<pappso::IntegrationScopeBase>();
}

ProcessingStep::ProcessingStep(const MsFragmentationSpec &ms_fragmentation_spec)
  : m_dateAndTime(QDateTime::currentDateTimeUtc())
{
  // So that we know it is not nullptr;
  msp_integrationScope    = std::make_shared<pappso::IntegrationScopeBase>();
  mpa_msFragmentationSpec = new MsFragmentationSpec(ms_fragmentation_spec);
}

ProcessingStep::ProcessingStep(const ProcessingStep &other)
  : m_dateAndTime(other.m_dateAndTime),
    m_xSrcType(other.m_xSrcType),
    m_ySrcType(other.m_ySrcType),
    m_destType(other.m_destType),
    msp_integrationScope(other.msp_integrationScope),
    m_xAxisDataKind(other.m_xAxisDataKind),
    m_yAxisDataKind(other.m_yAxisDataKind)
{
  if(other.mpa_mzIntegrationParams != nullptr)
    mpa_mzIntegrationParams = other.mpa_mzIntegrationParams->clone(nullptr);

  if(other.mpa_msFragmentationSpec != nullptr)
    mpa_msFragmentationSpec =
      new MsFragmentationSpec(*other.mpa_msFragmentationSpec);
}

ProcessingStep::~ProcessingStep()
{
  if(mpa_mzIntegrationParams != nullptr)
    delete mpa_mzIntegrationParams;

  if(mpa_msFragmentationSpec != nullptr)
    delete mpa_msFragmentationSpec;
}

ProcessingStep &
ProcessingStep::operator=(const ProcessingStep &other)
{
  if(this == &other)
    return *this;

  m_dateAndTime = other.m_dateAndTime;

  m_xSrcType = other.m_xSrcType;
  m_ySrcType = other.m_ySrcType;

  m_destType = other.m_destType;

  msp_integrationScope = other.msp_integrationScope;

  m_xAxisDataKind = other.m_xAxisDataKind;
  m_yAxisDataKind = other.m_yAxisDataKind;

  if(other.mpa_mzIntegrationParams != nullptr)
    mpa_mzIntegrationParams = mpa_mzIntegrationParams->clone(nullptr);

  if(other.mpa_msFragmentationSpec != nullptr)
    mpa_msFragmentationSpec =
      new MsFragmentationSpec(*other.mpa_msFragmentationSpec);

  return *this;
}

void
ProcessingStep::setSrcProcessingType(pappso::Enums::Axis axis,
                                     const ProcessingType &processing_type)
{
  if(axis == pappso::Enums::Axis::x)
    m_xSrcType = processing_type;
  else if(axis == pappso::Enums::Axis::y)
    m_ySrcType = processing_type;
  else
    qFatal() << "Programming error.";
}

void
ProcessingStep::setSrcProcessingType(pappso::Enums::Axis axis,
                                     const QString &processing_type)
{
  return setSrcProcessingType(axis, ProcessingType(processing_type));
}

ProcessingType
ProcessingStep::getSrcProcessingType(pappso::Enums::Axis axis) const
{
  if(axis == pappso::Enums::Axis::x)
    return m_xSrcType;
  else if(axis == pappso::Enums::Axis::y)
    return m_ySrcType;

  return ProcessingType("NOT_SET");
}

void
ProcessingStep::setDestProcessingType(const ProcessingType &processing_type)
{
  m_destType = processing_type;
}

void
ProcessingStep::setDestProcessingType(const QString &processing_type)
{
  setDestProcessingType(ProcessingType(processing_type));
}

ProcessingType
ProcessingStep::getDestProcessingType() const
{
  return m_destType;
}

void
ProcessingStep::setIntegrationScope(
  pappso::IntegrationScopeBaseCstSPtr integration_scope_csp)
{
  msp_integrationScope = integration_scope_csp;
}

pappso::IntegrationScopeBaseCstSPtr
ProcessingStep::getIntegrationScope() const
{
  return msp_integrationScope;
}

void
ProcessingStep::resetIntegrationScope()
{
  std::const_pointer_cast<pappso::IntegrationScopeBase>(msp_integrationScope)
    ->reset();
}

void
ProcessingStep::setDataKind(pappso::Enums::Axis axis,
                            pappso::Enums::DataKind data_kind)
{
  if(static_cast<int>(axis) & static_cast<int>(pappso::Enums::Axis::x))
    m_xAxisDataKind = data_kind;
  else if(static_cast<int>(axis) & static_cast<int>(pappso::Enums::Axis::y))
    m_yAxisDataKind = data_kind;
  else
    qFatal() << "The axis provided is not correct.";
}

pappso::Enums::DataKind
ProcessingStep::getDataKind(pappso::Enums::Axis axis) const
{
  if(static_cast<int>(axis) & static_cast<int>(pappso::Enums::Axis::x))
    return m_xAxisDataKind;
  else if(static_cast<int>(axis) & static_cast<int>(pappso::Enums::Axis::y))
    return m_yAxisDataKind;
  else
    qFatal() << "The axis provided is not correct.";

  // To avoid the warning.
  return pappso::Enums::DataKind::unset;
}

bool
ProcessingStep::getRangeForAxis(pappso::Enums::Axis axis,
                                double &range_start,
                                double &range_end) const
{
  return msp_integrationScope->range(axis, range_start, range_end);
}

bool
ProcessingStep::getRangeForAxisX(double &range_start, double &range_end) const
{
  return getRangeForAxis(pappso::Enums::Axis::x, range_start, range_end);
}

bool
ProcessingStep::getRangeForAxisY(double &range_start, double &range_end) const
{
  return getRangeForAxis(pappso::Enums::Axis::y, range_start, range_end);
}

// Taking ownership
void
ProcessingStep::setMzIntegrationParams(
  pappso::MzIntegrationParams *mz_integration_params_p)
{
  mpa_mzIntegrationParams = mz_integration_params_p;
}

void
ProcessingStep::setMzIntegrationParams(
  const pappso::MzIntegrationParams &mz_integration_params)
{
  mpa_mzIntegrationParams = mz_integration_params.clone(nullptr);
}

const pappso::MzIntegrationParams *
ProcessingStep::getMzIntegrationParams() const
{
  return mpa_mzIntegrationParams;
}

void
ProcessingStep::setMsFragmentationSpec(
  const MsFragmentationSpec &fragmentation_spec)
{
  if(mpa_msFragmentationSpec != nullptr)
    delete mpa_msFragmentationSpec;

  mpa_msFragmentationSpec = new MsFragmentationSpec(fragmentation_spec);
}

MsFragmentationSpec
ProcessingStep::getMsFragmentationSpec() const
{
  if(mpa_msFragmentationSpec != nullptr)
    return *mpa_msFragmentationSpec;

  return MsFragmentationSpec();
}

MsFragmentationSpec *
ProcessingStep::getMsFragmentationSpecPtr()
{
  return mpa_msFragmentationSpec;
}

size_t
ProcessingStep::getMsLevel() const
{
  if(mpa_msFragmentationSpec != nullptr && mpa_msFragmentationSpec->isValid())
    return mpa_msFragmentationSpec->getMsLevel();

  return 0;
}

bool
ProcessingStep::hasValidFragmentationSpec() const
{
  if(mpa_msFragmentationSpec != nullptr)
    return mpa_msFragmentationSpec->isValid();
  else
    return false;
}

bool
ProcessingStep::isValid() const
{
  // A valid processing step has necessarily valid processing types and a valid
  // selection polygon and valid axis data kinds.

  if(ProcessingType::compare(m_xSrcType, ProcessingType("NOT_SET")))
    return false;

  // We do not check the m_ySrcType because if the starting point is not
  // bidimensional, that value is rightfully NOT_SET.

  // The destination type cannot be unset because that would not mean anything
  // from an integration stand point.
  if(ProcessingType::compare(m_destType, ProcessingType("NOT_SET")))
    return false;

  if(!msp_integrationScope->is1D() && !msp_integrationScope->is2D())
    return false;

  if(m_xAxisDataKind == pappso::Enums::DataKind::unset ||
     m_yAxisDataKind == pappso::Enums::DataKind::unset)
    return false;

  return true;
}

bool
ProcessingStep::srcMatches(std::bitset<32> bit_set) const
{
  return (m_xSrcType.bitMatches(bit_set) || m_ySrcType.bitMatches(bit_set));
}

bool
ProcessingStep::srcMatches(const ProcessingType &processing_type) const
{
  return srcMatches(processing_type.bitSet());
}

bool
ProcessingStep::srcMatches(const QString &brief_desc) const
{
  return srcMatches(ProcessingType(brief_desc));
}

bool
ProcessingStep::destMatches(std::bitset<32> bit_set) const
{
  return (m_destType.bitMatches(bit_set));
}

bool
ProcessingStep::destMatches(const ProcessingType &processing_type) const
{
  return destMatches(processing_type.bitSet());
}

bool
ProcessingStep::destMatches(const QString &brief_desc) const
{
  return destMatches(ProcessingType(brief_desc));
}

QString
ProcessingStep::toString(int offset, const QString &spacer) const
{
  QString lead;

  for(int iter = 0; iter < offset; ++iter)
    lead += spacer;

  QString text  = lead;
  text         += "Processing step at: ";
  text         += m_dateAndTime.toString("yyyyMMdd-HH:mm:ss");
  text         += "\n";

  text += "X axis Source Processing type: ";
  text += m_xSrcType.toBriefDesc();
  text += "\n";

  text += "Y axis Source Processing type: ";
  text += m_ySrcType.toBriefDesc();
  text += "\n";

  text += "Destination Processing type: ";
  text += m_destType.toBriefDesc();
  text += "\n";

  text += "X axis data kind:";
  text += QString::number(static_cast<int>(m_xAxisDataKind), 10);
  text += "\n";

  text += "Y axis data kind:";
  text += QString::number(static_cast<int>(m_yAxisDataKind), 10);
  text += "\n";

  QString integration_scope = msp_integrationScope->toString();
  if(!integration_scope.isEmpty())
    {
      text += "Step's integration scope:";
      text += msp_integrationScope->toString();
      text += "\n";
    }

  if(mpa_mzIntegrationParams != nullptr)
    {
      text += "Step's m/z integration params:\n";
      // text += lead;
      // text +=  spacer;
      text += mpa_mzIntegrationParams->toString(offset + 2, spacer);
      // text += "\n";
    }

  if(mpa_msFragmentationSpec != nullptr)
    {
      text += "Step's fragmentation spec:\n";
      // text += lead;
      // text +=  spacer;
      text += mpa_msFragmentationSpec->toString(offset + 2, spacer);
      // text += "\n";
    }

  return text;
}


} // namespace MineXpert

} // namespace MsXpS
