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


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


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


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

namespace MsXpS
{
namespace MineXpert
{

bool ProcessingType::mapFilled = false;

// clang-format off
std::vector<ProcessingTypeStringPair> ProcessingType::processingTypes =
{
	std::pair<QString, QString>("NOT_SET", "Not set"),

  // These are the new types conforming to the new idea of a processing step's
  // types.
	std::pair<QString, QString>("FILE_RT", "File, Tic chrom."),

  // When a mass spectrum is loaded from file or memory or TCP socket.
	std::pair<QString, QString>("FILE_MZ", "File, Mass spectrum"),
	std::pair<QString, QString>("CLIPBOARD_MZ", "Clipboard, Mass spectrum"),
	std::pair<QString, QString>("SOCKET_MZ", "Socket, Mass spectrum"),

  // When a drift spectrum is loaded from file or memory or TCP socket.
	std::pair<QString, QString>("FILE_DT", "File, Drift spectrum"),

	std::pair<QString, QString>("DATA_TABLE_VIEW_DT", "Data table view, drift spectrum"),
	std::pair<QString, QString>("DATA_TABLE_VIEW_RT", "Data table view, drift spectrum"),

	std::pair<QString, QString>("DATA_XIC_MZ", "Data Xic chrom. limited by MZ"),

	std::pair<QString, QString>("DT", "Drift spectrum"),
	std::pair<QString, QString>("DT_MZ", "Drift spectrum, Mass spectrum"),
	std::pair<QString, QString>("DT_RT", "Drift spectrum, Tic|Xic chrom."),

	std::pair<QString, QString>("MZ", "Mass spectrum"),
	std::pair<QString, QString>("MZ_RT", "Mass spectrum, Tic|Xic chrom."),

	std::pair<QString, QString>("RT", "Tic|Xic chrom."),
	std::pair<QString, QString>("SOCKET_RT", "Socket, fake Tic|Xic chrom."),

	std::pair<QString, QString>("INT", "Tic intensity"),
 //std::pair<QString, QString>("", ""),
};
// clang-format on


std::unordered_map<std::bitset<32>, ProcessingTypeStringPair>
  ProcessingType::processingTypeRegistry = {};

void
ProcessingType::registerProcessingTypes()
{
  if(ProcessingType::mapFilled)
    return;

  // qDebug() << "Processing types vector size:"
  //<< ProcessingType::processingTypes.size();

  for(std::size_t iter = 0; iter < ProcessingType::processingTypes.size();
      ++iter)
    {
      // Instantiate a new bit set of which a single bit will be set to true:
      // the bit at index iter.

      std::bitset<32> bit_set;
      // All bits to false, first.
      bit_set.reset();
      // Now the single bit at position iter to true.
      bit_set.set(iter, true);

      ProcessingType::processingTypeRegistry.insert(
        std::pair<std::bitset<32>, ProcessingTypeStringPair>(
          bit_set, ProcessingType::processingTypes.at(iter)));

      // qDebug() << "Inserted new bit_set:"
      //<< ProcessingType::processingTypes.at(iter).first
      //<< "with value:" << QString::fromStdString(bit_set.to_string());
    }

  // Continue now with the composite bit sets:

  std::vector<QString> brief_desc_list;


  /* ANY_DT */
  // clang-format off
  brief_desc_list = {
    "FILE_DT",
    "DATA_TABLE_VIEW_DT",
    "DT",
    "DT_MZ",
    "DT_RT",
     };
  // clang-format on
  registerProcessingCompositeTypes(
    brief_desc_list,
    std::pair<QString, QString>("ANY_DT", "Whatever drift spectrum"));


  /* ANY_MZ */
  // clang-format off
  brief_desc_list = {
    "FILE_MZ",
    "CLIPBOARD_MZ",
    "SOCKET_MZ",
    "MZ",
    "DATA_XIC_MZ",
    "DT_MZ",
    "MZ_RT",
     };
  // clang-format on
  registerProcessingCompositeTypes(
    brief_desc_list,
    std::pair<QString, QString>("ANY_MZ", "Whatever mass spectrum"));


  /* ANY_RT */
  // clang-format off
  brief_desc_list = {
    "FILE_RT",
    "DATA_TABLE_VIEW_RT",
    "DT_RT",
    "MZ_RT",
    "RT",
    "SOCKET_RT",
     };
  // clang-format on
  registerProcessingCompositeTypes(
    brief_desc_list,
    std::pair<QString, QString>("ANY_RT", "Whatever Tic|Xic chrom."));


#if 0
  // This cannot work, because the map is not multimap and we cannot insert keys
  // that have the same value. Since we imagines ANY_INT as a synonym of INT,
  // the key would be the same.

  // We we decided to have only a INT processing type.

  /* ANY_INT */
  // clang-format off
  brief_desc_list = {
    "INT",
 };
  // clang-format on
  registerProcessingCompositeTypes(
    brief_desc_list,
    std::pair<QString, QString>("ANY_INT", "Whatever Tic intensity"));
#endif

  // brief_desc_list = {"", "", ""};
  // registerProcessingCompositeTypes(
  // brief_desc_list,
  // std::pair<QString, QString>("", ""));

  ProcessingType::mapFilled = true;
}

void
ProcessingType::registerProcessingCompositeTypes(
  std::vector<QString> brief_desc_list, ProcessingTypeStringPair dest_desc_pair)
{
  std::bitset<32> final_bit_set;
  final_bit_set.reset();

  // qDebug() << "The list of brief descriptions has size:"
  //<< brief_desc_list.size() << "and is titled:" << dest_desc_pair.first
  //<< "-" << dest_desc_pair.second;

  for(auto &&brief_desc : brief_desc_list)
    {
      // qDebug() << "Iterated brief_desc:" << brief_desc;

      if(brief_desc.isEmpty())
        {
          // qDebug() << "brief_desc is empty, continuing.";
          continue;
        }

      using Pair = std::pair<std::bitset<32>, ProcessingTypeStringPair>;
      using Map = std::unordered_map<std::bitset<32>, ProcessingTypeStringPair>;
      using Iterator = Map::const_iterator;

      Iterator found_iterator =
        std::find_if(ProcessingType::processingTypeRegistry.begin(),
                     ProcessingType::processingTypeRegistry.end(),
                     [brief_desc](const Pair &pair) {
                       return brief_desc == pair.second.first;
                     });

      if(found_iterator == ProcessingType::processingTypeRegistry.end())
        qFatal() << "Programming error.";

      std::bitset<32> iter_bit_set = found_iterator->first;

      // qDebug() << "The iterated bit set:"
      //<< QString::fromStdString(iter_bit_set.to_string());

      final_bit_set |= iter_bit_set;

      // qDebug() << "After composing final_bit_set:"
      //<< QString::fromStdString(final_bit_set.to_string());
    }

  // At this point we can effectively insert the new ProcessType

  ProcessingType::processingTypeRegistry.insert(
    std::pair<std::bitset<32>, ProcessingTypeStringPair>(final_bit_set,
                                                         dest_desc_pair));

  // qDebug() << "Inserted new bit_set:" << dest_desc_pair << "with value:"
  //<< QString::fromStdString(final_bit_set.to_string());
}

// static
bool
ProcessingType::isRegistered(std::bitset<32> bit_set)
{
  if(ProcessingType::processingTypeRegistry.find(bit_set) ==
     ProcessingType::processingTypeRegistry.end())
    return false;

  return true;
}

// static
bool
ProcessingType::isRegistered(QString brief_desc)
{
  using Pair = std::pair<std::bitset<32>, ProcessingTypeStringPair>;
  using Map  = std::unordered_map<std::bitset<32>, ProcessingTypeStringPair>;
  using Iterator = Map::const_iterator;

  Iterator found_iterator =
    std::find_if(ProcessingType::processingTypeRegistry.begin(),
                 ProcessingType::processingTypeRegistry.end(),
                 [brief_desc](const Pair &pair) {
                   return brief_desc == pair.second.first;
                 });

  if(found_iterator == ProcessingType::processingTypeRegistry.end())
    return false;

  return true;
}

// constructor
ProcessingType::ProcessingType(std::bitset<32> bit_set)
{
  registerProcessingTypes();

  // qDebug() << "Processing types vector size:"
  //<< ProcessingType::processingTypes.size()
  //<< "Processing types map size:"
  //<< ProcessingType::processingTypeRegistry.size();

  if(!isRegistered(bit_set))
    qFatal() << "Programming error.";

  m_briefDesc = ProcessingType::briefDesc(bit_set);
}

// constructor
ProcessingType::ProcessingType(const QString &brief_desc)
{
  registerProcessingTypes();

  if(!isRegistered(brief_desc))
    {

      qDebug() << "brief_desc:" << brief_desc << "Processing types vector size:"
               << ProcessingType::processingTypes.size()
               << "Processing types map size:"
               << ProcessingType::processingTypeRegistry.size();

      qFatal() << "Programming error.";
    }

  m_briefDesc = brief_desc;
}

// constructor
ProcessingType::ProcessingType(const ProcessingType &other)
{
  registerProcessingTypes();

  // qDebug() << "Processing types vector size:"
  //<< ProcessingType::processingTypes.size()
  //<< "Processing types map size:"
  //<< ProcessingType::processingTypeRegistry.size();

  // By essence, other needs to be a correctly constructed ProcessingType
  // instance. But check anyway for the moment.

  if(!ProcessingType::isRegistered(other.m_briefDesc))
    {

      qDebug() << "brief_desc:" << other.m_briefDesc
               << "Processing types vector size:"
               << ProcessingType::processingTypes.size()
               << "Processing types map size:"
               << ProcessingType::processingTypeRegistry.size();

      qFatal() << "Programming error.";
    }

  m_briefDesc = other.m_briefDesc;
}

// destructor
ProcessingType::~ProcessingType()
{
}

/*/// & = == operators */
ProcessingType &
ProcessingType::operator=(const ProcessingType &other)
{
  if(this == &other)
    return *this;

  // By essence, other needs to be a correctly constructed ProcessingType
  // instance. But check anyway for the moment.

  if(!ProcessingType::isRegistered(other.m_briefDesc))
    qFatal() << "Programming error.";

  m_briefDesc = other.m_briefDesc;

  return *this;
}

bool
ProcessingType::operator==(const ProcessingType &other)
{
  if(&other == this)
    return true;

  return m_briefDesc == other.m_briefDesc;
}

std::bitset<32>
ProcessingType::operator&(const ProcessingType &mask)
{
  return (bitSet() & mask.bitSet());
}

/* & = == operators */ ///


// Return the bit set on the basis of m_briefDesc.
std::bitset<32>
ProcessingType::bitSet() const
{
  // Return the bit set representation of *this processing type.

  return ProcessingType::bitSet(m_briefDesc);
}

const QString &
ProcessingType::getBriefDesc() const
{
  return m_briefDesc;
}

QString
ProcessingType::toBriefDesc() const
{
  return getBriefDesc();
}

QString
ProcessingType::toBitString() const
{
  return QString::fromStdString(bitSet().to_string());
}

/*/// static bit set -- brief/detailed desc relating functions */
/*/// static bit set -- brief/detailed desc relating functions */

// static
// Return the bit set on the basis of brief_desc.
std::bitset<32>
ProcessingType::bitSet(QString brief_desc)
{
  if(!isRegistered(brief_desc))
    {
      qDebug() << brief_desc << "is not registered.";
      qFatal() << "Programming error.";
    }

  using Pair = std::pair<std::bitset<32>, ProcessingTypeStringPair>;
  using Map  = std::unordered_map<std::bitset<32>, ProcessingTypeStringPair>;
  using Iterator = Map::const_iterator;

  // Search in the static map member the Pair that has its second element
  // (ProcessingTypeStringPair) first element identical to brief_desc.

  Iterator found_iterator =
    std::find_if(ProcessingType::processingTypeRegistry.begin(),
                 ProcessingType::processingTypeRegistry.end(),
                 [brief_desc](const Pair &pair) {
                   return brief_desc == pair.second.first;
                 });

  if(found_iterator == ProcessingType::processingTypeRegistry.end())
    {
      std::bitset<32> bit_set;
      bit_set.reset();
      return bit_set;
    }

  return found_iterator->first;
}

// static
std::bitset<32>
ProcessingType::bitSet(const ProcessingType &processing_type)
{
  // Get the brief description member out of processing_type, which we'll use
  // to find the bit set.

  return bitSet(processing_type.m_briefDesc);
}

// static
QString
ProcessingType::briefDesc(std::bitset<32> bit_set)
{
  using Map = std::unordered_map<std::bitset<32>, ProcessingTypeStringPair>;
  using Iterator = Map::const_iterator;

  Iterator found_iterator =
    ProcessingType::processingTypeRegistry.find(bit_set);

  if(found_iterator == ProcessingType::processingTypeRegistry.end())
    return QString();
  else
    // the second member of the map item is the string pair, of which we want
    // the first brief member.
    return found_iterator->second.first;
}

// static
QString
ProcessingType::detailedDesc(std::bitset<32> bit_set)
{
  using Map = std::unordered_map<std::bitset<32>, ProcessingTypeStringPair>;
  using Iterator = Map::const_iterator;

  Iterator found_iterator =
    ProcessingType::processingTypeRegistry.find(bit_set);

  if(found_iterator == ProcessingType::processingTypeRegistry.end())
    return QString();
  else
    // the second member of the map item is the string pair, of which we want
    // the second detailed member.
    return found_iterator->second.second;
}

/* static bit set -- brief desc/detailed relating functions */ ///
/* static bit set -- brief desc/detailed relating functions */ ///


/*/// static comparison and bit matching functions */
/*/// static comparison and bit matching functions */

// static
bool
ProcessingType::compare(const ProcessingType &a, const ProcessingType &b)
{
  return a.bitSet().to_string() < b.bitSet().to_string();
}

// static
bool
ProcessingType::bitMatches(const ProcessingType &processing_type,
                           std::bitset<32> mask)
{
  // qDebug() << "processing_type bit set representation:"
  //<< QString::fromStdString(processing_type.bitSet().to_string())
  //<< "while the mask is:" << QString::fromStdString(mask.to_string());

  std::bitset<32> test_bit_set = processing_type.bitSet();

  std::bitset<32> res_bit_set = test_bit_set & mask;
  bool res                    = res_bit_set == test_bit_set;

  // qDebug() << "res_bit_set:" <<
  // QString::fromStdString(res_bit_set.to_string())
  //<< "returning:" << res;

  return res;
}

// static
bool
ProcessingType::bitMatches(const ProcessingType &processing_type,
                           const QString &mask)
{
  return bitMatches(processing_type, ProcessingType::bitSet(mask));
}

// static
bool
ProcessingType::bitMatches(const ProcessingType &processing_type,
                           const ProcessingType &mask)
{
  return ProcessingType::bitMatches(processing_type, mask.bitSet());
}

/* static comparison and bit matching functions */ ///
/* static comparison and bit matching functions */ ///


/// Begin non static comparison and bit matching ///

bool
ProcessingType::compare(const ProcessingType &other)
{
  return ProcessingType::compare(*this, other);
}

bool
ProcessingType::bitMatches(std::bitset<32> mask) const
{
  return ProcessingType::bitMatches(*this, mask);
}

bool
ProcessingType::bitMatches(const ProcessingType &mask) const
{
  return ProcessingType::bitMatches(*this, mask);
}

bool
ProcessingType::bitMatches(const QString &mask) const
{
  return ProcessingType::bitMatches(*this, mask);
}

/// End non static comparison and bit matching ///


} // namespace MineXpert

} // namespace MsXpS
