/* 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 <vector>


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


/////////////////////// pappsomspp includes


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


namespace msxps
{
namespace minexpert
{


ProcessingFlow::ProcessingFlow()
{
}


ProcessingFlow::ProcessingFlow(MsRunDataSetCstSPtr ms_run_data_set_csp)
  : mcsp_msRunDataSet(ms_run_data_set_csp)
{
}

ProcessingFlow::ProcessingFlow(const ProcessingFlow &other)
  : std::vector<msxps::minexpert::ProcessingStep *>(),
    mcsp_msRunDataSet(other.mcsp_msRunDataSet),
    m_defaultMzIntegrationParams(other.m_defaultMzIntegrationParams),
    m_defaultMsFragmentationSpec(other.m_defaultMsFragmentationSpec)
{
  for(auto &&step_p : other)
    push_back(new ProcessingStep(*step_p));
}


ProcessingFlow::~ProcessingFlow()
{
  // Free all the steps in the vector, the objects are deleted.
  clear();
}


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

  mcsp_msRunDataSet            = other.mcsp_msRunDataSet;
  m_defaultMzIntegrationParams = other.m_defaultMzIntegrationParams;
  m_defaultMsFragmentationSpec = other.m_defaultMsFragmentationSpec;

  assign(other.begin(), other.end());

  return *this;
}


void
ProcessingFlow::setMsRunDataSetCstSPtr(MsRunDataSetCstSPtr ms_run_data_set_csp)
{
  mcsp_msRunDataSet = ms_run_data_set_csp;
}


MsRunDataSetCstSPtr
ProcessingFlow::getMsRunDataSetCstSPtr() const
{
  return mcsp_msRunDataSet;
}


void
ProcessingFlow::setDefaultMzIntegrationParams(
  const pappso::MzIntegrationParams &mz_integration_params)
{
  m_defaultMzIntegrationParams = mz_integration_params;
}


void
ProcessingFlow::setDefaultMsFragmentationSpec(
  const MsFragmentationSpec &ms_fragmentation_spec)
{
  // qDebug();

  m_defaultMsFragmentationSpec = ms_fragmentation_spec;
}


const pappso::MzIntegrationParams &
ProcessingFlow::getDefaultMzIntegrationParams() const
{
  return m_defaultMzIntegrationParams;
}


const MsFragmentationSpec &
ProcessingFlow::getDefaultMsFragmentationSpec() const
{
  return m_defaultMsFragmentationSpec;
}


std::vector<ProcessingStep *>
ProcessingFlow::steps()
{
  std::vector<ProcessingStep *> steps;

  for(auto &&step : *this)
    {
      steps.push_back(new ProcessingStep(*step));
    }

  return steps;
}


bool
ProcessingFlow::innermostRange(
  ProcessingType type_mask,
  std::pair<ProcessingType, ProcessingSpec *> &pair) const
{
  // Iterate in all the various ProcessingStep_s and check which of them has
  // ProcessingSpec instances that match ProcessingType integrations and that is
  // innermost of all.

  double temp_start = std::numeric_limits<double>::quiet_NaN();
  double temp_end   = std::numeric_limits<double>::quiet_NaN();

  bool was_spec_found = false;
  bool first_item     = true;

  // Remember that this processing flow is nothing but a vector of processing
  // steps.

  for(auto &&step : *this)
    {
      // Now search in the various (processing type / processing spec) items of
      // the step's map.

      for(auto &&iter_pair : step->getProcessingTypeSpecMap())
        {
          if(!iter_pair.first.bitMatches(type_mask))
            continue;

          // Ah, there is a pair of interest, here, because the type key of
          // the map pair matches the type passed as parameter.  Before
          // doing comparisons, we need to check if the processing spec has
          // a valid range (m_start--m_end). If not we just skip the
          // iter_pair, otherwise we make the checks.

          ProcessingSpec *spec_p = iter_pair.second;

          if(!spec_p->hasValidRange())
            continue;

          // Because we look of a matching type item that has the more
          // restricted range (that is, innermost range), we need to
          // store the first item's range for later comparison.

          if(first_item)
            {
              temp_start = iter_pair.second->m_start;
              temp_end   = iter_pair.second->m_end;

              // qDebug() << "First item has range:" << temp_start << "->"
              //<< temp_end;

              first_item = false;
            }
          else
            {
              // We seek the *innermost* range, so be aware of the
              // relational < > operators that may look used at reverse
              // but are not.

              if(iter_pair.second->m_start > temp_start)
                temp_start = iter_pair.second->m_start;

              if(iter_pair.second->m_end < temp_end)
                temp_end = iter_pair.second->m_end;

              // qDebug() << "New item has range:" << iter_pair.second->m_start
              //<< "-" << iter_pair.second->m_end;
            }

          // We found at least one item!
          was_spec_found = true;
        }

      if(was_spec_found)
        {

          // Set the pair to the parameter. We may change it later, but
          // for the moment, that is.
          pair.second->setRange(temp_start, temp_end);

          // qDebug() << "Set the new range:" << pair.second->getStart() << "->"
          //<< pair.second->getEnd();
        }
    }

  return was_spec_found;
}


bool
ProcessingFlow::innermostRange(ProcessingType type_mask,
                               double &start,
                               double &end) const
{
  ProcessingSpec spec;

  std::pair<ProcessingType, ProcessingSpec *> pair(type_mask, &spec);

  bool res = innermostRange(type_mask, pair);

  if(!res)
    return false;

  start = pair.second->getStart();
  end   = pair.second->getEnd();

  return true;
}


bool
ProcessingFlow::innermostRtRange(double &start, double &end) const
{
  return innermostRange(ProcessingType("RT_TO_ANY"), start, end);
}


bool
ProcessingFlow::innermostRtRange(
  std::pair<ProcessingType, ProcessingSpec *> &pair) const
{
  return innermostRange(ProcessingType("RT_TO_ANY"), pair);
}


bool
ProcessingFlow::innermostDtRange(double &start, double &end) const
{
  return innermostRange(ProcessingType("DT_TO_ANY"), start, end);
}


bool
ProcessingFlow::innermostDtRange(
  std::pair<ProcessingType, ProcessingSpec *> &pair) const
{
  return innermostRange(ProcessingType("DT_TO_ANY"), pair);
}


bool
ProcessingFlow::innermostMzRange(double &start, double &end) const
{
  return innermostRange(ProcessingType("MZ_TO_ANY"), start, end);
}


bool
ProcessingFlow::innermostMzRange(
  std::pair<ProcessingType, ProcessingSpec *> &pair) const
{
  return innermostRange(ProcessingType("MZ_TO_ANY"), pair);
}


size_t
ProcessingFlow::greatestMsLevel() const
{
  // We want to know what is the greatest MS level in the default ms frag spec
  // and in all the ms frag specs of the various processing specs.

  size_t greatest_ms_level = m_defaultMsFragmentationSpec.getMsLevel();

  for(auto &&step : *this)
    {
      if(step->greatestMsLevel() > greatest_ms_level)
        {
          greatest_ms_level = step->greatestMsLevel();
        }
    }

  // qDebug() << "The greatest MS level of the whole processing flow:"
  //<< greatest_ms_level;

  return greatest_ms_level;
}


const ProcessingStep *
ProcessingFlow::oldestStep() const
{
  // We rely on the QDateTime instance in the steps to check which of the step
  // was created last.

  if(!size())
    return nullptr;

  if(size() == 1)
    return front();

  const ProcessingStep *oldest_step_p = front();

  // Seed the temporary date and time with the first step in the vector.
  QDateTime temp_date_time = front()->m_dateAndTime;

  for(std::vector<ProcessingStep *>::const_iterator iterator = begin() + 1;
      iterator != end();
      ++iterator)
    {
      // Is the iterated step older than temp_date_time?
      if((*iterator)->m_dateAndTime < temp_date_time)
        {
          oldest_step_p  = *iterator;
          temp_date_time = (*iterator)->m_dateAndTime;
        }
    }

  return oldest_step_p;
}


const ProcessingStep *
ProcessingFlow::mostRecentStep() const
{
  // We rely on the QDateTime instance in the steps to check which of the step
  // was created last.

  if(!size())
    return nullptr;

  if(size() == 1)
    return front();

  const ProcessingStep *most_recent_step_p = front();
  QDateTime date_time                      = most_recent_step_p->m_dateAndTime;

  for(std::vector<ProcessingStep *>::const_iterator iterator = begin() + 1;
      iterator != end();
      ++iterator)
    {
      // qDebug();

      if((*iterator)->m_dateAndTime > date_time)
        most_recent_step_p = *iterator;
    }

  return most_recent_step_p;
}


const ProcessingStep *
ProcessingFlow::closestOlderStep(const QDateTime &date_time) const
{
  // qDebug() << "comparison date time:" << date_time
  //<< "number of steps:" << size();

  // We want a step that is older than date_time. But we also want that the
  // step be the most recent older step, that is, the closest older step.
  if(!size())
    return nullptr;

  if(size() == 1)
    {
      if(front()->m_dateAndTime < date_time)
        {
          // qDebug() << "Returning unique older:" << front()->m_dateAndTime;
          return front();
        }
    }
  else
    return nullptr;


  // Seed the temporary date time with the oldest step's date and time.

  const ProcessingStep *older_step_p = nullptr;

  for(std::vector<ProcessingStep *>::const_iterator iterator = begin();
      iterator != end();
      ++iterator)
    {
      // qDebug() << "Iterating in time:" << (*iterator)->m_dateAndTime;

      if((*iterator)->m_dateAndTime < date_time)
        {
          // qDebug() << "iter time is less than comparison date time.";

          if(older_step_p == nullptr)
            {
              // First iteration for which the date is less than comparison
              // time.
              older_step_p = *iterator;

              continue;
            }

          // The currently iterated step is older than date_time, fine, but is
          // it more recent that the temporaray time ? If
          // so set it as the last older step.

          if((*iterator)->m_dateAndTime > older_step_p->m_dateAndTime)
            {
              // qDebug() << "more than that: iter time is also more recent than
              // " "last itered time.";

              older_step_p = *iterator;
            }
        }
    }

  return older_step_p;
}


const pappso::MzIntegrationParams *
ProcessingFlow::mostRecentMzIntegrationParams() const
{

  // qDebug() << "*this processing flow:" << this->toString();

  const ProcessingStep *most_recent_step_p = mostRecentStep();

  if(most_recent_step_p != nullptr)
    {
      if(most_recent_step_p->mpa_mzIntegrationParams != nullptr)
        {
          // qDebug()
          //<< "Returning immediately the most recent step's mz integ. params.";

          return most_recent_step_p->mpa_mzIntegrationParams;
        }
    }
  else
    {
      // qDebug() << "Not a single most recent step there. The flow seems
      // empty.";
      // Apparently there is not a single step in the processing flow.
      return nullptr;
    }

  // If the most recent step had no mz integration params set, we need to
  // iterate back in time.
  while(1)
    {
      // Get a step older than the most recent step.

      const ProcessingStep *closest_older_step_p =
        closestOlderStep(most_recent_step_p->m_dateAndTime);

      if(closest_older_step_p != nullptr)
        {
          // qDebug()
          //<< "iterating in infinite while loop with closest older step time:"
          //<< closest_older_step_p->m_dateAndTime;

          // Found an older step, but check if it has mz integration params set
          // to it.
          if(closest_older_step_p->mpa_mzIntegrationParams != nullptr)
            {
              // qDebug() << "Found a step that is closest and that has mz "
              //"integration params.";

              return closest_older_step_p->mpa_mzIntegrationParams;
            }
          else
            {
              // qDebug() << "Continuing in the infinite while loop.";
              continue;
            }
        }
      else
        {
          // There are no more older steps.
          // qDebug()
          //<< "There are no more closest older steps. Returning nullptr";

          return nullptr;
        }
    }

  return nullptr;
}


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

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

  QString text = lead;
  // text += "Processing flow:\n";

  // text += lead;
  // text += spacer;
  text += "Sample name:\n";
  text += mcsp_msRunDataSet->getMsRunId()->getSampleName();
  text += "\n\n";

  // text += lead;
  // text += spacer;
  // text += "Default m/z integration params:\n";
  text += m_defaultMzIntegrationParams.toString(offset, spacer);
  text += "\n";

  // text += lead;
  // text += spacer;
  // text += "Default MS fragmentation spec:\n";
  text += m_defaultMsFragmentationSpec.toString(offset, spacer);
  text += "\n";

  // Only show the steps if there are any !
  if(size())
    {
      // text += lead;
      // text += spacer;
      // text += "The steps:\n";

      for(auto &&step : *this)
        {
          text += step->toString(offset, spacer);
          // text += "\n";
        }
    }

  return text;
}


} // namespace minexpert


} // namespace msxps
