/* massXpert - the true massist's program.
   --------------------------------------
   Copyright(C) 2006,2007 Filippo Rusconi

   http://www.massxpert.org/massXpert

   This file is part of the massXpert project.

   The massxpert project is the successor to the "GNU polyxmass"
   project that is an official GNU project package(see
   www.gnu.org). The massXpert project is not endorsed by the GNU
   project, although it is released ---in its entirety--- under the
   GNU General Public License. A huge part of the code in massXpert
   is actually a C++ rewrite of code in GNU polyxmass. As such
   massXpert was started at the Centre National de la Recherche
   Scientifique(FRANCE), that granted me the formal authorization to
   publish it under this Free Software License.

   This software is free software; you can redistribute it and/or
   modify it under the terms of the GNU  General Public
   License version 3, as published by the Free Software Foundation.
   

   This software 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 software; if not, write to the

   Free Software Foundation, Inc.,

   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/


/////////////////////// Local includes
#include "crossLink.hpp"
#include "polymer.hpp"


namespace massXpert
{

  CrossLink::CrossLink(const PolChemDef *polChemDef,
			Polymer *polymer,
			const QString &name,
			const QString &formula,
			const QString &comment)
    : CrossLinker(polChemDef, name, formula), mp_polymer(polymer),
      m_comment(comment)
  {
  
  }


  CrossLink::CrossLink(const CrossLinker &crossLinker,
			Polymer *polymer,
			const QString &comment)
    : CrossLinker(crossLinker),
      mp_polymer(polymer), m_comment(comment)
  {
  
  }


  CrossLink::CrossLink(const CrossLink &crossLink)
    : CrossLinker(crossLink), mp_polymer(crossLink.mp_polymer),
      m_comment(crossLink.m_comment)
  {


  }
  

  CrossLink::~CrossLink()
  {
  }


  bool 
  CrossLink::setMonomerAt(Monomer *monomer, int index)
  {
    Q_ASSERT(monomer);
    Q_ASSERT(index >= 0 && index < m_monomerList.size());

    m_monomerList.replace(index, monomer);
  
    return true;
  }


  bool 
  CrossLink::appendMonomer(const Monomer *monomer)
  {
    Q_ASSERT(monomer);
  
    m_monomerList.append(monomer);
  
    return true;
  }


  const Monomer *
  CrossLink::monomerAt(int index)
  {
    Q_ASSERT(index >= 0 && index < m_monomerList.size());
    
    return m_monomerList.at(index);
  }


  bool 
  CrossLink::removeMonomerAt(int index)
  {
    Q_ASSERT(index < m_monomerList.size());
  
    m_monomerList.removeAt(index);

    return true;
  }

  
  void
  CrossLink::setPolymer(Polymer *polymer)
  {
    Q_ASSERT(polymer);
  
    mp_polymer = polymer;
  }


  Polymer *
  CrossLink::polymer()
  {
    return mp_polymer;
  }


  void  
  CrossLink::setComment(const QString &comment)
  {
    m_comment = comment;
  }


  const QString &
  CrossLink::comment() const
  {
    return m_comment;
  }


  bool 
  CrossLink::operator ==(const CrossLink &other) const
  {
    int tests = 0;
  
    tests += CrossLinker::operator ==(other);
    tests += mp_polymer.data() == other.mp_polymer.data();
  
    if (m_monomerList.size() != other.m_monomerList.size())
      return false;
  
    for (int iter = 0; iter < m_monomerList.size(); ++iter)
      {
	if(m_monomerList.at(iter) != other.m_monomerList.at(iter))
	  return false;
      }
  
    tests += m_comment == other.m_comment;
    
    if (tests < 3)
      return false;
    else 
      return true;
  }


  int 
  CrossLink::populateMonomerList(QString text)
  {
    // We do not own the monomers pointed to by the pointers in
    // m_monomerList.

    while(m_monomerList.size())
      m_monomerList.removeFirst();
  
    QStringList stringList = text.split(';', 
					 QString::SkipEmptyParts, 
					 Qt::CaseSensitive );

    // There must be at least 2 monomers to make a crossLink !

    if (stringList.size() < 2)
      return -1;

    for (int iter = 0; iter < stringList.size(); ++iter)
      {
	QString value = stringList.at(iter);
      
	bool ok = false;
      
	int index = value.toInt(&ok);
      
	if(!index && !ok)
	  return -1;

	Q_ASSERT(index >=0 && index < mp_polymer->size());
            
	m_monomerList.append(mp_polymer->at(index));
      }
  
    return m_monomerList.size();
  }



  QList<const Monomer *> *
  CrossLink::monomerList()
  {
    return &m_monomerList;
  }


  // Returns the number of monomers involved in the cross-link.
  int
  CrossLink::monomerIndexList(QList<int> *list)
  {
    if(!list)
      qFatal("Fatal error at %s@%d. Aborting.",__FILE__, __LINE__);

    int count = 0;
  
    QList<int> localIndexList;
    
    for (int iter = 0;  iter < m_monomerList.size(); ++iter)
      {
	const Monomer *monomer = m_monomerList.at(iter);
	int index = mp_polymer->monomerIndex(monomer);
        
        // qDebug() << __FILE__ << __LINE__
        //          << "Other monomer index:" << index;

        localIndexList.append(index);
        
        ++count;
      }

    qSort(localIndexList.begin(), localIndexList.end());
    
    // Now that we have the minIndex and the maxIndex of the region
    // involved by the cross-link, we can fill-in the integer list
    // with all the values contained between these two borders.i

    for(int iter = localIndexList.first(); 
        iter <= localIndexList.last(); ++iter)
      {
        // If we had a cross-link between monomers [4] and [10] of the
        // polymer, then the indices appended to the list would be 4,
        // 5, 6, 7, 8, 9 and 10.
        list->append(iter);
      }
    
    return count;
  }


  QString 
  CrossLink::monomerIndexText()
  {
    QString text = ";";
  
    for (int iter = 0;  iter < m_monomerList.size(); ++iter)
      {
	const Monomer *monomer = m_monomerList.at(iter);
	int index = mp_polymer->monomerIndex(monomer);
      
	text += QString("%1;").arg(index);
      }
  
    return text;
  }


  QString 
  CrossLink::monomerPosText()
  {
    QString text = ";";
  
    for (int iter = 0;  iter < m_monomerList.size(); ++iter)
      {
	const Monomer *monomer = m_monomerList.at(iter);
	int index = mp_polymer->monomerIndex(monomer);
      
	text += QString("%1;").arg(index + 1);
      }
  
    return text;
  }


  int 
  CrossLink::involvesMonomer(const Monomer *monomer) const
  {
    Q_ASSERT(monomer);
  
    for (int iter = 0; iter < m_monomerList.size(); ++iter)
      {
	if(m_monomerList.at(iter) == monomer )
	  {
	    return iter;
	  }
      }
  
    return -1;
  }


  const Monomer *
  CrossLink::firstMonomer() const
  {
    if (!m_monomerList.size())
      return 0;
  
    return m_monomerList.first();
  }

  int
  CrossLink::encompassedBy(int start, int end, int *in, int *out)
  {
    // Iterate in the list of monomers, and for each monomer check if
    // their index is contained in the stretch.

    int countIn = 0;
    int countOut = 0;
  
    int localStart =(start < end ? start : end);
    int localEnd =(end >= start ? end : start);
  
    for (int iter = 0; iter < m_monomerList.size(); ++iter)
      {
	const Monomer *monomer = m_monomerList.at(iter);
      
	int index = mp_polymer->monomerIndex(monomer);
      
	if(index >= localStart && index <= localEnd)
	  ++countIn;
	else
	  ++countOut;
      }
  
    Q_ASSERT((countIn + countOut) == m_monomerList.size());
  
    if (in)
      *in = countIn;
    if (out)
      *out = countOut;
  
    if (countOut && countIn)
      return MXP_CROSS_LINK_ENCOMPASSED_PARTIAL;
  
    if (countOut)
      return MXP_CROSS_LINK_ENCOMPASSED_NO;
  
    if (countIn)
      return MXP_CROSS_LINK_ENCOMPASSED_FULL;

    return MXP_CROSS_LINK_ENCOMPASSED_NO;
  }


  int
  CrossLink::encompassedBy(const CoordinateList &coordinateList,
			    int *in, int *out)
  {
    // Iterate in the list of monomers involved in *this crossLink,
    // and for each monomer check if their index is contained in any
    // of the Coordinates [start--end] of the coordinateList passed as
    // parameter.

    int countIn = 0;
    int countOut = 0;
    
    for (int iter = 0; iter < m_monomerList.size(); ++iter)
      {
	const Monomer *monomer = m_monomerList.at(iter);
      
	int index = mp_polymer->monomerIndex(monomer);
	
// 	qDebug() << __FILE__ <<  __LINE__
// 		  << "           monomer at index:" << index
// 		  << "tested:";
	
	// Is the index encompassed by any of the Coordinates of the
	// CoordinateList ?
	
	bool countedIn = false;
	
	for(int jter = 0; jter < coordinateList.size(); ++jter)
	  {
	    Coordinates *coordinates = coordinateList.at(jter);
	    
	    if (index >= coordinates->start() && 
		index <= coordinates->end())
	      {
		++countIn;
		countedIn = true;
		
// 		qDebug() << __FILE__ <<  __LINE__
// 			  << "           encompassedBy YES by coordinates"
// 			  << "coordinates->start()" << coordinates->start()
// 			  << "coordinates->end()" << coordinates->end();
		
		break;
	      }
	    else
	      {
// 		qDebug() << __FILE__ <<  __LINE__
// 			  << "           encompassedBy NO by coordinates"
// 			  << "coordinates->start()" << coordinates->start()
// 			  << "coordinates->end()" << coordinates->end();
	      }
	  }
	
	if(!countedIn)
	  ++countOut;
      }
    
    Q_ASSERT((countIn + countOut) == m_monomerList.size());
  
    if (in)
      *in = countIn;
    if (out)
      *out = countOut;
  
    if (countOut && countIn)
      return MXP_CROSS_LINK_ENCOMPASSED_PARTIAL;
  
    if (countOut)
      return MXP_CROSS_LINK_ENCOMPASSED_NO;
  
    if (countIn)
      return MXP_CROSS_LINK_ENCOMPASSED_FULL;

    return MXP_CROSS_LINK_ENCOMPASSED_NO;
  }

  
  bool
  CrossLink::validate()
  {
    if (m_monomerList.size() <= 1)
      {
	qDebug() << __FILE__ << __LINE__ 
		  << "A crossLink with a single monomer "
	  "cannot be created.";
      
	return false;
      }
  
    if (mp_polymer.isNull())
      return false;
    
    // If the crossLinker has at least one modif in its definition, then
    // we have to make sure that the modification has as one of its
    // targets the corresponding monomer in the list of monomer
    // pointers.
  
    //Indeed, there is some logic here. Either the m_modifList in the
    // parent class CrossLinker contains no item, in which case
    // everything is fine, or it does contain items. In the latter case,
    // then, the number of items must match the number of monomers being
    // crosslinked, and then we get to know which modif is attributable
    // to which monomer, hence the possibility of a check.
  
    if (m_modifList.size())
      {
	if(m_modifList.size() != m_monomerList.size())
	  {
	    qDebug() << __FILE__ << __LINE__ 
		      << QObject::tr("The number of modification items "
				      "does not match the number of "
				      "monomers. This is an error.");
	    return false;
	  }
      
	// At this point, we can make the check for each modif:

	for(int iter = 0; iter < m_modifList.size(); ++iter)
	  {
	    Modif *modif = m_modifList.at(iter);
	    const Monomer *monomer = m_monomerList.at(iter);
	
	    if (!monomer->isModifTarget(*modif))
	      {
		// The monomer is not target for the modification that is
		// implied by the crossLinking.
	
		qDebug() << __FILE__ << __LINE__ 
			  << QObject::tr("The monomer '%1' is not target of "
					  "crossLinker modif '%2'")
		  .arg(monomer->name())
		  .arg(modif->name());
	      
		return false;
	      }
	  }
      }

    return true;
  }


  bool 
  CrossLink::calculateMasses()
  {
    return CrossLinker::calculateMasses();
  }


  bool 
  CrossLink::accountMasses(double *mono, double *avg, int times)
  {
    return CrossLinker::accountMasses(mono, avg, times);
  }


  bool 
  CrossLink::accountMasses(Ponderable *ponderable, int times)
  {
    return CrossLinker::accountMasses(ponderable, times);
  }


  QString *
  CrossLink::prepareResultsTxtString()
  {
    QString *text = new QString();

    *text += QObject::tr("\nCross-link:\n"
			  "===============\n"
			  "Cross-linker name: %1\n"
			  "Cross-link comment: %2\n")
      .arg(m_name)
      .arg(m_comment);
  
    for (int iter = 0; iter < m_monomerList.size(); ++iter)
      {
	const Monomer *monomer = m_monomerList.at(iter);
      
	int index = mp_polymer->monomerIndex(monomer);
      
	*text += QObject::tr("Partner %1: %2 at position: %3\n")
	  .arg(iter + 1)
	  .arg(monomer->code())
	  .arg(index + 1);
      }
  
    return text;
  }

} // namespace massXpert
