/* BEGIN software license
 *
 * msXpertSuite - mass spectrometry software suite
 * -----------------------------------------------
 * Copyright(C) 2009,...,2019 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


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


namespace msxps
{
namespace minexpert
{


ProcessingStep::ProcessingStep()
{
  m_dateAndTime = QDateTime::currentDateTimeUtc();
}


ProcessingStep::ProcessingStep(const ProcessingStep &other)
{
  m_dateAndTime = other.m_dateAndTime;

  if(other.mpa_mzIntegrationParams != nullptr)
    mpa_mzIntegrationParams =
      new pappso::MzIntegrationParams(*other.mpa_mzIntegrationParams);

  for(auto &&type_spec_pair : other.m_processingTypeSpecMultiMap)
    {
      ProcessingType processing_type = type_spec_pair.first;

      m_processingTypeSpecMultiMap.insert(
        std::pair<ProcessingType, ProcessingSpec *>(
          processing_type, new ProcessingSpec(*type_spec_pair.second)));
    }
}


ProcessingStep::~ProcessingStep()
{
  m_processingTypeSpecMultiMap.clear();

  if(mpa_mzIntegrationParams != nullptr)
    delete mpa_mzIntegrationParams;
}


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

  m_dateAndTime = other.m_dateAndTime;

  if(other.mpa_mzIntegrationParams != nullptr)
    mpa_mzIntegrationParams =
      new pappso::MzIntegrationParams(*other.mpa_mzIntegrationParams);

  for(auto &&item : m_processingTypeSpecMultiMap)
    {
      m_processingTypeSpecMultiMap.insert(
        std::pair<ProcessingType, ProcessingSpec *>(
          item.first, new ProcessingSpec(*item.second)));
    }

  return *this;
}


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


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


void
ProcessingStep::newSpec(ProcessingType processing_type,
                        ProcessingSpec *processing_spec_p)
{
  if(processing_spec_p == nullptr)
    qFatal("The pointer cannot be nullptr");

  m_processingTypeSpecMultiMap.insert(
    std::pair<ProcessingType, ProcessingSpec *>(processing_type,
                                                processing_spec_p));
}


ProcessingSpec *
ProcessingStep::newSpec(ProcessingType processing_type,
                        const ProcessingSpec &processing_spec)
{
  ProcessingSpec *new_spec_p = new ProcessingSpec(processing_spec);

  m_processingTypeSpecMultiMap.insert(
    std::pair<ProcessingType, ProcessingSpec *>(processing_type, new_spec_p));

  return new_spec_p;
}


void
ProcessingStep::newSpec(const QString &brief_desc,
                        ProcessingSpec *processing_spec_p)
{
  // We take ownership of the processing_spec_p
  m_processingTypeSpecMultiMap.insert(
    std::pair<ProcessingType, ProcessingSpec *>(ProcessingType(brief_desc),
                                                processing_spec_p));
}


void
ProcessingStep::newSpec(std::bitset<128> bit_set,
                        ProcessingSpec *processing_spec_p)
{
  // We take ownership of the processing_spec_p
  m_processingTypeSpecMultiMap.insert(
    std::pair<ProcessingType, ProcessingSpec *>(ProcessingType(bit_set),
                                                processing_spec_p));
}


const std::multimap<ProcessingType, ProcessingSpec *, ProcessingTypeCompare> &
ProcessingStep::getProcessingTypeSpecMap() const
{
  return m_processingTypeSpecMultiMap;
}


std::vector<ProcessingType>
ProcessingStep::processingTypes() const
{
  std::vector<ProcessingType> processing_types;

  for(auto &&iter_pair : m_processingTypeSpecMultiMap)
    {
      bool found = false;
      for(auto &&type : processing_types)
        {
          if(type == iter_pair.first)
            {
              found = true;
            }
        }

      if(!found)
        processing_types.push_back(iter_pair.first);
    }

  return processing_types;
}


bool
ProcessingStep::matches(std::bitset<128> bit_set) const
{

  // A processing step hold a map relating any number of ProcessingType objects
  // with a corresponding ProcessingSpec pointer.
  //
  // We need to go through all of these map items and for each check if it
  // matches the processing type described by the \p bit_set.

  using Pair = std::pair<ProcessingType, ProcessingSpec *>;
  using Map  = std::map<ProcessingType, ProcessingSpec *>;

  Map::const_iterator iterator = std::find_if(
    m_processingTypeSpecMultiMap.begin(),
    m_processingTypeSpecMultiMap.end(),
    [bit_set](const Pair &pair) { return pair.first.bitMatches(bit_set); });

  if(iterator != m_processingTypeSpecMultiMap.end())
    return true;

  return false;
}


bool
ProcessingStep::matches(ProcessingType type) const
{
  // qDebug() << "type's brief_desc:" << type.getBriefDesc() << "with bitset:"
  //<< QString::fromStdString(
  // ProcessingType::bitSet(type.getBriefDesc()).to_string())
  //<< "and this bitset:"
  //<< QString::fromStdString(
  // ProcessingType::bitSet(type.getBriefDesc()).to_string());

  return ProcessingStep::matches(type.bitSet());
}


bool
ProcessingStep::matches(const QString &brief_desc) const
{
  // qDebug()
  //<< "brief_desc:" << brief_desc << "with bitset:"
  //<< QString::fromStdString(ProcessingType::bitSet(brief_desc).to_string())
  //<< "and this bitset:"
  //<< QString::fromStdString(ProcessingType::bitSet(brief_desc).to_string());

  return ProcessingStep::matches(ProcessingType::bitSet(brief_desc));
}


bool
ProcessingStep::contains(const QString &brief_desc) const
{
  using Pair = std::pair<ProcessingType, ProcessingSpec *>;
  using Map  = std::map<ProcessingType, ProcessingSpec *>;

  Map::const_iterator iterator =
    std::find_if(m_processingTypeSpecMultiMap.begin(),
                 m_processingTypeSpecMultiMap.end(),
                 [brief_desc](const Pair &pair) {
                   return pair.first.getBriefDesc() == brief_desc;
                 });

  if(iterator != m_processingTypeSpecMultiMap.end())
    return true;

  return false;
}


size_t
ProcessingStep::greatestMsLevel() const
{

  size_t greatest_ms_level = 0;

  using Map      = std::map<ProcessingType, ProcessingSpec *>;
  using Iterator = Map::const_iterator;

  for(Iterator iterator = m_processingTypeSpecMultiMap.begin();
      iterator != m_processingTypeSpecMultiMap.end();
      ++iterator)
    {
      auto &&pair = *iterator;

      ProcessingSpec *processing_spec_p = pair.second;

      MsFragmentationSpec *ms_fragmentation_spec_p =
        processing_spec_p->getMsFragmentationSpecPtr();

      if(ms_fragmentation_spec_p != nullptr &&
         ms_fragmentation_spec_p->isValid())
        {
          if(ms_fragmentation_spec_p->getMsLevel() > greatest_ms_level)
            greatest_ms_level = ms_fragmentation_spec_p->getMsLevel();
        }
    }

  return greatest_ms_level;
}


std::vector<ProcessingSpec *>
ProcessingStep::allProcessingSpecsMatching(
  const ProcessingType &processing_type,
  bool only_with_valid_ms_fragmentation_spec) const
{
  std::vector<ProcessingSpec *> processing_specs;

  using Map      = std::map<ProcessingType, ProcessingSpec *>;
  using Iterator = Map::const_iterator;

  for(Iterator iterator = m_processingTypeSpecMultiMap.begin();
      iterator != m_processingTypeSpecMultiMap.end();
      ++iterator)
    {
      auto &&pair = *iterator;

      if(ProcessingType::bitMatches(pair.first, processing_type))
        {
          if(only_with_valid_ms_fragmentation_spec &&
             !pair.second->hasValidFragmentationSpec())
            {
              continue;
            }
          else
            {
              processing_specs.push_back(pair.second);
            }
        }
    }

  return processing_specs;
}


std::pair<double, double>
ProcessingStep::innermostRangeProcessingSpecsMatching(
  const ProcessingType &processing_type,
  bool only_with_valid_ms_fragmentation_spec) const
{
  double start = std::numeric_limits<double>::min();
  double end   = std::numeric_limits<double>::max();

  using Map      = std::map<ProcessingType, ProcessingSpec *>;
  using Iterator = Map::const_iterator;

  for(Iterator iterator = m_processingTypeSpecMultiMap.begin();
      iterator != m_processingTypeSpecMultiMap.end();
      ++iterator)
    {
      auto &&pair = *iterator;

      if(ProcessingType::bitMatches(pair.first, processing_type))
        {
          if(only_with_valid_ms_fragmentation_spec &&
             !pair.second->hasValidFragmentationSpec())
            {
              continue;
            }
          else
            {
              if(pair.second->m_start > start)
                start = pair.second->m_start;

              if(pair.second->m_end < end)
                end = pair.second->m_end;
            }
        }
    }

  return std::pair<double, double>(start, end);
}


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";

  if(mpa_mzIntegrationParams != nullptr)
    {
      // text += lead;
      // text +=  spacer;
      text += mpa_mzIntegrationParams->toString(offset + 2, spacer);
      // text += "\n";
    }

  if(m_processingTypeSpecMultiMap.size())
    {
      for(auto &&pair : m_processingTypeSpecMultiMap)
        {
          // pair.second is the ProcessingSpec and pair.first is the
          // ProcessingType.

          text += pair.second->toString(offset + 3, spacer, pair.first);
          // text += "\n";
        }
    }

  return text;
}


} // namespace minexpert

} // namespace msxps
