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


/////////////////////// Qt includes
#include <QObject>


/////////////////////// Local includes
#include <massxpert/nongui/MonomerDictionary.hpp>
#include <massxpert/nongui/Sequence.hpp>


namespace msXpSmassXpert
{

MonomerDictionary::MonomerDictionary(QString filePath,
                                     const QStringList &inputChainStringList,
                                     int inputCodeLength,
                                     int outputCodeLength)
  : m_filePath(filePath),
    m_inputChainStringList(inputChainStringList),
    m_inputCodeLength(inputCodeLength),
    m_outputCodeLength(outputCodeLength)
{
}


//! Destroys the cleavage rule.
MonomerDictionary::~MonomerDictionary()
{
}

void
MonomerDictionary::setFilePath(QString &filePath)
{
  m_filePath = filePath;
}

void
MonomerDictionary::setInputChainStringList(
  const QStringList &inputChainStringList)
{
  m_inputChainStringList = inputChainStringList;
}

void
MonomerDictionary::setInputCodeLength(int codeLength)
{
  m_inputCodeLength = codeLength;
}

void
MonomerDictionary::setOutputCodeLength(int codeLength)
{
  m_outputCodeLength = codeLength;
}

bool
MonomerDictionary::isLineProperSectionDivider(const QString &line)
{
  // Section dividers in the monomer dictionary file format are
  // lines containing the following syntax: X>Y, that is for example
  // 3>1. This means that the following conversion rules (like
  // ILE>I) should convert 3-letter codes into 1-letter codes.

  // However, this line should only be considered proper if X is
  // actually the value of m_inputCodeLength and Y the value of
  // m_outputCodeLength.

  //     qDebug() << __FILE__ << __LINE__
  // 	     << "Checking if line is proper section divider :" << line;

  if(line.contains(QRegExp("[0-9]+>[0-9]+")))
    {
      // We are opening a new section, get the input/output code
      // lengths and if they math what we expect, then set the
      // current stream position and call the section parser.

      int greaterThanIndex = line.indexOf('>');

      QString codeLengthString = line.left(greaterThanIndex);

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << "Left codeLengthString:" << codeLengthString
      // 		 << "m_inputCodeLength:" << m_inputCodeLength;

      bool ok        = false;
      int codeLength = codeLengthString.toInt(&ok, 10);

      if(!codeLength && !ok)
        {
          qDebug() << __FILE__ << __LINE__ << "Monomer dictionary"
                   << "Failed to parse file " << m_filePath << "at line "
                   << line;

          return false;
        }

      if(codeLength != m_inputCodeLength)
        {
          return false;
        }

      codeLengthString = line.mid(greaterThanIndex + 1, -1);

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << "Right codeLengthString:" << codeLengthString
      // 		 << "m_outputCodeLength:" << m_outputCodeLength;

      ok         = false;
      codeLength = codeLengthString.toInt(&ok, 10);

      if(!codeLength && !ok)
        {
          qDebug() << __FILE__ << __LINE__ << "Monomer dictionary"
                   << "Failed to parse file " << m_filePath << "at line "
                   << line;

          return false;
        }

      if(codeLength != m_outputCodeLength)
        {
          return false;
        }

      // At this point, it seems we are in the proper
      // section.

      return true;
    }

  // If we are here, that means that the section is not for us.

  //     qDebug() << __FILE__ << __LINE__
  // 	     << "Line is no proper section divider.";

  return false;
}


void
MonomerDictionary::skipSection(QTextStream *stream)
{
  // We have entered a section, all we have to do is go through it
  // and return when we have found either the end of the stream or
  // the {END} marker.

  qint64 lineLength = 1024;
  QString line;

  while(!stream->atEnd())
    {
      line = stream->readLine(lineLength);

      if(!line.contains("{END}"))
        {
          line = stream->readLine(lineLength);
        }
      else
        return;
    }
}


int
MonomerDictionary::parseSection(QTextStream *stream)
{
  Q_ASSERT(stream);

  qint64 lineLength = 1024;
  QString line;

  // Iterate in the file using the stream and for each line create
  // an item to insert into the dictionary hash.

  while(!stream->atEnd())
    {
      line = stream->readLine(lineLength);

      // We might encounter the end of the section, that is a line
      // having {END} as its sole content.

      if(line.contains("{END}"))
        break;

      QStringList stringList = line.split('>');

      QString inputCode  = stringList.first();
      QString outputCode = stringList.last();

      // Check that the monomer codes have the proper length.

      if(inputCode.length() != m_inputCodeLength ||
         outputCode.length() != m_outputCodeLength)
        {
          qDebug() << __FILE__ << __LINE__ << QObject::tr("Monomer dictionary:")
                   << QObject::tr("Failed to load dictionary.")
                   << QObject::tr("Monomer code lengths do not match:")
                   << QObject::tr("inputCode:") << inputCode
                   << QObject::tr("outputCode:") << outputCode;


          // We have to empty the hash
          m_dictionaryHash.clear();

          break;
        }

      m_dictionaryHash.insert(inputCode, outputCode);

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << stringList.first () << stringList.last ();
    }

  // At this point the parsing is finished, either because we
  // encountered the {END} section-ending delimiter, or because we
  // reached the en of file.

  int hashSize = m_dictionaryHash.size();

  if(hashSize)
    m_dictionaryLoaded = true;
  else
    {
      qDebug() << __FILE__ << __LINE__ << QObject::tr("Monomer dictionary:")
               << QObject::tr("Failed to load dictionary.");

      m_dictionaryLoaded = false;
    }

  return hashSize;
}


bool
MonomerDictionary::loadDictionary()
{
  // Load the file and for each line deconstruct the item into two
  // QString objects that are used to make a QHash entry in
  // QHash<QString, QString> m_dictionaryHash.
  bool success      = true;
  qint64 lineLength = 1024;
  QString line;

  QFile file(m_filePath);

  if(!file.open(QIODevice::ReadOnly))
    {

      m_dictionaryLoaded = false;

      qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:"
               << "Failed to open file" << m_filePath << "for writing.";

      return false;
    }

  if(m_inputCodeLength < 1 || m_outputCodeLength < 1)
    {
      qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:"
               << "Failed to parse file " << m_filePath
               << "Please, set the m_inputCodeLength and "
                  "m_ouputCodeLength variables first.";

      return false;
    }

  QTextStream *stream = new QTextStream(&file);
  stream->setCodec("UTF-8");

  while(!stream->atEnd())
    {
      line = stream->readLine(lineLength);

      // 	  qDebug() << __FILE__ << __LINE__
      // 		   << "line: " << line;

      // Remove spaces from start and end of line.
      line = line.simplified();

      if(line.startsWith('#') || line.isEmpty())
        {
          line = stream->readLine(lineLength);
          continue;
        }

      // There might be any number of sections in the file, all
      // delimited with a X>Y directive, indicating how many
      // characters are allowed for the input code and for the
      // output code.

      if(!isLineProperSectionDivider(line))
        {
          // 	    qDebug() << __FILE__ << __LINE__
          // 		     << "skipping line:" << line;

          line = stream->readLine(lineLength);
          continue;
        }
      else
        {
          // 	    qDebug() << __FILE__ << __LINE__
          // 		     << "parsing section: " << line;

          if(parseSection(stream) < 1)
            {
              qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:"
                       << "Failed to parse file " << m_filePath;

              success = false;
              break;
            }
          else
            {
              // We successfully parsed the section. Our work is done.

              success = true;
              break;
            }
        }
    }

  delete stream;

  return success;
}


QStringList *
MonomerDictionary::translate(const QStringList &chainStringList)
{
  // The string in sequence is a space-separated list of monomer
  // codes in the original monomer code format. We have to translate
  // that to the proper monomer code format using the hash in this
  // dictionary.

  QStringList *outputChainStringList = new QStringList();

  if(!chainStringList.isEmpty())
    m_inputChainStringList = chainStringList;

  // If there is nothing to do return an empty string list so that
  // caller knows nothing is actually wrong, only there is no
  // sequence to translate.
  if(m_inputChainStringList.isEmpty())
    return outputChainStringList;

  // Iterate in each chain string of the list and perform the
  // translation.

  for(int iter = 0; iter < m_inputChainStringList.size(); ++iter)
    {
      QString iterString = chainStringList.at(iter);

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << "translating sequence:" << iterString;

      QStringList codeList =
        iterString.split(QRegExp("\\s+"), QString::SkipEmptyParts);

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << "codeList:" << codeList;

      // 	qDebug() << __FILE__ << __LINE__
      // 		 << "hash:"
      // 		 << m_dictionaryHash;

      for(int jter = 0; jter < codeList.size(); ++jter)
        {
          QString code = codeList.at(jter);

          QHash<QString, QString>::const_iterator hashIter =
            m_dictionaryHash.find(code);

          if(hashIter != m_dictionaryHash.end())
            codeList.replace(jter, hashIter.value());
          else
            {
              // Delete the string list, set the pointer to 0 and
              // return that pointer so that caller knows something
              // has gone wrong.

              qDebug() << __FILE__ << __LINE__ << "Monomer dictionary:"
                       << "Failed to convert monomer code " << code;

              outputChainStringList->clear();

              delete outputChainStringList;
              outputChainStringList = 0;

              return outputChainStringList;
            }
        }

      // At this point the sequence codes have been translated. Join all
      // the item of the codeList into one single string.

      outputChainStringList->append(codeList.join(QString("")));
    }

  // End of
  // for (int iter = 0; iter < chainStringList.size(); ++iter)

  // If no translation could be performed, return a n

  if(!outputChainStringList->size())
    {
      outputChainStringList->clear();

      delete outputChainStringList;
      outputChainStringList = 0;
    }

  return outputChainStringList;
}


} // namespace msXpSmassXpert
